feat(utils,types): add registerUsages for promotions + computeActions consider usage (#6094)

RESOLVES CORE-1639
RESOLVES CORE-1640
RESOLVES CORE-1634
This commit is contained in:
Riqwan Thamir
2024-01-16 20:06:06 +00:00
committed by GitHub
parent 90328ca02b
commit 68ddd866a5
15 changed files with 943 additions and 54 deletions
@@ -2,6 +2,7 @@ import { IPromotionModuleService } from "@medusajs/types"
import { PromotionType } from "@medusajs/utils"
import { SqlEntityManager } from "@mikro-orm/postgresql"
import { initialize } from "../../../../src"
import { createCampaigns } from "../../../__fixtures__/campaigns"
import { DB_URL, MikroOrmWrapper } from "../../../utils"
jest.setTimeout(30000)
@@ -459,10 +460,10 @@ describe("Promotion Service: computeActions", () => {
},
])
})
})
describe("when promotion is for items and allocation is across", () => {
it("should compute the correct item amendments", async () => {
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",
@@ -474,12 +475,13 @@ describe("Promotion Service: computeActions", () => {
values: ["VIP", "top100"],
},
],
campaign_id: "campaign-id-1",
application_method: {
type: "fixed",
target_type: "items",
allocation: "across",
value: "200",
max_quantity: 2,
allocation: "each",
value: "500",
max_quantity: 5,
target_rules: [
{
attribute: "product_category.id",
@@ -500,7 +502,126 @@ describe("Promotion Service: computeActions", () => {
items: [
{
id: "item_cotton_tshirt",
quantity: 1,
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: "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 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"],
},
],
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",
@@ -512,7 +633,7 @@ describe("Promotion Service: computeActions", () => {
{
id: "item_cotton_sweater",
quantity: 2,
unit_price: 150,
unit_price: 300,
product_category: {
id: "catg_cotton",
},
@@ -527,13 +648,13 @@ describe("Promotion Service: computeActions", () => {
{
action: "addItemAdjustment",
item_id: "item_cotton_tshirt",
amount: 50,
amount: 100,
code: "PROMOTION_TEST",
},
{
action: "addItemAdjustment",
item_id: "item_cotton_sweater",
amount: 150,
amount: 300,
code: "PROMOTION_TEST",
},
])
@@ -556,7 +677,6 @@ describe("Promotion Service: computeActions", () => {
target_type: "items",
allocation: "across",
value: "30",
max_quantity: 2,
target_rules: [
{
attribute: "product_category.id",
@@ -584,7 +704,6 @@ describe("Promotion Service: computeActions", () => {
target_type: "items",
allocation: "across",
value: "50",
max_quantity: 1,
target_rules: [
{
attribute: "product_category.id",
@@ -676,7 +795,6 @@ describe("Promotion Service: computeActions", () => {
target_type: "items",
allocation: "across",
value: "500",
max_quantity: 2,
target_rules: [
{
attribute: "product_category.id",
@@ -704,7 +822,6 @@ describe("Promotion Service: computeActions", () => {
target_type: "items",
allocation: "across",
value: "50",
max_quantity: 1,
target_rules: [
{
attribute: "product_category.id",
@@ -778,6 +895,125 @@ describe("Promotion Service: computeActions", () => {
},
])
})
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 promotion is for shipping_method and allocation is each", () => {
@@ -1076,6 +1312,119 @@ describe("Promotion Service: computeActions", () => {
},
])
})
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",
},
},
shipping_methods: [
{
id: "shipping_method_express",
unit_price: 1200,
shipping_option: {
id: "express",
},
},
],
})
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: [
{
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",
},
},
],
})
expect(result).toEqual([
{ action: "campaignBudgetExceeded", code: "PROMOTION_TEST" },
])
})
})
describe("when promotion is for shipping_method and allocation is across", () => {
@@ -1096,7 +1445,6 @@ describe("Promotion Service: computeActions", () => {
target_type: "shipping_methods",
allocation: "across",
value: "200",
max_quantity: 2,
target_rules: [
{
attribute: "shipping_option.id",
@@ -1172,7 +1520,6 @@ describe("Promotion Service: computeActions", () => {
target_type: "shipping_methods",
allocation: "across",
value: "200",
max_quantity: 2,
target_rules: [
{
attribute: "shipping_option.id",
@@ -1200,7 +1547,6 @@ describe("Promotion Service: computeActions", () => {
target_type: "shipping_methods",
allocation: "across",
value: "200",
max_quantity: 2,
target_rules: [
{
attribute: "shipping_option.id",
@@ -1291,7 +1637,6 @@ describe("Promotion Service: computeActions", () => {
target_type: "shipping_methods",
allocation: "across",
value: "1000",
max_quantity: 2,
target_rules: [
{
attribute: "shipping_option.id",
@@ -1319,7 +1664,6 @@ describe("Promotion Service: computeActions", () => {
target_type: "shipping_methods",
allocation: "across",
value: "200",
max_quantity: 2,
target_rules: [
{
attribute: "shipping_option.id",
@@ -1380,6 +1724,117 @@ describe("Promotion Service: computeActions", () => {
},
])
})
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: "across",
value: "1200",
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: [
{
id: "shipping_method_express",
unit_price: 1200,
shipping_option: {
id: "express",
},
},
],
})
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: "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_express",
unit_price: 1200,
shipping_option: {
id: "express",
},
},
],
})
expect(result).toEqual([
{ action: "campaignBudgetExceeded", code: "PROMOTION_TEST" },
])
})
})
describe("when promotion is for the entire order", () => {
@@ -1740,6 +2195,7 @@ describe("Promotion Service: computeActions", () => {
{
action: "removeItemAdjustment",
adjustment_id: "test-adjustment",
code: "ADJUSTMENT_CODE",
},
{
action: "addItemAdjustment",
@@ -1780,7 +2236,6 @@ describe("Promotion Service: computeActions", () => {
target_type: "shipping_methods",
allocation: "across",
value: "200",
max_quantity: 2,
target_rules: [
{
attribute: "shipping_option.id",
@@ -1833,6 +2288,7 @@ describe("Promotion Service: computeActions", () => {
{
action: "removeShippingMethodAdjustment",
adjustment_id: "test-adjustment",
code: "ADJUSTMENT_CODE",
},
{
action: "addShippingMethodAdjustment",
@@ -0,0 +1,199 @@
import { IPromotionModuleService } from "@medusajs/types"
import { SqlEntityManager } from "@mikro-orm/postgresql"
import { initialize } from "../../../../src"
import { createCampaigns } from "../../../__fixtures__/campaigns"
import { DB_URL, MikroOrmWrapper } from "../../../utils"
jest.setTimeout(30000)
describe("Promotion Service: campaign usage", () => {
let service: IPromotionModuleService
let repositoryManager: SqlEntityManager
beforeEach(async () => {
await MikroOrmWrapper.setupDatabase()
repositoryManager = MikroOrmWrapper.forkManager()
await createCampaigns(repositoryManager)
service = await initialize({
database: {
clientUrl: DB_URL,
schema: process.env.MEDUSA_PROMOTION_DB_SCHEMA,
},
})
})
afterEach(async () => {
await MikroOrmWrapper.clearDatabase()
})
describe("registerUsage", () => {
it("should register usage for type spend", async () => {
const createdPromotion = await service.create({
code: "TEST_PROMO_SPEND",
type: "standard",
campaign_id: "campaign-id-1",
})
await service.registerUsage([
{
action: "addShippingMethodAdjustment",
shipping_method_id: "shipping_method_express",
amount: 200,
code: createdPromotion.code!,
},
{
action: "addShippingMethodAdjustment",
shipping_method_id: "shipping_method_standard",
amount: 500,
code: createdPromotion.code!,
},
])
const campaign = await service.retrieveCampaign("campaign-id-1", {
relations: ["budget"],
})
expect(campaign.budget).toEqual(
expect.objectContaining({
type: "spend",
limit: 1000,
used: 700,
})
)
})
it("should register usage for type usage", async () => {
const createdPromotion = await service.create({
code: "TEST_PROMO_USAGE",
type: "standard",
campaign_id: "campaign-id-2",
})
await service.registerUsage([
{
action: "addShippingMethodAdjustment",
shipping_method_id: "shipping_method_express",
amount: 200,
code: createdPromotion.code!,
},
{
action: "addShippingMethodAdjustment",
shipping_method_id: "shipping_method_standard",
amount: 500,
code: createdPromotion.code!,
},
])
const campaign = await service.retrieveCampaign("campaign-id-2", {
relations: ["budget"],
})
expect(campaign.budget).toEqual(
expect.objectContaining({
type: "usage",
limit: 1000,
used: 1,
})
)
})
it("should not throw an error when compute action with code does not exist", async () => {
const response = await service
.registerUsage([
{
action: "addShippingMethodAdjustment",
shipping_method_id: "shipping_method_express",
amount: 200,
code: "DOESNOTEXIST",
},
])
.catch((e) => e)
expect(response).toEqual(undefined)
})
it("should not register usage when limit is exceed for type usage", async () => {
const createdPromotion = await service.create({
code: "TEST_PROMO_USAGE",
type: "standard",
campaign_id: "campaign-id-2",
})
await service.updateCampaigns({
id: "campaign-id-2",
budget: { used: 1000, limit: 1000 },
})
await service.registerUsage([
{
action: "addShippingMethodAdjustment",
shipping_method_id: "shipping_method_express",
amount: 200,
code: createdPromotion.code!,
},
{
action: "addShippingMethodAdjustment",
shipping_method_id: "shipping_method_standard",
amount: 500,
code: createdPromotion.code!,
},
])
const campaign = await service.retrieveCampaign("campaign-id-2", {
relations: ["budget"],
})
expect(campaign).toEqual(
expect.objectContaining({
budget: expect.objectContaining({
limit: 1000,
used: 1000,
}),
})
)
})
it("should not register usage above limit when exceeded for type spend", async () => {
const createdPromotion = await service.create({
code: "TEST_PROMO_SPEND",
type: "standard",
campaign_id: "campaign-id-1",
})
await service.updateCampaigns({
id: "campaign-id-1",
budget: { used: 900, limit: 1000 },
})
await service.registerUsage([
{
action: "addShippingMethodAdjustment",
shipping_method_id: "shipping_method_express",
amount: 100,
code: createdPromotion.code!,
},
{
action: "addShippingMethodAdjustment",
shipping_method_id: "shipping_method_standard",
amount: 100,
code: createdPromotion.code!,
},
])
const campaign = await service.retrieveCampaign("campaign-id-1", {
relations: ["budget"],
})
expect(campaign).toEqual(
expect.objectContaining({
budget: expect.objectContaining({
limit: 1000,
used: 1000,
}),
})
)
})
})
})