feat(types): added computed actions for automatic promotions (#6272)
what: - compute actions account for automatic promotions (RESOLVES CORE-1701) RESOLVES CORE-1689
This commit is contained in:
1 parent
7903a15e0f
commit
e749dd653c
4 files changed
+470
-6
No files matched your search
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/types": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(types): added computed actions for automatic promotions
|
||||||
+448
-2
@@ -1,11 +1,11 @@
|
|||||||
|
import { Modules } from "@medusajs/modules-sdk"
|
||||||
import { IPromotionModuleService } from "@medusajs/types"
|
import { IPromotionModuleService } from "@medusajs/types"
|
||||||
import { PromotionType } from "@medusajs/utils"
|
import { PromotionType } from "@medusajs/utils"
|
||||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||||
|
import { initModules } from "medusa-test-utils"
|
||||||
import { createCampaigns } from "../../../__fixtures__/campaigns"
|
import { createCampaigns } from "../../../__fixtures__/campaigns"
|
||||||
import { MikroOrmWrapper } from "../../../utils"
|
import { MikroOrmWrapper } from "../../../utils"
|
||||||
import { getInitModuleConfig } from "../../../utils/get-init-module-config"
|
import { getInitModuleConfig } from "../../../utils/get-init-module-config"
|
||||||
import { Modules } from "@medusajs/modules-sdk"
|
|
||||||
import { initModules } from "medusa-test-utils"
|
|
||||||
|
|
||||||
jest.setTimeout(30000)
|
jest.setTimeout(30000)
|
||||||
|
|
||||||
@@ -239,6 +239,84 @@ describe("Promotion Service: computeActions", () => {
|
|||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should compute the correct item amendments when promotion is automatic", async () => {
|
||||||
|
const [createdPromotion] = await service.create([
|
||||||
|
{
|
||||||
|
code: "PROMOTION_TEST",
|
||||||
|
is_automatic: true,
|
||||||
|
type: PromotionType.STANDARD,
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
attribute: "customer.customer_group.id",
|
||||||
|
operator: "in",
|
||||||
|
values: ["VIP", "top100"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
application_method: {
|
||||||
|
type: "fixed",
|
||||||
|
target_type: "items",
|
||||||
|
allocation: "each",
|
||||||
|
value: "200",
|
||||||
|
max_quantity: 1,
|
||||||
|
target_rules: [
|
||||||
|
{
|
||||||
|
attribute: "product_category.id",
|
||||||
|
operator: "eq",
|
||||||
|
values: ["catg_cotton"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const result = await service.computeActions([], {
|
||||||
|
customer: {
|
||||||
|
customer_group: {
|
||||||
|
id: "VIP",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: "item_cotton_tshirt",
|
||||||
|
quantity: 1,
|
||||||
|
unit_price: 100,
|
||||||
|
product_category: {
|
||||||
|
id: "catg_cotton",
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
id: "prod_tshirt",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "item_cotton_sweater",
|
||||||
|
quantity: 5,
|
||||||
|
unit_price: 150,
|
||||||
|
product_category: {
|
||||||
|
id: "catg_cotton",
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
id: "prod_sweater",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result).toEqual([
|
||||||
|
{
|
||||||
|
action: "addItemAdjustment",
|
||||||
|
item_id: "item_cotton_tshirt",
|
||||||
|
amount: 100,
|
||||||
|
code: "PROMOTION_TEST",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: "addItemAdjustment",
|
||||||
|
item_id: "item_cotton_sweater",
|
||||||
|
amount: 150,
|
||||||
|
code: "PROMOTION_TEST",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
it("should compute the correct item amendments when there are multiple promotions to apply", async () => {
|
it("should compute the correct item amendments when there are multiple promotions to apply", async () => {
|
||||||
const [createdPromotion] = await service.create([
|
const [createdPromotion] = await service.create([
|
||||||
{
|
{
|
||||||
@@ -666,6 +744,83 @@ describe("Promotion Service: computeActions", () => {
|
|||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should compute the correct item amendments when promotion is automatic", async () => {
|
||||||
|
const [createdPromotion] = await service.create([
|
||||||
|
{
|
||||||
|
code: "PROMOTION_TEST",
|
||||||
|
type: PromotionType.STANDARD,
|
||||||
|
is_automatic: true,
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
attribute: "customer.customer_group.id",
|
||||||
|
operator: "in",
|
||||||
|
values: ["VIP", "top100"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
application_method: {
|
||||||
|
type: "fixed",
|
||||||
|
target_type: "items",
|
||||||
|
allocation: "across",
|
||||||
|
value: "400",
|
||||||
|
target_rules: [
|
||||||
|
{
|
||||||
|
attribute: "product_category.id",
|
||||||
|
operator: "eq",
|
||||||
|
values: ["catg_cotton"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const result = await service.computeActions([], {
|
||||||
|
customer: {
|
||||||
|
customer_group: {
|
||||||
|
id: "VIP",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: "item_cotton_tshirt",
|
||||||
|
quantity: 2,
|
||||||
|
unit_price: 100,
|
||||||
|
product_category: {
|
||||||
|
id: "catg_cotton",
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
id: "prod_tshirt",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "item_cotton_sweater",
|
||||||
|
quantity: 2,
|
||||||
|
unit_price: 300,
|
||||||
|
product_category: {
|
||||||
|
id: "catg_cotton",
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
id: "prod_sweater",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result).toEqual([
|
||||||
|
{
|
||||||
|
action: "addItemAdjustment",
|
||||||
|
item_id: "item_cotton_tshirt",
|
||||||
|
amount: 100,
|
||||||
|
code: "PROMOTION_TEST",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: "addItemAdjustment",
|
||||||
|
item_id: "item_cotton_sweater",
|
||||||
|
amount: 300,
|
||||||
|
code: "PROMOTION_TEST",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
it("should compute the correct item amendments when there are multiple promotions to apply", async () => {
|
it("should compute the correct item amendments when there are multiple promotions to apply", async () => {
|
||||||
const [createdPromotion] = await service.create([
|
const [createdPromotion] = await service.create([
|
||||||
{
|
{
|
||||||
@@ -1099,6 +1254,151 @@ describe("Promotion Service: computeActions", () => {
|
|||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should compute the correct shipping_method amendments when promotion is automatic", async () => {
|
||||||
|
const [createdPromotion] = await service.create([
|
||||||
|
{
|
||||||
|
code: "PROMOTION_TEST",
|
||||||
|
type: PromotionType.STANDARD,
|
||||||
|
is_automatic: true,
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
attribute: "customer.customer_group.id",
|
||||||
|
operator: "in",
|
||||||
|
values: ["VIP", "top100"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
application_method: {
|
||||||
|
type: "fixed",
|
||||||
|
target_type: "shipping_methods",
|
||||||
|
allocation: "each",
|
||||||
|
value: "200",
|
||||||
|
max_quantity: 2,
|
||||||
|
target_rules: [
|
||||||
|
{
|
||||||
|
attribute: "shipping_option.id",
|
||||||
|
operator: "in",
|
||||||
|
values: ["express", "standard"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const result = await service.computeActions([], {
|
||||||
|
customer: {
|
||||||
|
customer_group: {
|
||||||
|
id: "VIP",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
shipping_methods: [
|
||||||
|
{
|
||||||
|
id: "shipping_method_express",
|
||||||
|
unit_price: 250,
|
||||||
|
shipping_option: {
|
||||||
|
id: "express",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "shipping_method_standard",
|
||||||
|
unit_price: 150,
|
||||||
|
shipping_option: {
|
||||||
|
id: "standard",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "shipping_method_snail",
|
||||||
|
unit_price: 200,
|
||||||
|
shipping_option: {
|
||||||
|
id: "snail",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result).toEqual([
|
||||||
|
{
|
||||||
|
action: "addShippingMethodAdjustment",
|
||||||
|
shipping_method_id: "shipping_method_express",
|
||||||
|
amount: 200,
|
||||||
|
code: "PROMOTION_TEST",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: "addShippingMethodAdjustment",
|
||||||
|
shipping_method_id: "shipping_method_standard",
|
||||||
|
amount: 150,
|
||||||
|
code: "PROMOTION_TEST",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should compute the correct shipping_method amendments when promotion is automatic and prevent_auto_promotions is false", async () => {
|
||||||
|
const [createdPromotion] = await service.create([
|
||||||
|
{
|
||||||
|
code: "PROMOTION_TEST",
|
||||||
|
type: PromotionType.STANDARD,
|
||||||
|
is_automatic: true,
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
attribute: "customer.customer_group.id",
|
||||||
|
operator: "in",
|
||||||
|
values: ["VIP", "top100"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
application_method: {
|
||||||
|
type: "fixed",
|
||||||
|
target_type: "shipping_methods",
|
||||||
|
allocation: "each",
|
||||||
|
value: "200",
|
||||||
|
max_quantity: 2,
|
||||||
|
target_rules: [
|
||||||
|
{
|
||||||
|
attribute: "shipping_option.id",
|
||||||
|
operator: "in",
|
||||||
|
values: ["express", "standard"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const result = await service.computeActions(
|
||||||
|
[],
|
||||||
|
{
|
||||||
|
customer: {
|
||||||
|
customer_group: {
|
||||||
|
id: "VIP",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
shipping_methods: [
|
||||||
|
{
|
||||||
|
id: "shipping_method_express",
|
||||||
|
unit_price: 250,
|
||||||
|
shipping_option: {
|
||||||
|
id: "express",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "shipping_method_standard",
|
||||||
|
unit_price: 150,
|
||||||
|
shipping_option: {
|
||||||
|
id: "standard",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "shipping_method_snail",
|
||||||
|
unit_price: 200,
|
||||||
|
shipping_option: {
|
||||||
|
id: "snail",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ prevent_auto_promotions: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(result).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
it("should compute the correct item amendments when there are multiple promotions to apply", async () => {
|
it("should compute the correct item amendments when there are multiple promotions to apply", async () => {
|
||||||
const [createdPromotion] = await service.create([
|
const [createdPromotion] = await service.create([
|
||||||
{
|
{
|
||||||
@@ -1509,6 +1809,82 @@ describe("Promotion Service: computeActions", () => {
|
|||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should compute the correct shipping_method amendments when promotion is automatic", async () => {
|
||||||
|
const [createdPromotion] = await service.create([
|
||||||
|
{
|
||||||
|
code: "PROMOTION_TEST",
|
||||||
|
type: PromotionType.STANDARD,
|
||||||
|
is_automatic: true,
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
attribute: "customer.customer_group.id",
|
||||||
|
operator: "in",
|
||||||
|
values: ["VIP", "top100"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
application_method: {
|
||||||
|
type: "fixed",
|
||||||
|
target_type: "shipping_methods",
|
||||||
|
allocation: "across",
|
||||||
|
value: "200",
|
||||||
|
target_rules: [
|
||||||
|
{
|
||||||
|
attribute: "shipping_option.id",
|
||||||
|
operator: "in",
|
||||||
|
values: ["express", "standard"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const result = await service.computeActions([], {
|
||||||
|
customer: {
|
||||||
|
customer_group: {
|
||||||
|
id: "VIP",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
shipping_methods: [
|
||||||
|
{
|
||||||
|
id: "shipping_method_express",
|
||||||
|
unit_price: 500,
|
||||||
|
shipping_option: {
|
||||||
|
id: "express",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "shipping_method_standard",
|
||||||
|
unit_price: 100,
|
||||||
|
shipping_option: {
|
||||||
|
id: "standard",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "shipping_method_snail",
|
||||||
|
unit_price: 200,
|
||||||
|
shipping_option: {
|
||||||
|
id: "snail",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result).toEqual([
|
||||||
|
{
|
||||||
|
action: "addShippingMethodAdjustment",
|
||||||
|
shipping_method_id: "shipping_method_express",
|
||||||
|
amount: 166.66666666666669,
|
||||||
|
code: "PROMOTION_TEST",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: "addShippingMethodAdjustment",
|
||||||
|
shipping_method_id: "shipping_method_standard",
|
||||||
|
amount: 33.33333333333333,
|
||||||
|
code: "PROMOTION_TEST",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
it("should compute the correct item amendments when there are multiple promotions to apply", async () => {
|
it("should compute the correct item amendments when there are multiple promotions to apply", async () => {
|
||||||
const [createdPromotion] = await service.create([
|
const [createdPromotion] = await service.create([
|
||||||
{
|
{
|
||||||
@@ -1913,6 +2289,76 @@ describe("Promotion Service: computeActions", () => {
|
|||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should compute the correct item amendments when promotion is automatic", async () => {
|
||||||
|
const [createdPromotion] = await service.create([
|
||||||
|
{
|
||||||
|
code: "PROMOTION_TEST",
|
||||||
|
type: PromotionType.STANDARD,
|
||||||
|
is_automatic: true,
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
attribute: "customer.customer_group.id",
|
||||||
|
operator: "in",
|
||||||
|
values: ["VIP", "top100"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
application_method: {
|
||||||
|
type: "fixed",
|
||||||
|
target_type: "order",
|
||||||
|
value: "200",
|
||||||
|
max_quantity: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const result = await service.computeActions([], {
|
||||||
|
customer: {
|
||||||
|
customer_group: {
|
||||||
|
id: "VIP",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: "item_cotton_tshirt",
|
||||||
|
quantity: 1,
|
||||||
|
unit_price: 100,
|
||||||
|
product_category: {
|
||||||
|
id: "catg_cotton",
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
id: "prod_tshirt",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "item_cotton_sweater",
|
||||||
|
quantity: 2,
|
||||||
|
unit_price: 150,
|
||||||
|
product_category: {
|
||||||
|
id: "catg_cotton",
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
id: "prod_sweater",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result).toEqual([
|
||||||
|
{
|
||||||
|
action: "addItemAdjustment",
|
||||||
|
item_id: "item_cotton_tshirt",
|
||||||
|
amount: 50,
|
||||||
|
code: "PROMOTION_TEST",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: "addItemAdjustment",
|
||||||
|
item_id: "item_cotton_sweater",
|
||||||
|
amount: 150,
|
||||||
|
code: "PROMOTION_TEST",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
it("should compute the correct item amendments when there are multiple promotions to apply", async () => {
|
it("should compute the correct item amendments when there are multiple promotions to apply", async () => {
|
||||||
const [createdPromotion] = await service.create([
|
const [createdPromotion] = await service.create([
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -216,11 +216,11 @@ export default class PromotionModuleService<
|
|||||||
}
|
}
|
||||||
|
|
||||||
async computeActions(
|
async computeActions(
|
||||||
promotionCodesToApply: string[],
|
promotionCodes: string[],
|
||||||
applicationContext: PromotionTypes.ComputeActionContext,
|
applicationContext: PromotionTypes.ComputeActionContext,
|
||||||
// TODO: specify correct type with options
|
options: PromotionTypes.ComputeActionOptions = {}
|
||||||
options: Record<string, any> = {}
|
|
||||||
): Promise<PromotionTypes.ComputeActions[]> {
|
): Promise<PromotionTypes.ComputeActions[]> {
|
||||||
|
const { prevent_auto_promotions: preventAutoPromotions } = options
|
||||||
const computedActions: PromotionTypes.ComputeActions[] = []
|
const computedActions: PromotionTypes.ComputeActions[] = []
|
||||||
const { items = [], shipping_methods: shippingMethods = [] } =
|
const { items = [], shipping_methods: shippingMethods = [] } =
|
||||||
applicationContext
|
applicationContext
|
||||||
@@ -231,6 +231,15 @@ export default class PromotionModuleService<
|
|||||||
PromotionTypes.ComputeActionAdjustmentLine
|
PromotionTypes.ComputeActionAdjustmentLine
|
||||||
>()
|
>()
|
||||||
const methodIdPromoValueMap = new Map<string, number>()
|
const methodIdPromoValueMap = new Map<string, number>()
|
||||||
|
const automaticPromotions = preventAutoPromotions
|
||||||
|
? []
|
||||||
|
: await this.list({ is_automatic: true }, { select: ["code"] })
|
||||||
|
|
||||||
|
const automaticPromotionCodes = automaticPromotions.map((p) => p.code!)
|
||||||
|
const promotionCodesToApply = [
|
||||||
|
...promotionCodes,
|
||||||
|
...automaticPromotionCodes,
|
||||||
|
]
|
||||||
|
|
||||||
items.forEach((item) => {
|
items.forEach((item) => {
|
||||||
item.adjustments?.forEach((adjustment) => {
|
item.adjustments?.forEach((adjustment) => {
|
||||||
@@ -291,7 +300,7 @@ export default class PromotionModuleService<
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (promotionCodesToApply.includes(appliedCode)) {
|
if (promotionCodes.includes(appliedCode)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,3 +65,7 @@ export interface ComputeActionContext extends Record<string, unknown> {
|
|||||||
items?: ComputeActionItemLine[]
|
items?: ComputeActionItemLine[]
|
||||||
shipping_methods?: ComputeActionShippingLine[]
|
shipping_methods?: ComputeActionShippingLine[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ComputeActionOptions {
|
||||||
|
prevent_auto_promotions?: boolean
|
||||||
|
}
|
||||||
Reference in new issue
Block a user