shipping-method.entity.ts 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. import { ConfigurableOperation } from '@vendure/common/lib/generated-types';
  2. import { omit } from '@vendure/common/lib/omit';
  3. import { DeepPartial } from '@vendure/common/lib/shared-types';
  4. import { Column, Entity, JoinTable, ManyToMany, OneToMany } from 'typeorm';
  5. import { RequestContext } from '../../api/common/request-context';
  6. import { roundMoney } from '../../common/round-money';
  7. import { ChannelAware, SoftDeletable } from '../../common/types/common-types';
  8. import { LocaleString, Translatable, Translation } from '../../common/types/locale-types';
  9. import { getConfig } from '../../config/config-helpers';
  10. import { HasCustomFields } from '../../config/custom-field/custom-field-types';
  11. import {
  12. ShippingCalculationResult,
  13. ShippingCalculator,
  14. } from '../../config/shipping-method/shipping-calculator';
  15. import { ShippingEligibilityChecker } from '../../config/shipping-method/shipping-eligibility-checker';
  16. import { VendureEntity } from '../base/base.entity';
  17. import { Channel } from '../channel/channel.entity';
  18. import { CustomShippingMethodFields } from '../custom-entity-fields';
  19. import { Order } from '../order/order.entity';
  20. import { ShippingMethodTranslation } from './shipping-method-translation.entity';
  21. /**
  22. * @description
  23. * A ShippingMethod is used to apply a shipping price to an {@link Order}. It is composed of a
  24. * {@link ShippingEligibilityChecker} and a {@link ShippingCalculator}. For a given Order,
  25. * the `checker` is used to determine whether this ShippingMethod can be used. If yes, then
  26. * the ShippingMethod can be applied and the `calculator` is used to determine the price of
  27. * shipping.
  28. *
  29. * @docsCategory entities
  30. */
  31. @Entity()
  32. export class ShippingMethod
  33. extends VendureEntity
  34. implements ChannelAware, SoftDeletable, HasCustomFields, Translatable
  35. {
  36. private readonly allCheckers: { [code: string]: ShippingEligibilityChecker } = {};
  37. private readonly allCalculators: { [code: string]: ShippingCalculator } = {};
  38. constructor(input?: DeepPartial<ShippingMethod>) {
  39. super(input);
  40. const checkers = getConfig().shippingOptions.shippingEligibilityCheckers || [];
  41. const calculators = getConfig().shippingOptions.shippingCalculators || [];
  42. this.allCheckers = checkers.reduce((hash, o) => ({ ...hash, [o.code]: o }), {});
  43. this.allCalculators = calculators.reduce((hash, o) => ({ ...hash, [o.code]: o }), {});
  44. }
  45. @Column({ type: Date, nullable: true })
  46. deletedAt: Date | null;
  47. @Column() code: string;
  48. name: LocaleString;
  49. description: LocaleString;
  50. @Column('simple-json') checker: ConfigurableOperation;
  51. @Column('simple-json') calculator: ConfigurableOperation;
  52. @Column()
  53. fulfillmentHandlerCode: string;
  54. @ManyToMany(type => Channel, channel => channel.shippingMethods)
  55. @JoinTable()
  56. channels: Channel[];
  57. @OneToMany(type => ShippingMethodTranslation, translation => translation.base, { eager: true })
  58. translations: Array<Translation<ShippingMethod>>;
  59. @Column(type => CustomShippingMethodFields)
  60. customFields: CustomShippingMethodFields;
  61. async apply(ctx: RequestContext, order: Order): Promise<ShippingCalculationResult | undefined> {
  62. const calculator = this.allCalculators[this.calculator.code];
  63. if (calculator) {
  64. const response = await calculator.calculate(ctx, order, this.calculator.args, this);
  65. if (response) {
  66. const { price, priceIncludesTax, taxRate, metadata } = response;
  67. return {
  68. price: roundMoney(price),
  69. priceIncludesTax,
  70. taxRate,
  71. metadata,
  72. };
  73. }
  74. }
  75. }
  76. async test(ctx: RequestContext, order: Order): Promise<boolean> {
  77. const checker = this.allCheckers[this.checker.code];
  78. if (checker) {
  79. return checker.check(ctx, order, this.checker.args, this);
  80. } else {
  81. return false;
  82. }
  83. }
  84. /**
  85. * This is a fix for https://github.com/vendure-ecommerce/vendure/issues/3277,
  86. * to prevent circular references which cause the JSON.stringify() to fail.
  87. */
  88. protected toJSON(): any {
  89. return omit(this, ['allCheckers', 'allCalculators'] as any);
  90. }
  91. }