Browse Source

feat(core): Extend OrderLine type with more discount & tax info

Relates to #573
Michael Bromley 5 years ago
parent
commit
aa5513f259

+ 28 - 6
packages/asset-server-plugin/e2e/graphql/generated-e2e-asset-server-plugin-types.ts

@@ -3330,17 +3330,18 @@ export type OrderItem = Node & {
     createdAt: Scalars['DateTime'];
     createdAt: Scalars['DateTime'];
     updatedAt: Scalars['DateTime'];
     updatedAt: Scalars['DateTime'];
     cancelled: Scalars['Boolean'];
     cancelled: Scalars['Boolean'];
+    /** The price of a single unit, excluding tax and discounts */
+    unitPrice: Scalars['Int'];
+    /** The price of a single unit, including tax but excluding discounts */
+    unitPriceWithTax: Scalars['Int'];
     /**
     /**
-     * The price of a single unit, excluding tax and discounts
+     * The price of a single unit including discounts, excluding tax.
      *
      *
      * If Order-level discounts have been applied, this will not be the
      * If Order-level discounts have been applied, this will not be the
-     * actual taxable unit price, but is generally the correct price to display to customers to avoid confusion
+     * actual taxable unit price (see `proratedUnitPrice`), but is generally the
+     * correct price to display to customers to avoid confusion
      * about the internal handling of distributed Order-level discounts.
      * about the internal handling of distributed Order-level discounts.
      */
      */
-    unitPrice: Scalars['Int'];
-    /** The price of a single unit, including tax but excluding discounts */
-    unitPriceWithTax: Scalars['Int'];
-    /** The price of a single unit including discounts, excluding tax */
     discountedUnitPrice: Scalars['Int'];
     discountedUnitPrice: Scalars['Int'];
     /** The price of a single unit including discounts and tax */
     /** The price of a single unit including discounts and tax */
     discountedUnitPriceWithTax: Scalars['Int'];
     discountedUnitPriceWithTax: Scalars['Int'];
@@ -3368,8 +3369,29 @@ export type OrderLine = Node & {
     updatedAt: Scalars['DateTime'];
     updatedAt: Scalars['DateTime'];
     productVariant: ProductVariant;
     productVariant: ProductVariant;
     featuredAsset?: Maybe<Asset>;
     featuredAsset?: Maybe<Asset>;
+    /** The price of a single unit, excluding tax and discounts */
     unitPrice: Scalars['Int'];
     unitPrice: Scalars['Int'];
+    /** The price of a single unit, including tax but excluding discounts */
     unitPriceWithTax: Scalars['Int'];
     unitPriceWithTax: Scalars['Int'];
+    /**
+     * The price of a single unit including discounts, excluding tax.
+     *
+     * If Order-level discounts have been applied, this will not be the
+     * actual taxable unit price (see `proratedUnitPrice`), but is generally the
+     * correct price to display to customers to avoid confusion
+     * about the internal handling of distributed Order-level discounts.
+     */
+    discountedUnitPrice: Scalars['Int'];
+    /** The price of a single unit including discounts and tax */
+    discountedUnitPriceWithTax: Scalars['Int'];
+    /**
+     * The actual unit price, taking into account both item discounts _and_ prorated (proportially-distributed)
+     * Order-level discounts. This value is the true economic value of the OrderItem, and is used in tax
+     * and refund calculations.
+     */
+    proratedUnitPrice: Scalars['Int'];
+    /** The proratedUnitPrice including tax */
+    proratedUnitPriceWithTax: Scalars['Int'];
     quantity: Scalars['Int'];
     quantity: Scalars['Int'];
     items: Array<OrderItem>;
     items: Array<OrderItem>;
     /** @deprecated Use `linePriceWithTax` instead */
     /** @deprecated Use `linePriceWithTax` instead */

+ 28 - 6
packages/common/src/generated-shop-types.ts

@@ -1809,17 +1809,18 @@ export type OrderItem = Node & {
     createdAt: Scalars['DateTime'];
     createdAt: Scalars['DateTime'];
     updatedAt: Scalars['DateTime'];
     updatedAt: Scalars['DateTime'];
     cancelled: Scalars['Boolean'];
     cancelled: Scalars['Boolean'];
+    /** The price of a single unit, excluding tax and discounts */
+    unitPrice: Scalars['Int'];
+    /** The price of a single unit, including tax but excluding discounts */
+    unitPriceWithTax: Scalars['Int'];
     /**
     /**
-     * The price of a single unit, excluding tax and discounts
+     * The price of a single unit including discounts, excluding tax.
      *
      *
      * If Order-level discounts have been applied, this will not be the
      * If Order-level discounts have been applied, this will not be the
-     * actual taxable unit price, but is generally the correct price to display to customers to avoid confusion
+     * actual taxable unit price (see `proratedUnitPrice`), but is generally the
+     * correct price to display to customers to avoid confusion
      * about the internal handling of distributed Order-level discounts.
      * about the internal handling of distributed Order-level discounts.
      */
      */
-    unitPrice: Scalars['Int'];
-    /** The price of a single unit, including tax but excluding discounts */
-    unitPriceWithTax: Scalars['Int'];
-    /** The price of a single unit including discounts, excluding tax */
     discountedUnitPrice: Scalars['Int'];
     discountedUnitPrice: Scalars['Int'];
     /** The price of a single unit including discounts and tax */
     /** The price of a single unit including discounts and tax */
     discountedUnitPriceWithTax: Scalars['Int'];
     discountedUnitPriceWithTax: Scalars['Int'];
@@ -1848,8 +1849,29 @@ export type OrderLine = Node & {
     updatedAt: Scalars['DateTime'];
     updatedAt: Scalars['DateTime'];
     productVariant: ProductVariant;
     productVariant: ProductVariant;
     featuredAsset?: Maybe<Asset>;
     featuredAsset?: Maybe<Asset>;
+    /** The price of a single unit, excluding tax and discounts */
     unitPrice: Scalars['Int'];
     unitPrice: Scalars['Int'];
+    /** The price of a single unit, including tax but excluding discounts */
     unitPriceWithTax: Scalars['Int'];
     unitPriceWithTax: Scalars['Int'];
+    /**
+     * The price of a single unit including discounts, excluding tax.
+     *
+     * If Order-level discounts have been applied, this will not be the
+     * actual taxable unit price (see `proratedUnitPrice`), but is generally the
+     * correct price to display to customers to avoid confusion
+     * about the internal handling of distributed Order-level discounts.
+     */
+    discountedUnitPrice: Scalars['Int'];
+    /** The price of a single unit including discounts and tax */
+    discountedUnitPriceWithTax: Scalars['Int'];
+    /**
+     * The actual unit price, taking into account both item discounts _and_ prorated (proportially-distributed)
+     * Order-level discounts. This value is the true economic value of the OrderItem, and is used in tax
+     * and refund calculations.
+     */
+    proratedUnitPrice: Scalars['Int'];
+    /** The proratedUnitPrice including tax */
+    proratedUnitPriceWithTax: Scalars['Int'];
     quantity: Scalars['Int'];
     quantity: Scalars['Int'];
     items: Array<OrderItem>;
     items: Array<OrderItem>;
     /** @deprecated Use `linePriceWithTax` instead */
     /** @deprecated Use `linePriceWithTax` instead */

+ 28 - 6
packages/common/src/generated-types.ts

@@ -3536,17 +3536,18 @@ export type OrderItem = Node & {
   createdAt: Scalars['DateTime'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
   cancelled: Scalars['Boolean'];
   cancelled: Scalars['Boolean'];
+  /** The price of a single unit, excluding tax and discounts */
+  unitPrice: Scalars['Int'];
+  /** The price of a single unit, including tax but excluding discounts */
+  unitPriceWithTax: Scalars['Int'];
   /**
   /**
-   * The price of a single unit, excluding tax and discounts
+   * The price of a single unit including discounts, excluding tax.
    * 
    * 
    * If Order-level discounts have been applied, this will not be the
    * If Order-level discounts have been applied, this will not be the
-   * actual taxable unit price, but is generally the correct price to display to customers to avoid confusion
+   * actual taxable unit price (see `proratedUnitPrice`), but is generally the
+   * correct price to display to customers to avoid confusion
    * about the internal handling of distributed Order-level discounts.
    * about the internal handling of distributed Order-level discounts.
    */
    */
-  unitPrice: Scalars['Int'];
-  /** The price of a single unit, including tax but excluding discounts */
-  unitPriceWithTax: Scalars['Int'];
-  /** The price of a single unit including discounts, excluding tax */
   discountedUnitPrice: Scalars['Int'];
   discountedUnitPrice: Scalars['Int'];
   /** The price of a single unit including discounts and tax */
   /** The price of a single unit including discounts and tax */
   discountedUnitPriceWithTax: Scalars['Int'];
   discountedUnitPriceWithTax: Scalars['Int'];
@@ -3575,8 +3576,29 @@ export type OrderLine = Node & {
   updatedAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
   productVariant: ProductVariant;
   productVariant: ProductVariant;
   featuredAsset?: Maybe<Asset>;
   featuredAsset?: Maybe<Asset>;
+  /** The price of a single unit, excluding tax and discounts */
   unitPrice: Scalars['Int'];
   unitPrice: Scalars['Int'];
+  /** The price of a single unit, including tax but excluding discounts */
   unitPriceWithTax: Scalars['Int'];
   unitPriceWithTax: Scalars['Int'];
+  /**
+   * The price of a single unit including discounts, excluding tax.
+   * 
+   * If Order-level discounts have been applied, this will not be the
+   * actual taxable unit price (see `proratedUnitPrice`), but is generally the
+   * correct price to display to customers to avoid confusion
+   * about the internal handling of distributed Order-level discounts.
+   */
+  discountedUnitPrice: Scalars['Int'];
+  /** The price of a single unit including discounts and tax */
+  discountedUnitPriceWithTax: Scalars['Int'];
+  /**
+   * The actual unit price, taking into account both item discounts _and_ prorated (proportially-distributed)
+   * Order-level discounts. This value is the true economic value of the OrderItem, and is used in tax
+   * and refund calculations.
+   */
+  proratedUnitPrice: Scalars['Int'];
+  /** The proratedUnitPrice including tax */
+  proratedUnitPriceWithTax: Scalars['Int'];
   quantity: Scalars['Int'];
   quantity: Scalars['Int'];
   items: Array<OrderItem>;
   items: Array<OrderItem>;
   /** @deprecated Use `linePriceWithTax` instead */
   /** @deprecated Use `linePriceWithTax` instead */

+ 28 - 6
packages/core/e2e/graphql/generated-e2e-admin-types.ts

@@ -3330,17 +3330,18 @@ export type OrderItem = Node & {
     createdAt: Scalars['DateTime'];
     createdAt: Scalars['DateTime'];
     updatedAt: Scalars['DateTime'];
     updatedAt: Scalars['DateTime'];
     cancelled: Scalars['Boolean'];
     cancelled: Scalars['Boolean'];
+    /** The price of a single unit, excluding tax and discounts */
+    unitPrice: Scalars['Int'];
+    /** The price of a single unit, including tax but excluding discounts */
+    unitPriceWithTax: Scalars['Int'];
     /**
     /**
-     * The price of a single unit, excluding tax and discounts
+     * The price of a single unit including discounts, excluding tax.
      *
      *
      * If Order-level discounts have been applied, this will not be the
      * If Order-level discounts have been applied, this will not be the
-     * actual taxable unit price, but is generally the correct price to display to customers to avoid confusion
+     * actual taxable unit price (see `proratedUnitPrice`), but is generally the
+     * correct price to display to customers to avoid confusion
      * about the internal handling of distributed Order-level discounts.
      * about the internal handling of distributed Order-level discounts.
      */
      */
-    unitPrice: Scalars['Int'];
-    /** The price of a single unit, including tax but excluding discounts */
-    unitPriceWithTax: Scalars['Int'];
-    /** The price of a single unit including discounts, excluding tax */
     discountedUnitPrice: Scalars['Int'];
     discountedUnitPrice: Scalars['Int'];
     /** The price of a single unit including discounts and tax */
     /** The price of a single unit including discounts and tax */
     discountedUnitPriceWithTax: Scalars['Int'];
     discountedUnitPriceWithTax: Scalars['Int'];
@@ -3368,8 +3369,29 @@ export type OrderLine = Node & {
     updatedAt: Scalars['DateTime'];
     updatedAt: Scalars['DateTime'];
     productVariant: ProductVariant;
     productVariant: ProductVariant;
     featuredAsset?: Maybe<Asset>;
     featuredAsset?: Maybe<Asset>;
+    /** The price of a single unit, excluding tax and discounts */
     unitPrice: Scalars['Int'];
     unitPrice: Scalars['Int'];
+    /** The price of a single unit, including tax but excluding discounts */
     unitPriceWithTax: Scalars['Int'];
     unitPriceWithTax: Scalars['Int'];
+    /**
+     * The price of a single unit including discounts, excluding tax.
+     *
+     * If Order-level discounts have been applied, this will not be the
+     * actual taxable unit price (see `proratedUnitPrice`), but is generally the
+     * correct price to display to customers to avoid confusion
+     * about the internal handling of distributed Order-level discounts.
+     */
+    discountedUnitPrice: Scalars['Int'];
+    /** The price of a single unit including discounts and tax */
+    discountedUnitPriceWithTax: Scalars['Int'];
+    /**
+     * The actual unit price, taking into account both item discounts _and_ prorated (proportially-distributed)
+     * Order-level discounts. This value is the true economic value of the OrderItem, and is used in tax
+     * and refund calculations.
+     */
+    proratedUnitPrice: Scalars['Int'];
+    /** The proratedUnitPrice including tax */
+    proratedUnitPriceWithTax: Scalars['Int'];
     quantity: Scalars['Int'];
     quantity: Scalars['Int'];
     items: Array<OrderItem>;
     items: Array<OrderItem>;
     /** @deprecated Use `linePriceWithTax` instead */
     /** @deprecated Use `linePriceWithTax` instead */

+ 28 - 6
packages/core/e2e/graphql/generated-e2e-shop-types.ts

@@ -1753,17 +1753,18 @@ export type OrderItem = Node & {
     createdAt: Scalars['DateTime'];
     createdAt: Scalars['DateTime'];
     updatedAt: Scalars['DateTime'];
     updatedAt: Scalars['DateTime'];
     cancelled: Scalars['Boolean'];
     cancelled: Scalars['Boolean'];
+    /** The price of a single unit, excluding tax and discounts */
+    unitPrice: Scalars['Int'];
+    /** The price of a single unit, including tax but excluding discounts */
+    unitPriceWithTax: Scalars['Int'];
     /**
     /**
-     * The price of a single unit, excluding tax and discounts
+     * The price of a single unit including discounts, excluding tax.
      *
      *
      * If Order-level discounts have been applied, this will not be the
      * If Order-level discounts have been applied, this will not be the
-     * actual taxable unit price, but is generally the correct price to display to customers to avoid confusion
+     * actual taxable unit price (see `proratedUnitPrice`), but is generally the
+     * correct price to display to customers to avoid confusion
      * about the internal handling of distributed Order-level discounts.
      * about the internal handling of distributed Order-level discounts.
      */
      */
-    unitPrice: Scalars['Int'];
-    /** The price of a single unit, including tax but excluding discounts */
-    unitPriceWithTax: Scalars['Int'];
-    /** The price of a single unit including discounts, excluding tax */
     discountedUnitPrice: Scalars['Int'];
     discountedUnitPrice: Scalars['Int'];
     /** The price of a single unit including discounts and tax */
     /** The price of a single unit including discounts and tax */
     discountedUnitPriceWithTax: Scalars['Int'];
     discountedUnitPriceWithTax: Scalars['Int'];
@@ -1791,8 +1792,29 @@ export type OrderLine = Node & {
     updatedAt: Scalars['DateTime'];
     updatedAt: Scalars['DateTime'];
     productVariant: ProductVariant;
     productVariant: ProductVariant;
     featuredAsset?: Maybe<Asset>;
     featuredAsset?: Maybe<Asset>;
+    /** The price of a single unit, excluding tax and discounts */
     unitPrice: Scalars['Int'];
     unitPrice: Scalars['Int'];
+    /** The price of a single unit, including tax but excluding discounts */
     unitPriceWithTax: Scalars['Int'];
     unitPriceWithTax: Scalars['Int'];
+    /**
+     * The price of a single unit including discounts, excluding tax.
+     *
+     * If Order-level discounts have been applied, this will not be the
+     * actual taxable unit price (see `proratedUnitPrice`), but is generally the
+     * correct price to display to customers to avoid confusion
+     * about the internal handling of distributed Order-level discounts.
+     */
+    discountedUnitPrice: Scalars['Int'];
+    /** The price of a single unit including discounts and tax */
+    discountedUnitPriceWithTax: Scalars['Int'];
+    /**
+     * The actual unit price, taking into account both item discounts _and_ prorated (proportially-distributed)
+     * Order-level discounts. This value is the true economic value of the OrderItem, and is used in tax
+     * and refund calculations.
+     */
+    proratedUnitPrice: Scalars['Int'];
+    /** The proratedUnitPrice including tax */
+    proratedUnitPriceWithTax: Scalars['Int'];
     quantity: Scalars['Int'];
     quantity: Scalars['Int'];
     items: Array<OrderItem>;
     items: Array<OrderItem>;
     /** @deprecated Use `linePriceWithTax` instead */
     /** @deprecated Use `linePriceWithTax` instead */

+ 4 - 4
packages/core/e2e/order-promotion.e2e-spec.ts

@@ -745,7 +745,7 @@ describe('Promotions applied to Orders', () => {
 
 
                 expect(applyCouponCode!.total).toBe(1600);
                 expect(applyCouponCode!.total).toBe(1600);
                 expect(applyCouponCode!.totalWithTax).toBe(1920);
                 expect(applyCouponCode!.totalWithTax).toBe(1920);
-                expect(getItemSale1Line(applyCouponCode!.lines).discounts.length).toBe(2); // 2x promotion
+                expect(getItemSale1Line(applyCouponCode!.lines).discounts.length).toBe(1); // 1x promotion
 
 
                 const { removeCouponCode } = await shopClient.query<
                 const { removeCouponCode } = await shopClient.query<
                     RemoveCouponCode.Mutation,
                     RemoveCouponCode.Mutation,
@@ -798,7 +798,7 @@ describe('Promotions applied to Orders', () => {
 
 
                 expect(applyCouponCode!.total).toBe(1334);
                 expect(applyCouponCode!.total).toBe(1334);
                 expect(applyCouponCode!.totalWithTax).toBe(1600);
                 expect(applyCouponCode!.totalWithTax).toBe(1600);
-                expect(getItemSale1Line(applyCouponCode!.lines).discounts.length).toBe(2); // 2x promotion
+                expect(getItemSale1Line(applyCouponCode!.lines).discounts.length).toBe(1); // 1x promotion
 
 
                 const { removeCouponCode } = await shopClient.query<
                 const { removeCouponCode } = await shopClient.query<
                     RemoveCouponCode.Mutation,
                     RemoveCouponCode.Mutation,
@@ -969,7 +969,7 @@ describe('Promotions applied to Orders', () => {
                 const saleItemLine = apply1.lines.find(
                 const saleItemLine = apply1.lines.find(
                     l => l.productVariant.id === getVariantBySlug('item-sale-1000').id,
                     l => l.productVariant.id === getVariantBySlug('item-sale-1000').id,
                 )!;
                 )!;
-                expect(saleItemLine.discounts.length).toBe(2); // 1x promotion for each item
+                expect(saleItemLine.discounts.length).toBe(1); // 1x promotion
                 expect(
                 expect(
                     saleItemLine.discounts.find(a => a.type === AdjustmentType.PROMOTION)?.description,
                     saleItemLine.discounts.find(a => a.type === AdjustmentType.PROMOTION)?.description,
                 ).toBe('item promo');
                 ).toBe('item promo');
@@ -1016,7 +1016,7 @@ describe('Promotions applied to Orders', () => {
                 const saleItemLine = apply1.lines.find(
                 const saleItemLine = apply1.lines.find(
                     l => l.productVariant.id === getVariantBySlug('item-sale-1000').id,
                     l => l.productVariant.id === getVariantBySlug('item-sale-1000').id,
                 )!;
                 )!;
-                expect(saleItemLine.discounts.length).toBe(2); // 1x promotion for each item
+                expect(saleItemLine.discounts.length).toBe(1); // 1x promotion
                 expect(
                 expect(
                     saleItemLine.discounts.find(a => a.type === AdjustmentType.PROMOTION)?.description,
                     saleItemLine.discounts.find(a => a.type === AdjustmentType.PROMOTION)?.description,
                 ).toBe('item promo');
                 ).toBe('item promo');

+ 28 - 6
packages/core/src/api/schema/common/order.type.graphql

@@ -99,17 +99,18 @@ type OrderItem implements Node {
     createdAt: DateTime!
     createdAt: DateTime!
     updatedAt: DateTime!
     updatedAt: DateTime!
     cancelled: Boolean!
     cancelled: Boolean!
+    "The price of a single unit, excluding tax and discounts"
+    unitPrice: Int!
+    "The price of a single unit, including tax but excluding discounts"
+    unitPriceWithTax: Int!
     """
     """
-    The price of a single unit, excluding tax and discounts
+    The price of a single unit including discounts, excluding tax.
 
 
     If Order-level discounts have been applied, this will not be the
     If Order-level discounts have been applied, this will not be the
-    actual taxable unit price, but is generally the correct price to display to customers to avoid confusion
+    actual taxable unit price (see `proratedUnitPrice`), but is generally the
+    correct price to display to customers to avoid confusion
     about the internal handling of distributed Order-level discounts.
     about the internal handling of distributed Order-level discounts.
     """
     """
-    unitPrice: Int!
-    "The price of a single unit, including tax but excluding discounts"
-    unitPriceWithTax: Int!
-    "The price of a single unit including discounts, excluding tax"
     discountedUnitPrice: Int!
     discountedUnitPrice: Int!
     "The price of a single unit including discounts and tax"
     "The price of a single unit including discounts and tax"
     discountedUnitPriceWithTax: Int!
     discountedUnitPriceWithTax: Int!
@@ -136,8 +137,29 @@ type OrderLine implements Node {
     updatedAt: DateTime!
     updatedAt: DateTime!
     productVariant: ProductVariant!
     productVariant: ProductVariant!
     featuredAsset: Asset
     featuredAsset: Asset
+    "The price of a single unit, excluding tax and discounts"
     unitPrice: Int!
     unitPrice: Int!
+    "The price of a single unit, including tax but excluding discounts"
     unitPriceWithTax: Int!
     unitPriceWithTax: Int!
+    """
+    The price of a single unit including discounts, excluding tax.
+
+    If Order-level discounts have been applied, this will not be the
+    actual taxable unit price (see `proratedUnitPrice`), but is generally the
+    correct price to display to customers to avoid confusion
+    about the internal handling of distributed Order-level discounts.
+    """
+    discountedUnitPrice: Int!
+    "The price of a single unit including discounts and tax"
+    discountedUnitPriceWithTax: Int!
+    """
+    The actual unit price, taking into account both item discounts _and_ prorated (proportially-distributed)
+    Order-level discounts. This value is the true economic value of the OrderItem, and is used in tax
+    and refund calculations.
+    """
+    proratedUnitPrice: Int!
+    "The proratedUnitPrice including tax"
+    proratedUnitPriceWithTax: Int!
     quantity: Int!
     quantity: Int!
     items: [OrderItem!]!
     items: [OrderItem!]!
     totalPrice: Int! @deprecated(reason: "Use `linePriceWithTax` instead")
     totalPrice: Int! @deprecated(reason: "Use `linePriceWithTax` instead")

+ 3 - 3
packages/core/src/config/payment-method/example-payment-method-handler.ts

@@ -33,18 +33,18 @@ export const examplePaymentHandler = new PaymentMethodHandler({
         try {
         try {
             const result = await gripeSDK.charges.create({
             const result = await gripeSDK.charges.create({
                 apiKey: args.apiKey,
                 apiKey: args.apiKey,
-                amount: order.total,
+                amount: order.totalWithTax,
                 source: metadata.authToken,
                 source: metadata.authToken,
             });
             });
             return {
             return {
-                amount: order.total,
+                amount: order.totalWithTax,
                 state: args.automaticCapture ? 'Settled' : 'Authorized',
                 state: args.automaticCapture ? 'Settled' : 'Authorized',
                 transactionId: result.id.toString(),
                 transactionId: result.id.toString(),
                 metadata,
                 metadata,
             };
             };
         } catch (err) {
         } catch (err) {
             return {
             return {
-                amount: order.total,
+                amount: order.totalWithTax,
                 state: 'Declined' as 'Declined',
                 state: 'Declined' as 'Declined',
                 metadata: {
                 metadata: {
                     errorMessage: err.message,
                     errorMessage: err.message,

+ 28 - 4
packages/core/src/entity/order-line/order-line.entity.ts

@@ -64,6 +64,16 @@ export class OrderLine extends VendureEntity implements HasCustomFields {
         return this.firstActiveItemPropOr('discountedUnitPriceWithTax', 0);
         return this.firstActiveItemPropOr('discountedUnitPriceWithTax', 0);
     }
     }
 
 
+    @Calculated()
+    get proratedUnitPrice(): number {
+        return this.firstActiveItemPropOr('proratedUnitPrice', 0);
+    }
+
+    @Calculated()
+    get proratedUnitPriceWithTax(): number {
+        return this.firstActiveItemPropOr('proratedUnitPriceWithTax', 0);
+    }
+
     @Calculated()
     @Calculated()
     get quantity(): number {
     get quantity(): number {
         return this.activeItems.length;
         return this.activeItems.length;
@@ -110,10 +120,24 @@ export class OrderLine extends VendureEntity implements HasCustomFields {
     @Calculated()
     @Calculated()
     get discounts(): Adjustment[] {
     get discounts(): Adjustment[] {
         const priceIncludesTax = this.items?.[0]?.listPriceIncludesTax ?? false;
         const priceIncludesTax = this.items?.[0]?.listPriceIncludesTax ?? false;
-        return this.adjustments.map(adjustment => ({
-            ...adjustment,
-            amount: priceIncludesTax ? adjustment.amount : grossPriceOf(adjustment.amount, this.taxRate),
-        }));
+        // Group discounts together, so that it does not list a new
+        // discount row for each OrderItem in the line
+        const groupedAdjustments = new Map<string, Adjustment>();
+        for (const discount of this.adjustments) {
+            const adjustment = groupedAdjustments.get(discount.adjustmentSource);
+            const amountWithTax = priceIncludesTax
+                ? discount.amount
+                : grossPriceOf(discount.amount, this.taxRate);
+            if (adjustment) {
+                adjustment.amount += amountWithTax;
+            } else {
+                groupedAdjustments.set(discount.adjustmentSource, {
+                    ...discount,
+                    amount: amountWithTax,
+                });
+            }
+        }
+        return [...groupedAdjustments.values()];
     }
     }
 
 
     @Calculated()
     @Calculated()

+ 28 - 6
packages/elasticsearch-plugin/e2e/graphql/generated-e2e-elasticsearch-plugin-types.ts

@@ -3330,17 +3330,18 @@ export type OrderItem = Node & {
     createdAt: Scalars['DateTime'];
     createdAt: Scalars['DateTime'];
     updatedAt: Scalars['DateTime'];
     updatedAt: Scalars['DateTime'];
     cancelled: Scalars['Boolean'];
     cancelled: Scalars['Boolean'];
+    /** The price of a single unit, excluding tax and discounts */
+    unitPrice: Scalars['Int'];
+    /** The price of a single unit, including tax but excluding discounts */
+    unitPriceWithTax: Scalars['Int'];
     /**
     /**
-     * The price of a single unit, excluding tax and discounts
+     * The price of a single unit including discounts, excluding tax.
      *
      *
      * If Order-level discounts have been applied, this will not be the
      * If Order-level discounts have been applied, this will not be the
-     * actual taxable unit price, but is generally the correct price to display to customers to avoid confusion
+     * actual taxable unit price (see `proratedUnitPrice`), but is generally the
+     * correct price to display to customers to avoid confusion
      * about the internal handling of distributed Order-level discounts.
      * about the internal handling of distributed Order-level discounts.
      */
      */
-    unitPrice: Scalars['Int'];
-    /** The price of a single unit, including tax but excluding discounts */
-    unitPriceWithTax: Scalars['Int'];
-    /** The price of a single unit including discounts, excluding tax */
     discountedUnitPrice: Scalars['Int'];
     discountedUnitPrice: Scalars['Int'];
     /** The price of a single unit including discounts and tax */
     /** The price of a single unit including discounts and tax */
     discountedUnitPriceWithTax: Scalars['Int'];
     discountedUnitPriceWithTax: Scalars['Int'];
@@ -3368,8 +3369,29 @@ export type OrderLine = Node & {
     updatedAt: Scalars['DateTime'];
     updatedAt: Scalars['DateTime'];
     productVariant: ProductVariant;
     productVariant: ProductVariant;
     featuredAsset?: Maybe<Asset>;
     featuredAsset?: Maybe<Asset>;
+    /** The price of a single unit, excluding tax and discounts */
     unitPrice: Scalars['Int'];
     unitPrice: Scalars['Int'];
+    /** The price of a single unit, including tax but excluding discounts */
     unitPriceWithTax: Scalars['Int'];
     unitPriceWithTax: Scalars['Int'];
+    /**
+     * The price of a single unit including discounts, excluding tax.
+     *
+     * If Order-level discounts have been applied, this will not be the
+     * actual taxable unit price (see `proratedUnitPrice`), but is generally the
+     * correct price to display to customers to avoid confusion
+     * about the internal handling of distributed Order-level discounts.
+     */
+    discountedUnitPrice: Scalars['Int'];
+    /** The price of a single unit including discounts and tax */
+    discountedUnitPriceWithTax: Scalars['Int'];
+    /**
+     * The actual unit price, taking into account both item discounts _and_ prorated (proportially-distributed)
+     * Order-level discounts. This value is the true economic value of the OrderItem, and is used in tax
+     * and refund calculations.
+     */
+    proratedUnitPrice: Scalars['Int'];
+    /** The proratedUnitPrice including tax */
+    proratedUnitPriceWithTax: Scalars['Int'];
     quantity: Scalars['Int'];
     quantity: Scalars['Int'];
     items: Array<OrderItem>;
     items: Array<OrderItem>;
     /** @deprecated Use `linePriceWithTax` instead */
     /** @deprecated Use `linePriceWithTax` instead */

File diff suppressed because it is too large
+ 0 - 0
schema-admin.json


File diff suppressed because it is too large
+ 0 - 0
schema-shop.json


Some files were not shown because too many files changed in this diff