Browse Source

feat(core): Implement "containsProducts" PromotionCondition

Relates to #400
Michael Bromley 5 years ago
parent
commit
688d3049e4

+ 2 - 0
packages/common/src/shared-types.ts

@@ -120,6 +120,7 @@ export type DefaultFormComponentId =
     | 'facet-value-form-input'
     | 'number-form-input'
     | 'select-form-input'
+    | 'product-selector-form-input'
     | 'text-form-input';
 
 /**
@@ -132,6 +133,7 @@ type DefaultFormConfigHash = {
     'boolean-form-input': {};
     'currency-form-input': {};
     'facet-value-form-input': {};
+    'product-selector-form-input': {};
     'text-form-input': {};
 };
 

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

@@ -2,6 +2,7 @@
 import { omit } from '@vendure/common/lib/omit';
 import { pick } from '@vendure/common/lib/pick';
 import {
+    containsProducts,
     discountOnItemWithFacets,
     hasFacetValues,
     minimumOrderAmount,
@@ -327,6 +328,53 @@ describe('Promotions applied to Orders', () => {
 
             await deletePromotion(promotion.id);
         });
+
+        it('containsProducts', async () => {
+            const item60 = getVariantBySlug('item-60');
+            const item12 = getVariantBySlug('item-12');
+            const promotion = await createPromotion({
+                enabled: true,
+                name: 'Free if buying 3 or more offer products',
+                conditions: [
+                    {
+                        code: containsProducts.code,
+                        arguments: [
+                            { name: 'minimum', value: '3' },
+                            { name: 'productVariantIds', value: JSON.stringify([item60.id, item12.id]) },
+                        ],
+                    },
+                ],
+                actions: [freeOrderAction],
+            });
+            await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(ADD_ITEM_TO_ORDER, {
+                productVariantId: item60.id,
+                quantity: 1,
+            });
+            const { addItemToOrder } = await shopClient.query<
+                AddItemToOrder.Mutation,
+                AddItemToOrder.Variables
+            >(ADD_ITEM_TO_ORDER, {
+                productVariantId: item12.id,
+                quantity: 1,
+            });
+            expect(addItemToOrder!.total).toBe(7200);
+            expect(addItemToOrder!.adjustments.length).toBe(0);
+
+            const { adjustOrderLine } = await shopClient.query<
+                AdjustItemQuantity.Mutation,
+                AdjustItemQuantity.Variables
+            >(ADJUST_ITEM_QUANTITY, {
+                orderLineId: addItemToOrder!.lines[0].id,
+                quantity: 2,
+            });
+            expect(adjustOrderLine!.total).toBe(0);
+            expect(adjustOrderLine!.adjustments[0].description).toBe(
+                'Free if buying 3 or more offer products',
+            );
+            expect(adjustOrderLine!.adjustments[0].amount).toBe(-13200);
+
+            await deletePromotion(promotion.id);
+        });
     });
 
     describe('default PromotionActions', () => {

+ 37 - 0
packages/core/src/config/promotion/conditions/contains-products-condition.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 { Order } from '../../../entity/order/order.entity';
+import { PromotionCondition } from '../promotion-condition';
+
+export const containsProducts = new PromotionCondition({
+    code: 'contains_products',
+    description: [
+        { languageCode: LanguageCode.en, value: 'Buy at least { minimum } of the specified products' },
+    ],
+    args: {
+        minimum: { type: 'int' },
+        productVariantIds: {
+            type: 'ID',
+            list: true,
+            ui: { component: 'product-selector-form-input' },
+            label: [{ languageCode: LanguageCode.en, value: 'Product variants' }],
+        },
+    },
+    async check(order: Order, args) {
+        const ids = args.productVariantIds;
+        let matches = 0;
+        for (const line of order.lines) {
+            if (lineContainsIds(ids, line)) {
+                matches += line.quantity;
+            }
+        }
+        return args.minimum <= matches;
+    },
+});
+
+function lineContainsIds(ids: ID[], line: OrderLine): boolean {
+    return !!ids.find(id => idsAreEqual(id, line.productVariant.id));
+}

+ 3 - 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 { containsProducts } from './conditions/contains-products-condition';
 import { hasFacetValues } from './conditions/has-facet-values-condition';
 import { minimumOrderAmount } from './conditions/min-order-amount-condition';
 
@@ -9,6 +10,7 @@ export * from './actions/facet-values-discount-action';
 export * from './actions/order-percentage-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 defaultPromotionConditions = [minimumOrderAmount, hasFacetValues];
+export const defaultPromotionConditions = [minimumOrderAmount, hasFacetValues, containsProducts];