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

fix(core): Prevent negative total from compounded promotions

Fixes #2385
Michael Bromley 2 лет назад
Родитель
Сommit
0740c87334

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

@@ -1550,8 +1550,8 @@ describe('Promotions applied to Orders', () => {
                 >(APPLY_COUPON_CODE, { couponCode: TEST_COUPON_CODE });
                 >(APPLY_COUPON_CODE, { couponCode: TEST_COUPON_CODE });
                 orderResultGuard.assertSuccess(applyCouponCode);
                 orderResultGuard.assertSuccess(applyCouponCode);
 
 
-                expect(applyCouponCode!.totalWithTax).toBe(0);
-                expect(applyCouponCode!.couponCodes).toEqual([TEST_COUPON_CODE]);
+                expect(applyCouponCode.totalWithTax).toBe(0);
+                expect(applyCouponCode.couponCodes).toEqual([TEST_COUPON_CODE]);
 
 
                 await shopClient.query<SetCustomerForOrder.Mutation, SetCustomerForOrder.Variables>(
                 await shopClient.query<SetCustomerForOrder.Mutation, SetCustomerForOrder.Variables>(
                     SET_CUSTOMER,
                     SET_CUSTOMER,
@@ -1565,8 +1565,8 @@ describe('Promotions applied to Orders', () => {
                 );
                 );
 
 
                 const { activeOrder } = await shopClient.query<GetActiveOrder.Query>(GET_ACTIVE_ORDER);
                 const { activeOrder } = await shopClient.query<GetActiveOrder.Query>(GET_ACTIVE_ORDER);
-                expect(activeOrder!.couponCodes).toEqual([TEST_COUPON_CODE]);
-                expect(applyCouponCode!.totalWithTax).toBe(0);
+                expect(activeOrder.couponCodes).toEqual([TEST_COUPON_CODE]);
+                expect(applyCouponCode.totalWithTax).toBe(0);
             });
             });
         });
         });
 
 
@@ -1755,6 +1755,81 @@ describe('Promotions applied to Orders', () => {
         expect(applyCouponCode.totalWithTax).toBe(96);
         expect(applyCouponCode.totalWithTax).toBe(96);
     });
     });
 
 
+    // https://github.com/vendure-ecommerce/vendure/issues/2385
+    it('prevents negative line price', async () => {
+        await shopClient.asAnonymousUser();
+        const item1000 = getVariantBySlug('item-1000')!;
+        const couponCode1 = '100%_off';
+        const couponCode2 = '100%_off';
+        await createPromotion({
+            enabled: true,
+            name: '100% discount ',
+            couponCode: couponCode1,
+            conditions: [],
+            actions: [
+                {
+                    code: productsPercentageDiscount.code,
+                    arguments: [
+                        { name: 'discount', value: '100' },
+                        {
+                            name: 'productVariantIds',
+                            value: `["${item1000.id}"]`,
+                        },
+                    ],
+                },
+            ],
+        });
+        await createPromotion({
+            enabled: true,
+            name: '20% discount ',
+            couponCode: couponCode2,
+            conditions: [],
+            actions: [
+                {
+                    code: productsPercentageDiscount.code,
+                    arguments: [
+                        { name: 'discount', value: '20' },
+                        {
+                            name: 'productVariantIds',
+                            value: `["${item1000.id}"]`,
+                        },
+                    ],
+                },
+            ],
+        });
+
+        await shopClient.query<
+            CodegenShop.ApplyCouponCodeMutation,
+            CodegenShop.ApplyCouponCodeMutationVariables
+        >(APPLY_COUPON_CODE, { couponCode: couponCode1 });
+
+        await shopClient.query<
+            CodegenShop.AddItemToOrderMutation,
+            CodegenShop.AddItemToOrderMutationVariables
+        >(ADD_ITEM_TO_ORDER, {
+            productVariantId: item1000.id,
+            quantity: 1,
+        });
+
+        const { activeOrder: check1 } = await shopClient.query<CodegenShop.GetActiveOrderQuery>(
+            GET_ACTIVE_ORDER,
+        );
+
+        expect(check1!.lines[0].discountedUnitPriceWithTax).toBe(0);
+        expect(check1!.totalWithTax).toBe(0);
+
+        await shopClient.query<
+            CodegenShop.ApplyCouponCodeMutation,
+            CodegenShop.ApplyCouponCodeMutationVariables
+        >(APPLY_COUPON_CODE, { couponCode: couponCode2 });
+
+        const { activeOrder: check2 } = await shopClient.query<CodegenShop.GetActiveOrderQuery>(
+            GET_ACTIVE_ORDER,
+        );
+        expect(check2!.lines[0].discountedUnitPriceWithTax).toBe(0);
+        expect(check2!.totalWithTax).toBe(0);
+    });
+
     async function getProducts() {
     async function getProducts() {
         const result = await adminClient.query<Codegen.GetProductsWithVariantPricesQuery>(
         const result = await adminClient.query<Codegen.GetProductsWithVariantPricesQuery>(
             GET_PRODUCTS_WITH_VARIANT_PRICES,
             GET_PRODUCTS_WITH_VARIANT_PRICES,

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

@@ -330,7 +330,16 @@ export class OrderLine extends VendureEntity implements HasCustomFields {
     }
     }
 
 
     addAdjustment(adjustment: Adjustment) {
     addAdjustment(adjustment: Adjustment) {
-        this.adjustments = this.adjustments.concat(adjustment);
+        // We should not allow adding adjustments which would
+        // result in a negative unit price
+        const maxDiscount = this.proratedLinePrice * -1;
+        const limitedAdjustment: Adjustment = {
+            ...adjustment,
+            amount: Math.max(maxDiscount, adjustment.amount),
+        };
+        if (limitedAdjustment.amount !== 0) {
+            this.adjustments = this.adjustments.concat(limitedAdjustment);
+        }
     }
     }
 
 
     /**
     /**