From 68013a5e7bb022f0393d88bb8e904b4546534eb1 Mon Sep 17 00:00:00 2001 From: Riqwan Thamir Date: Thu, 1 Feb 2024 19:25:00 +0530 Subject: [PATCH 1/2] chore: added percentage calculations for all cases --- .../promotion-module/compute-actions.spec.ts | 5131 +++++++++++------ .../promotion-module/promotion.spec.ts | 29 +- .../src/utils/compute-actions/items.ts | 28 +- .../utils/compute-actions/shipping-methods.ts | 15 +- .../utils/validations/application-method.ts | 12 + 5 files changed, 3591 insertions(+), 1624 deletions(-) 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 ee44ad54453b3..9065c30bf2df5 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,6 +1,6 @@ import { Modules } from "@medusajs/modules-sdk" import { IPromotionModuleService } from "@medusajs/types" -import { PromotionType } from "@medusajs/utils" +import { ApplicationMethodType, PromotionType } from "@medusajs/utils" import { SqlEntityManager } from "@mikro-orm/postgresql" import { initModules } from "medusa-test-utils" import { createCampaigns } from "../../../__fixtures__/campaigns" @@ -162,221 +162,1789 @@ describe("Promotion Service: computeActions", () => { }) describe("when promotion is for items and allocation is each", () => { - it("should compute the correct item amendments", async () => { - const [createdPromotion] = await service.create([ - { - code: "PROMOTION_TEST", - type: PromotionType.STANDARD, - rules: [ + describe("when application type is fixed", () => { + it("should compute the correct item amendments", async () => { + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + 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(["PROMOTION_TEST"], { + 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([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + application_method: { + type: "fixed", + target_type: "items", + allocation: "each", + value: "30", + max_quantity: 2, + target_rules: [ + { + attribute: "product_category.id", + operator: "eq", + values: ["catg_cotton"], + }, + ], + }, + }, + ]) + + const [createdPromotionTwo] = await service.create([ + { + code: "PROMOTION_TEST_2", + type: PromotionType.STANDARD, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + application_method: { + type: "fixed", + target_type: "items", + allocation: "each", + value: "50", + max_quantity: 1, + target_rules: [ + { + attribute: "product_category.id", + operator: "eq", + values: ["catg_cotton"], + }, + ], + }, + }, + ]) + + const result = await service.computeActions( + ["PROMOTION_TEST", "PROMOTION_TEST_2"], + { + customer: { + customer_group: { + id: "VIP", + }, + }, + items: [ + { + id: "item_cotton_tshirt", + quantity: 1, + unit_price: 50, + product_category: { + id: "catg_cotton", + }, + product: { + id: "prod_tshirt", + }, + }, + { + id: "item_cotton_sweater", + quantity: 1, + unit_price: 150, + product_category: { + id: "catg_cotton", + }, + product: { + id: "prod_sweater", + }, + }, + ], + } + ) + + expect(result).toEqual([ + { + action: "addItemAdjustment", + item_id: "item_cotton_tshirt", + amount: 30, + code: "PROMOTION_TEST", + }, + { + action: "addItemAdjustment", + item_id: "item_cotton_sweater", + amount: 30, + code: "PROMOTION_TEST", + }, + { + action: "addItemAdjustment", + item_id: "item_cotton_tshirt", + amount: 20, + code: "PROMOTION_TEST_2", + }, + { + action: "addItemAdjustment", + item_id: "item_cotton_sweater", + amount: 50, + code: "PROMOTION_TEST_2", + }, + ]) + }) + + it("should not compute actions when applicable total is 0", async () => { + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + application_method: { + type: "fixed", + target_type: "items", + allocation: "each", + value: "500", + max_quantity: 2, + target_rules: [ + { + attribute: "product_category.id", + operator: "eq", + values: ["catg_cotton"], + }, + ], + }, + }, + ]) + + const [createdPromotionTwo] = await service.create([ + { + code: "PROMOTION_TEST_2", + type: PromotionType.STANDARD, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + application_method: { + type: "fixed", + target_type: "items", + allocation: "each", + value: "50", + max_quantity: 1, + target_rules: [ + { + attribute: "product_category.id", + operator: "eq", + values: ["catg_cotton"], + }, + ], + }, + }, + ]) + + const result = await service.computeActions( + ["PROMOTION_TEST", "PROMOTION_TEST_2"], + { + customer: { + customer_group: { + id: "VIP", + }, + }, + items: [ + { + id: "item_cotton_tshirt", + quantity: 1, + unit_price: 50, + product_category: { + id: "catg_cotton", + }, + product: { + id: "prod_tshirt", + }, + }, + { + id: "item_cotton_sweater", + quantity: 1, + 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 budget exceeded action when applicable total exceeds campaign budget for type spend", async () => { + await createCampaigns(repositoryManager) + + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + campaign_id: "campaign-id-1", + application_method: { + type: "fixed", + target_type: "items", + allocation: "each", + value: "500", + max_quantity: 5, + target_rules: [ + { + attribute: "product_category.id", + operator: "eq", + values: ["catg_cotton"], + }, + ], + }, + }, + ]) + + const result = await service.computeActions(["PROMOTION_TEST"], { + customer: { + customer_group: { + id: "VIP", + }, + }, + items: [ + { + id: "item_cotton_tshirt", + quantity: 5, + unit_price: 1000, + product_category: { + id: "catg_cotton", + }, + product: { + id: "prod_tshirt", + }, + }, + ], + }) + + expect(result).toEqual([ + { + action: "addItemAdjustment", + item_id: "item_cotton_tshirt", + amount: 500, + code: "PROMOTION_TEST", + }, + ]) + }) + + it("should compute budget exceeded action when applicable total exceeds campaign budget for type usage", async () => { + await createCampaigns(repositoryManager) + + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + campaign_id: "campaign-id-2", + application_method: { + type: "fixed", + target_type: "items", + allocation: "each", + value: "500", + max_quantity: 5, + target_rules: [ + { + attribute: "product_category.id", + operator: "eq", + values: ["catg_cotton"], + }, + ], + }, + }, + ]) + + await service.updateCampaigns({ + id: "campaign-id-2", + budget: { used: 1000 }, + }) + + const result = await service.computeActions(["PROMOTION_TEST"], { + customer: { + customer_group: { + id: "VIP", + }, + }, + items: [ + { + id: "item_cotton_tshirt", + quantity: 5, + unit_price: 1000, + product_category: { + id: "catg_cotton", + }, + product: { + id: "prod_tshirt", + }, + }, + ], + }) + + expect(result).toEqual([ + { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" }, + ]) + }) + }) + + describe("when application type is percentage", () => { + it("should compute the correct item amendments", async () => { + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + application_method: { + type: ApplicationMethodType.PERCENTAGE, + target_type: "items", + allocation: "each", + value: "10", + max_quantity: 1, + target_rules: [ + { + attribute: "product_category.id", + operator: "eq", + values: ["catg_cotton"], + }, + ], + }, + }, + ]) + + const result = await service.computeActions(["PROMOTION_TEST"], { + 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: 10, + code: "PROMOTION_TEST", + }, + { + action: "addItemAdjustment", + item_id: "item_cotton_sweater", + amount: 15, + code: "PROMOTION_TEST", + }, + ]) + }) + + it("should compute the correct item amendments when there are multiple promotions to apply", async () => { + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + application_method: { + type: ApplicationMethodType.PERCENTAGE, + target_type: "items", + allocation: "each", + value: "30", + max_quantity: 2, + target_rules: [ + { + attribute: "product_category.id", + operator: "eq", + values: ["catg_cotton"], + }, + ], + }, + }, + ]) + + const [createdPromotionTwo] = await service.create([ + { + code: "PROMOTION_TEST_2", + type: PromotionType.STANDARD, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + application_method: { + type: ApplicationMethodType.PERCENTAGE, + target_type: "items", + allocation: "each", + value: "10", + max_quantity: 1, + target_rules: [ + { + attribute: "product_category.id", + operator: "eq", + values: ["catg_cotton"], + }, + ], + }, + }, + ]) + + const result = await service.computeActions( + ["PROMOTION_TEST", "PROMOTION_TEST_2"], + { + customer: { + customer_group: { + id: "VIP", + }, + }, + items: [ + { + id: "item_cotton_tshirt", + quantity: 3, + unit_price: 50, + product_category: { + id: "catg_cotton", + }, + product: { + id: "prod_tshirt", + }, + }, + { + id: "item_cotton_sweater", + quantity: 1, + unit_price: 150, + product_category: { + id: "catg_cotton", + }, + product: { + id: "prod_sweater", + }, + }, + ], + } + ) + + expect(result).toEqual([ + { + action: "addItemAdjustment", + item_id: "item_cotton_tshirt", + amount: 30, + code: "PROMOTION_TEST", + }, + { + action: "addItemAdjustment", + item_id: "item_cotton_sweater", + amount: 45, + code: "PROMOTION_TEST", + }, + { + action: "addItemAdjustment", + item_id: "item_cotton_tshirt", + amount: 2, + code: "PROMOTION_TEST_2", + }, + { + action: "addItemAdjustment", + item_id: "item_cotton_sweater", + amount: 10.5, + code: "PROMOTION_TEST_2", + }, + ]) + }) + + it("should not compute actions when applicable total is 0", async () => { + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + application_method: { + type: ApplicationMethodType.PERCENTAGE, + target_type: "items", + allocation: "each", + value: "100", + max_quantity: 10, + target_rules: [ + { + attribute: "product_category.id", + operator: "eq", + values: ["catg_cotton"], + }, + ], + }, + }, + ]) + + const [createdPromotionTwo] = await service.create([ + { + code: "PROMOTION_TEST_2", + type: PromotionType.STANDARD, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + application_method: { + type: ApplicationMethodType.PERCENTAGE, + target_type: "items", + allocation: "each", + value: "50", + max_quantity: 10, + target_rules: [ + { + attribute: "product_category.id", + operator: "eq", + values: ["catg_cotton"], + }, + ], + }, + }, + ]) + + const result = await service.computeActions( + ["PROMOTION_TEST", "PROMOTION_TEST_2"], + { + customer: { + customer_group: { + id: "VIP", + }, + }, + items: [ + { + id: "item_cotton_tshirt", + quantity: 1, + unit_price: 50, + product_category: { + id: "catg_cotton", + }, + product: { + id: "prod_tshirt", + }, + }, + { + id: "item_cotton_sweater", + quantity: 1, + 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 budget exceeded action when applicable total exceeds campaign budget for type spend", async () => { + await createCampaigns(repositoryManager) + + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + campaign_id: "campaign-id-1", + application_method: { + type: ApplicationMethodType.PERCENTAGE, + target_type: "items", + allocation: "each", + value: "100", + max_quantity: 5, + target_rules: [ + { + attribute: "product_category.id", + operator: "eq", + values: ["catg_cotton"], + }, + ], + }, + }, + ]) + + const result = await service.computeActions(["PROMOTION_TEST"], { + customer: { + customer_group: { + id: "VIP", + }, + }, + items: [ + { + id: "item_cotton_tshirt", + quantity: 5, + unit_price: 2000, + product_category: { + id: "catg_cotton", + }, + product: { + id: "prod_tshirt", + }, + }, + ], + }) + + expect(result).toEqual([ + { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" }, + ]) + }) + + it("should compute budget exceeded action when applicable total exceeds campaign budget for type usage", async () => { + await createCampaigns(repositoryManager) + + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + campaign_id: "campaign-id-2", + application_method: { + type: ApplicationMethodType.PERCENTAGE, + target_type: "items", + allocation: "each", + value: "10", + max_quantity: 5, + target_rules: [ + { + attribute: "product_category.id", + operator: "eq", + values: ["catg_cotton"], + }, + ], + }, + }, + ]) + + await service.updateCampaigns({ + id: "campaign-id-2", + budget: { used: 1000 }, + }) + + const result = await service.computeActions(["PROMOTION_TEST"], { + customer: { + customer_group: { + id: "VIP", + }, + }, + items: [ + { + id: "item_cotton_tshirt", + quantity: 5, + unit_price: 1000, + product_category: { + id: "catg_cotton", + }, + product: { + id: "prod_tshirt", + }, + }, + ], + }) + + expect(result).toEqual([ + { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" }, + ]) + }) + }) + }) + + describe("when promotion is for items and allocation is across", () => { + describe("when application type is fixed", () => { + it("should compute the correct item amendments", async () => { + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + 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(["PROMOTION_TEST"], { + 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 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: [ { - attribute: "customer.customer_group.id", - operator: "in", - values: ["VIP", "top100"], + 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", + }, }, ], - application_method: { - type: "fixed", - target_type: "items", - allocation: "each", - value: "200", - max_quantity: 1, - target_rules: [ + }) + + 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([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + application_method: { + type: "fixed", + target_type: "items", + allocation: "across", + value: "30", + target_rules: [ + { + attribute: "product_category.id", + operator: "eq", + values: ["catg_cotton"], + }, + ], + }, + }, + ]) + + const [createdPromotionTwo] = await service.create([ + { + code: "PROMOTION_TEST_2", + type: PromotionType.STANDARD, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + application_method: { + type: "fixed", + target_type: "items", + allocation: "across", + value: "50", + target_rules: [ + { + attribute: "product_category.id", + operator: "eq", + values: ["catg_cotton"], + }, + ], + }, + }, + ]) + + const result = await service.computeActions( + ["PROMOTION_TEST", "PROMOTION_TEST_2"], + { + customer: { + customer_group: { + id: "VIP", + }, + }, + items: [ + { + id: "item_cotton_tshirt", + quantity: 1, + unit_price: 50, + product_category: { + id: "catg_cotton", + }, + product: { + id: "prod_tshirt", + }, + }, + { + id: "item_cotton_sweater", + quantity: 1, + unit_price: 150, + product_category: { + id: "catg_cotton", + }, + product: { + id: "prod_sweater", + }, + }, + ], + } + ) + + expect(result).toEqual([ + { + action: "addItemAdjustment", + item_id: "item_cotton_tshirt", + amount: 7.5, + code: "PROMOTION_TEST", + }, + { + action: "addItemAdjustment", + item_id: "item_cotton_sweater", + amount: 22.5, + code: "PROMOTION_TEST", + }, + { + action: "addItemAdjustment", + item_id: "item_cotton_tshirt", + amount: 12.5, + code: "PROMOTION_TEST_2", + }, + { + action: "addItemAdjustment", + item_id: "item_cotton_sweater", + amount: 37.5, + code: "PROMOTION_TEST_2", + }, + ]) + }) + + it("should not compute actions when applicable total is 0", async () => { + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + application_method: { + type: "fixed", + target_type: "items", + allocation: "across", + value: "1000", + target_rules: [ + { + attribute: "product_category.id", + operator: "eq", + values: ["catg_cotton"], + }, + ], + }, + }, + ]) + + const [createdPromotionTwo] = await service.create([ + { + code: "PROMOTION_TEST_2", + type: PromotionType.STANDARD, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + application_method: { + type: "fixed", + target_type: "items", + allocation: "across", + value: "50", + target_rules: [ + { + attribute: "product_category.id", + operator: "eq", + values: ["catg_cotton"], + }, + ], + }, + }, + ]) + + const result = await service.computeActions( + ["PROMOTION_TEST", "PROMOTION_TEST_2"], + { + customer: { + customer_group: { + id: "VIP", + }, + }, + items: [ + { + id: "item_cotton_tshirt", + quantity: 1, + unit_price: 50, + product_category: { + id: "catg_cotton", + }, + product: { + id: "prod_tshirt", + }, + }, + { + id: "item_cotton_sweater", + quantity: 1, + 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 budget exceeded action when applicable total exceeds campaign budget for type spend", async () => { + await createCampaigns(repositoryManager) + + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + campaign_id: "campaign-id-1", + application_method: { + type: "fixed", + target_type: "items", + allocation: "across", + value: "1500", + target_rules: [ + { + attribute: "product_category.id", + operator: "eq", + values: ["catg_cotton"], + }, + ], + }, + }, + ]) + + const result = await service.computeActions(["PROMOTION_TEST"], { + customer: { + customer_group: { + id: "VIP", + }, + }, + items: [ + { + id: "item_cotton_tshirt", + quantity: 5, + unit_price: 1000, + product_category: { + id: "catg_cotton", + }, + product: { + id: "prod_tshirt", + }, + }, + ], + }) + + expect(result).toEqual([ + { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" }, + ]) + }) + + it("should compute budget exceeded action when applicable total exceeds campaign budget for type usage", async () => { + await createCampaigns(repositoryManager) + + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + campaign_id: "campaign-id-2", + application_method: { + type: "fixed", + target_type: "items", + allocation: "across", + value: "500", + target_rules: [ + { + attribute: "product_category.id", + operator: "eq", + values: ["catg_cotton"], + }, + ], + }, + }, + ]) + + await service.updateCampaigns({ + id: "campaign-id-2", + budget: { used: 1000 }, + }) + + const result = await service.computeActions(["PROMOTION_TEST"], { + customer: { + customer_group: { + id: "VIP", + }, + }, + items: [ + { + id: "item_cotton_tshirt", + quantity: 5, + unit_price: 1000, + product_category: { + id: "catg_cotton", + }, + product: { + id: "prod_tshirt", + }, + }, + ], + }) + + expect(result).toEqual([ + { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" }, + ]) + }) + }) + + describe("when application type is percentage", () => { + it("should compute the correct item amendments", async () => { + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + application_method: { + type: ApplicationMethodType.PERCENTAGE, + target_type: "items", + allocation: "across", + value: "10", + target_rules: [ + { + attribute: "product_category.id", + operator: "eq", + values: ["catg_cotton"], + }, + ], + }, + }, + ]) + + const result = await service.computeActions(["PROMOTION_TEST"], { + 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: 20, + code: "PROMOTION_TEST", + }, + { + action: "addItemAdjustment", + item_id: "item_cotton_sweater", + amount: 60, + code: "PROMOTION_TEST", + }, + ]) + }) + + 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: "product_category.id", - operator: "eq", - values: ["catg_cotton"], + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], }, ], + application_method: { + type: ApplicationMethodType.PERCENTAGE, + target_type: "items", + allocation: "across", + value: "10", + target_rules: [ + { + attribute: "product_category.id", + operator: "eq", + values: ["catg_cotton"], + }, + ], + }, }, - }, - ]) + ]) - const result = await service.computeActions(["PROMOTION_TEST"], { - customer: { - customer_group: { - id: "VIP", + 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", + items: [ + { + id: "item_cotton_tshirt", + quantity: 2, + unit_price: 100, + product_category: { + id: "catg_cotton", + }, + product: { + id: "prod_tshirt", + }, }, - 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: 20, + code: "PROMOTION_TEST", }, { - id: "item_cotton_sweater", - quantity: 5, - unit_price: 150, - product_category: { - id: "catg_cotton", - }, - product: { - id: "prod_sweater", - }, + action: "addItemAdjustment", + item_id: "item_cotton_sweater", + amount: 60, + code: "PROMOTION_TEST", }, - ], + ]) }) - 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 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: [ + it("should compute the correct item amendments when there are multiple promotions to apply", async () => { + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + rules: [ { - attribute: "product_category.id", - operator: "eq", - values: ["catg_cotton"], + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], }, ], + application_method: { + type: ApplicationMethodType.PERCENTAGE, + target_type: "items", + allocation: "across", + value: "10", + target_rules: [ + { + attribute: "product_category.id", + operator: "eq", + values: ["catg_cotton"], + }, + ], + }, }, - }, - ]) + ]) - const result = await service.computeActions([], { - customer: { - customer_group: { - id: "VIP", - }, - }, - items: [ + const [createdPromotionTwo] = await service.create([ { - id: "item_cotton_tshirt", - quantity: 1, - unit_price: 100, - product_category: { - id: "catg_cotton", - }, - product: { - id: "prod_tshirt", + code: "PROMOTION_TEST_2", + type: PromotionType.STANDARD, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + application_method: { + type: ApplicationMethodType.PERCENTAGE, + target_type: "items", + allocation: "across", + value: "10", + target_rules: [ + { + attribute: "product_category.id", + operator: "eq", + values: ["catg_cotton"], + }, + ], }, }, + ]) + + const result = await service.computeActions( + ["PROMOTION_TEST", "PROMOTION_TEST_2"], { - id: "item_cotton_sweater", - quantity: 5, - unit_price: 150, - product_category: { - id: "catg_cotton", - }, - product: { - id: "prod_sweater", + customer: { + customer_group: { + id: "VIP", + }, }, + items: [ + { + id: "item_cotton_tshirt", + quantity: 1, + unit_price: 50, + product_category: { + id: "catg_cotton", + }, + product: { + id: "prod_tshirt", + }, + }, + { + id: "item_cotton_sweater", + quantity: 1, + unit_price: 150, + product_category: { + id: "catg_cotton", + }, + product: { + id: "prod_sweater", + }, + }, + ], + } + ) + + expect(result).toEqual([ + { + action: "addItemAdjustment", + item_id: "item_cotton_tshirt", + amount: 5, + code: "PROMOTION_TEST", }, - ], + { + action: "addItemAdjustment", + item_id: "item_cotton_sweater", + amount: 15, + code: "PROMOTION_TEST", + }, + { + action: "addItemAdjustment", + item_id: "item_cotton_tshirt", + amount: 4.5, + code: "PROMOTION_TEST_2", + }, + { + action: "addItemAdjustment", + item_id: "item_cotton_sweater", + amount: 13.5, + code: "PROMOTION_TEST_2", + }, + ]) }) - 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([ - { - code: "PROMOTION_TEST", - type: PromotionType.STANDARD, - rules: [ - { - attribute: "customer.customer_group.id", - operator: "in", - values: ["VIP", "top100"], + it("should not compute actions when applicable total is 0", async () => { + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + application_method: { + type: ApplicationMethodType.PERCENTAGE, + target_type: "items", + allocation: "across", + value: "10", + target_rules: [ + { + attribute: "product_category.id", + operator: "eq", + values: ["catg_cotton"], + }, + ], }, - ], - application_method: { - type: "fixed", - target_type: "items", - allocation: "each", - value: "30", - max_quantity: 2, - target_rules: [ + }, + ]) + + const [createdPromotionTwo] = await service.create([ + { + code: "PROMOTION_TEST_2", + type: PromotionType.STANDARD, + rules: [ { - attribute: "product_category.id", - operator: "eq", - values: ["catg_cotton"], + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], }, ], + application_method: { + type: ApplicationMethodType.PERCENTAGE, + target_type: "items", + allocation: "across", + value: "10", + target_rules: [ + { + attribute: "product_category.id", + operator: "eq", + values: ["catg_cotton"], + }, + ], + }, }, - }, - ]) + ]) - const [createdPromotionTwo] = await service.create([ - { - code: "PROMOTION_TEST_2", - type: PromotionType.STANDARD, - rules: [ - { - attribute: "customer.customer_group.id", - operator: "in", - values: ["VIP", "top100"], + const result = await service.computeActions( + ["PROMOTION_TEST", "PROMOTION_TEST_2"], + { + customer: { + customer_group: { + id: "VIP", + }, }, - ], - application_method: { - type: "fixed", - target_type: "items", - allocation: "each", - value: "50", - max_quantity: 1, - target_rules: [ + items: [ + { + id: "item_cotton_tshirt", + quantity: 1, + unit_price: 50, + product_category: { + id: "catg_cotton", + }, + product: { + id: "prod_tshirt", + }, + }, + { + id: "item_cotton_sweater", + quantity: 1, + unit_price: 150, + product_category: { + id: "catg_cotton", + }, + product: { + id: "prod_sweater", + }, + }, + ], + } + ) + + expect(result).toEqual([ + { + action: "addItemAdjustment", + item_id: "item_cotton_tshirt", + amount: 5, + code: "PROMOTION_TEST", + }, + { + action: "addItemAdjustment", + item_id: "item_cotton_sweater", + amount: 15, + code: "PROMOTION_TEST", + }, + { + action: "addItemAdjustment", + item_id: "item_cotton_tshirt", + amount: 4.5, + code: "PROMOTION_TEST_2", + }, + { + action: "addItemAdjustment", + item_id: "item_cotton_sweater", + amount: 13.5, + code: "PROMOTION_TEST_2", + }, + ]) + }) + + it("should compute budget exceeded action when applicable total exceeds campaign budget for type spend", async () => { + await createCampaigns(repositoryManager) + + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + rules: [ { - attribute: "product_category.id", - operator: "eq", - values: ["catg_cotton"], + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], }, ], + campaign_id: "campaign-id-1", + application_method: { + type: ApplicationMethodType.PERCENTAGE, + target_type: "items", + allocation: "across", + value: "100", + target_rules: [ + { + attribute: "product_category.id", + operator: "eq", + values: ["catg_cotton"], + }, + ], + }, }, - }, - ]) + ]) - const result = await service.computeActions( - ["PROMOTION_TEST", "PROMOTION_TEST_2"], - { + const result = await service.computeActions(["PROMOTION_TEST"], { customer: { customer_group: { id: "VIP", @@ -385,8 +1953,8 @@ describe("Promotion Service: computeActions", () => { items: [ { id: "item_cotton_tshirt", - quantity: 1, - unit_price: 50, + quantity: 5, + unit_price: 1000, product_category: { id: "catg_cotton", }, @@ -394,109 +1962,51 @@ describe("Promotion Service: computeActions", () => { id: "prod_tshirt", }, }, - { - id: "item_cotton_sweater", - quantity: 1, - unit_price: 150, - product_category: { - id: "catg_cotton", - }, - product: { - id: "prod_sweater", - }, - }, ], - } - ) + }) - expect(result).toEqual([ - { - action: "addItemAdjustment", - item_id: "item_cotton_tshirt", - amount: 30, - code: "PROMOTION_TEST", - }, - { - action: "addItemAdjustment", - item_id: "item_cotton_sweater", - amount: 30, - code: "PROMOTION_TEST", - }, - { - action: "addItemAdjustment", - item_id: "item_cotton_tshirt", - amount: 20, - code: "PROMOTION_TEST_2", - }, - { - action: "addItemAdjustment", - item_id: "item_cotton_sweater", - amount: 50, - code: "PROMOTION_TEST_2", - }, - ]) - }) + expect(result).toEqual([ + { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" }, + ]) + }) - it("should not compute actions when applicable total is 0", async () => { - const [createdPromotion] = await service.create([ - { - code: "PROMOTION_TEST", - type: PromotionType.STANDARD, - rules: [ - { - attribute: "customer.customer_group.id", - operator: "in", - values: ["VIP", "top100"], - }, - ], - application_method: { - type: "fixed", - target_type: "items", - allocation: "each", - value: "500", - max_quantity: 2, - target_rules: [ - { - attribute: "product_category.id", - operator: "eq", - values: ["catg_cotton"], - }, - ], - }, - }, - ]) + it("should compute budget exceeded action when applicable total exceeds campaign budget for type usage", async () => { + await createCampaigns(repositoryManager) - const [createdPromotionTwo] = await service.create([ - { - code: "PROMOTION_TEST_2", - type: PromotionType.STANDARD, - rules: [ - { - attribute: "customer.customer_group.id", - operator: "in", - values: ["VIP", "top100"], - }, - ], - application_method: { - type: "fixed", - target_type: "items", - allocation: "each", - value: "50", - max_quantity: 1, - target_rules: [ + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + rules: [ { - attribute: "product_category.id", - operator: "eq", - values: ["catg_cotton"], + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], }, ], + campaign_id: "campaign-id-2", + application_method: { + type: ApplicationMethodType.PERCENTAGE, + target_type: "items", + allocation: "across", + value: "10", + target_rules: [ + { + attribute: "product_category.id", + operator: "eq", + values: ["catg_cotton"], + }, + ], + }, }, - }, - ]) + ]) - const result = await service.computeActions( - ["PROMOTION_TEST", "PROMOTION_TEST_2"], - { + await service.updateCampaigns({ + id: "campaign-id-2", + budget: { used: 1000 }, + }) + + const result = await service.computeActions(["PROMOTION_TEST"], { customer: { customer_group: { id: "VIP", @@ -505,8 +2015,8 @@ describe("Promotion Service: computeActions", () => { items: [ { id: "item_cotton_tshirt", - quantity: 1, - unit_price: 50, + quantity: 5, + unit_price: 1000, product_category: { id: "catg_cotton", }, @@ -514,951 +2024,1066 @@ describe("Promotion Service: computeActions", () => { id: "prod_tshirt", }, }, - { - id: "item_cotton_sweater", - quantity: 1, - 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", - }, - ]) + expect(result).toEqual([ + { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" }, + ]) + }) }) + }) - it("should compute budget exceeded action when applicable total exceeds campaign budget for type spend", async () => { - await createCampaigns(repositoryManager) - - const [createdPromotion] = await service.create([ - { - code: "PROMOTION_TEST", - type: PromotionType.STANDARD, - rules: [ - { - attribute: "customer.customer_group.id", - operator: "in", - values: ["VIP", "top100"], - }, - ], - campaign_id: "campaign-id-1", - application_method: { - type: "fixed", - target_type: "items", - allocation: "each", - value: "500", - max_quantity: 5, - target_rules: [ + describe("when promotion is for shipping_method and allocation is each", () => { + describe("when application type is fixed", () => { + it("should compute the correct shipping_method amendments", async () => { + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + rules: [ { - attribute: "product_category.id", - operator: "eq", - values: ["catg_cotton"], + 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(["PROMOTION_TEST"], { - customer: { - customer_group: { - id: "VIP", + const result = await service.computeActions(["PROMOTION_TEST"], { + customer: { + customer_group: { + id: "VIP", + }, }, - }, - items: [ - { - id: "item_cotton_tshirt", - quantity: 5, - unit_price: 1000, - product_category: { - id: "catg_cotton", + shipping_methods: [ + { + id: "shipping_method_express", + unit_price: 250, + shipping_option: { + id: "express", + }, }, - product: { - id: "prod_tshirt", + { + id: "shipping_method_standard", + unit_price: 150, + shipping_option: { + id: "standard", + }, }, - }, - ], - }) - - expect(result).toEqual([ - { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" }, - ]) - }) - - it("should compute budget exceeded action when applicable total exceeds campaign budget for type usage", async () => { - await createCampaigns(repositoryManager) - - const [createdPromotion] = await service.create([ - { - code: "PROMOTION_TEST", - type: PromotionType.STANDARD, - rules: [ { - attribute: "customer.customer_group.id", - operator: "in", - values: ["VIP", "top100"], + id: "shipping_method_snail", + unit_price: 200, + shipping_option: { + id: "snail", + }, }, ], - campaign_id: "campaign-id-2", - application_method: { - type: "fixed", - target_type: "items", - allocation: "each", - value: "500", - max_quantity: 5, - target_rules: [ + }) + + 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", async () => { + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + is_automatic: true, + rules: [ { - attribute: "product_category.id", - operator: "eq", - values: ["catg_cotton"], + 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"], + }, + ], + }, }, - }, - ]) - - await service.updateCampaigns({ - id: "campaign-id-2", - budget: { used: 1000 }, - }) + ]) - const result = await service.computeActions(["PROMOTION_TEST"], { - customer: { - customer_group: { - id: "VIP", + const result = await service.computeActions([], { + customer: { + customer_group: { + id: "VIP", + }, }, - }, - items: [ - { - id: "item_cotton_tshirt", - quantity: 5, - unit_price: 1000, - product_category: { - id: "catg_cotton", + shipping_methods: [ + { + id: "shipping_method_express", + unit_price: 250, + shipping_option: { + id: "express", + }, }, - product: { - id: "prod_tshirt", + { + 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", + }, + ]) }) - expect(result).toEqual([ - { action: "campaignBudgetExceeded", 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"], + }, + ], + }, + }, + ]) - describe("when promotion is for items and allocation is across", () => { - it("should compute the correct item amendments", async () => { - const [createdPromotion] = await service.create([ - { - code: "PROMOTION_TEST", - type: PromotionType.STANDARD, - rules: [ - { - attribute: "customer.customer_group.id", - operator: "in", - values: ["VIP", "top100"], + const result = await service.computeActions( + [], + { + customer: { + customer_group: { + id: "VIP", + }, }, - ], - application_method: { - type: "fixed", - target_type: "items", - allocation: "across", - value: "400", - target_rules: [ + shipping_methods: [ { - attribute: "product_category.id", - operator: "eq", - values: ["catg_cotton"], + 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 } + ) - const result = await service.computeActions(["PROMOTION_TEST"], { - customer: { - customer_group: { - id: "VIP", - }, - }, - items: [ + expect(result).toEqual([]) + }) + + it("should compute the correct item amendments when there are multiple promotions to apply", async () => { + const [createdPromotion] = await service.create([ { - id: "item_cotton_tshirt", - quantity: 2, - unit_price: 100, - product_category: { - id: "catg_cotton", - }, - product: { - id: "prod_tshirt", + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + 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 [createdPromotionTwo] = await service.create([ { - id: "item_cotton_sweater", - quantity: 2, - unit_price: 300, - product_category: { - id: "catg_cotton", - }, - product: { - id: "prod_sweater", + code: "PROMOTION_TEST_2", + type: PromotionType.STANDARD, + 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"], + }, + ], }, }, - ], - }) - - 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 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"], + const result = await service.computeActions( + ["PROMOTION_TEST", "PROMOTION_TEST_2"], + { + customer: { + customer_group: { + id: "VIP", + }, }, - ], - application_method: { - type: "fixed", - target_type: "items", - allocation: "across", - value: "400", - target_rules: [ + shipping_methods: [ { - attribute: "product_category.id", - operator: "eq", - values: ["catg_cotton"], + 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", + }, }, ], - }, - }, - ]) + } + ) - const result = await service.computeActions([], { - customer: { - customer_group: { - id: "VIP", + expect(result).toEqual([ + { + action: "addShippingMethodAdjustment", + shipping_method_id: "shipping_method_express", + amount: 200, + code: "PROMOTION_TEST", }, - }, - items: [ { - id: "item_cotton_tshirt", - quantity: 2, - unit_price: 100, - product_category: { - id: "catg_cotton", - }, - product: { - id: "prod_tshirt", - }, + action: "addShippingMethodAdjustment", + shipping_method_id: "shipping_method_standard", + amount: 150, + code: "PROMOTION_TEST", }, { - id: "item_cotton_sweater", - quantity: 2, - unit_price: 300, - product_category: { - id: "catg_cotton", - }, - product: { - id: "prod_sweater", - }, + action: "addShippingMethodAdjustment", + shipping_method_id: "shipping_method_express", + amount: 50, + code: "PROMOTION_TEST_2", }, - ], + ]) }) - 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([ - { - code: "PROMOTION_TEST", - type: PromotionType.STANDARD, - rules: [ - { - attribute: "customer.customer_group.id", - operator: "in", - values: ["VIP", "top100"], + it("should not compute actions when applicable total is 0", async () => { + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + application_method: { + type: "fixed", + target_type: "shipping_methods", + allocation: "each", + value: "500", + max_quantity: 2, + target_rules: [ + { + attribute: "shipping_option.id", + operator: "in", + values: ["express", "standard"], + }, + ], }, - ], - application_method: { - type: "fixed", - target_type: "items", - allocation: "across", - value: "30", - target_rules: [ + }, + ]) + + const [createdPromotionTwo] = await service.create([ + { + code: "PROMOTION_TEST_2", + type: PromotionType.STANDARD, + rules: [ { - attribute: "product_category.id", - operator: "eq", - values: ["catg_cotton"], + 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 [createdPromotionTwo] = await service.create([ - { - code: "PROMOTION_TEST_2", - type: PromotionType.STANDARD, - rules: [ - { - attribute: "customer.customer_group.id", - operator: "in", - values: ["VIP", "top100"], + const result = await service.computeActions( + ["PROMOTION_TEST", "PROMOTION_TEST_2"], + { + customer: { + customer_group: { + id: "VIP", + }, }, - ], - application_method: { - type: "fixed", - target_type: "items", - allocation: "across", - value: "50", - target_rules: [ + shipping_methods: [ { - attribute: "product_category.id", - operator: "eq", - values: ["catg_cotton"], + 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: 250, + code: "PROMOTION_TEST", }, - }, - ]) + { + action: "addShippingMethodAdjustment", + shipping_method_id: "shipping_method_standard", + amount: 150, + code: "PROMOTION_TEST", + }, + ]) + }) - const result = await service.computeActions( - ["PROMOTION_TEST", "PROMOTION_TEST_2"], - { + it("should compute budget exceeded action when applicable total exceeds campaign budget for type spend", async () => { + await createCampaigns(repositoryManager) + + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + campaign_id: "campaign-id-1", + application_method: { + type: "fixed", + target_type: "shipping_methods", + allocation: "each", + value: "1200", + max_quantity: 2, + target_rules: [ + { + attribute: "shipping_option.id", + operator: "in", + values: ["express", "standard"], + }, + ], + }, + }, + ]) + + const result = await service.computeActions(["PROMOTION_TEST"], { customer: { customer_group: { id: "VIP", }, }, - items: [ - { - id: "item_cotton_tshirt", - quantity: 1, - unit_price: 50, - product_category: { - id: "catg_cotton", - }, - product: { - id: "prod_tshirt", - }, - }, + shipping_methods: [ { - id: "item_cotton_sweater", - quantity: 1, - unit_price: 150, - product_category: { - id: "catg_cotton", - }, - product: { - id: "prod_sweater", + id: "shipping_method_express", + unit_price: 1200, + shipping_option: { + id: "express", }, }, ], - } - ) + }) - expect(result).toEqual([ - { - action: "addItemAdjustment", - item_id: "item_cotton_tshirt", - amount: 7.5, - code: "PROMOTION_TEST", - }, - { - action: "addItemAdjustment", - item_id: "item_cotton_sweater", - amount: 22.5, - code: "PROMOTION_TEST", - }, - { - action: "addItemAdjustment", - item_id: "item_cotton_tshirt", - amount: 12.5, - code: "PROMOTION_TEST_2", - }, - { - action: "addItemAdjustment", - item_id: "item_cotton_sweater", - amount: 37.5, - code: "PROMOTION_TEST_2", - }, - ]) - }) + expect(result).toEqual([ + { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" }, + ]) + }) - it("should not compute actions when applicable total is 0", async () => { - const [createdPromotion] = await service.create([ - { - code: "PROMOTION_TEST", - type: PromotionType.STANDARD, - rules: [ - { - attribute: "customer.customer_group.id", - operator: "in", - values: ["VIP", "top100"], - }, - ], - application_method: { - type: "fixed", - target_type: "items", - allocation: "across", - value: "500", - target_rules: [ + it("should compute budget exceeded action when applicable total exceeds campaign budget for type usage", async () => { + await createCampaigns(repositoryManager) + + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + rules: [ { - attribute: "product_category.id", - operator: "eq", - values: ["catg_cotton"], + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], }, ], + campaign_id: "campaign-id-2", + application_method: { + type: "fixed", + target_type: "shipping_methods", + allocation: "each", + value: "1200", + max_quantity: 2, + target_rules: [ + { + attribute: "shipping_option.id", + operator: "in", + values: ["express", "standard"], + }, + ], + }, }, - }, - ]) + ]) - const [createdPromotionTwo] = await service.create([ - { - code: "PROMOTION_TEST_2", - type: PromotionType.STANDARD, - rules: [ + await service.updateCampaigns({ + id: "campaign-id-2", + budget: { used: 1000 }, + }) + + const result = await service.computeActions(["PROMOTION_TEST"], { + customer: { + customer_group: { + id: "VIP", + }, + }, + shipping_methods: [ { - attribute: "customer.customer_group.id", - operator: "in", - values: ["VIP", "top100"], + id: "shipping_method_express", + unit_price: 1200, + shipping_option: { + id: "express", + }, }, ], - application_method: { - type: "fixed", - target_type: "items", - allocation: "across", - value: "50", - target_rules: [ + }) + + expect(result).toEqual([ + { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" }, + ]) + }) + }) + + describe("when application type is percentage", () => { + it("should compute the correct shipping_method amendments", async () => { + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + rules: [ { - attribute: "product_category.id", - operator: "eq", - values: ["catg_cotton"], + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], }, ], + application_method: { + type: ApplicationMethodType.PERCENTAGE, + target_type: "shipping_methods", + allocation: "each", + value: "10", + max_quantity: 2, + target_rules: [ + { + attribute: "shipping_option.id", + operator: "in", + values: ["express", "standard"], + }, + ], + }, }, - }, - ]) + ]) - const result = await service.computeActions( - ["PROMOTION_TEST", "PROMOTION_TEST_2"], - { + const result = await service.computeActions(["PROMOTION_TEST"], { customer: { customer_group: { id: "VIP", }, }, - items: [ + shipping_methods: [ { - id: "item_cotton_tshirt", - quantity: 1, - unit_price: 50, - product_category: { - id: "catg_cotton", - }, - product: { - id: "prod_tshirt", + id: "shipping_method_express", + unit_price: 250, + shipping_option: { + id: "express", }, }, { - id: "item_cotton_sweater", - quantity: 1, + id: "shipping_method_standard", 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", - }, - { - action: "addItemAdjustment", - item_id: "item_cotton_tshirt", - amount: 12.5, - code: "PROMOTION_TEST_2", - }, - { - action: "addItemAdjustment", - item_id: "item_cotton_sweater", - amount: 37.5, - code: "PROMOTION_TEST_2", - }, - ]) - }) - - it("should compute budget exceeded action when applicable total exceeds campaign budget for type spend", async () => { - await createCampaigns(repositoryManager) - - const [createdPromotion] = await service.create([ - { - code: "PROMOTION_TEST", - type: PromotionType.STANDARD, - rules: [ + shipping_option: { + id: "standard", + }, + }, { - attribute: "customer.customer_group.id", - operator: "in", - values: ["VIP", "top100"], + id: "shipping_method_snail", + unit_price: 200, + shipping_option: { + id: "snail", + }, }, ], - campaign_id: "campaign-id-1", - application_method: { - type: "fixed", - target_type: "items", - allocation: "across", - value: "1500", - target_rules: [ - { - attribute: "product_category.id", - operator: "eq", - values: ["catg_cotton"], - }, - ], - }, - }, - ]) + }) - const result = await service.computeActions(["PROMOTION_TEST"], { - customer: { - customer_group: { - id: "VIP", + expect(result).toEqual([ + { + action: "addShippingMethodAdjustment", + shipping_method_id: "shipping_method_express", + amount: 25, + code: "PROMOTION_TEST", }, - }, - items: [ { - id: "item_cotton_tshirt", - quantity: 5, - unit_price: 1000, - product_category: { - id: "catg_cotton", - }, - product: { - id: "prod_tshirt", - }, + action: "addShippingMethodAdjustment", + shipping_method_id: "shipping_method_standard", + amount: 15, + code: "PROMOTION_TEST", }, - ], + ]) }) - expect(result).toEqual([ - { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" }, - ]) - }) - - it("should compute budget exceeded action when applicable total exceeds campaign budget for type usage", async () => { - await createCampaigns(repositoryManager) - - const [createdPromotion] = await service.create([ - { - code: "PROMOTION_TEST", - type: PromotionType.STANDARD, - rules: [ - { - attribute: "customer.customer_group.id", - operator: "in", - values: ["VIP", "top100"], - }, - ], - campaign_id: "campaign-id-2", - application_method: { - type: "fixed", - target_type: "items", - allocation: "across", - value: "500", - target_rules: [ + 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: "product_category.id", - operator: "eq", - values: ["catg_cotton"], + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], }, ], + application_method: { + type: ApplicationMethodType.PERCENTAGE, + target_type: "shipping_methods", + allocation: "each", + value: "10", + max_quantity: 2, + target_rules: [ + { + attribute: "shipping_option.id", + operator: "in", + values: ["express", "standard"], + }, + ], + }, }, - }, - ]) - - await service.updateCampaigns({ - id: "campaign-id-2", - budget: { used: 1000 }, - }) + ]) - const result = await service.computeActions(["PROMOTION_TEST"], { - customer: { - customer_group: { - id: "VIP", + const result = await service.computeActions([], { + customer: { + customer_group: { + id: "VIP", + }, }, - }, - items: [ - { - id: "item_cotton_tshirt", - quantity: 5, - unit_price: 1000, - product_category: { - id: "catg_cotton", + shipping_methods: [ + { + id: "shipping_method_express", + unit_price: 250, + shipping_option: { + id: "express", + }, }, - product: { - id: "prod_tshirt", + { + id: "shipping_method_standard", + unit_price: 150, + shipping_option: { + id: "standard", + }, }, - }, - ], - }) - - expect(result).toEqual([ - { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" }, - ]) - }) - }) - - describe("when promotion is for shipping_method and allocation is each", () => { - it("should compute the correct shipping_method amendments", async () => { - const [createdPromotion] = await service.create([ - { - code: "PROMOTION_TEST", - type: PromotionType.STANDARD, - rules: [ { - attribute: "customer.customer_group.id", - operator: "in", - values: ["VIP", "top100"], + id: "shipping_method_snail", + unit_price: 200, + shipping_option: { + id: "snail", + }, }, ], - application_method: { - type: "fixed", - target_type: "shipping_methods", - allocation: "each", - value: "200", - max_quantity: 2, - target_rules: [ + }) + + expect(result).toEqual([ + { + action: "addShippingMethodAdjustment", + shipping_method_id: "shipping_method_express", + amount: 25, + code: "PROMOTION_TEST", + }, + { + action: "addShippingMethodAdjustment", + shipping_method_id: "shipping_method_standard", + amount: 15, + 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: "shipping_option.id", + attribute: "customer.customer_group.id", operator: "in", - values: ["express", "standard"], + values: ["VIP", "top100"], }, ], + application_method: { + type: ApplicationMethodType.PERCENTAGE, + target_type: "shipping_methods", + allocation: "each", + value: "10", + max_quantity: 2, + target_rules: [ + { + attribute: "shipping_option.id", + operator: "in", + values: ["express", "standard"], + }, + ], + }, }, - }, - ]) + ]) - const result = await service.computeActions(["PROMOTION_TEST"], { - customer: { - customer_group: { - id: "VIP", - }, - }, - shipping_methods: [ + const result = await service.computeActions( + [], { - id: "shipping_method_express", - unit_price: 250, - shipping_option: { - id: "express", + 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([ { - id: "shipping_method_standard", - unit_price: 150, - shipping_option: { - id: "standard", + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + application_method: { + type: ApplicationMethodType.PERCENTAGE, + target_type: "shipping_methods", + allocation: "each", + value: "10", + max_quantity: 2, + target_rules: [ + { + attribute: "shipping_option.id", + operator: "in", + values: ["express", "standard"], + }, + ], }, }, + ]) + + const [createdPromotionTwo] = await service.create([ { - id: "shipping_method_snail", - unit_price: 200, - shipping_option: { - id: "snail", + code: "PROMOTION_TEST_2", + type: PromotionType.STANDARD, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + application_method: { + type: ApplicationMethodType.PERCENTAGE, + target_type: "shipping_methods", + allocation: "each", + value: "10", + max_quantity: 2, + target_rules: [ + { + attribute: "shipping_option.id", + operator: "in", + values: ["express", "standard"], + }, + ], }, }, - ], - }) - - 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", 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"], + const result = await service.computeActions( + ["PROMOTION_TEST", "PROMOTION_TEST_2"], + { + customer: { + customer_group: { + id: "VIP", + }, }, - ], - application_method: { - type: "fixed", - target_type: "shipping_methods", - allocation: "each", - value: "200", - max_quantity: 2, - target_rules: [ + shipping_methods: [ { - attribute: "shipping_option.id", - operator: "in", - values: ["express", "standard"], + 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", + }, }, ], - }, - }, - ]) + } + ) - const result = await service.computeActions([], { - customer: { - customer_group: { - id: "VIP", + expect(result).toEqual([ + { + action: "addShippingMethodAdjustment", + shipping_method_id: "shipping_method_express", + amount: 25, + code: "PROMOTION_TEST", }, - }, - shipping_methods: [ { - id: "shipping_method_express", - unit_price: 250, - shipping_option: { - id: "express", - }, + action: "addShippingMethodAdjustment", + shipping_method_id: "shipping_method_standard", + amount: 15, + code: "PROMOTION_TEST", }, { - id: "shipping_method_standard", - unit_price: 150, - shipping_option: { - id: "standard", - }, + action: "addShippingMethodAdjustment", + shipping_method_id: "shipping_method_express", + amount: 22.5, + code: "PROMOTION_TEST_2", }, { - id: "shipping_method_snail", - unit_price: 200, - shipping_option: { - id: "snail", - }, + action: "addShippingMethodAdjustment", + shipping_method_id: "shipping_method_standard", + amount: 13.5, + code: "PROMOTION_TEST_2", }, - ], + ]) }) - 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: [ + it("should not compute actions when applicable total is 0", async () => { + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + rules: [ { - attribute: "shipping_option.id", + attribute: "customer.customer_group.id", operator: "in", - values: ["express", "standard"], + values: ["VIP", "top100"], }, ], + application_method: { + type: ApplicationMethodType.PERCENTAGE, + target_type: "shipping_methods", + allocation: "each", + value: "10", + max_quantity: 2, + target_rules: [ + { + attribute: "shipping_option.id", + operator: "in", + values: ["express", "standard"], + }, + ], + }, }, - }, - ]) + ]) - const result = await service.computeActions( - [], - { - customer: { - customer_group: { - id: "VIP", + const [createdPromotionTwo] = await service.create([ + { + code: "PROMOTION_TEST_2", + type: PromotionType.STANDARD, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + application_method: { + type: ApplicationMethodType.PERCENTAGE, + target_type: "shipping_methods", + allocation: "each", + value: "10", + max_quantity: 2, + target_rules: [ + { + attribute: "shipping_option.id", + operator: "in", + values: ["express", "standard"], + }, + ], }, }, - shipping_methods: [ - { - id: "shipping_method_express", - unit_price: 250, - shipping_option: { - id: "express", + ]) + + const result = await service.computeActions( + ["PROMOTION_TEST", "PROMOTION_TEST_2"], + { + customer: { + customer_group: { + id: "VIP", }, }, - { - id: "shipping_method_standard", - unit_price: 150, - shipping_option: { - id: "standard", + shipping_methods: [ + { + id: "shipping_method_express", + unit_price: 250, + shipping_option: { + id: "express", + }, }, - }, - { - id: "shipping_method_snail", - unit_price: 200, - shipping_option: { - id: "snail", + { + id: "shipping_method_standard", + unit_price: 150, + shipping_option: { + id: "standard", + }, }, - }, - ], - }, - { 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([ - { - code: "PROMOTION_TEST", - type: PromotionType.STANDARD, - 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"], + id: "shipping_method_snail", + unit_price: 200, + shipping_option: { + id: "snail", + }, }, ], + } + ) + + expect(result).toEqual([ + { + action: "addShippingMethodAdjustment", + shipping_method_id: "shipping_method_express", + amount: 25, + code: "PROMOTION_TEST", }, - }, - ]) + { + action: "addShippingMethodAdjustment", + shipping_method_id: "shipping_method_standard", + amount: 15, + code: "PROMOTION_TEST", + }, + { + action: "addShippingMethodAdjustment", + shipping_method_id: "shipping_method_express", + amount: 22.5, + code: "PROMOTION_TEST_2", + }, + { + action: "addShippingMethodAdjustment", + shipping_method_id: "shipping_method_standard", + amount: 13.5, + code: "PROMOTION_TEST_2", + }, + ]) + }) - const [createdPromotionTwo] = await service.create([ - { - code: "PROMOTION_TEST_2", - type: PromotionType.STANDARD, - 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: [ + it("should compute budget exceeded action when applicable total exceeds campaign budget for type spend", async () => { + await createCampaigns(repositoryManager) + + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + rules: [ { - attribute: "shipping_option.id", + attribute: "customer.customer_group.id", operator: "in", - values: ["express", "standard"], + values: ["VIP", "top100"], }, ], + campaign_id: "campaign-id-1", + application_method: { + type: ApplicationMethodType.PERCENTAGE, + target_type: "shipping_methods", + allocation: "each", + value: "100", + max_quantity: 2, + target_rules: [ + { + attribute: "shipping_option.id", + operator: "in", + values: ["express", "standard"], + }, + ], + }, }, - }, - ]) + ]) - const result = await service.computeActions( - ["PROMOTION_TEST", "PROMOTION_TEST_2"], - { + const result = await service.computeActions(["PROMOTION_TEST"], { customer: { customer_group: { id: "VIP", @@ -1467,111 +3092,111 @@ describe("Promotion Service: computeActions", () => { shipping_methods: [ { id: "shipping_method_express", - unit_price: 250, + unit_price: 1200, 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", - }, - { - action: "addShippingMethodAdjustment", - shipping_method_id: "shipping_method_express", - amount: 50, - code: "PROMOTION_TEST_2", - }, - ]) - }) + expect(result).toEqual([ + { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" }, + ]) + }) - it("should not compute actions when applicable total is 0", async () => { - const [createdPromotion] = await service.create([ - { - code: "PROMOTION_TEST", - type: PromotionType.STANDARD, - rules: [ - { - attribute: "customer.customer_group.id", - operator: "in", - values: ["VIP", "top100"], - }, - ], - application_method: { - type: "fixed", - target_type: "shipping_methods", - allocation: "each", - value: "500", - max_quantity: 2, - target_rules: [ + it("should compute budget exceeded action when applicable total exceeds campaign budget for type usage", async () => { + await createCampaigns(repositoryManager) + + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + rules: [ { - attribute: "shipping_option.id", + attribute: "customer.customer_group.id", operator: "in", - values: ["express", "standard"], + values: ["VIP", "top100"], }, ], + campaign_id: "campaign-id-2", + application_method: { + type: ApplicationMethodType.PERCENTAGE, + target_type: "shipping_methods", + allocation: "each", + value: "10", + max_quantity: 2, + target_rules: [ + { + attribute: "shipping_option.id", + operator: "in", + values: ["express", "standard"], + }, + ], + }, }, - }, - ]) + ]) - const [createdPromotionTwo] = await service.create([ - { - code: "PROMOTION_TEST_2", - type: PromotionType.STANDARD, - rules: [ + await service.updateCampaigns({ + id: "campaign-id-2", + budget: { used: 1000 }, + }) + + const result = await service.computeActions(["PROMOTION_TEST"], { + customer: { + customer_group: { + id: "VIP", + }, + }, + shipping_methods: [ { - attribute: "customer.customer_group.id", - operator: "in", - values: ["VIP", "top100"], + id: "shipping_method_express", + unit_price: 1200, + shipping_option: { + id: "express", + }, }, ], - application_method: { - type: "fixed", - target_type: "shipping_methods", - allocation: "each", - value: "200", - max_quantity: 2, - target_rules: [ + }) + + expect(result).toEqual([ + { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" }, + ]) + }) + }) + }) + + describe("when promotion is for shipping_method and allocation is across", () => { + describe("when application type is fixed", () => { + it("should compute the correct shipping_method amendments", async () => { + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + rules: [ { - attribute: "shipping_option.id", + attribute: "customer.customer_group.id", operator: "in", - values: ["express", "standard"], + values: ["VIP", "top100"], }, ], + application_method: { + type: ApplicationMethodType.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( - ["PROMOTION_TEST", "PROMOTION_TEST_2"], - { + const result = await service.computeActions(["PROMOTION_TEST"], { customer: { customer_group: { id: "VIP", @@ -1580,14 +3205,14 @@ describe("Promotion Service: computeActions", () => { shipping_methods: [ { id: "shipping_method_express", - unit_price: 250, + unit_price: 500, shipping_option: { id: "express", }, }, { id: "shipping_method_standard", - unit_price: 150, + unit_price: 100, shipping_option: { id: "standard", }, @@ -1600,349 +3225,354 @@ describe("Promotion Service: computeActions", () => { }, }, ], - } - ) - - expect(result).toEqual([ - { - action: "addShippingMethodAdjustment", - shipping_method_id: "shipping_method_express", - amount: 250, - code: "PROMOTION_TEST", - }, - { - action: "addShippingMethodAdjustment", - shipping_method_id: "shipping_method_standard", - amount: 150, - code: "PROMOTION_TEST", - }, - ]) - }) - - it("should compute budget exceeded action when applicable total exceeds campaign budget for type spend", async () => { - await createCampaigns(repositoryManager) - - const [createdPromotion] = await service.create([ - { - code: "PROMOTION_TEST", - type: PromotionType.STANDARD, - rules: [ - { - attribute: "customer.customer_group.id", - operator: "in", - values: ["VIP", "top100"], - }, - ], - campaign_id: "campaign-id-1", - application_method: { - type: "fixed", - target_type: "shipping_methods", - allocation: "each", - value: "1200", - max_quantity: 2, - target_rules: [ - { - attribute: "shipping_option.id", - operator: "in", - values: ["express", "standard"], - }, - ], - }, - }, - ]) + }) - const result = await service.computeActions(["PROMOTION_TEST"], { - customer: { - customer_group: { - id: "VIP", + expect(result).toEqual([ + { + action: "addShippingMethodAdjustment", + shipping_method_id: "shipping_method_express", + amount: 166.66666666666669, + code: "PROMOTION_TEST", }, - }, - shipping_methods: [ { - id: "shipping_method_express", - unit_price: 1200, - shipping_option: { - id: "express", - }, + action: "addShippingMethodAdjustment", + shipping_method_id: "shipping_method_standard", + amount: 33.33333333333333, + code: "PROMOTION_TEST", }, - ], + ]) }) - expect(result).toEqual([ - { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" }, - ]) - }) - - it("should compute budget exceeded action when applicable total exceeds campaign budget for type usage", async () => { - await createCampaigns(repositoryManager) - - const [createdPromotion] = await service.create([ - { - code: "PROMOTION_TEST", - type: PromotionType.STANDARD, - rules: [ - { - attribute: "customer.customer_group.id", - operator: "in", - values: ["VIP", "top100"], - }, - ], - campaign_id: "campaign-id-2", - application_method: { - type: "fixed", - target_type: "shipping_methods", - allocation: "each", - value: "1200", - max_quantity: 2, - target_rules: [ + 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: "shipping_option.id", + attribute: "customer.customer_group.id", operator: "in", - values: ["express", "standard"], + values: ["VIP", "top100"], }, ], + application_method: { + type: ApplicationMethodType.FIXED, + target_type: "shipping_methods", + allocation: "across", + value: "200", + target_rules: [ + { + attribute: "shipping_option.id", + operator: "in", + values: ["express", "standard"], + }, + ], + }, }, - }, - ]) - - await service.updateCampaigns({ - id: "campaign-id-2", - budget: { used: 1000 }, - }) + ]) - const result = await service.computeActions(["PROMOTION_TEST"], { - customer: { - customer_group: { - id: "VIP", - }, - }, - shipping_methods: [ - { - id: "shipping_method_express", - unit_price: 1200, - shipping_option: { - id: "express", + const result = await service.computeActions([], { + customer: { + customer_group: { + id: "VIP", }, }, - ], - }) - - expect(result).toEqual([ - { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" }, - ]) - }) - }) - - describe("when promotion is for shipping_method and allocation is across", () => { - it("should compute the correct shipping_method amendments", async () => { - const [createdPromotion] = await service.create([ - { - code: "PROMOTION_TEST", - type: PromotionType.STANDARD, - rules: [ + shipping_methods: [ { - attribute: "customer.customer_group.id", - operator: "in", - values: ["VIP", "top100"], + id: "shipping_method_express", + unit_price: 500, + shipping_option: { + id: "express", + }, }, - ], - application_method: { - type: "fixed", - target_type: "shipping_methods", - allocation: "across", - value: "200", - target_rules: [ - { - attribute: "shipping_option.id", - operator: "in", - values: ["express", "standard"], + { + id: "shipping_method_standard", + unit_price: 100, + shipping_option: { + id: "standard", }, - ], - }, - }, - ]) - - const result = await service.computeActions(["PROMOTION_TEST"], { - 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", }, { - id: "shipping_method_snail", - unit_price: 200, - shipping_option: { - id: "snail", - }, + action: "addShippingMethodAdjustment", + shipping_method_id: "shipping_method_standard", + amount: 33.33333333333333, + code: "PROMOTION_TEST", }, - ], + ]) }) - 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 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: [ + it("should compute the correct item amendments when there are multiple promotions to apply", async () => { + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + application_method: { + type: ApplicationMethodType.FIXED, + target_type: "shipping_methods", + allocation: "across", + value: "200", + target_rules: [ + { + attribute: "shipping_option.id", + operator: "in", + values: ["express", "standard"], + }, + ], + }, + }, + ]) + + const [createdPromotion2] = await service.create([ + { + code: "PROMOTION_TEST_2", + type: PromotionType.STANDARD, + rules: [ { - attribute: "shipping_option.id", + attribute: "customer.customer_group.id", operator: "in", - values: ["express", "standard"], + values: ["VIP", "top100"], }, ], + application_method: { + type: ApplicationMethodType.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: [ + const result = await service.computeActions( + ["PROMOTION_TEST", "PROMOTION_TEST_2"], { - id: "shipping_method_express", - unit_price: 500, - shipping_option: { - id: "express", + 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", }, { - id: "shipping_method_standard", - unit_price: 100, - shipping_option: { - id: "standard", - }, + action: "addShippingMethodAdjustment", + shipping_method_id: "shipping_method_standard", + amount: 33.33333333333333, + code: "PROMOTION_TEST", }, { - id: "shipping_method_snail", - unit_price: 200, - shipping_option: { - id: "snail", - }, + action: "addShippingMethodAdjustment", + shipping_method_id: "shipping_method_express", + amount: 83.33333333333331, + code: "PROMOTION_TEST_2", }, - ], + { + action: "addShippingMethodAdjustment", + shipping_method_id: "shipping_method_standard", + amount: 16.66666666666667, + code: "PROMOTION_TEST_2", + }, + ]) }) - 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([ - { - code: "PROMOTION_TEST", - type: PromotionType.STANDARD, - rules: [ - { - attribute: "customer.customer_group.id", - operator: "in", - values: ["VIP", "top100"], + it("should not compute actions when applicable total is 0", async () => { + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + application_method: { + type: ApplicationMethodType.FIXED, + target_type: "shipping_methods", + allocation: "across", + value: "1000", + target_rules: [ + { + attribute: "shipping_option.id", + operator: "in", + values: ["express", "standard"], + }, + ], }, - ], - application_method: { - type: "fixed", - target_type: "shipping_methods", - allocation: "across", - value: "200", - target_rules: [ + }, + ]) + + const [createdPromotion2] = await service.create([ + { + code: "PROMOTION_TEST_2", + type: PromotionType.STANDARD, + rules: [ { - attribute: "shipping_option.id", + attribute: "customer.customer_group.id", operator: "in", - values: ["express", "standard"], + values: ["VIP", "top100"], }, ], + application_method: { + type: ApplicationMethodType.FIXED, + target_type: "shipping_methods", + allocation: "across", + value: "200", + target_rules: [ + { + attribute: "shipping_option.id", + operator: "in", + values: ["express", "standard"], + }, + ], + }, }, - }, - ]) + ]) - const [createdPromotion2] = await service.create([ - { - code: "PROMOTION_TEST_2", - type: PromotionType.STANDARD, - rules: [ - { - attribute: "customer.customer_group.id", - operator: "in", - values: ["VIP", "top100"], + const result = await service.computeActions( + ["PROMOTION_TEST", "PROMOTION_TEST_2"], + { + customer: { + customer_group: { + id: "VIP", + }, }, - ], - application_method: { - type: "fixed", - target_type: "shipping_methods", - allocation: "across", - value: "200", - target_rules: [ + shipping_methods: [ { - attribute: "shipping_option.id", + 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: 500, + code: "PROMOTION_TEST", + }, + { + action: "addShippingMethodAdjustment", + shipping_method_id: "shipping_method_standard", + amount: 100, + code: "PROMOTION_TEST", + }, + ]) + }) + + it("should compute budget exceeded action when applicable total exceeds campaign budget for type spend", async () => { + await createCampaigns(repositoryManager) + + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + rules: [ + { + attribute: "customer.customer_group.id", operator: "in", - values: ["express", "standard"], + values: ["VIP", "top100"], }, ], + campaign_id: "campaign-id-1", + application_method: { + type: ApplicationMethodType.FIXED, + target_type: "shipping_methods", + allocation: "across", + value: "1200", + target_rules: [ + { + attribute: "shipping_option.id", + operator: "in", + values: ["express", "standard"], + }, + ], + }, }, - }, - ]) + ]) - const result = await service.computeActions( - ["PROMOTION_TEST", "PROMOTION_TEST_2"], - { + const result = await service.computeActions(["PROMOTION_TEST"], { customer: { customer_group: { id: "VIP", @@ -1951,115 +3581,184 @@ describe("Promotion Service: computeActions", () => { shipping_methods: [ { id: "shipping_method_express", - unit_price: 500, + unit_price: 1200, shipping_option: { id: "express", }, }, - { - id: "shipping_method_standard", - unit_price: 100, - shipping_option: { - id: "standard", + ], + }) + + expect(result).toEqual([ + { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" }, + ]) + }) + + it("should compute budget exceeded action when applicable total exceeds campaign budget for type usage", async () => { + await createCampaigns(repositoryManager) + + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], }, + ], + campaign_id: "campaign-id-2", + application_method: { + type: ApplicationMethodType.FIXED, + target_type: "shipping_methods", + allocation: "across", + value: "1200", + target_rules: [ + { + attribute: "shipping_option.id", + operator: "in", + values: ["express", "standard"], + }, + ], + }, + }, + ]) + + await service.updateCampaigns({ + id: "campaign-id-2", + budget: { used: 1000 }, + }) + + const result = await service.computeActions(["PROMOTION_TEST"], { + customer: { + customer_group: { + id: "VIP", }, + }, + shipping_methods: [ { - id: "shipping_method_snail", - unit_price: 200, + id: "shipping_method_express", + unit_price: 1200, shipping_option: { - id: "snail", + id: "express", }, }, ], - } - ) + }) - 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", - }, - { - action: "addShippingMethodAdjustment", - shipping_method_id: "shipping_method_express", - amount: 83.33333333333331, - code: "PROMOTION_TEST_2", - }, - { - action: "addShippingMethodAdjustment", - shipping_method_id: "shipping_method_standard", - amount: 16.66666666666667, - code: "PROMOTION_TEST_2", - }, - ]) + expect(result).toEqual([ + { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" }, + ]) + }) }) - it("should not compute actions when applicable total is 0", async () => { - const [createdPromotion] = await service.create([ - { - code: "PROMOTION_TEST", - type: PromotionType.STANDARD, - rules: [ - { - attribute: "customer.customer_group.id", - operator: "in", - values: ["VIP", "top100"], - }, - ], - application_method: { - type: "fixed", - target_type: "shipping_methods", - allocation: "across", - value: "1000", - target_rules: [ + describe("when application type is percentage", () => { + it("should compute the correct shipping_method amendments", async () => { + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + rules: [ { - attribute: "shipping_option.id", + attribute: "customer.customer_group.id", operator: "in", - values: ["express", "standard"], + values: ["VIP", "top100"], }, ], + application_method: { + type: ApplicationMethodType.PERCENTAGE, + target_type: "shipping_methods", + allocation: "across", + value: "10", + target_rules: [ + { + attribute: "shipping_option.id", + operator: "in", + values: ["express", "standard"], + }, + ], + }, }, - }, - ]) + ]) - const [createdPromotion2] = await service.create([ - { - code: "PROMOTION_TEST_2", - type: PromotionType.STANDARD, - rules: [ + const result = await service.computeActions(["PROMOTION_TEST"], { + 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", + }, + }, { - attribute: "customer.customer_group.id", - operator: "in", - values: ["VIP", "top100"], + id: "shipping_method_snail", + unit_price: 200, + shipping_option: { + id: "snail", + }, }, ], - application_method: { - type: "fixed", - target_type: "shipping_methods", - allocation: "across", - value: "200", - target_rules: [ + }) + + expect(result).toEqual([ + { + action: "addShippingMethodAdjustment", + shipping_method_id: "shipping_method_express", + amount: 50, + code: "PROMOTION_TEST", + }, + { + action: "addShippingMethodAdjustment", + shipping_method_id: "shipping_method_standard", + amount: 10, + code: "PROMOTION_TEST", + }, + ]) + }) + + 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: "shipping_option.id", + attribute: "customer.customer_group.id", operator: "in", - values: ["express", "standard"], + values: ["VIP", "top100"], }, ], + application_method: { + type: ApplicationMethodType.PERCENTAGE, + target_type: "shipping_methods", + allocation: "across", + value: "10", + target_rules: [ + { + attribute: "shipping_option.id", + operator: "in", + values: ["express", "standard"], + }, + ], + }, }, - }, - ]) + ]) - const result = await service.computeActions( - ["PROMOTION_TEST", "PROMOTION_TEST_2"], - { + const result = await service.computeActions([], { customer: { customer_group: { id: "VIP", @@ -2088,134 +3787,356 @@ describe("Promotion Service: computeActions", () => { }, }, ], - } - ) + }) - expect(result).toEqual([ - { - action: "addShippingMethodAdjustment", - shipping_method_id: "shipping_method_express", - amount: 500, - code: "PROMOTION_TEST", - }, - { - action: "addShippingMethodAdjustment", - shipping_method_id: "shipping_method_standard", - amount: 100, - code: "PROMOTION_TEST", - }, - ]) - }) + expect(result).toEqual([ + { + action: "addShippingMethodAdjustment", + shipping_method_id: "shipping_method_express", + amount: 50, + code: "PROMOTION_TEST", + }, + { + action: "addShippingMethodAdjustment", + shipping_method_id: "shipping_method_standard", + amount: 10, + code: "PROMOTION_TEST", + }, + ]) + }) + + it("should compute the correct item amendments when there are multiple promotions to apply", async () => { + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + application_method: { + type: ApplicationMethodType.PERCENTAGE, + target_type: "shipping_methods", + allocation: "across", + value: "10", + target_rules: [ + { + attribute: "shipping_option.id", + operator: "in", + values: ["express", "standard"], + }, + ], + }, + }, + ]) - it("should compute budget exceeded action when applicable total exceeds campaign budget for type spend", async () => { - await createCampaigns(repositoryManager) + const [createdPromotion2] = await service.create([ + { + code: "PROMOTION_TEST_2", + type: PromotionType.STANDARD, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + application_method: { + type: ApplicationMethodType.PERCENTAGE, + target_type: "shipping_methods", + allocation: "across", + value: "10", + target_rules: [ + { + attribute: "shipping_option.id", + operator: "in", + values: ["express", "standard"], + }, + ], + }, + }, + ]) - const [createdPromotion] = await service.create([ - { - code: "PROMOTION_TEST", - type: PromotionType.STANDARD, - rules: [ - { - attribute: "customer.customer_group.id", - operator: "in", - values: ["VIP", "top100"], + const result = await service.computeActions( + ["PROMOTION_TEST", "PROMOTION_TEST_2"], + { + customer: { + customer_group: { + id: "VIP", + }, }, - ], - campaign_id: "campaign-id-1", - application_method: { - type: "fixed", - target_type: "shipping_methods", - allocation: "across", - value: "1200", - target_rules: [ + shipping_methods: [ { - attribute: "shipping_option.id", + 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: 50, + code: "PROMOTION_TEST", + }, + { + action: "addShippingMethodAdjustment", + shipping_method_id: "shipping_method_standard", + amount: 10, + code: "PROMOTION_TEST", + }, + { + action: "addShippingMethodAdjustment", + shipping_method_id: "shipping_method_express", + amount: 45, + code: "PROMOTION_TEST_2", + }, + { + action: "addShippingMethodAdjustment", + shipping_method_id: "shipping_method_standard", + amount: 9, + code: "PROMOTION_TEST_2", + }, + ]) + }) + + it("should not compute actions when applicable total is 0", async () => { + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + rules: [ + { + attribute: "customer.customer_group.id", operator: "in", - values: ["express", "standard"], + values: ["VIP", "top100"], }, ], + application_method: { + type: ApplicationMethodType.PERCENTAGE, + target_type: "shipping_methods", + allocation: "across", + value: "100", + target_rules: [ + { + attribute: "shipping_option.id", + operator: "in", + values: ["express", "standard"], + }, + ], + }, }, - }, - ]) + ]) - const result = await service.computeActions(["PROMOTION_TEST"], { - customer: { - customer_group: { - id: "VIP", + const [createdPromotion2] = await service.create([ + { + code: "PROMOTION_TEST_2", + type: PromotionType.STANDARD, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + application_method: { + type: ApplicationMethodType.PERCENTAGE, + target_type: "shipping_methods", + allocation: "across", + value: "10", + target_rules: [ + { + attribute: "shipping_option.id", + operator: "in", + values: ["express", "standard"], + }, + ], + }, }, - }, - shipping_methods: [ + ]) + + const result = await service.computeActions( + ["PROMOTION_TEST", "PROMOTION_TEST_2"], { - id: "shipping_method_express", - unit_price: 1200, - shipping_option: { - id: "express", + 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: 500, + code: "PROMOTION_TEST", }, - ], + { + action: "addShippingMethodAdjustment", + shipping_method_id: "shipping_method_standard", + amount: 100, + code: "PROMOTION_TEST", + }, + ]) }) - expect(result).toEqual([ - { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" }, - ]) - }) + it("should compute budget exceeded action when applicable total exceeds campaign budget for type spend", async () => { + await createCampaigns(repositoryManager) - it("should compute budget exceeded action when applicable total exceeds campaign budget for type usage", async () => { - await createCampaigns(repositoryManager) + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + rules: [ + { + attribute: "customer.customer_group.id", + operator: "in", + values: ["VIP", "top100"], + }, + ], + campaign_id: "campaign-id-1", + application_method: { + type: ApplicationMethodType.PERCENTAGE, + target_type: "shipping_methods", + allocation: "across", + value: "100", + target_rules: [ + { + attribute: "shipping_option.id", + operator: "in", + values: ["express", "standard"], + }, + ], + }, + }, + ]) - const [createdPromotion] = await service.create([ - { - code: "PROMOTION_TEST", - type: PromotionType.STANDARD, - rules: [ + const result = await service.computeActions(["PROMOTION_TEST"], { + customer: { + customer_group: { + id: "VIP", + }, + }, + shipping_methods: [ { - attribute: "customer.customer_group.id", - operator: "in", - values: ["VIP", "top100"], + id: "shipping_method_express", + unit_price: 1200, + shipping_option: { + id: "express", + }, }, ], - campaign_id: "campaign-id-2", - application_method: { - type: "fixed", - target_type: "shipping_methods", - allocation: "across", - value: "1200", - target_rules: [ + }) + + expect(result).toEqual([ + { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" }, + ]) + }) + + it("should compute budget exceeded action when applicable total exceeds campaign budget for type usage", async () => { + await createCampaigns(repositoryManager) + + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + rules: [ { - attribute: "shipping_option.id", + attribute: "customer.customer_group.id", operator: "in", - values: ["express", "standard"], + values: ["VIP", "top100"], }, ], + campaign_id: "campaign-id-2", + application_method: { + type: ApplicationMethodType.PERCENTAGE, + target_type: "shipping_methods", + allocation: "across", + value: "10", + target_rules: [ + { + attribute: "shipping_option.id", + operator: "in", + values: ["express", "standard"], + }, + ], + }, }, - }, - ]) + ]) - await service.updateCampaigns({ - id: "campaign-id-2", - budget: { used: 1000 }, - }) + await service.updateCampaigns({ + id: "campaign-id-2", + budget: { used: 1000 }, + }) - const result = await service.computeActions(["PROMOTION_TEST"], { - customer: { - customer_group: { - id: "VIP", - }, - }, - shipping_methods: [ - { - id: "shipping_method_express", - unit_price: 1200, - shipping_option: { - id: "express", + const result = await service.computeActions(["PROMOTION_TEST"], { + customer: { + customer_group: { + id: "VIP", }, }, - ], - }) + shipping_methods: [ + { + id: "shipping_method_express", + unit_price: 1200, + shipping_option: { + id: "express", + }, + }, + ], + }) - expect(result).toEqual([ - { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" }, - ]) + expect(result).toEqual([ + { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" }, + ]) + }) }) }) @@ -2552,18 +4473,6 @@ describe("Promotion Service: computeActions", () => { amount: 150, code: "PROMOTION_TEST", }, - { - action: "addItemAdjustment", - item_id: "item_cotton_tshirt", - amount: 12.5, - code: "PROMOTION_TEST_2", - }, - { - action: "addItemAdjustment", - item_id: "item_cotton_sweater", - amount: 37.5, - code: "PROMOTION_TEST_2", - }, ]) }) }) diff --git a/packages/promotion/integration-tests/__tests__/services/promotion-module/promotion.spec.ts b/packages/promotion/integration-tests/__tests__/services/promotion-module/promotion.spec.ts index 909f31dbc49ac..dc36e066132d7 100644 --- a/packages/promotion/integration-tests/__tests__/services/promotion-module/promotion.spec.ts +++ b/packages/promotion/integration-tests/__tests__/services/promotion-module/promotion.spec.ts @@ -1,16 +1,17 @@ +import { Modules } from "@medusajs/modules-sdk" import { IPromotionModuleService } from "@medusajs/types" import { + ApplicationMethodTargetType, ApplicationMethodType, CampaignBudgetType, PromotionType, } from "@medusajs/utils" import { SqlEntityManager } from "@mikro-orm/postgresql" +import { initModules } from "medusa-test-utils" import { createCampaigns } from "../../../__fixtures__/campaigns" import { createPromotions } from "../../../__fixtures__/promotion" import { MikroOrmWrapper } from "../../../utils" import { getInitModuleConfig } from "../../../utils/get-init-module-config" -import { Modules } from "@medusajs/modules-sdk" -import { initModules } from "medusa-test-utils/dist" jest.setTimeout(30000) @@ -114,6 +115,24 @@ describe("Promotion Service", () => { ) }) + it("should throw error when percentage type and value is greater than 100", async () => { + const error = await service + .create({ + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + application_method: { + type: ApplicationMethodType.PERCENTAGE, + target_type: ApplicationMethodTargetType.ORDER, + value: "1000", + }, + }) + .catch((e) => e) + + expect(error.message).toContain( + "Application Method value should be a percentage number between 0 and 100" + ) + }) + it("should throw an error when both campaign and campaign_id are provided", async () => { const startsAt = new Date("01/01/2023") const endsAt = new Date("01/01/2023") @@ -655,7 +674,7 @@ describe("Promotion Service", () => { is_automatic: true, code: "TEST", type: PromotionType.BUYGET, - }, + } as any, ]) expect(updatedPromotion).toEqual( @@ -899,12 +918,12 @@ describe("Promotion Service", () => { value: "200", target_type: "items", }, - }, + } as any, { id: "promotion-id-2", code: "PROMOTION_2", type: PromotionType.STANDARD, - }, + } as any, ]) }) diff --git a/packages/promotion/src/utils/compute-actions/items.ts b/packages/promotion/src/utils/compute-actions/items.ts index afbf400b564ee..d499464b179cb 100644 --- a/packages/promotion/src/utils/compute-actions/items.ts +++ b/packages/promotion/src/utils/compute-actions/items.ts @@ -5,6 +5,7 @@ import { import { ApplicationMethodAllocation, ApplicationMethodTargetType, + ApplicationMethodType, ComputedActions, MedusaError, } from "@medusajs/utils" @@ -67,10 +68,14 @@ export function applyPromotionToItems( method.quantity, applicationMethod?.max_quantity! ) - const promotionValue = - parseFloat(applicationMethod!.value!) * quantityMultiplier - const applicableTotal = - method.unit_price * quantityMultiplier - appliedPromoValue + const totalItemValue = method.unit_price * quantityMultiplier + let promotionValue = parseFloat(applicationMethod!.value!) + const applicableTotal = totalItemValue - appliedPromoValue + + if (applicationMethod?.type === ApplicationMethodType.PERCENTAGE) { + promotionValue = (promotionValue / 100) * applicableTotal + } + const amount = Math.min(promotionValue, applicableTotal) if (amount <= 0) { @@ -110,14 +115,23 @@ export function applyPromotionToItems( }, 0) for (const method of items!) { - const promotionValue = parseFloat(applicationMethod!.value!) const appliedPromoValue = methodIdPromoValueMap.get(method.id) || 0 + const promotionValue = parseFloat(applicationMethod!.value!) const applicableTotal = method.unit_price * method.quantity - appliedPromoValue + if (applicableTotal <= 0) { + continue + } + // TODO: should we worry about precision here? - const applicablePromotionValue = + let applicablePromotionValue = (applicableTotal / totalApplicableValue) * promotionValue + + if (applicationMethod?.type === ApplicationMethodType.PERCENTAGE) { + applicablePromotionValue = (promotionValue / 100) * applicableTotal + } + const amount = Math.min(applicablePromotionValue, applicableTotal) if (amount <= 0) { @@ -135,6 +149,8 @@ export function applyPromotionToItems( continue } + methodIdPromoValueMap.set(method.id, appliedPromoValue + amount) + computedActions.push({ action: ComputedActions.ADD_ITEM_ADJUSTMENT, item_id: method.id, diff --git a/packages/promotion/src/utils/compute-actions/shipping-methods.ts b/packages/promotion/src/utils/compute-actions/shipping-methods.ts index 440021f482bdf..5a3d11dd508d9 100644 --- a/packages/promotion/src/utils/compute-actions/shipping-methods.ts +++ b/packages/promotion/src/utils/compute-actions/shipping-methods.ts @@ -2,6 +2,7 @@ import { PromotionTypes } from "@medusajs/types" import { ApplicationMethodAllocation, ApplicationMethodTargetType, + ApplicationMethodType, ComputedActions, MedusaError, } from "@medusajs/utils" @@ -55,8 +56,13 @@ export function applyPromotionToShippingMethods( if (allocation === ApplicationMethodAllocation.EACH) { for (const method of shippingMethods!) { const appliedPromoValue = methodIdPromoValueMap.get(method.id) || 0 - const promotionValue = parseFloat(applicationMethod!.value!) + let promotionValue = parseFloat(applicationMethod!.value!) const applicableTotal = method.unit_price - appliedPromoValue + + if (applicationMethod?.type === ApplicationMethodType.PERCENTAGE) { + promotionValue = (promotionValue / 100) * applicableTotal + } + const amount = Math.min(promotionValue, applicableTotal) if (amount <= 0) { @@ -102,10 +108,15 @@ export function applyPromotionToShippingMethods( const appliedPromoValue = methodIdPromoValueMap.get(method.id) || 0 // TODO: should we worry about precision here? - const applicablePromotionValue = + let applicablePromotionValue = (applicableTotal / totalApplicableValue) * promotionValue - appliedPromoValue + if (applicationMethod?.type === ApplicationMethodType.PERCENTAGE) { + applicablePromotionValue = + (promotionValue / 100) * (applicableTotal - appliedPromoValue) + } + const amount = Math.min(applicablePromotionValue, applicableTotal) if (amount <= 0) { diff --git a/packages/promotion/src/utils/validations/application-method.ts b/packages/promotion/src/utils/validations/application-method.ts index 99c6296349cc4..7d022f0e9d519 100644 --- a/packages/promotion/src/utils/validations/application-method.ts +++ b/packages/promotion/src/utils/validations/application-method.ts @@ -37,11 +37,23 @@ export function validateApplicationMethodAttributes( const applyToQuantity = data.apply_to_quantity || applicationMethod?.apply_to_quantity const targetType = data.target_type || applicationMethod?.target_type + const type = data.type || applicationMethod?.type const applicationMethodType = data.type || applicationMethod?.type + const value = parseFloat(data.value! || applicationMethod?.value!) const maxQuantity = data.max_quantity || applicationMethod.max_quantity const allocation = data.allocation || applicationMethod.allocation const allTargetTypes: string[] = Object.values(ApplicationMethodTargetType) + if ( + type === ApplicationMethodType.PERCENTAGE && + (value <= 0 || value > 100) + ) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `Application Method value should be a percentage number between 0 and 100` + ) + } + if (promotion?.type === PromotionType.BUYGET) { if (!isPresent(applyToQuantity)) { throw new MedusaError( From 3bc4d16319130495cdc28df40835c2da06420676 Mon Sep 17 00:00:00 2001 From: Riqwan Thamir Date: Fri, 2 Feb 2024 16:42:51 +0530 Subject: [PATCH 2/2] chore: use subtotal instead of unit_price --- .../promotion-module/compute-actions.spec.ts | 258 +++++++++--------- .../src/utils/compute-actions/buy-get.ts | 7 +- .../src/utils/compute-actions/items.ts | 12 +- .../utils/compute-actions/shipping-methods.ts | 6 +- .../src/promotion/common/compute-actions.ts | 4 +- 5 files changed, 148 insertions(+), 139 deletions(-) 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 9065c30bf2df5..7ba8ffcd66f21 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 @@ -49,7 +49,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt", quantity: 1, - unit_price: 100, + subtotal: 100, product_category: { id: "catg_cotton", }, @@ -60,7 +60,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_sweater", quantity: 5, - unit_price: 150, + subtotal: 750, product_category: { id: "catg_cotton", }, @@ -95,7 +95,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt", quantity: 1, - unit_price: 100, + subtotal: 100, adjustments: [ { id: "test-adjustment", @@ -106,7 +106,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_sweater", quantity: 5, - unit_price: 150, + subtotal: 750, }, ], }) @@ -138,12 +138,12 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt", quantity: 1, - unit_price: 100, + subtotal: 100, }, { id: "item_cotton_sweater", quantity: 5, - unit_price: 150, + subtotal: 750, adjustments: [ { id: "test-adjustment", @@ -202,7 +202,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt", quantity: 1, - unit_price: 100, + subtotal: 100, product_category: { id: "catg_cotton", }, @@ -213,7 +213,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_sweater", quantity: 5, - unit_price: 150, + subtotal: 750, product_category: { id: "catg_cotton", }, @@ -309,7 +309,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt", quantity: 1, - unit_price: 50, + subtotal: 50, product_category: { id: "catg_cotton", }, @@ -320,7 +320,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_sweater", quantity: 1, - unit_price: 150, + subtotal: 150, product_category: { id: "catg_cotton", }, @@ -429,7 +429,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt", quantity: 1, - unit_price: 50, + subtotal: 50, product_category: { id: "catg_cotton", }, @@ -440,7 +440,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_sweater", quantity: 1, - unit_price: 150, + subtotal: 150, product_category: { id: "catg_cotton", }, @@ -510,7 +510,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt", quantity: 5, - unit_price: 1000, + subtotal: 5000, product_category: { id: "catg_cotton", }, @@ -578,7 +578,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt", quantity: 5, - unit_price: 1000, + subtotal: 5000, product_category: { id: "catg_cotton", }, @@ -635,7 +635,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt", quantity: 1, - unit_price: 100, + subtotal: 100, product_category: { id: "catg_cotton", }, @@ -646,7 +646,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_sweater", quantity: 5, - unit_price: 150, + subtotal: 750, product_category: { id: "catg_cotton", }, @@ -742,7 +742,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt", quantity: 3, - unit_price: 50, + subtotal: 150, product_category: { id: "catg_cotton", }, @@ -753,7 +753,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_sweater", quantity: 1, - unit_price: 150, + subtotal: 150, product_category: { id: "catg_cotton", }, @@ -862,7 +862,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt", quantity: 1, - unit_price: 50, + subtotal: 50, product_category: { id: "catg_cotton", }, @@ -873,7 +873,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_sweater", quantity: 1, - unit_price: 150, + subtotal: 150, product_category: { id: "catg_cotton", }, @@ -943,7 +943,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt", quantity: 5, - unit_price: 2000, + subtotal: 10000, product_category: { id: "catg_cotton", }, @@ -1006,7 +1006,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt", quantity: 5, - unit_price: 1000, + subtotal: 5000, product_category: { id: "catg_cotton", }, @@ -1064,7 +1064,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt", quantity: 2, - unit_price: 100, + subtotal: 200, product_category: { id: "catg_cotton", }, @@ -1075,7 +1075,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_sweater", quantity: 2, - unit_price: 300, + subtotal: 600, product_category: { id: "catg_cotton", }, @@ -1141,7 +1141,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt", quantity: 2, - unit_price: 100, + subtotal: 200, product_category: { id: "catg_cotton", }, @@ -1152,7 +1152,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_sweater", quantity: 2, - unit_price: 300, + subtotal: 600, product_category: { id: "catg_cotton", }, @@ -1246,7 +1246,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt", quantity: 1, - unit_price: 50, + subtotal: 50, product_category: { id: "catg_cotton", }, @@ -1257,7 +1257,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_sweater", quantity: 1, - unit_price: 150, + subtotal: 150, product_category: { id: "catg_cotton", }, @@ -1364,7 +1364,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt", quantity: 1, - unit_price: 50, + subtotal: 50, product_category: { id: "catg_cotton", }, @@ -1375,7 +1375,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_sweater", quantity: 1, - unit_price: 150, + subtotal: 150, product_category: { id: "catg_cotton", }, @@ -1444,7 +1444,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt", quantity: 5, - unit_price: 1000, + subtotal: 5000, product_category: { id: "catg_cotton", }, @@ -1506,7 +1506,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt", quantity: 5, - unit_price: 1000, + subtotal: 5000, product_category: { id: "catg_cotton", }, @@ -1562,7 +1562,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt", quantity: 2, - unit_price: 100, + subtotal: 200, product_category: { id: "catg_cotton", }, @@ -1573,7 +1573,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_sweater", quantity: 2, - unit_price: 300, + subtotal: 600, product_category: { id: "catg_cotton", }, @@ -1639,7 +1639,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt", quantity: 2, - unit_price: 100, + subtotal: 200, product_category: { id: "catg_cotton", }, @@ -1650,7 +1650,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_sweater", quantity: 2, - unit_price: 300, + subtotal: 600, product_category: { id: "catg_cotton", }, @@ -1744,7 +1744,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt", quantity: 1, - unit_price: 50, + subtotal: 50, product_category: { id: "catg_cotton", }, @@ -1755,7 +1755,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_sweater", quantity: 1, - unit_price: 150, + subtotal: 150, product_category: { id: "catg_cotton", }, @@ -1862,7 +1862,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt", quantity: 1, - unit_price: 50, + subtotal: 50, product_category: { id: "catg_cotton", }, @@ -1873,7 +1873,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_sweater", quantity: 1, - unit_price: 150, + subtotal: 150, product_category: { id: "catg_cotton", }, @@ -1954,7 +1954,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt", quantity: 5, - unit_price: 1000, + subtotal: 5000, product_category: { id: "catg_cotton", }, @@ -2016,7 +2016,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt", quantity: 5, - unit_price: 1000, + subtotal: 5000, product_category: { id: "catg_cotton", }, @@ -2074,21 +2074,21 @@ describe("Promotion Service: computeActions", () => { shipping_methods: [ { id: "shipping_method_express", - unit_price: 250, + subtotal: 250, shipping_option: { id: "express", }, }, { id: "shipping_method_standard", - unit_price: 150, + subtotal: 150, shipping_option: { id: "standard", }, }, { id: "shipping_method_snail", - unit_price: 200, + subtotal: 200, shipping_option: { id: "snail", }, @@ -2151,21 +2151,21 @@ describe("Promotion Service: computeActions", () => { shipping_methods: [ { id: "shipping_method_express", - unit_price: 250, + subtotal: 250, shipping_option: { id: "express", }, }, { id: "shipping_method_standard", - unit_price: 150, + subtotal: 150, shipping_option: { id: "standard", }, }, { id: "shipping_method_snail", - unit_price: 200, + subtotal: 200, shipping_option: { id: "snail", }, @@ -2230,21 +2230,21 @@ describe("Promotion Service: computeActions", () => { shipping_methods: [ { id: "shipping_method_express", - unit_price: 250, + subtotal: 250, shipping_option: { id: "express", }, }, { id: "shipping_method_standard", - unit_price: 150, + subtotal: 150, shipping_option: { id: "standard", }, }, { id: "shipping_method_snail", - unit_price: 200, + subtotal: 200, shipping_option: { id: "snail", }, @@ -2325,21 +2325,21 @@ describe("Promotion Service: computeActions", () => { shipping_methods: [ { id: "shipping_method_express", - unit_price: 250, + subtotal: 250, shipping_option: { id: "express", }, }, { id: "shipping_method_standard", - unit_price: 150, + subtotal: 150, shipping_option: { id: "standard", }, }, { id: "shipping_method_snail", - unit_price: 200, + subtotal: 200, shipping_option: { id: "snail", }, @@ -2438,21 +2438,21 @@ describe("Promotion Service: computeActions", () => { shipping_methods: [ { id: "shipping_method_express", - unit_price: 250, + subtotal: 250, shipping_option: { id: "express", }, }, { id: "shipping_method_standard", - unit_price: 150, + subtotal: 150, shipping_option: { id: "standard", }, }, { id: "shipping_method_snail", - unit_price: 200, + subtotal: 200, shipping_option: { id: "snail", }, @@ -2518,7 +2518,7 @@ describe("Promotion Service: computeActions", () => { shipping_methods: [ { id: "shipping_method_express", - unit_price: 1200, + subtotal: 1200, shipping_option: { id: "express", }, @@ -2577,7 +2577,7 @@ describe("Promotion Service: computeActions", () => { shipping_methods: [ { id: "shipping_method_express", - unit_price: 1200, + subtotal: 1200, shipping_option: { id: "express", }, @@ -2630,21 +2630,21 @@ describe("Promotion Service: computeActions", () => { shipping_methods: [ { id: "shipping_method_express", - unit_price: 250, + subtotal: 250, shipping_option: { id: "express", }, }, { id: "shipping_method_standard", - unit_price: 150, + subtotal: 150, shipping_option: { id: "standard", }, }, { id: "shipping_method_snail", - unit_price: 200, + subtotal: 200, shipping_option: { id: "snail", }, @@ -2707,21 +2707,21 @@ describe("Promotion Service: computeActions", () => { shipping_methods: [ { id: "shipping_method_express", - unit_price: 250, + subtotal: 250, shipping_option: { id: "express", }, }, { id: "shipping_method_standard", - unit_price: 150, + subtotal: 150, shipping_option: { id: "standard", }, }, { id: "shipping_method_snail", - unit_price: 200, + subtotal: 200, shipping_option: { id: "snail", }, @@ -2786,21 +2786,21 @@ describe("Promotion Service: computeActions", () => { shipping_methods: [ { id: "shipping_method_express", - unit_price: 250, + subtotal: 250, shipping_option: { id: "express", }, }, { id: "shipping_method_standard", - unit_price: 150, + subtotal: 150, shipping_option: { id: "standard", }, }, { id: "shipping_method_snail", - unit_price: 200, + subtotal: 200, shipping_option: { id: "snail", }, @@ -2881,21 +2881,21 @@ describe("Promotion Service: computeActions", () => { shipping_methods: [ { id: "shipping_method_express", - unit_price: 250, + subtotal: 250, shipping_option: { id: "express", }, }, { id: "shipping_method_standard", - unit_price: 150, + subtotal: 150, shipping_option: { id: "standard", }, }, { id: "shipping_method_snail", - unit_price: 200, + subtotal: 200, shipping_option: { id: "snail", }, @@ -3000,21 +3000,21 @@ describe("Promotion Service: computeActions", () => { shipping_methods: [ { id: "shipping_method_express", - unit_price: 250, + subtotal: 250, shipping_option: { id: "express", }, }, { id: "shipping_method_standard", - unit_price: 150, + subtotal: 150, shipping_option: { id: "standard", }, }, { id: "shipping_method_snail", - unit_price: 200, + subtotal: 200, shipping_option: { id: "snail", }, @@ -3092,7 +3092,7 @@ describe("Promotion Service: computeActions", () => { shipping_methods: [ { id: "shipping_method_express", - unit_price: 1200, + subtotal: 1200, shipping_option: { id: "express", }, @@ -3151,7 +3151,7 @@ describe("Promotion Service: computeActions", () => { shipping_methods: [ { id: "shipping_method_express", - unit_price: 1200, + subtotal: 1200, shipping_option: { id: "express", }, @@ -3205,21 +3205,21 @@ describe("Promotion Service: computeActions", () => { shipping_methods: [ { id: "shipping_method_express", - unit_price: 500, + subtotal: 500, shipping_option: { id: "express", }, }, { id: "shipping_method_standard", - unit_price: 100, + subtotal: 100, shipping_option: { id: "standard", }, }, { id: "shipping_method_snail", - unit_price: 200, + subtotal: 200, shipping_option: { id: "snail", }, @@ -3281,21 +3281,21 @@ describe("Promotion Service: computeActions", () => { shipping_methods: [ { id: "shipping_method_express", - unit_price: 500, + subtotal: 500, shipping_option: { id: "express", }, }, { id: "shipping_method_standard", - unit_price: 100, + subtotal: 100, shipping_option: { id: "standard", }, }, { id: "shipping_method_snail", - unit_price: 200, + subtotal: 200, shipping_option: { id: "snail", }, @@ -3385,21 +3385,21 @@ describe("Promotion Service: computeActions", () => { shipping_methods: [ { id: "shipping_method_express", - unit_price: 500, + subtotal: 500, shipping_option: { id: "express", }, }, { id: "shipping_method_standard", - unit_price: 100, + subtotal: 100, shipping_option: { id: "standard", }, }, { id: "shipping_method_snail", - unit_price: 200, + subtotal: 200, shipping_option: { id: "snail", }, @@ -3502,21 +3502,21 @@ describe("Promotion Service: computeActions", () => { shipping_methods: [ { id: "shipping_method_express", - unit_price: 500, + subtotal: 500, shipping_option: { id: "express", }, }, { id: "shipping_method_standard", - unit_price: 100, + subtotal: 100, shipping_option: { id: "standard", }, }, { id: "shipping_method_snail", - unit_price: 200, + subtotal: 200, shipping_option: { id: "snail", }, @@ -3581,7 +3581,7 @@ describe("Promotion Service: computeActions", () => { shipping_methods: [ { id: "shipping_method_express", - unit_price: 1200, + subtotal: 1200, shipping_option: { id: "express", }, @@ -3639,7 +3639,7 @@ describe("Promotion Service: computeActions", () => { shipping_methods: [ { id: "shipping_method_express", - unit_price: 1200, + subtotal: 1200, shipping_option: { id: "express", }, @@ -3691,21 +3691,21 @@ describe("Promotion Service: computeActions", () => { shipping_methods: [ { id: "shipping_method_express", - unit_price: 500, + subtotal: 500, shipping_option: { id: "express", }, }, { id: "shipping_method_standard", - unit_price: 100, + subtotal: 100, shipping_option: { id: "standard", }, }, { id: "shipping_method_snail", - unit_price: 200, + subtotal: 200, shipping_option: { id: "snail", }, @@ -3767,21 +3767,21 @@ describe("Promotion Service: computeActions", () => { shipping_methods: [ { id: "shipping_method_express", - unit_price: 500, + subtotal: 500, shipping_option: { id: "express", }, }, { id: "shipping_method_standard", - unit_price: 100, + subtotal: 100, shipping_option: { id: "standard", }, }, { id: "shipping_method_snail", - unit_price: 200, + subtotal: 200, shipping_option: { id: "snail", }, @@ -3871,21 +3871,21 @@ describe("Promotion Service: computeActions", () => { shipping_methods: [ { id: "shipping_method_express", - unit_price: 500, + subtotal: 500, shipping_option: { id: "express", }, }, { id: "shipping_method_standard", - unit_price: 100, + subtotal: 100, shipping_option: { id: "standard", }, }, { id: "shipping_method_snail", - unit_price: 200, + subtotal: 200, shipping_option: { id: "snail", }, @@ -3988,21 +3988,21 @@ describe("Promotion Service: computeActions", () => { shipping_methods: [ { id: "shipping_method_express", - unit_price: 500, + subtotal: 500, shipping_option: { id: "express", }, }, { id: "shipping_method_standard", - unit_price: 100, + subtotal: 100, shipping_option: { id: "standard", }, }, { id: "shipping_method_snail", - unit_price: 200, + subtotal: 200, shipping_option: { id: "snail", }, @@ -4067,7 +4067,7 @@ describe("Promotion Service: computeActions", () => { shipping_methods: [ { id: "shipping_method_express", - unit_price: 1200, + subtotal: 1200, shipping_option: { id: "express", }, @@ -4125,7 +4125,7 @@ describe("Promotion Service: computeActions", () => { shipping_methods: [ { id: "shipping_method_express", - unit_price: 1200, + subtotal: 1200, shipping_option: { id: "express", }, @@ -4172,7 +4172,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt", quantity: 1, - unit_price: 100, + subtotal: 100, product_category: { id: "catg_cotton", }, @@ -4183,7 +4183,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_sweater", quantity: 2, - unit_price: 150, + subtotal: 300, product_category: { id: "catg_cotton", }, @@ -4242,7 +4242,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt", quantity: 1, - unit_price: 100, + subtotal: 100, product_category: { id: "catg_cotton", }, @@ -4253,7 +4253,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_sweater", quantity: 2, - unit_price: 150, + subtotal: 300, product_category: { id: "catg_cotton", }, @@ -4333,7 +4333,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt", quantity: 1, - unit_price: 50, + subtotal: 50, product_category: { id: "catg_cotton", }, @@ -4344,7 +4344,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_sweater", quantity: 1, - unit_price: 150, + subtotal: 150, product_category: { id: "catg_cotton", }, @@ -4437,7 +4437,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt", quantity: 1, - unit_price: 50, + subtotal: 50, product_category: { id: "catg_cotton", }, @@ -4448,7 +4448,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_sweater", quantity: 1, - unit_price: 150, + subtotal: 150, product_category: { id: "catg_cotton", }, @@ -4524,7 +4524,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt", quantity: 1, - unit_price: 100, + subtotal: 100, product_category: { id: "catg_cotton", }, @@ -4541,7 +4541,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_sweater", quantity: 5, - unit_price: 150, + subtotal: 750, product_category: { id: "catg_cotton", }, @@ -4617,7 +4617,7 @@ describe("Promotion Service: computeActions", () => { shipping_methods: [ { id: "shipping_method_express", - unit_price: 500, + subtotal: 500, shipping_option: { id: "express", }, @@ -4630,14 +4630,14 @@ describe("Promotion Service: computeActions", () => { }, { id: "shipping_method_standard", - unit_price: 100, + subtotal: 100, shipping_option: { id: "standard", }, }, { id: "shipping_method_snail", - unit_price: 200, + subtotal: 200, shipping_option: { id: "snail", }, @@ -4679,7 +4679,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt", quantity: 2, - unit_price: 500, + subtotal: 1000, product_category: { id: "catg_tshirt", }, @@ -4690,7 +4690,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt2", quantity: 2, - unit_price: 1000, + subtotal: 2000, product_category: { id: "catg_tshirt", }, @@ -4701,7 +4701,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_sweater", quantity: 2, - unit_price: 1000, + subtotal: 2000, product_category: { id: "catg_sweater", }, @@ -4771,7 +4771,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt", quantity: 2, - unit_price: 500, + subtotal: 1000, product_category: { id: "catg_tshirt", }, @@ -4782,7 +4782,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt2", quantity: 2, - unit_price: 1000, + subtotal: 2000, product_category: { id: "catg_tshirt", }, @@ -4793,7 +4793,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_sweater", quantity: 2, - unit_price: 1000, + subtotal: 2000, product_category: { id: "catg_sweater", }, @@ -4856,7 +4856,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt", quantity: 2, - unit_price: 500, + subtotal: 1000, product_category: { id: "catg_tshirt", }, @@ -4867,7 +4867,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt2", quantity: 2, - unit_price: 1000, + subtotal: 2000, product_category: { id: "catg_tshirt", }, @@ -4878,7 +4878,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_sweater", quantity: 2, - unit_price: 1000, + subtotal: 2000, product_category: { id: "catg_sweater", }, @@ -4954,7 +4954,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt", quantity: 2, - unit_price: 500, + subtotal: 1000, product_category: { id: "catg_tshirt", }, @@ -4965,7 +4965,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_tshirt2", quantity: 2, - unit_price: 1000, + subtotal: 2000, product_category: { id: "catg_tshirt", }, @@ -4976,7 +4976,7 @@ describe("Promotion Service: computeActions", () => { { id: "item_cotton_sweater", quantity: 2, - unit_price: 1000, + subtotal: 2000, product_category: { id: "catg_sweater", }, diff --git a/packages/promotion/src/utils/compute-actions/buy-get.ts b/packages/promotion/src/utils/compute-actions/buy-get.ts index cc6eaa44e114f..d3bcc3f68d249 100644 --- a/packages/promotion/src/utils/compute-actions/buy-get.ts +++ b/packages/promotion/src/utils/compute-actions/buy-get.ts @@ -46,7 +46,10 @@ export function getComputedActionsForBuyGet( const validItemsForTargetRules = itemsContext .filter((item) => areRulesValidForContext(targetRules, item)) .sort((a, b) => { - return b.unit_price - a.unit_price + const aPrice = a.subtotal / a.quantity + const bPrice = b.subtotal / b.quantity + + return bPrice - aPrice }) let remainingQtyToApply = applyToQuantity @@ -54,7 +57,7 @@ export function getComputedActionsForBuyGet( for (const method of validItemsForTargetRules) { const appliedPromoValue = methodIdPromoValueMap.get(method.id) || 0 const multiplier = Math.min(method.quantity, remainingQtyToApply) - const amount = method.unit_price * multiplier + const amount = (method.subtotal / method.quantity) * multiplier const newRemainingQtyToApply = remainingQtyToApply - multiplier if (newRemainingQtyToApply < 0 || amount <= 0) { diff --git a/packages/promotion/src/utils/compute-actions/items.ts b/packages/promotion/src/utils/compute-actions/items.ts index d499464b179cb..555a27548f612 100644 --- a/packages/promotion/src/utils/compute-actions/items.ts +++ b/packages/promotion/src/utils/compute-actions/items.ts @@ -68,7 +68,8 @@ export function applyPromotionToItems( method.quantity, applicationMethod?.max_quantity! ) - const totalItemValue = method.unit_price * quantityMultiplier + const totalItemValue = + (method.subtotal / method.quantity) * quantityMultiplier let promotionValue = parseFloat(applicationMethod!.value!) const applicableTotal = totalItemValue - appliedPromoValue @@ -111,14 +112,19 @@ export function applyPromotionToItems( ) { const totalApplicableValue = items!.reduce((acc, method) => { const appliedPromoValue = methodIdPromoValueMap.get(method.id) || 0 - return acc + method.unit_price * method.quantity - appliedPromoValue + return ( + acc + + (method.subtotal / method.quantity) * method.quantity - + appliedPromoValue + ) }, 0) for (const method of items!) { const appliedPromoValue = methodIdPromoValueMap.get(method.id) || 0 const promotionValue = parseFloat(applicationMethod!.value!) const applicableTotal = - method.unit_price * method.quantity - appliedPromoValue + (method.subtotal / method.quantity) * method.quantity - + appliedPromoValue if (applicableTotal <= 0) { continue diff --git a/packages/promotion/src/utils/compute-actions/shipping-methods.ts b/packages/promotion/src/utils/compute-actions/shipping-methods.ts index 5a3d11dd508d9..6e96db1fd2aec 100644 --- a/packages/promotion/src/utils/compute-actions/shipping-methods.ts +++ b/packages/promotion/src/utils/compute-actions/shipping-methods.ts @@ -57,7 +57,7 @@ export function applyPromotionToShippingMethods( for (const method of shippingMethods!) { const appliedPromoValue = methodIdPromoValueMap.get(method.id) || 0 let promotionValue = parseFloat(applicationMethod!.value!) - const applicableTotal = method.unit_price - appliedPromoValue + const applicableTotal = method.subtotal - appliedPromoValue if (applicationMethod?.type === ApplicationMethodType.PERCENTAGE) { promotionValue = (promotionValue / 100) * applicableTotal @@ -95,7 +95,7 @@ export function applyPromotionToShippingMethods( const totalApplicableValue = shippingMethods!.reduce((acc, method) => { const appliedPromoValue = methodIdPromoValueMap.get(method.id) || 0 - return acc + method.unit_price - appliedPromoValue + return acc + method.subtotal - appliedPromoValue }, 0) if (totalApplicableValue <= 0) { @@ -104,7 +104,7 @@ export function applyPromotionToShippingMethods( for (const method of shippingMethods!) { const promotionValue = parseFloat(applicationMethod!.value!) - const applicableTotal = method.unit_price + const applicableTotal = method.subtotal const appliedPromoValue = methodIdPromoValueMap.get(method.id) || 0 // TODO: should we worry about precision here? diff --git a/packages/types/src/promotion/common/compute-actions.ts b/packages/types/src/promotion/common/compute-actions.ts index 267da4b5c38d9..1384daa93fe5f 100644 --- a/packages/types/src/promotion/common/compute-actions.ts +++ b/packages/types/src/promotion/common/compute-actions.ts @@ -51,13 +51,13 @@ export interface ComputeActionAdjustmentLine extends Record { export interface ComputeActionItemLine extends Record { id: string quantity: number - unit_price: number + subtotal: number adjustments?: ComputeActionAdjustmentLine[] } export interface ComputeActionShippingLine extends Record { id: string - unit_price: number + subtotal: number adjustments?: ComputeActionAdjustmentLine[] }