소스 검색

feat(core): Include with/without tax amounts on discounts

Relates to #749

BREAKING CHANGE: A minor breaking change has been made to the GraphQL API: The `Order.discounts` and
`OrderLine.discounts` fields now return `amount` and `amountWithTax`. Previously they only had
`amount`, which was actually the tax-inclusive value. So if you want to show discount amounts with
tax, use `amountWithTax` and otherwise use `amount`.
Michael Bromley 4 년 전
부모
커밋
2de6bf5f58

+ 11 - 3
packages/asset-server-plugin/e2e/graphql/generated-e2e-asset-server-plugin-types.ts

@@ -1393,7 +1393,7 @@ export type Order = Node & {
      * @deprecated Use `discounts` instead
      */
     adjustments: Array<Adjustment>;
-    discounts: Array<Adjustment>;
+    discounts: Array<Discount>;
     /** An array of all coupon codes applied to the Order */
     couponCodes: Array<Scalars['String']>;
     /** Promotions applied to the order. Only gets populated after the payment process has completed. */
@@ -3691,7 +3691,15 @@ export type ShippingLine = {
     priceWithTax: Scalars['Int'];
     discountedPrice: Scalars['Int'];
     discountedPriceWithTax: Scalars['Int'];
-    discounts: Array<Adjustment>;
+    discounts: Array<Discount>;
+};
+
+export type Discount = {
+    adjustmentSource: Scalars['String'];
+    type: AdjustmentType;
+    description: Scalars['String'];
+    amount: Scalars['Int'];
+    amountWithTax: Scalars['Int'];
 };
 
 export type OrderItem = Node & {
@@ -3790,7 +3798,7 @@ export type OrderLine = Node & {
     lineTax: Scalars['Int'];
     /** @deprecated Use `discounts` instead */
     adjustments: Array<Adjustment>;
-    discounts: Array<Adjustment>;
+    discounts: Array<Discount>;
     taxLines: Array<TaxLine>;
     order: Order;
     customFields?: Maybe<Scalars['JSON']>;

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 542 - 579
packages/common/src/generated-shop-types.ts


+ 12 - 3
packages/common/src/generated-types.ts

@@ -1564,7 +1564,7 @@ export type Order = Node & {
    * @deprecated Use `discounts` instead
    */
   adjustments: Array<Adjustment>;
-  discounts: Array<Adjustment>;
+  discounts: Array<Discount>;
   /** An array of all coupon codes applied to the Order */
   couponCodes: Array<Scalars['String']>;
   /** Promotions applied to the order. Only gets populated after the payment process has completed. */
@@ -3911,7 +3911,16 @@ export type ShippingLine = {
   priceWithTax: Scalars['Int'];
   discountedPrice: Scalars['Int'];
   discountedPriceWithTax: Scalars['Int'];
-  discounts: Array<Adjustment>;
+  discounts: Array<Discount>;
+};
+
+export type Discount = {
+  __typename?: 'Discount';
+  adjustmentSource: Scalars['String'];
+  type: AdjustmentType;
+  description: Scalars['String'];
+  amount: Scalars['Int'];
+  amountWithTax: Scalars['Int'];
 };
 
 export type OrderItem = Node & {
@@ -4012,7 +4021,7 @@ export type OrderLine = Node & {
   lineTax: Scalars['Int'];
   /** @deprecated Use `discounts` instead */
   adjustments: Array<Adjustment>;
-  discounts: Array<Adjustment>;
+  discounts: Array<Discount>;
   taxLines: Array<TaxLine>;
   order: Order;
   customFields?: Maybe<Scalars['JSON']>;

+ 11 - 3
packages/core/e2e/graphql/generated-e2e-admin-types.ts

@@ -1393,7 +1393,7 @@ export type Order = Node & {
      * @deprecated Use `discounts` instead
      */
     adjustments: Array<Adjustment>;
-    discounts: Array<Adjustment>;
+    discounts: Array<Discount>;
     /** An array of all coupon codes applied to the Order */
     couponCodes: Array<Scalars['String']>;
     /** Promotions applied to the order. Only gets populated after the payment process has completed. */
@@ -3691,7 +3691,15 @@ export type ShippingLine = {
     priceWithTax: Scalars['Int'];
     discountedPrice: Scalars['Int'];
     discountedPriceWithTax: Scalars['Int'];
-    discounts: Array<Adjustment>;
+    discounts: Array<Discount>;
+};
+
+export type Discount = {
+    adjustmentSource: Scalars['String'];
+    type: AdjustmentType;
+    description: Scalars['String'];
+    amount: Scalars['Int'];
+    amountWithTax: Scalars['Int'];
 };
 
 export type OrderItem = Node & {
@@ -3790,7 +3798,7 @@ export type OrderLine = Node & {
     lineTax: Scalars['Int'];
     /** @deprecated Use `discounts` instead */
     adjustments: Array<Adjustment>;
-    discounts: Array<Adjustment>;
+    discounts: Array<Discount>;
     taxLines: Array<TaxLine>;
     order: Order;
     customFields?: Maybe<Scalars['JSON']>;

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 508 - 545
packages/core/e2e/graphql/generated-e2e-shop-types.ts


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

@@ -18,6 +18,7 @@ export const TEST_ORDER_FRAGMENT = gql`
         discounts {
             adjustmentSource
             amount
+            amountWithTax
             description
             type
         }
@@ -36,6 +37,7 @@ export const TEST_ORDER_FRAGMENT = gql`
             discounts {
                 adjustmentSource
                 amount
+                amountWithTax
                 description
                 type
             }
@@ -86,6 +88,7 @@ export const UPDATED_ORDER_FRAGMENT = gql`
             discounts {
                 adjustmentSource
                 amount
+                amountWithTax
                 description
                 type
             }
@@ -93,6 +96,7 @@ export const UPDATED_ORDER_FRAGMENT = gql`
         discounts {
             adjustmentSource
             amount
+            amountWithTax
             description
             type
         }

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

@@ -353,7 +353,7 @@ describe('Promotions applied to Orders', () => {
             orderResultGuard.assertSuccess(adjustOrderLine);
             expect(adjustOrderLine!.totalWithTax).toBe(0);
             expect(adjustOrderLine!.discounts[0].description).toBe('Free if order total greater than 100');
-            expect(adjustOrderLine!.discounts[0].amount).toBe(-12000);
+            expect(adjustOrderLine!.discounts[0].amountWithTax).toBe(-12000);
 
             await deletePromotion(promotion.id);
         });
@@ -401,7 +401,7 @@ describe('Promotions applied to Orders', () => {
             expect(res2!.discounts[0].description).toBe(
                 'Free if order contains 2 items with Sale facet value',
             );
-            expect(res2!.discounts[0].amount).toBe(-1320);
+            expect(res2!.discounts[0].amountWithTax).toBe(-1320);
 
             await deletePromotion(promotion.id);
         });
@@ -452,7 +452,7 @@ describe('Promotions applied to Orders', () => {
             orderResultGuard.assertSuccess(adjustOrderLine);
             expect(adjustOrderLine!.total).toBe(0);
             expect(adjustOrderLine!.discounts[0].description).toBe('Free if buying 3 or more offer products');
-            expect(adjustOrderLine!.discounts[0].amount).toBe(-13200);
+            expect(adjustOrderLine!.discounts[0].amountWithTax).toBe(-13200);
 
             await deletePromotion(promotion.id);
         });
@@ -490,7 +490,7 @@ describe('Promotions applied to Orders', () => {
             expect(addItemToOrder!.totalWithTax).toBe(0);
             expect(addItemToOrder!.discounts.length).toBe(1);
             expect(addItemToOrder!.discounts[0].description).toBe('Free for group members');
-            expect(addItemToOrder!.discounts[0].amount).toBe(-6000);
+            expect(addItemToOrder!.discounts[0].amountWithTax).toBe(-6000);
 
             await adminClient.query<RemoveCustomersFromGroup.Mutation, RemoveCustomersFromGroup.Variables>(
                 REMOVE_CUSTOMERS_FROM_GROUP,

+ 11 - 3
packages/core/src/api/schema/common/order.type.graphql

@@ -25,7 +25,7 @@ type Order implements Node {
     surcharges: [Surcharge!]!
     "Order-level adjustments to the order total, such as discounts from promotions"
     adjustments: [Adjustment!]! @deprecated(reason: "Use `discounts` instead")
-    discounts: [Adjustment!]!
+    discounts: [Discount!]!
     "An array of all coupon codes applied to the Order"
     couponCodes: [String!]!
     "Promotions applied to the order. Only gets populated after the payment process has completed."
@@ -100,7 +100,15 @@ type ShippingLine {
     priceWithTax: Int!
     discountedPrice: Int!
     discountedPriceWithTax: Int!
-    discounts: [Adjustment!]!
+    discounts: [Discount!]!
+}
+
+type Discount {
+    adjustmentSource: String!
+    type: AdjustmentType!
+    description: String!
+    amount: Int!
+    amountWithTax: Int!
 }
 
 type OrderItem implements Node {
@@ -204,7 +212,7 @@ type OrderLine implements Node {
     "The total tax on this line"
     lineTax: Int!
     adjustments: [Adjustment!]! @deprecated(reason: "Use `discounts` instead")
-    discounts: [Adjustment!]!
+    discounts: [Discount!]!
     taxLines: [TaxLine!]!
     order: Order!
 }

+ 16 - 13
packages/core/src/entity/order-line/order-line.entity.ts

@@ -1,4 +1,4 @@
-import { Adjustment, AdjustmentType, TaxLine } from '@vendure/common/lib/generated-types';
+import { Adjustment, AdjustmentType, Discount, 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, OneToMany } from 'typeorm';
@@ -144,26 +144,29 @@ export class OrderLine extends VendureEntity implements HasCustomFields {
     }
 
     @Calculated()
-    get discounts(): Adjustment[] {
+    get discounts(): Discount[] {
         const priceIncludesTax = this.items?.[0]?.listPriceIncludesTax ?? false;
         // 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 groupedDiscounts = new Map<string, Discount>();
+        for (const adjustment of this.adjustments) {
+            const discountGroup = groupedDiscounts.get(adjustment.adjustmentSource);
+            const amount = priceIncludesTax ? netPriceOf(adjustment.amount, this.taxRate) : adjustment.amount;
             const amountWithTax = priceIncludesTax
-                ? discount.amount
-                : grossPriceOf(discount.amount, this.taxRate);
-            if (adjustment) {
-                adjustment.amount += amountWithTax;
+                ? adjustment.amount
+                : grossPriceOf(adjustment.amount, this.taxRate);
+            if (discountGroup) {
+                discountGroup.amount += amount;
+                discountGroup.amountWithTax += amountWithTax;
             } else {
-                groupedAdjustments.set(discount.adjustmentSource, {
-                    ...discount,
-                    amount: amountWithTax,
+                groupedDiscounts.set(adjustment.adjustmentSource, {
+                    ...(adjustment as Omit<Adjustment, '__typename'>),
+                    amount,
+                    amountWithTax,
                 });
             }
         }
-        return [...groupedAdjustments.values()];
+        return [...groupedDiscounts.values()];
     }
 
     @Calculated()

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

@@ -1,6 +1,7 @@
 import {
     Adjustment,
     CurrencyCode,
+    Discount,
     OrderAddress,
     OrderTaxSummary,
     TaxLine,
@@ -10,7 +11,6 @@ import { summate } from '@vendure/common/lib/shared-utils';
 import { Column, Entity, JoinTable, ManyToMany, ManyToOne, OneToMany } from 'typeorm';
 
 import { Calculated } from '../../common/calculated-decorator';
-import { taxPayableOn } from '../../common/tax-utils';
 import { ChannelAware } from '../../common/types/common-types';
 import { HasCustomFields } from '../../config/custom-field/custom-field-types';
 import { OrderState } from '../../service/helpers/order-state-machine/order-state';
@@ -25,7 +25,6 @@ import { OrderModification } from '../order-modification/order-modification.enti
 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';
 
 /**
@@ -110,13 +109,14 @@ export class Order extends VendureEntity implements ChannelAware, HasCustomField
     shippingWithTax: number;
 
     @Calculated()
-    get discounts(): Adjustment[] {
-        const groupedAdjustments = new Map<string, Adjustment>();
+    get discounts(): Discount[] {
+        const groupedAdjustments = new Map<string, Discount>();
         for (const line of this.lines) {
             for (const discount of line.discounts) {
                 const adjustment = groupedAdjustments.get(discount.adjustmentSource);
                 if (adjustment) {
                     adjustment.amount += discount.amount;
+                    adjustment.amountWithTax += discount.amountWithTax;
                 } else {
                     groupedAdjustments.set(discount.adjustmentSource, { ...discount });
                 }
@@ -127,6 +127,7 @@ export class Order extends VendureEntity implements ChannelAware, HasCustomField
                 const adjustment = groupedAdjustments.get(discount.adjustmentSource);
                 if (adjustment) {
                     adjustment.amount += discount.amount;
+                    adjustment.amountWithTax += discount.amountWithTax;
                 } else {
                     groupedAdjustments.set(discount.adjustmentSource, { ...discount });
                 }

+ 17 - 3
packages/core/src/entity/shipping-line/shipping-line.entity.ts

@@ -1,4 +1,4 @@
-import { Adjustment, AdjustmentType, TaxLine } from '@vendure/common/lib/generated-types';
+import { Adjustment, AdjustmentType, Discount, TaxLine } from '@vendure/common/lib/generated-types';
 import { DeepPartial, ID } from '@vendure/common/lib/shared-types';
 import { summate } from '@vendure/common/lib/shared-utils';
 import { Column, Entity, ManyToOne } from 'typeorm';
@@ -66,8 +66,22 @@ export class ShippingLine extends VendureEntity {
     }
 
     @Calculated()
-    get discounts(): Adjustment[] {
-        return this.adjustments || [];
+    get discounts(): Discount[] {
+        return (
+            this.adjustments?.map(adjustment => {
+                const amount = this.listPriceIncludesTax
+                    ? netPriceOf(adjustment.amount, this.taxRate)
+                    : adjustment.amount;
+                const amountWithTax = this.listPriceIncludesTax
+                    ? adjustment.amount
+                    : grossPriceOf(adjustment.amount, this.taxRate);
+                return {
+                    ...(adjustment as Omit<Adjustment, '__typename'>),
+                    amount,
+                    amountWithTax,
+                };
+            }) ?? []
+        );
     }
 
     addAdjustment(adjustment: Adjustment) {

+ 14 - 1
packages/core/src/service/helpers/order-calculator/order-calculator.spec.ts

@@ -1572,6 +1572,16 @@ function assertOrderTotalsAddUp(order: Order) {
         expect(line.linePrice).toBe(itemUnitPriceSum);
         const itemUnitPriceWithTaxSum = summate(line.items, 'unitPriceWithTax');
         expect(line.linePriceWithTax).toBe(itemUnitPriceWithTaxSum);
+
+        const pricesIncludeTax = line.items[0].listPriceIncludesTax;
+
+        if (pricesIncludeTax) {
+            const lineDiscountsAmountWithTaxSum = summate(line.discounts, 'amountWithTax');
+            expect(line.linePriceWithTax + lineDiscountsAmountWithTaxSum).toBe(line.proratedLinePriceWithTax);
+        } else {
+            const lineDiscountsAmountSum = summate(line.discounts, 'amount');
+            expect(line.linePrice + lineDiscountsAmountSum).toBe(line.proratedLinePrice);
+        }
     }
     const taxableLinePriceSum = summate(order.lines, 'proratedLinePrice');
     const surchargeSum = summate(order.surcharges, 'price');
@@ -1583,12 +1593,15 @@ function assertOrderTotalsAddUp(order: Order) {
     const orderDiscountsSum = order.discounts
         .filter(d => d.type === AdjustmentType.DISTRIBUTED_ORDER_PROMOTION)
         .reduce((sum, d) => sum + d.amount, 0);
+    const orderDiscountsWithTaxSum = order.discounts
+        .filter(d => d.type === AdjustmentType.DISTRIBUTED_ORDER_PROMOTION)
+        .reduce((sum, d) => sum + d.amountWithTax, 0);
 
     // The sum of the display prices + order discounts should in theory exactly
     // equal the subTotalWithTax. In practice, there are occasionally 1cent differences
     // cause by rounding errors. This should be tolerable.
     const differenceBetweenSumAndActual = Math.abs(
-        displayPriceWithTaxSum + orderDiscountsSum + surchargeWithTaxSum - order.subTotalWithTax,
+        displayPriceWithTaxSum + orderDiscountsWithTaxSum + surchargeWithTaxSum - order.subTotalWithTax,
     );
     expect(differenceBetweenSumAndActual).toBeLessThanOrEqual(1);
 }

+ 11 - 3
packages/elasticsearch-plugin/e2e/graphql/generated-e2e-elasticsearch-plugin-types.ts

@@ -1393,7 +1393,7 @@ export type Order = Node & {
      * @deprecated Use `discounts` instead
      */
     adjustments: Array<Adjustment>;
-    discounts: Array<Adjustment>;
+    discounts: Array<Discount>;
     /** An array of all coupon codes applied to the Order */
     couponCodes: Array<Scalars['String']>;
     /** Promotions applied to the order. Only gets populated after the payment process has completed. */
@@ -3691,7 +3691,15 @@ export type ShippingLine = {
     priceWithTax: Scalars['Int'];
     discountedPrice: Scalars['Int'];
     discountedPriceWithTax: Scalars['Int'];
-    discounts: Array<Adjustment>;
+    discounts: Array<Discount>;
+};
+
+export type Discount = {
+    adjustmentSource: Scalars['String'];
+    type: AdjustmentType;
+    description: Scalars['String'];
+    amount: Scalars['Int'];
+    amountWithTax: Scalars['Int'];
 };
 
 export type OrderItem = Node & {
@@ -3790,7 +3798,7 @@ export type OrderLine = Node & {
     lineTax: Scalars['Int'];
     /** @deprecated Use `discounts` instead */
     adjustments: Array<Adjustment>;
-    discounts: Array<Adjustment>;
+    discounts: Array<Discount>;
     taxLines: Array<TaxLine>;
     order: Order;
     customFields?: Maybe<Scalars['JSON']>;

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 0
schema-admin.json


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 0
schema-shop.json


이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.