Browse Source

feat(core): Implement product discount promotion action

Relates to #400
Michael Bromley 5 years ago
parent
commit
7da0d46fe1

+ 53 - 0
packages/core/e2e/order-promotion.e2e-spec.ts

@@ -7,6 +7,7 @@ import {
     hasFacetValues,
     minimumOrderAmount,
     orderPercentageDiscount,
+    productsPercentageDiscount,
 } from '@vendure/core';
 import { createTestEnvironment } from '@vendure/testing';
 import gql from 'graphql-tag';
@@ -486,6 +487,58 @@ describe('Promotions applied to Orders', () => {
             await deletePromotion(promotion.id);
         });
 
+        it('productsPercentageDiscount', async () => {
+            const item60 = getVariantBySlug('item-60');
+            const couponCode = '50%_off_product';
+            const promotion = await createPromotion({
+                enabled: true,
+                name: '50% off product',
+                couponCode,
+                conditions: [],
+                actions: [
+                    {
+                        code: productsPercentageDiscount.code,
+                        arguments: [
+                            { name: 'discount', value: '50' },
+                            { name: 'productVariantIds', value: `["${item60.id}"]` },
+                        ],
+                    },
+                ],
+            });
+            const { addItemToOrder } = await shopClient.query<
+                AddItemToOrder.Mutation,
+                AddItemToOrder.Variables
+            >(ADD_ITEM_TO_ORDER, {
+                productVariantId: item60.id,
+                quantity: 1,
+            });
+            expect(addItemToOrder!.adjustments.length).toBe(0);
+            expect(addItemToOrder!.lines[0].adjustments.length).toBe(1); // 1x tax
+            expect(addItemToOrder!.total).toBe(6000);
+
+            const { applyCouponCode } = await shopClient.query<
+                ApplyCouponCode.Mutation,
+                ApplyCouponCode.Variables
+            >(APPLY_COUPON_CODE, {
+                couponCode,
+            });
+
+            expect(applyCouponCode!.total).toBe(3000);
+            expect(applyCouponCode!.lines[0].adjustments.length).toBe(2); // 1x tax, 1x promotion
+
+            const { removeCouponCode } = await shopClient.query<
+                RemoveCouponCode.Mutation,
+                RemoveCouponCode.Variables
+            >(REMOVE_COUPON_CODE, {
+                couponCode,
+            });
+
+            expect(removeCouponCode!.lines[0].adjustments.length).toBe(1); // 1x tax
+            expect(removeCouponCode!.total).toBe(6000);
+
+            await deletePromotion(promotion.id);
+        });
+
         it('multiple promotions simultaneously', async () => {
             const { facets } = await adminClient.query<GetFacetList.Query>(GET_FACET_LIST);
             const saleFacetValue = facets.items[0].values[0];

+ 37 - 0
packages/core/src/config/promotion/actions/product-discount-action.ts

@@ -0,0 +1,37 @@
+import { LanguageCode } from '@vendure/common/lib/generated-types';
+import { ID } from '@vendure/common/lib/shared-types';
+
+import { idsAreEqual } from '../../../common/utils';
+import { OrderLine } from '../../../entity/order-line/order-line.entity';
+import { PromotionItemAction } from '../promotion-action';
+
+export const productsPercentageDiscount = new PromotionItemAction({
+    code: 'products_percentage_discount',
+    description: [{ languageCode: LanguageCode.en, value: 'Discount specified products by { discount }%' }],
+    args: {
+        discount: {
+            type: 'int',
+            ui: {
+                component: 'number-form-input',
+                suffix: '%',
+            },
+        },
+        productVariantIds: {
+            type: 'ID',
+            list: true,
+            ui: { component: 'product-selector-form-input' },
+            label: [{ languageCode: LanguageCode.en, value: 'Product variants' }],
+        },
+    },
+
+    execute(orderItem, orderLine, args) {
+        if (lineContainsIds(args.productVariantIds, orderLine)) {
+            return -orderLine.unitPrice * (args.discount / 100);
+        }
+        return 0;
+    },
+});
+
+function lineContainsIds(ids: ID[], line: OrderLine): boolean {
+    return !!ids.find(id => idsAreEqual(id, line.productVariant.id));
+}

+ 7 - 1
packages/core/src/config/promotion/index.ts

@@ -1,5 +1,6 @@
 import { discountOnItemWithFacets } from './actions/facet-values-discount-action';
 import { orderPercentageDiscount } from './actions/order-percentage-discount-action';
+import { productsPercentageDiscount } from './actions/product-discount-action';
 import { containsProducts } from './conditions/contains-products-condition';
 import { hasFacetValues } from './conditions/has-facet-values-condition';
 import { minimumOrderAmount } from './conditions/min-order-amount-condition';
@@ -8,9 +9,14 @@ export * from './promotion-action';
 export * from './promotion-condition';
 export * from './actions/facet-values-discount-action';
 export * from './actions/order-percentage-discount-action';
+export * from './actions/product-discount-action';
 export * from './conditions/has-facet-values-condition';
 export * from './conditions/min-order-amount-condition';
 export * from './conditions/contains-products-condition';
 
-export const defaultPromotionActions = [orderPercentageDiscount, discountOnItemWithFacets];
+export const defaultPromotionActions = [
+    orderPercentageDiscount,
+    discountOnItemWithFacets,
+    productsPercentageDiscount,
+];
 export const defaultPromotionConditions = [minimumOrderAmount, hasFacetValues, containsProducts];

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