From 688d3049e4361ce4cfedc1d43eff86a3df97a8fe Mon Sep 17 00:00:00 2001 From: Michael Bromley Date: Thu, 13 Aug 2020 20:50:34 +0200 Subject: [PATCH] feat(core): Implement "containsProducts" PromotionCondition Relates to #400 --- packages/common/src/shared-types.ts | 2 + packages/core/e2e/order-promotion.e2e-spec.ts | 48 +++++++++++++++++++ .../conditions/contains-products-condition.ts | 37 ++++++++++++++ packages/core/src/config/promotion/index.ts | 4 +- 4 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 packages/core/src/config/promotion/conditions/contains-products-condition.ts diff --git a/packages/common/src/shared-types.ts b/packages/common/src/shared-types.ts index 3386679601..7bd08419aa 100644 --- a/packages/common/src/shared-types.ts +++ b/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': {}; }; diff --git a/packages/core/e2e/order-promotion.e2e-spec.ts b/packages/core/e2e/order-promotion.e2e-spec.ts index 934813e189..59c9047a1d 100644 --- a/packages/core/e2e/order-promotion.e2e-spec.ts +++ b/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(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', () => { diff --git a/packages/core/src/config/promotion/conditions/contains-products-condition.ts b/packages/core/src/config/promotion/conditions/contains-products-condition.ts new file mode 100644 index 0000000000..0fe1c93d0d --- /dev/null +++ b/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)); +} diff --git a/packages/core/src/config/promotion/index.ts b/packages/core/src/config/promotion/index.ts index 46bb96f46e..611fc9a56b 100644 --- a/packages/core/src/config/promotion/index.ts +++ b/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];