Просмотр исходного кода

feat(core): Implement Order surcharges

Relates to #583. This commit defines the entity and relations, as well as making sure surcharges
are used in calculating the Order subTotal. It does not include any special APIs for adding or
removing surcharges, however.
Michael Bromley 5 лет назад
Родитель
Сommit
b608e145bf

+ 20 - 0
packages/admin-ui/src/lib/core/src/common/generated-types.ts

@@ -1421,6 +1421,13 @@ export type Order = Node & {
   shippingAddress?: Maybe<OrderAddress>;
   billingAddress?: Maybe<OrderAddress>;
   lines: Array<OrderLine>;
+  /**
+   * Surcharges are arbitrary modifications to the Order total which are neither
+   * ProductVariants nor discounts resulting from applied Promotions. For example,
+   * one-off discounts based on customer interaction, or surcharges based on payment
+   * methods.
+   */
+  surcharges: Array<Surcharge>;
   /**
    * Order-level adjustments to the order total, such as discounts from promotions
    * @deprecated Use `discounts` instead
@@ -3710,6 +3717,19 @@ export type Refund = Node & {
   metadata?: Maybe<Scalars['JSON']>;
 };
 
+export type Surcharge = Node & {
+  __typename?: 'Surcharge';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  description: Scalars['String'];
+  sku?: Maybe<Scalars['String']>;
+  taxLines: Array<TaxLine>;
+  price: Scalars['Int'];
+  priceWithTax: Scalars['Int'];
+  taxRate: Scalars['Float'];
+};
+
 export type ProductOptionGroup = Node & {
   __typename?: 'ProductOptionGroup';
   id: Scalars['ID'];

+ 1 - 0
packages/admin-ui/src/lib/core/src/common/introspection-result.ts

@@ -104,6 +104,7 @@ const result: PossibleTypesResultData = {
             'OrderLine',
             'Payment',
             'Refund',
+            'Surcharge',
             'ProductOptionGroup',
             'ProductOption',
             'Promotion',

+ 19 - 0
packages/asset-server-plugin/e2e/graphql/generated-e2e-asset-server-plugin-types.ts

@@ -1234,6 +1234,13 @@ export type Order = Node & {
     shippingAddress?: Maybe<OrderAddress>;
     billingAddress?: Maybe<OrderAddress>;
     lines: Array<OrderLine>;
+    /**
+     * Surcharges are arbitrary modifications to the Order total which are neither
+     * ProductVariants nor discounts resulting from applied Promotions. For example,
+     * one-off discounts based on customer interaction, or surcharges based on payment
+     * methods.
+     */
+    surcharges: Array<Surcharge>;
     /**
      * Order-level adjustments to the order total, such as discounts from promotions
      * @deprecated Use `discounts` instead
@@ -3468,6 +3475,18 @@ export type Refund = Node & {
     metadata?: Maybe<Scalars['JSON']>;
 };
 
+export type Surcharge = Node & {
+    id: Scalars['ID'];
+    createdAt: Scalars['DateTime'];
+    updatedAt: Scalars['DateTime'];
+    description: Scalars['String'];
+    sku?: Maybe<Scalars['String']>;
+    taxLines: Array<TaxLine>;
+    price: Scalars['Int'];
+    priceWithTax: Scalars['Int'];
+    taxRate: Scalars['Float'];
+};
+
 export type ProductOptionGroup = Node & {
     id: Scalars['ID'];
     createdAt: Scalars['DateTime'];

+ 20 - 0
packages/common/src/generated-shop-types.ts

@@ -1722,6 +1722,13 @@ export type Order = Node & {
     shippingAddress?: Maybe<OrderAddress>;
     billingAddress?: Maybe<OrderAddress>;
     lines: Array<OrderLine>;
+    /**
+     * Surcharges are arbitrary modifications to the Order total which are neither
+     * ProductVariants nor discounts resulting from applied Promotions. For example,
+     * one-off discounts based on customer interaction, or surcharges based on payment
+     * methods.
+     */
+    surcharges: Array<Surcharge>;
     /**
      * Order-level adjustments to the order total, such as discounts from promotions
      * @deprecated Use `discounts` instead
@@ -1962,6 +1969,19 @@ export type Fulfillment = Node & {
     customFields?: Maybe<Scalars['JSON']>;
 };
 
+export type Surcharge = Node & {
+    __typename?: 'Surcharge';
+    id: Scalars['ID'];
+    createdAt: Scalars['DateTime'];
+    updatedAt: Scalars['DateTime'];
+    description: Scalars['String'];
+    sku?: Maybe<Scalars['String']>;
+    taxLines: Array<TaxLine>;
+    price: Scalars['Int'];
+    priceWithTax: Scalars['Int'];
+    taxRate: Scalars['Float'];
+};
+
 export type ProductOptionGroup = Node & {
     __typename?: 'ProductOptionGroup';
     id: Scalars['ID'];

+ 20 - 0
packages/common/src/generated-types.ts

@@ -1390,6 +1390,13 @@ export type Order = Node & {
   shippingAddress?: Maybe<OrderAddress>;
   billingAddress?: Maybe<OrderAddress>;
   lines: Array<OrderLine>;
+  /**
+   * Surcharges are arbitrary modifications to the Order total which are neither
+   * ProductVariants nor discounts resulting from applied Promotions. For example,
+   * one-off discounts based on customer interaction, or surcharges based on payment
+   * methods.
+   */
+  surcharges: Array<Surcharge>;
   /**
    * Order-level adjustments to the order total, such as discounts from promotions
    * @deprecated Use `discounts` instead
@@ -3678,6 +3685,19 @@ export type Refund = Node & {
   metadata?: Maybe<Scalars['JSON']>;
 };
 
+export type Surcharge = Node & {
+  __typename?: 'Surcharge';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  description: Scalars['String'];
+  sku?: Maybe<Scalars['String']>;
+  taxLines: Array<TaxLine>;
+  price: Scalars['Int'];
+  priceWithTax: Scalars['Int'];
+  taxRate: Scalars['Float'];
+};
+
 export type ProductOptionGroup = Node & {
   __typename?: 'ProductOptionGroup';
   id: Scalars['ID'];

+ 19 - 0
packages/core/e2e/graphql/generated-e2e-admin-types.ts

@@ -1234,6 +1234,13 @@ export type Order = Node & {
     shippingAddress?: Maybe<OrderAddress>;
     billingAddress?: Maybe<OrderAddress>;
     lines: Array<OrderLine>;
+    /**
+     * Surcharges are arbitrary modifications to the Order total which are neither
+     * ProductVariants nor discounts resulting from applied Promotions. For example,
+     * one-off discounts based on customer interaction, or surcharges based on payment
+     * methods.
+     */
+    surcharges: Array<Surcharge>;
     /**
      * Order-level adjustments to the order total, such as discounts from promotions
      * @deprecated Use `discounts` instead
@@ -3468,6 +3475,18 @@ export type Refund = Node & {
     metadata?: Maybe<Scalars['JSON']>;
 };
 
+export type Surcharge = Node & {
+    id: Scalars['ID'];
+    createdAt: Scalars['DateTime'];
+    updatedAt: Scalars['DateTime'];
+    description: Scalars['String'];
+    sku?: Maybe<Scalars['String']>;
+    taxLines: Array<TaxLine>;
+    price: Scalars['Int'];
+    priceWithTax: Scalars['Int'];
+    taxRate: Scalars['Float'];
+};
+
 export type ProductOptionGroup = Node & {
     id: Scalars['ID'];
     createdAt: Scalars['DateTime'];

+ 19 - 0
packages/core/e2e/graphql/generated-e2e-shop-types.ts

@@ -1671,6 +1671,13 @@ export type Order = Node & {
     shippingAddress?: Maybe<OrderAddress>;
     billingAddress?: Maybe<OrderAddress>;
     lines: Array<OrderLine>;
+    /**
+     * Surcharges are arbitrary modifications to the Order total which are neither
+     * ProductVariants nor discounts resulting from applied Promotions. For example,
+     * one-off discounts based on customer interaction, or surcharges based on payment
+     * methods.
+     */
+    surcharges: Array<Surcharge>;
     /**
      * Order-level adjustments to the order total, such as discounts from promotions
      * @deprecated Use `discounts` instead
@@ -1901,6 +1908,18 @@ export type Fulfillment = Node & {
     customFields?: Maybe<Scalars['JSON']>;
 };
 
+export type Surcharge = Node & {
+    id: Scalars['ID'];
+    createdAt: Scalars['DateTime'];
+    updatedAt: Scalars['DateTime'];
+    description: Scalars['String'];
+    sku?: Maybe<Scalars['String']>;
+    taxLines: Array<TaxLine>;
+    price: Scalars['Int'];
+    priceWithTax: Scalars['Int'];
+    taxRate: Scalars['Float'];
+};
+
 export type ProductOptionGroup = Node & {
     id: Scalars['ID'];
     createdAt: Scalars['DateTime'];

+ 8 - 0
packages/core/src/api/resolvers/entity/order-entity.resolver.ts

@@ -32,6 +32,14 @@ export class OrderEntityResolver {
         return this.orderService.getOrderFulfillments(ctx, order);
     }
 
+    @ResolveField()
+    async surcharges(@Ctx() ctx: RequestContext, @Parent() order: Order) {
+        if (order.surcharges) {
+            return order.surcharges;
+        }
+        return this.orderService.getOrderSurcharges(ctx, order.id);
+    }
+
     @ResolveField()
     async history(
         @Ctx() ctx: RequestContext,

+ 19 - 0
packages/core/src/api/schema/common/order.type.graphql

@@ -16,6 +16,13 @@ type Order implements Node {
     shippingAddress: OrderAddress
     billingAddress: OrderAddress
     lines: [OrderLine!]!
+    """
+    Surcharges are arbitrary modifications to the Order total which are neither
+    ProductVariants nor discounts resulting from applied Promotions. For example,
+    one-off discounts based on customer interaction, or surcharges based on payment
+    methods.
+    """
+    surcharges: [Surcharge!]!
     "Order-level adjustments to the order total, such as discounts from promotions"
     adjustments: [Adjustment!]! @deprecated(reason: "Use `discounts` instead")
     discounts: [Adjustment!]!
@@ -242,3 +249,15 @@ type Fulfillment implements Node {
     method: String!
     trackingCode: String
 }
+
+type Surcharge implements Node {
+    id: ID!
+    createdAt: DateTime!
+    updatedAt: DateTime!
+    description: String!
+    sku: String
+    taxLines: [TaxLine!]!
+    price: Int!
+    priceWithTax: Int!
+    taxRate: Float!
+}

+ 2 - 0
packages/core/src/entity/entities.ts

@@ -52,6 +52,7 @@ import { Release } from './stock-movement/release.entity';
 import { Sale } from './stock-movement/sale.entity';
 import { StockAdjustment } from './stock-movement/stock-adjustment.entity';
 import { StockMovement } from './stock-movement/stock-movement.entity';
+import { Surcharge } from './surcharge/surcharge.entity';
 import { TaxCategory } from './tax-category/tax-category.entity';
 import { TaxRate } from './tax-rate/tax-rate.entity';
 import { User } from './user/user.entity';
@@ -115,6 +116,7 @@ export const coreEntitiesMap = {
     ShippingMethodTranslation,
     StockAdjustment,
     StockMovement,
+    Surcharge,
     TaxCategory,
     TaxRate,
     User,

+ 1 - 0
packages/core/src/entity/index.ts

@@ -40,6 +40,7 @@ export * from './role/role.entity';
 export * from './session/session.entity';
 export * from './session/anonymous-session.entity';
 export * from './session/authenticated-session.entity';
+export * from './surcharge/surcharge.entity';
 export * from './shipping-method/shipping-method.entity';
 export * from './tax-category/tax-category.entity';
 export * from './tax-rate/tax-rate.entity';

+ 4 - 0
packages/core/src/entity/order/order.entity.ts

@@ -25,6 +25,7 @@ import { Payment } from '../payment/payment.entity';
 import { Promotion } from '../promotion/promotion.entity';
 import { ShippingLine } from '../shipping-line/shipping-line.entity';
 import { ShippingMethod } from '../shipping-method/shipping-method.entity';
+import { Surcharge } from '../surcharge/surcharge.entity';
 
 /**
  * @description
@@ -59,6 +60,9 @@ export class Order extends VendureEntity implements ChannelAware, HasCustomField
     @OneToMany(type => OrderLine, line => line.order)
     lines: OrderLine[];
 
+    @OneToMany(type => Surcharge, surcharge => surcharge.order)
+    surcharges: Surcharge[];
+
     @Column('simple-array')
     couponCodes: string[];
 

+ 56 - 0
packages/core/src/entity/surcharge/surcharge.entity.ts

@@ -0,0 +1,56 @@
+import { TaxLine } from '@vendure/common/lib/generated-types';
+import { DeepPartial } from '@vendure/common/lib/shared-types';
+import { summate } from '@vendure/common/lib/shared-utils';
+import { Column, Entity, ManyToOne } from 'typeorm';
+
+import { Calculated } from '../../common/calculated-decorator';
+import { grossPriceOf, netPriceOf } from '../../common/tax-utils';
+import { VendureEntity } from '../base/base.entity';
+import { Order } from '../order/order.entity';
+
+/**
+ * @description
+ * A Surcharge represents an arbitrary extra item on an {@link Order} which is not
+ * a ProductVariant. It can be used to e.g. represent payment-related surcharges.
+ *
+ * @docsCategory entities
+ */
+@Entity()
+export class Surcharge extends VendureEntity {
+    constructor(input?: DeepPartial<Surcharge>) {
+        super(input);
+    }
+
+    @Column()
+    description: string;
+
+    @Column()
+    listPrice: number;
+
+    @Column()
+    listPriceIncludesTax: boolean;
+
+    @Column()
+    sku: string;
+
+    @Column('simple-json')
+    taxLines: TaxLine[];
+
+    @ManyToOne(type => Order, order => order.surcharges)
+    order: Order;
+
+    @Calculated()
+    get price(): number {
+        return this.listPriceIncludesTax ? netPriceOf(this.listPrice, this.taxRate) : this.listPrice;
+    }
+
+    @Calculated()
+    get priceWithTax(): number {
+        return this.listPriceIncludesTax ? this.listPrice : grossPriceOf(this.listPrice, this.taxRate);
+    }
+
+    @Calculated()
+    get taxRate(): number {
+        return summate(this.taxLines, 'taxRate');
+    }
+}

+ 251 - 2
packages/core/src/service/helpers/order-calculator/order-calculator.spec.ts

@@ -17,6 +17,7 @@ import { Promotion } from '../../../entity';
 import { OrderItem } from '../../../entity/order-item/order-item.entity';
 import { Order } from '../../../entity/order/order.entity';
 import { ShippingLine } from '../../../entity/shipping-line/shipping-line.entity';
+import { Surcharge } from '../../../entity/surcharge/surcharge.entity';
 import { EventBus } from '../../../event-bus/event-bus';
 import {
     createOrder,
@@ -1068,6 +1069,252 @@ describe('OrderCalculator', () => {
             });
         });
     });
+
+    describe('surcharges', () => {
+        describe('positive surcharge without tax', () => {
+            it('prices exclude tax', async () => {
+                const ctx = createRequestContext({ pricesIncludeTax: false });
+                const order = createOrder({
+                    ctx,
+                    lines: [
+                        {
+                            listPrice: 1000,
+                            taxCategory: taxCategoryStandard,
+                            quantity: 2,
+                        },
+                        {
+                            listPrice: 3499,
+                            taxCategory: taxCategoryReduced,
+                            quantity: 1,
+                        },
+                    ],
+                });
+                order.surcharges = [
+                    new Surcharge({
+                        description: 'payment surcharge',
+                        listPrice: 240,
+                        listPriceIncludesTax: false,
+                        taxLines: [],
+                        sku: 'PSC',
+                    }),
+                ];
+
+                await orderCalculator.applyPriceAdjustments(ctx, order, []);
+
+                expect(order.subTotal).toBe(5739);
+                expect(order.subTotalWithTax).toBe(6489);
+                assertOrderTotalsAddUp(order);
+            });
+
+            it('prices include tax', async () => {
+                const ctx = createRequestContext({ pricesIncludeTax: true });
+                const order = createOrder({
+                    ctx,
+                    lines: [
+                        {
+                            listPrice: 1000,
+                            taxCategory: taxCategoryStandard,
+                            quantity: 2,
+                        },
+                        {
+                            listPrice: 3499,
+                            taxCategory: taxCategoryReduced,
+                            quantity: 1,
+                        },
+                    ],
+                });
+                order.surcharges = [
+                    new Surcharge({
+                        description: 'payment surcharge',
+                        listPrice: 240,
+                        listPriceIncludesTax: true,
+                        taxLines: [],
+                        sku: 'PSC',
+                    }),
+                ];
+
+                await orderCalculator.applyPriceAdjustments(ctx, order, []);
+
+                expect(order.subTotal).toBe(5087);
+                expect(order.subTotalWithTax).toBe(5739);
+                assertOrderTotalsAddUp(order);
+            });
+        });
+
+        describe('positive surcharge with tax', () => {
+            it('prices exclude tax', async () => {
+                const ctx = createRequestContext({ pricesIncludeTax: false });
+                const order = createOrder({
+                    ctx,
+                    lines: [
+                        {
+                            listPrice: 1000,
+                            taxCategory: taxCategoryStandard,
+                            quantity: 1,
+                        },
+                    ],
+                });
+                order.surcharges = [
+                    new Surcharge({
+                        description: 'payment surcharge',
+                        listPrice: 240,
+                        listPriceIncludesTax: false,
+                        taxLines: [
+                            {
+                                description: 'standard tax',
+                                taxRate: 20,
+                            },
+                        ],
+                        sku: 'PSC',
+                    }),
+                ];
+
+                await orderCalculator.applyPriceAdjustments(ctx, order, []);
+
+                expect(order.subTotal).toBe(1240);
+                expect(order.subTotalWithTax).toBe(1488);
+                assertOrderTotalsAddUp(order);
+            });
+
+            it('prices include tax', async () => {
+                const ctx = createRequestContext({ pricesIncludeTax: true });
+                const order = createOrder({
+                    ctx,
+                    lines: [
+                        {
+                            listPrice: 1000,
+                            taxCategory: taxCategoryStandard,
+                            quantity: 1,
+                        },
+                    ],
+                });
+                order.surcharges = [
+                    new Surcharge({
+                        description: 'payment surcharge',
+                        listPrice: 240,
+                        listPriceIncludesTax: true,
+                        taxLines: [
+                            {
+                                description: 'standard tax',
+                                taxRate: 20,
+                            },
+                        ],
+                        sku: 'PSC',
+                    }),
+                ];
+
+                await orderCalculator.applyPriceAdjustments(ctx, order, []);
+
+                expect(order.subTotal).toBe(1033);
+                expect(order.subTotalWithTax).toBe(1240);
+                assertOrderTotalsAddUp(order);
+            });
+        });
+
+        describe('negative surcharge with tax', () => {
+            it('prices exclude tax', async () => {
+                const ctx = createRequestContext({ pricesIncludeTax: false });
+                const order = createOrder({
+                    ctx,
+                    lines: [
+                        {
+                            listPrice: 1000,
+                            taxCategory: taxCategoryStandard,
+                            quantity: 1,
+                        },
+                    ],
+                });
+                order.surcharges = [
+                    new Surcharge({
+                        description: 'custom discount',
+                        listPrice: -240,
+                        listPriceIncludesTax: false,
+                        taxLines: [
+                            {
+                                description: 'standard tax',
+                                taxRate: 20,
+                            },
+                        ],
+                        sku: 'PSC',
+                    }),
+                ];
+
+                await orderCalculator.applyPriceAdjustments(ctx, order, []);
+
+                expect(order.subTotal).toBe(760);
+                expect(order.subTotalWithTax).toBe(912);
+                assertOrderTotalsAddUp(order);
+            });
+
+            it('prices include tax', async () => {
+                const ctx = createRequestContext({ pricesIncludeTax: true });
+                const order = createOrder({
+                    ctx,
+                    lines: [
+                        {
+                            listPrice: 1000,
+                            taxCategory: taxCategoryStandard,
+                            quantity: 1,
+                        },
+                    ],
+                });
+                order.surcharges = [
+                    new Surcharge({
+                        description: 'custom discount',
+                        listPrice: -240,
+                        listPriceIncludesTax: true,
+                        taxLines: [
+                            {
+                                description: 'standard tax',
+                                taxRate: 20,
+                            },
+                        ],
+                        sku: 'PSC',
+                    }),
+                ];
+
+                await orderCalculator.applyPriceAdjustments(ctx, order, []);
+
+                expect(order.subTotal).toBe(633);
+                expect(order.subTotalWithTax).toBe(760);
+                assertOrderTotalsAddUp(order);
+            });
+
+            it('prices exclude tax but surcharge includes tax', async () => {
+                const ctx = createRequestContext({ pricesIncludeTax: false });
+                const order = createOrder({
+                    ctx,
+                    lines: [
+                        {
+                            listPrice: 1000,
+                            taxCategory: taxCategoryStandard,
+                            quantity: 1,
+                        },
+                    ],
+                });
+                order.surcharges = [
+                    new Surcharge({
+                        description: 'custom discount',
+                        listPrice: -240,
+                        listPriceIncludesTax: true,
+                        taxLines: [
+                            {
+                                description: 'standard tax',
+                                taxRate: 20,
+                            },
+                        ],
+                        sku: 'PSC',
+                    }),
+                ];
+
+                await orderCalculator.applyPriceAdjustments(ctx, order, []);
+
+                expect(order.subTotal).toBe(800);
+                expect(order.subTotalWithTax).toBe(960);
+                assertOrderTotalsAddUp(order);
+            });
+        });
+    });
 });
 
 describe('OrderCalculator with custom TaxLineCalculationStrategy', () => {
@@ -1265,10 +1512,12 @@ function assertOrderTotalsAddUp(order: Order) {
         expect(line.linePriceWithTax).toBe(itemUnitPriceWithTaxSum);
     }
     const taxableLinePriceSum = summate(order.lines, 'proratedLinePrice');
-    expect(order.subTotal).toBe(taxableLinePriceSum);
+    const surchargeSum = summate(order.surcharges, 'price');
+    expect(order.subTotal).toBe(taxableLinePriceSum + surchargeSum);
 
     // Make sure the customer-facing totals also add up
     const displayPriceWithTaxSum = summate(order.lines, 'discountedLinePriceWithTax');
+    const surchargeWithTaxSum = summate(order.surcharges, 'priceWithTax');
     const orderDiscountsSum = order.discounts
         .filter(d => d.type === AdjustmentType.DISTRIBUTED_ORDER_PROMOTION)
         .reduce((sum, d) => sum + d.amount, 0);
@@ -1277,7 +1526,7 @@ function assertOrderTotalsAddUp(order: Order) {
     // equal the subTotalWithTax. In practice, there are occasionally 1cent differences
     // cause by rounding errors. This should be tolerable.
     const differenceBetweenSumAndActual = Math.abs(
-        displayPriceWithTaxSum + orderDiscountsSum - order.subTotalWithTax,
+        displayPriceWithTaxSum + orderDiscountsSum + surchargeWithTaxSum - order.subTotalWithTax,
     );
     expect(differenceBetweenSumAndActual).toBeLessThanOrEqual(1);
 }

+ 4 - 0
packages/core/src/service/helpers/order-calculator/order-calculator.ts

@@ -377,6 +377,10 @@ export class OrderCalculator {
             totalPrice += line.proratedLinePrice;
             totalPriceWithTax += line.proratedLinePriceWithTax;
         }
+        for (const surcharge of order.surcharges) {
+            totalPrice += surcharge.price;
+            totalPriceWithTax += surcharge.priceWithTax;
+        }
 
         order.subTotal = totalPrice;
         order.subTotalWithTax = totalPriceWithTax;

+ 1 - 0
packages/core/src/service/services/order-testing.service.ts

@@ -107,6 +107,7 @@ export class OrderTestingService {
         const { orderItemPriceCalculationStrategy } = this.configService.orderOptions;
         const mockOrder = new Order({
             lines: [],
+            surcharges: [],
         });
         mockOrder.shippingAddress = shippingAddress;
         for (const line of lines) {

+ 11 - 0
packages/core/src/service/services/order.service.ts

@@ -75,6 +75,7 @@ import { ProductVariant } from '../../entity/product-variant/product-variant.ent
 import { Promotion } from '../../entity/promotion/promotion.entity';
 import { Refund } from '../../entity/refund/refund.entity';
 import { ShippingLine } from '../../entity/shipping-line/shipping-line.entity';
+import { Surcharge } from '../../entity/surcharge/surcharge.entity';
 import { User } from '../../entity/user/user.entity';
 import { EventBus } from '../../event-bus/event-bus';
 import { OrderStateTransitionEvent } from '../../event-bus/events/order-state-transition-event';
@@ -163,6 +164,7 @@ export class OrderService {
             .leftJoin('order.channels', 'channel')
             .leftJoinAndSelect('order.customer', 'customer')
             .leftJoinAndSelect('order.shippingLines', 'shippingLines')
+            .leftJoinAndSelect('order.surcharges', 'surcharges')
             .leftJoinAndSelect('customer.user', 'user')
             .leftJoinAndSelect('order.lines', 'lines')
             .leftJoinAndSelect('lines.productVariant', 'productVariant')
@@ -284,6 +286,7 @@ export class OrderService {
             code: await this.configService.orderOptions.orderCodeStrategy.generate(ctx),
             state: this.orderStateMachine.getInitialState(),
             lines: [],
+            surcharges: [],
             couponCodes: [],
             shippingAddress: {},
             billingAddress: {},
@@ -826,6 +829,14 @@ export class OrderService {
         return unique(fulfillments, 'id');
     }
 
+    async getOrderSurcharges(ctx: RequestContext, orderId: ID): Promise<Surcharge[]> {
+        const order = await this.connection.getEntityOrThrow(ctx, Order, orderId, {
+            channelId: ctx.channelId,
+            relations: ['surcharges'],
+        });
+        return order.surcharges || [];
+    }
+
     async cancelOrder(
         ctx: RequestContext,
         input: CancelOrderInput,

+ 1 - 0
packages/core/src/testing/order-test-utils.ts

@@ -156,5 +156,6 @@ export function createOrder(
         couponCodes: [],
         lines,
         shippingLines: [],
+        surcharges: [],
     });
 }

+ 19 - 0
packages/elasticsearch-plugin/e2e/graphql/generated-e2e-elasticsearch-plugin-types.ts

@@ -1234,6 +1234,13 @@ export type Order = Node & {
     shippingAddress?: Maybe<OrderAddress>;
     billingAddress?: Maybe<OrderAddress>;
     lines: Array<OrderLine>;
+    /**
+     * Surcharges are arbitrary modifications to the Order total which are neither
+     * ProductVariants nor discounts resulting from applied Promotions. For example,
+     * one-off discounts based on customer interaction, or surcharges based on payment
+     * methods.
+     */
+    surcharges: Array<Surcharge>;
     /**
      * Order-level adjustments to the order total, such as discounts from promotions
      * @deprecated Use `discounts` instead
@@ -3468,6 +3475,18 @@ export type Refund = Node & {
     metadata?: Maybe<Scalars['JSON']>;
 };
 
+export type Surcharge = Node & {
+    id: Scalars['ID'];
+    createdAt: Scalars['DateTime'];
+    updatedAt: Scalars['DateTime'];
+    description: Scalars['String'];
+    sku?: Maybe<Scalars['String']>;
+    taxLines: Array<TaxLine>;
+    price: Scalars['Int'];
+    priceWithTax: Scalars['Int'];
+    taxRate: Scalars['Float'];
+};
+
 export type ProductOptionGroup = Node & {
     id: Scalars['ID'];
     createdAt: Scalars['DateTime'];

+ 1 - 0
packages/email-plugin/src/mock-events.ts

@@ -96,6 +96,7 @@ export const mockOrderStateTransitionEvent = new OrderStateTransitionEvent(
                 },
             },
         ],
+        surcharges: [],
         shippingAddress: {
             fullName: 'Test Customer',
             company: '',

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
schema-admin.json


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
schema-shop.json


Некоторые файлы не были показаны из-за большого количества измененных файлов