Răsfoiți Sursa

feat(core): Implement ChangedPriceHandlingStrategy

Relates to #664

BREAKING CHANGE: The OrderItem entity has a new field, `initialListPrice`, used to better
handle price changes to items in an active Order. This schema change will require a DB migration.
Michael Bromley 5 ani în urmă
părinte
comite
3aae4fbc8d

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

@@ -3926,6 +3926,10 @@ export type OrderLine = Node & {
   unitPrice: Scalars['Int'];
   /** The price of a single unit, including tax but excluding discounts */
   unitPriceWithTax: Scalars['Int'];
+  /** If the unitPrice has changed since initially added to Order */
+  unitPriceChangeSinceAdded: Scalars['Int'];
+  /** If the unitPriceWithTax has changed since initially added to Order */
+  unitPriceWithTaxChangeSinceAdded: Scalars['Int'];
   /**
    * The price of a single unit including discounts, excluding tax.
    * 

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

@@ -3671,6 +3671,10 @@ export type OrderLine = Node & {
     unitPrice: Scalars['Int'];
     /** The price of a single unit, including tax but excluding discounts */
     unitPriceWithTax: Scalars['Int'];
+    /** If the unitPrice has changed since initially added to Order */
+    unitPriceChangeSinceAdded: Scalars['Int'];
+    /** If the unitPriceWithTax has changed since initially added to Order */
+    unitPriceWithTaxChangeSinceAdded: Scalars['Int'];
     /**
      * The price of a single unit including discounts, excluding tax.
      *

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

@@ -1908,6 +1908,10 @@ export type OrderLine = Node & {
     unitPrice: Scalars['Int'];
     /** The price of a single unit, including tax but excluding discounts */
     unitPriceWithTax: Scalars['Int'];
+    /** If the unitPrice has changed since initially added to Order */
+    unitPriceChangeSinceAdded: Scalars['Int'];
+    /** If the unitPriceWithTax has changed since initially added to Order */
+    unitPriceWithTaxChangeSinceAdded: Scalars['Int'];
     /**
      * The price of a single unit including discounts, excluding tax.
      *

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

@@ -3888,6 +3888,10 @@ export type OrderLine = Node & {
   unitPrice: Scalars['Int'];
   /** The price of a single unit, including tax but excluding discounts */
   unitPriceWithTax: Scalars['Int'];
+  /** If the unitPrice has changed since initially added to Order */
+  unitPriceChangeSinceAdded: Scalars['Int'];
+  /** If the unitPriceWithTax has changed since initially added to Order */
+  unitPriceWithTaxChangeSinceAdded: Scalars['Int'];
   /**
    * The price of a single unit including discounts, excluding tax.
    * 

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

@@ -3671,6 +3671,10 @@ export type OrderLine = Node & {
     unitPrice: Scalars['Int'];
     /** The price of a single unit, including tax but excluding discounts */
     unitPriceWithTax: Scalars['Int'];
+    /** If the unitPrice has changed since initially added to Order */
+    unitPriceChangeSinceAdded: Scalars['Int'];
+    /** If the unitPriceWithTax has changed since initially added to Order */
+    unitPriceWithTaxChangeSinceAdded: Scalars['Int'];
     /**
      * The price of a single unit including discounts, excluding tax.
      *

+ 13 - 2
packages/core/e2e/graphql/generated-e2e-shop-types.ts

@@ -1848,6 +1848,10 @@ export type OrderLine = Node & {
     unitPrice: Scalars['Int'];
     /** The price of a single unit, including tax but excluding discounts */
     unitPriceWithTax: Scalars['Int'];
+    /** If the unitPrice has changed since initially added to Order */
+    unitPriceChangeSinceAdded: Scalars['Int'];
+    /** If the unitPriceWithTax has changed since initially added to Order */
+    unitPriceWithTaxChangeSinceAdded: Scalars['Int'];
     /**
      * The price of a single unit including discounts, excluding tax.
      *
@@ -2709,11 +2713,18 @@ export type TestOrderFragmentFragment = Pick<
     lines: Array<
         Pick<
             OrderLine,
-            'id' | 'quantity' | 'linePrice' | 'linePriceWithTax' | 'unitPrice' | 'unitPriceWithTax'
+            | 'id'
+            | 'quantity'
+            | 'linePrice'
+            | 'linePriceWithTax'
+            | 'unitPrice'
+            | 'unitPriceWithTax'
+            | 'unitPriceChangeSinceAdded'
+            | 'unitPriceWithTaxChangeSinceAdded'
         > & {
             productVariant: Pick<ProductVariant, 'id'>;
             discounts: Array<Pick<Adjustment, 'adjustmentSource' | 'amount' | 'description' | 'type'>>;
-            items: Array<Pick<OrderItem, 'id'>>;
+            items: Array<Pick<OrderItem, 'id' | 'unitPrice' | 'unitPriceWithTax'>>;
         }
     >;
     shippingLines: Array<{ shippingMethod: Pick<ShippingMethod, 'id' | 'code' | 'description'> }>;

+ 4 - 0
packages/core/e2e/graphql/shop-definitions.ts

@@ -28,6 +28,8 @@ export const TEST_ORDER_FRAGMENT = gql`
             linePriceWithTax
             unitPrice
             unitPriceWithTax
+            unitPriceChangeSinceAdded
+            unitPriceWithTaxChangeSinceAdded
             productVariant {
                 id
             }
@@ -39,6 +41,8 @@ export const TEST_ORDER_FRAGMENT = gql`
             }
             items {
                 id
+                unitPrice
+                unitPriceWithTax
             }
         }
         shippingLines {

+ 217 - 0
packages/core/e2e/order-changed-price-handling.e2e-spec.ts

@@ -0,0 +1,217 @@
+/* tslint:disable:no-non-null-assertion */
+import {
+    ChangedPriceHandlingStrategy,
+    mergeConfig,
+    OrderItem,
+    PriceCalculationResult,
+    RequestContext,
+} from '@vendure/core';
+import { createTestEnvironment } from '@vendure/testing';
+import path from 'path';
+
+import { initialData } from '../../../e2e-common/e2e-initial-data';
+import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
+
+import { UpdateProductVariants } from './graphql/generated-e2e-admin-types';
+import { AddItemToOrder, AdjustItemQuantity, GetActiveOrder } from './graphql/generated-e2e-shop-types';
+import { UPDATE_PRODUCT_VARIANTS } from './graphql/shared-definitions';
+import { ADD_ITEM_TO_ORDER, ADJUST_ITEM_QUANTITY, GET_ACTIVE_ORDER } from './graphql/shop-definitions';
+
+class TestChangedPriceStrategy implements ChangedPriceHandlingStrategy {
+    static spy = jest.fn();
+    static useLatestPrice = true;
+
+    handlePriceChange(
+        ctx: RequestContext,
+        current: PriceCalculationResult,
+        existingItems: OrderItem[],
+    ): PriceCalculationResult {
+        TestChangedPriceStrategy.spy(current);
+        if (TestChangedPriceStrategy.useLatestPrice) {
+            return current;
+        } else {
+            return {
+                price: existingItems[0].listPrice,
+                priceIncludesTax: existingItems[0].listPriceIncludesTax,
+            };
+        }
+    }
+}
+
+describe('ChangedPriceHandlingStrategy', () => {
+    const { server, shopClient, adminClient } = createTestEnvironment(
+        mergeConfig(testConfig, {
+            orderOptions: {
+                changedPriceHandlingStrategy: new TestChangedPriceStrategy(),
+            },
+        }),
+    );
+
+    beforeAll(async () => {
+        await server.init({
+            initialData,
+            productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
+            customerCount: 1,
+        });
+        await adminClient.asSuperAdmin();
+    }, TEST_SETUP_TIMEOUT_MS);
+
+    afterAll(async () => {
+        await server.destroy();
+    });
+
+    it('unitPriceChangeSinceAdded starts as 0', async () => {
+        TestChangedPriceStrategy.spy.mockClear();
+
+        await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(ADD_ITEM_TO_ORDER, {
+            productVariantId: 'T_12',
+            quantity: 1,
+        });
+
+        const { activeOrder } = await shopClient.query<GetActiveOrder.Query>(GET_ACTIVE_ORDER);
+
+        expect(activeOrder?.lines[0].unitPriceChangeSinceAdded).toBe(0);
+        expect(activeOrder?.lines[0].unitPrice).toBe(5374);
+        expect(TestChangedPriceStrategy.spy).not.toHaveBeenCalled();
+    });
+
+    describe('use latest price', () => {
+        let firstOrderLineId: string;
+
+        beforeAll(() => {
+            TestChangedPriceStrategy.useLatestPrice = true;
+        });
+
+        it('calls handlePriceChange on addItemToOrder', async () => {
+            TestChangedPriceStrategy.spy.mockClear();
+
+            await adminClient.query<UpdateProductVariants.Mutation, UpdateProductVariants.Variables>(
+                UPDATE_PRODUCT_VARIANTS,
+                {
+                    input: [
+                        {
+                            id: 'T_12',
+                            price: 6000,
+                        },
+                    ],
+                },
+            );
+
+            await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(ADD_ITEM_TO_ORDER, {
+                productVariantId: 'T_12',
+                quantity: 1,
+            });
+
+            const { activeOrder } = await shopClient.query<GetActiveOrder.Query>(GET_ACTIVE_ORDER);
+            expect(activeOrder?.lines[0].unitPriceChangeSinceAdded).toBe(626);
+            expect(activeOrder?.lines[0].unitPrice).toBe(6000);
+            expect(activeOrder?.lines[0].items.every(i => i.unitPrice === 6000)).toBe(true);
+            expect(TestChangedPriceStrategy.spy).toHaveBeenCalledTimes(1);
+
+            firstOrderLineId = activeOrder!.lines[0].id;
+        });
+
+        it('calls handlePriceChange on adjustOrderLine', async () => {
+            TestChangedPriceStrategy.spy.mockClear();
+
+            await adminClient.query<UpdateProductVariants.Mutation, UpdateProductVariants.Variables>(
+                UPDATE_PRODUCT_VARIANTS,
+                {
+                    input: [
+                        {
+                            id: 'T_12',
+                            price: 3000,
+                        },
+                    ],
+                },
+            );
+
+            await shopClient.query<AdjustItemQuantity.Mutation, AdjustItemQuantity.Variables>(
+                ADJUST_ITEM_QUANTITY,
+                {
+                    orderLineId: firstOrderLineId,
+                    quantity: 3,
+                },
+            );
+
+            const { activeOrder } = await shopClient.query<GetActiveOrder.Query>(GET_ACTIVE_ORDER);
+            expect(activeOrder?.lines[0].unitPriceChangeSinceAdded).toBe(-2374);
+            expect(activeOrder?.lines[0].unitPrice).toBe(3000);
+            expect(activeOrder?.lines[0].items.every(i => i.unitPrice === 3000)).toBe(true);
+            expect(TestChangedPriceStrategy.spy).toHaveBeenCalledTimes(1);
+        });
+    });
+
+    describe('use original price', () => {
+        let secondOrderLineId: string;
+        const ORIGINAL_PRICE = 7896;
+
+        beforeAll(() => {
+            TestChangedPriceStrategy.useLatestPrice = false;
+        });
+
+        it('calls handlePriceChange on addItemToOrder', async () => {
+            TestChangedPriceStrategy.spy.mockClear();
+
+            await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(ADD_ITEM_TO_ORDER, {
+                productVariantId: 'T_13',
+                quantity: 1,
+            });
+
+            await adminClient.query<UpdateProductVariants.Mutation, UpdateProductVariants.Variables>(
+                UPDATE_PRODUCT_VARIANTS,
+                {
+                    input: [
+                        {
+                            id: 'T_13',
+                            price: 8000,
+                        },
+                    ],
+                },
+            );
+
+            await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(ADD_ITEM_TO_ORDER, {
+                productVariantId: 'T_13',
+                quantity: 1,
+            });
+
+            const { activeOrder } = await shopClient.query<GetActiveOrder.Query>(GET_ACTIVE_ORDER);
+            expect(activeOrder?.lines[1].unitPriceChangeSinceAdded).toBe(0);
+            expect(activeOrder?.lines[1].unitPrice).toBe(ORIGINAL_PRICE);
+            expect(activeOrder?.lines[1].items.every(i => i.unitPrice === ORIGINAL_PRICE)).toBe(true);
+            expect(TestChangedPriceStrategy.spy).toHaveBeenCalledTimes(1);
+
+            secondOrderLineId = activeOrder!.lines[1].id;
+        });
+
+        it('calls handlePriceChange on adjustOrderLine', async () => {
+            TestChangedPriceStrategy.spy.mockClear();
+
+            await adminClient.query<UpdateProductVariants.Mutation, UpdateProductVariants.Variables>(
+                UPDATE_PRODUCT_VARIANTS,
+                {
+                    input: [
+                        {
+                            id: 'T_13',
+                            price: 3000,
+                        },
+                    ],
+                },
+            );
+
+            await shopClient.query<AdjustItemQuantity.Mutation, AdjustItemQuantity.Variables>(
+                ADJUST_ITEM_QUANTITY,
+                {
+                    orderLineId: secondOrderLineId,
+                    quantity: 3,
+                },
+            );
+
+            const { activeOrder } = await shopClient.query<GetActiveOrder.Query>(GET_ACTIVE_ORDER);
+            expect(activeOrder?.lines[1].unitPriceChangeSinceAdded).toBe(0);
+            expect(activeOrder?.lines[1].unitPrice).toBe(ORIGINAL_PRICE);
+            expect(activeOrder?.lines[1].items.every(i => i.unitPrice === ORIGINAL_PRICE)).toBe(true);
+            expect(TestChangedPriceStrategy.spy).toHaveBeenCalledTimes(1);
+        });
+    });
+});

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

@@ -151,6 +151,14 @@ type OrderLine implements Node {
     "The price of a single unit, including tax but excluding discounts"
     unitPriceWithTax: Int!
     """
+    Non-zero if the unitPrice has changed since it was initially added to Order
+    """
+    unitPriceChangeSinceAdded: Int!
+    """
+    Non-zero if the unitPriceWithTax has changed since it was initially added to Order
+    """
+    unitPriceWithTaxChangeSinceAdded: Int!
+    """
     The price of a single unit including discounts, excluding tax.
 
     If Order-level discounts have been applied, this will not be the

+ 2 - 0
packages/core/src/config/default-config.ts

@@ -17,6 +17,7 @@ import { DefaultProductVariantPriceCalculationStrategy } from './catalog/default
 import { AutoIncrementIdStrategy } from './entity-id-strategy/auto-increment-id-strategy';
 import { manualFulfillmentHandler } from './fulfillment/manual-fulfillment-handler';
 import { DefaultLogger } from './logger/default-logger';
+import { DefaultChangedPriceHandlingStrategy } from './order/default-changed-price-handling-strategy';
 import { DefaultOrderItemPriceCalculationStrategy } from './order/default-order-item-price-calculation-strategy';
 import { DefaultStockAllocationStrategy } from './order/default-stock-allocation-strategy';
 import { MergeOrdersStrategy } from './order/merge-orders-strategy';
@@ -113,6 +114,7 @@ export const defaultConfig: RuntimeVendureConfig = {
         process: [],
         stockAllocationStrategy: new DefaultStockAllocationStrategy(),
         orderCodeStrategy: new DefaultOrderCodeStrategy(),
+        changedPriceHandlingStrategy: new DefaultChangedPriceHandlingStrategy(),
     },
     paymentOptions: {
         paymentMethodEligibilityCheckers: [],

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

@@ -21,6 +21,7 @@ export * from './logger/noop-logger';
 export * from './logger/vendure-logger';
 export * from './merge-config';
 export * from './order/custom-order-process';
+export * from './order/changed-price-handling-strategy';
 export * from './order/default-stock-allocation-strategy';
 export * from './order/merge-orders-strategy';
 export * from './order/order-code-strategy';

+ 29 - 0
packages/core/src/config/order/changed-price-handling-strategy.ts

@@ -0,0 +1,29 @@
+import { RequestContext } from '../../api/common/request-context';
+import { PriceCalculationResult } from '../../common/types/common-types';
+import { InjectableStrategy } from '../../common/types/injectable-strategy';
+import { OrderItem } from '../../entity/order-item/order-item.entity';
+
+/**
+ * @description
+ * This strategy defines how we handle the situation where an OrderItem exists in an Order, and
+ * then later on another is added but in the mean time the price of the ProductVariant has changed.
+ *
+ * By default, the latest price will be used. Any price changes resulting from using a newer price
+ * will be reflected in the GraphQL `OrderLine.unitPrice[WithTax]ChangeSinceAdded` field.
+ *
+ * @docsCategory orders
+ */
+export interface ChangedPriceHandlingStrategy extends InjectableStrategy {
+    /**
+     * @description
+     * This method is called when adding to or adjusting OrderLines, if the latest price
+     * (as determined by the ProductVariant price, potentially modified by the configured
+     * {@link OrderItemPriceCalculationStrategy}) differs from the initial price at the time
+     * that the OrderLine was created.
+     */
+    handlePriceChange(
+        ctx: RequestContext,
+        current: PriceCalculationResult,
+        existingItems: OrderItem[],
+    ): PriceCalculationResult | Promise<PriceCalculationResult>;
+}

+ 15 - 0
packages/core/src/config/order/default-changed-price-handling-strategy.ts

@@ -0,0 +1,15 @@
+import { RequestContext } from '../../api/common/request-context';
+import { PriceCalculationResult } from '../../common/types/common-types';
+
+import { ChangedPriceHandlingStrategy } from './changed-price-handling-strategy';
+
+/**
+ * @description
+ * The default {@link ChangedPriceHandlingStrategy} will always use the latest price when
+ * updating existing OrderLines.
+ */
+export class DefaultChangedPriceHandlingStrategy implements ChangedPriceHandlingStrategy {
+    handlePriceChange(ctx: RequestContext, current: PriceCalculationResult): PriceCalculationResult {
+        return current;
+    }
+}

+ 12 - 0
packages/core/src/config/vendure-config.ts

@@ -20,6 +20,7 @@ import { CustomFulfillmentProcess } from './fulfillment/custom-fulfillment-proce
 import { FulfillmentHandler } from './fulfillment/fulfillment-handler';
 import { JobQueueStrategy } from './job-queue/job-queue-strategy';
 import { VendureLogger } from './logger/vendure-logger';
+import { ChangedPriceHandlingStrategy } from './order/changed-price-handling-strategy';
 import { CustomOrderProcess } from './order/custom-order-process';
 import { OrderCodeStrategy } from './order/order-code-strategy';
 import { OrderItemPriceCalculationStrategy } from './order/order-item-price-calculation-strategy';
@@ -433,6 +434,17 @@ export interface OrderOptions {
      * @default DefaultOrderCodeStrategy
      */
     orderCodeStrategy?: OrderCodeStrategy;
+    /**
+     * @description
+     * Defines how we handle the situation where an OrderItem exists in an Order, and
+     * then later on another is added but in the mean time the price of the ProductVariant has changed.
+     *
+     * By default, the latest price will be used. Any price changes resulting from using a newer price
+     * will be reflected in the GraphQL `OrderLine.unitPrice[WithTax]ChangeSinceAdded` field.
+     *
+     * @default DefaultChangedPriceHandlingStrategy
+     */
+    changedPriceHandlingStrategy?: ChangedPriceHandlingStrategy;
 }
 
 /**

+ 11 - 2
packages/core/src/entity/order-item/order-item.entity.ts

@@ -29,8 +29,17 @@ export class OrderItem extends VendureEntity {
 
     /**
      * @description
-     * This is the price as listed by the ProductVariant, which, depending on the
-     * current Channel, may or may not include tax.
+     * The price as calculated when the OrderItem was first added to the Order. Usually will be identical to the
+     * `listPrice`, except when the ProductVariant price has changed in the mean time and a re-calculation of
+     * the Order has been performed.
+     */
+    @Column({ nullable: true })
+    initialListPrice: number;
+
+    /**
+     * @description
+     * This is the price as listed by the ProductVariant (and possibly modified by the {@link OrderItemPriceCalculationStrategy}),
+     * which, depending on the current Channel, may or may not include tax.
      */
     @Column()
     listPrice: number;

+ 27 - 1
packages/core/src/entity/order-line/order-line.entity.ts

@@ -4,7 +4,7 @@ import { summate } from '@vendure/common/lib/shared-utils';
 import { Column, Entity, ManyToOne, OneToMany } from 'typeorm';
 
 import { Calculated } from '../../common/calculated-decorator';
-import { grossPriceOf } from '../../common/tax-utils';
+import { grossPriceOf, netPriceOf } from '../../common/tax-utils';
 import { HasCustomFields } from '../../config/custom-field/custom-field-types';
 import { Asset } from '../asset/asset.entity';
 import { VendureEntity } from '../base/base.entity';
@@ -54,6 +54,32 @@ export class OrderLine extends VendureEntity implements HasCustomFields {
         return this.firstActiveItemPropOr('unitPriceWithTax', 0);
     }
 
+    @Calculated()
+    get unitPriceChangeSinceAdded(): number {
+        const firstItem = this.activeItems[0];
+        if (!firstItem) {
+            return 0;
+        }
+        const { initialListPrice, listPriceIncludesTax } = firstItem;
+        const initialPrice = listPriceIncludesTax
+            ? netPriceOf(initialListPrice, this.taxRate)
+            : initialListPrice;
+        return this.unitPrice - initialPrice;
+    }
+
+    @Calculated()
+    get unitPriceWithTaxChangeSinceAdded(): number {
+        const firstItem = this.activeItems[0];
+        if (!firstItem) {
+            return 0;
+        }
+        const { initialListPrice, listPriceIncludesTax } = firstItem;
+        const initialPriceWithTax = listPriceIncludesTax
+            ? initialListPrice
+            : grossPriceOf(initialListPrice, this.taxRate);
+        return this.unitPriceWithTax - initialPriceWithTax;
+    }
+
     @Calculated()
     get discountedUnitPrice(): number {
         return this.firstActiveItemPropOr('discountedUnitPrice', 0);

+ 20 - 4
packages/core/src/service/services/order.service.ts

@@ -1291,15 +1291,31 @@ export class OrderService {
         updatedOrderLine?: OrderLine,
     ): Promise<Order> {
         if (updatedOrderLine) {
-            const { orderItemPriceCalculationStrategy } = this.configService.orderOptions;
-            const { price, priceIncludesTax } = await orderItemPriceCalculationStrategy.calculateUnitPrice(
+            const {
+                orderItemPriceCalculationStrategy,
+                changedPriceHandlingStrategy,
+            } = this.configService.orderOptions;
+            let priceResult = await orderItemPriceCalculationStrategy.calculateUnitPrice(
                 ctx,
                 updatedOrderLine.productVariant,
                 updatedOrderLine.customFields || {},
             );
+            const initialListPrice =
+                updatedOrderLine.items.find(i => i.initialListPrice != null)?.initialListPrice ??
+                priceResult.price;
+            if (initialListPrice !== priceResult.price) {
+                priceResult = await changedPriceHandlingStrategy.handlePriceChange(
+                    ctx,
+                    priceResult,
+                    updatedOrderLine.items,
+                );
+            }
             for (const item of updatedOrderLine.items) {
-                item.listPrice = price;
-                item.listPriceIncludesTax = priceIncludesTax;
+                if (item.initialListPrice == null) {
+                    item.initialListPrice = initialListPrice;
+                }
+                item.listPrice = priceResult.price;
+                item.listPriceIncludesTax = priceResult.priceIncludesTax;
             }
         }
         const promotions = await this.connection.getRepository(ctx, Promotion).find({

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

@@ -3671,6 +3671,10 @@ export type OrderLine = Node & {
     unitPrice: Scalars['Int'];
     /** The price of a single unit, including tax but excluding discounts */
     unitPriceWithTax: Scalars['Int'];
+    /** If the unitPrice has changed since initially added to Order */
+    unitPriceChangeSinceAdded: Scalars['Int'];
+    /** If the unitPriceWithTax has changed since initially added to Order */
+    unitPriceWithTaxChangeSinceAdded: Scalars['Int'];
     /**
      * The price of a single unit including discounts, excluding tax.
      *

Fișier diff suprimat deoarece este prea mare
+ 0 - 0
schema-admin.json


Fișier diff suprimat deoarece este prea mare
+ 0 - 0
schema-shop.json


Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff