From e749dd653c755bfc3632b134d32c15ceaee0a852 Mon Sep 17 00:00:00 2001 From: Riqwan Thamir Date: Wed, 31 Jan 2024 13:30:33 +0100 Subject: [PATCH] feat(types): added computed actions for automatic promotions (#6272) what: - compute actions account for automatic promotions (RESOLVES CORE-1701) RESOLVES CORE-1689 --- .changeset/perfect-spies-hug.md | 5 + .../promotion-module/compute-actions.spec.ts | 450 +++++++++++++++++- .../src/services/promotion-module.ts | 17 +- .../src/promotion/common/compute-actions.ts | 4 + 4 files changed, 470 insertions(+), 6 deletions(-) create mode 100644 .changeset/perfect-spies-hug.md diff --git a/.changeset/perfect-spies-hug.md b/.changeset/perfect-spies-hug.md new file mode 100644 index 0000000000..16f08ba6fd --- /dev/null +++ b/.changeset/perfect-spies-hug.md @@ -0,0 +1,5 @@ +--- +"@medusajs/types": patch +--- + +feat(types): added computed actions for automatic promotions diff --git a/packages/promotion/integration-tests/__tests__/services/promotion-module/compute-actions.spec.ts b/packages/promotion/integration-tests/__tests__/services/promotion-module/compute-actions.spec.ts index 09c7cdcaaf..ee44ad5445 100644 --- a/packages/promotion/integration-tests/__tests__/services/promotion-module/compute-actions.spec.ts +++ b/packages/promotion/integration-tests/__tests__/services/promotion-module/compute-actions.spec.ts @@ -1,11 +1,11 @@ +import { Modules } from "@medusajs/modules-sdk" import { IPromotionModuleService } from "@medusajs/types" import { PromotionType } from "@medusajs/utils" import { SqlEntityManager } from "@mikro-orm/postgresql" +import { initModules } from "medusa-test-utils" import { createCampaigns } from "../../../__fixtures__/campaigns" import { MikroOrmWrapper } from "../../../utils" import { getInitModuleConfig } from "../../../utils/get-init-module-config" -import { Modules } from "@medusajs/modules-sdk" -import { initModules } from "medusa-test-utils" jest.setTimeout(30000) @@ -239,6 +239,84 @@ describe("Promotion Service: computeActions", () => { ]) }) + it("should compute the correct item amendments when promotion is automatic", async () => { + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + is_automatic: true, + type: PromotionType.STANDARD, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + application_method: { + type: "fixed", + target_type: "items", + allocation: "each", + value: "200", + max_quantity: 1, + target_rules: [ + { + attribute: "product_category.id", + operator: "eq", + values: ["catg_cotton"], + }, + ], + }, + }, + ]) + + const result = await service.computeActions([], { + customer: { + customer_group: { + id: "VIP", + }, + }, + items: [ + { + id: "item_cotton_tshirt", + quantity: 1, + unit_price: 100, + product_category: { + id: "catg_cotton", + }, + product: { + id: "prod_tshirt", + }, + }, + { + id: "item_cotton_sweater", + quantity: 5, + unit_price: 150, + product_category: { + id: "catg_cotton", + }, + product: { + id: "prod_sweater", + }, + }, + ], + }) + + expect(result).toEqual([ + { + action: "addItemAdjustment", + item_id: "item_cotton_tshirt", + amount: 100, + code: "PROMOTION_TEST", + }, + { + action: "addItemAdjustment", + item_id: "item_cotton_sweater", + amount: 150, + code: "PROMOTION_TEST", + }, + ]) + }) + it("should compute the correct item amendments when there are multiple promotions to apply", async () => { const [createdPromotion] = await service.create([ { @@ -666,6 +744,83 @@ describe("Promotion Service: computeActions", () => { ]) }) + it("should compute the correct item amendments when promotion is automatic", async () => { + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + is_automatic: true, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + application_method: { + type: "fixed", + target_type: "items", + allocation: "across", + value: "400", + target_rules: [ + { + attribute: "product_category.id", + operator: "eq", + values: ["catg_cotton"], + }, + ], + }, + }, + ]) + + const result = await service.computeActions([], { + customer: { + customer_group: { + id: "VIP", + }, + }, + items: [ + { + id: "item_cotton_tshirt", + quantity: 2, + unit_price: 100, + product_category: { + id: "catg_cotton", + }, + product: { + id: "prod_tshirt", + }, + }, + { + id: "item_cotton_sweater", + quantity: 2, + unit_price: 300, + product_category: { + id: "catg_cotton", + }, + product: { + id: "prod_sweater", + }, + }, + ], + }) + + expect(result).toEqual([ + { + action: "addItemAdjustment", + item_id: "item_cotton_tshirt", + amount: 100, + code: "PROMOTION_TEST", + }, + { + action: "addItemAdjustment", + item_id: "item_cotton_sweater", + amount: 300, + code: "PROMOTION_TEST", + }, + ]) + }) + it("should compute the correct item amendments when there are multiple promotions to apply", async () => { const [createdPromotion] = await service.create([ { @@ -1099,6 +1254,151 @@ describe("Promotion Service: computeActions", () => { ]) }) + it("should compute the correct shipping_method amendments when promotion is automatic", async () => { + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + is_automatic: true, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + application_method: { + type: "fixed", + target_type: "shipping_methods", + allocation: "each", + value: "200", + max_quantity: 2, + target_rules: [ + { + attribute: "shipping_option.id", + operator: "in", + values: ["express", "standard"], + }, + ], + }, + }, + ]) + + const result = await service.computeActions([], { + customer: { + customer_group: { + id: "VIP", + }, + }, + shipping_methods: [ + { + id: "shipping_method_express", + unit_price: 250, + shipping_option: { + id: "express", + }, + }, + { + id: "shipping_method_standard", + unit_price: 150, + shipping_option: { + id: "standard", + }, + }, + { + id: "shipping_method_snail", + unit_price: 200, + shipping_option: { + id: "snail", + }, + }, + ], + }) + + expect(result).toEqual([ + { + action: "addShippingMethodAdjustment", + shipping_method_id: "shipping_method_express", + amount: 200, + code: "PROMOTION_TEST", + }, + { + action: "addShippingMethodAdjustment", + shipping_method_id: "shipping_method_standard", + amount: 150, + code: "PROMOTION_TEST", + }, + ]) + }) + + it("should compute the correct shipping_method amendments when promotion is automatic and prevent_auto_promotions is false", async () => { + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + is_automatic: true, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + application_method: { + type: "fixed", + target_type: "shipping_methods", + allocation: "each", + value: "200", + max_quantity: 2, + target_rules: [ + { + attribute: "shipping_option.id", + operator: "in", + values: ["express", "standard"], + }, + ], + }, + }, + ]) + + const result = await service.computeActions( + [], + { + customer: { + customer_group: { + id: "VIP", + }, + }, + shipping_methods: [ + { + id: "shipping_method_express", + unit_price: 250, + shipping_option: { + id: "express", + }, + }, + { + id: "shipping_method_standard", + unit_price: 150, + shipping_option: { + id: "standard", + }, + }, + { + id: "shipping_method_snail", + unit_price: 200, + shipping_option: { + id: "snail", + }, + }, + ], + }, + { prevent_auto_promotions: true } + ) + + expect(result).toEqual([]) + }) + it("should compute the correct item amendments when there are multiple promotions to apply", async () => { const [createdPromotion] = await service.create([ { @@ -1509,6 +1809,82 @@ describe("Promotion Service: computeActions", () => { ]) }) + it("should compute the correct shipping_method amendments when promotion is automatic", async () => { + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + is_automatic: true, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + application_method: { + type: "fixed", + target_type: "shipping_methods", + allocation: "across", + value: "200", + target_rules: [ + { + attribute: "shipping_option.id", + operator: "in", + values: ["express", "standard"], + }, + ], + }, + }, + ]) + + const result = await service.computeActions([], { + customer: { + customer_group: { + id: "VIP", + }, + }, + shipping_methods: [ + { + id: "shipping_method_express", + unit_price: 500, + shipping_option: { + id: "express", + }, + }, + { + id: "shipping_method_standard", + unit_price: 100, + shipping_option: { + id: "standard", + }, + }, + { + id: "shipping_method_snail", + unit_price: 200, + shipping_option: { + id: "snail", + }, + }, + ], + }) + + expect(result).toEqual([ + { + action: "addShippingMethodAdjustment", + shipping_method_id: "shipping_method_express", + amount: 166.66666666666669, + code: "PROMOTION_TEST", + }, + { + action: "addShippingMethodAdjustment", + shipping_method_id: "shipping_method_standard", + amount: 33.33333333333333, + code: "PROMOTION_TEST", + }, + ]) + }) + it("should compute the correct item amendments when there are multiple promotions to apply", async () => { const [createdPromotion] = await service.create([ { @@ -1913,6 +2289,76 @@ describe("Promotion Service: computeActions", () => { ]) }) + it("should compute the correct item amendments when promotion is automatic", async () => { + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + is_automatic: true, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + application_method: { + type: "fixed", + target_type: "order", + value: "200", + max_quantity: 2, + }, + }, + ]) + + const result = await service.computeActions([], { + customer: { + customer_group: { + id: "VIP", + }, + }, + items: [ + { + id: "item_cotton_tshirt", + quantity: 1, + unit_price: 100, + product_category: { + id: "catg_cotton", + }, + product: { + id: "prod_tshirt", + }, + }, + { + id: "item_cotton_sweater", + quantity: 2, + unit_price: 150, + product_category: { + id: "catg_cotton", + }, + product: { + id: "prod_sweater", + }, + }, + ], + }) + + expect(result).toEqual([ + { + action: "addItemAdjustment", + item_id: "item_cotton_tshirt", + amount: 50, + code: "PROMOTION_TEST", + }, + { + action: "addItemAdjustment", + item_id: "item_cotton_sweater", + amount: 150, + code: "PROMOTION_TEST", + }, + ]) + }) + it("should compute the correct item amendments when there are multiple promotions to apply", async () => { const [createdPromotion] = await service.create([ { diff --git a/packages/promotion/src/services/promotion-module.ts b/packages/promotion/src/services/promotion-module.ts index ecdc1b5113..dfa0510242 100644 --- a/packages/promotion/src/services/promotion-module.ts +++ b/packages/promotion/src/services/promotion-module.ts @@ -216,11 +216,11 @@ export default class PromotionModuleService< } async computeActions( - promotionCodesToApply: string[], + promotionCodes: string[], applicationContext: PromotionTypes.ComputeActionContext, - // TODO: specify correct type with options - options: Record = {} + options: PromotionTypes.ComputeActionOptions = {} ): Promise { + const { prevent_auto_promotions: preventAutoPromotions } = options const computedActions: PromotionTypes.ComputeActions[] = [] const { items = [], shipping_methods: shippingMethods = [] } = applicationContext @@ -231,6 +231,15 @@ export default class PromotionModuleService< PromotionTypes.ComputeActionAdjustmentLine >() const methodIdPromoValueMap = new Map() + const automaticPromotions = preventAutoPromotions + ? [] + : await this.list({ is_automatic: true }, { select: ["code"] }) + + const automaticPromotionCodes = automaticPromotions.map((p) => p.code!) + const promotionCodesToApply = [ + ...promotionCodes, + ...automaticPromotionCodes, + ] items.forEach((item) => { item.adjustments?.forEach((adjustment) => { @@ -291,7 +300,7 @@ export default class PromotionModuleService< ) } - if (promotionCodesToApply.includes(appliedCode)) { + if (promotionCodes.includes(appliedCode)) { continue } diff --git a/packages/types/src/promotion/common/compute-actions.ts b/packages/types/src/promotion/common/compute-actions.ts index b1f97a3c0c..267da4b5c3 100644 --- a/packages/types/src/promotion/common/compute-actions.ts +++ b/packages/types/src/promotion/common/compute-actions.ts @@ -65,3 +65,7 @@ export interface ComputeActionContext extends Record { items?: ComputeActionItemLine[] shipping_methods?: ComputeActionShippingLine[] } + +export interface ComputeActionOptions { + prevent_auto_promotions?: boolean +}