feat(workflows-sdk,core-flows,medusa,types): add workflow to update promotions to cart (#6474)
what: - adds API + workflow to add/remove promotions in a cart - minor fixes in promotions module - minor type fixes in cart module - typing fix in workflows-sdk (Thanks @adrien2p) - fix step result in workflows-sdk (Thanks @adrien2p) RESOLVES CORE-1768 Co-authored-by: Adrien de Peretti <25098370+adrien2p@users.noreply.github.com>
This commit is contained in:
1 parent
63be07031b
commit
ac86362e81
30 files changed
+1178
-92
No files matched your search
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/workflows-sdk": patch
|
||||||
|
"@medusajs/core-flows": patch
|
||||||
|
"@medusajs/medusa": patch
|
||||||
|
"@medusajs/types": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(workflows-sdk,core-flows,medusa,types): add workflow to add promotions to cart
|
||||||
@@ -0,0 +1,309 @@
|
|||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import { ICartModuleService, IPromotionModuleService } from "@medusajs/types"
|
||||||
|
import { PromotionType } from "@medusajs/utils"
|
||||||
|
import path from "path"
|
||||||
|
import { startBootstrapApp } from "../../../../environment-helpers/bootstrap-app"
|
||||||
|
import { useApi } from "../../../../environment-helpers/use-api"
|
||||||
|
import { getContainer } from "../../../../environment-helpers/use-container"
|
||||||
|
import { initDb, useDb } from "../../../../environment-helpers/use-db"
|
||||||
|
import adminSeeder from "../../../../helpers/admin-seeder"
|
||||||
|
|
||||||
|
jest.setTimeout(50000)
|
||||||
|
|
||||||
|
const env = { MEDUSA_FF_MEDUSA_V2: true }
|
||||||
|
|
||||||
|
describe("Store Carts API: Add promotions to cart", () => {
|
||||||
|
let dbConnection
|
||||||
|
let appContainer
|
||||||
|
let shutdownServer
|
||||||
|
let cartModuleService: ICartModuleService
|
||||||
|
let promotionModuleService: IPromotionModuleService
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
||||||
|
dbConnection = await initDb({ cwd, env } as any)
|
||||||
|
shutdownServer = await startBootstrapApp({ cwd, env })
|
||||||
|
appContainer = getContainer()
|
||||||
|
cartModuleService = appContainer.resolve(ModuleRegistrationName.CART)
|
||||||
|
promotionModuleService = appContainer.resolve(
|
||||||
|
ModuleRegistrationName.PROMOTION
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
const db = useDb()
|
||||||
|
await db.shutdown()
|
||||||
|
await shutdownServer()
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await adminSeeder(dbConnection)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
const db = useDb()
|
||||||
|
await db.teardown()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("POST /store/carts/:id/promotions", () => {
|
||||||
|
it("should add line item adjustments to a cart based on promotions", async () => {
|
||||||
|
const appliedPromotion = await promotionModuleService.create({
|
||||||
|
code: "PROMOTION_APPLIED",
|
||||||
|
type: PromotionType.STANDARD,
|
||||||
|
application_method: {
|
||||||
|
type: "fixed",
|
||||||
|
target_type: "items",
|
||||||
|
allocation: "each",
|
||||||
|
value: "300",
|
||||||
|
apply_to_quantity: 1,
|
||||||
|
max_quantity: 1,
|
||||||
|
target_rules: [
|
||||||
|
{
|
||||||
|
attribute: "product_id",
|
||||||
|
operator: "eq",
|
||||||
|
values: "prod_tshirt",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const createdPromotion = await promotionModuleService.create({
|
||||||
|
code: "PROMOTION_TEST",
|
||||||
|
type: PromotionType.STANDARD,
|
||||||
|
application_method: {
|
||||||
|
type: "fixed",
|
||||||
|
target_type: "items",
|
||||||
|
allocation: "across",
|
||||||
|
value: "1000",
|
||||||
|
apply_to_quantity: 1,
|
||||||
|
target_rules: [
|
||||||
|
{
|
||||||
|
attribute: "product_id",
|
||||||
|
operator: "eq",
|
||||||
|
values: "prod_mat",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const cart = await cartModuleService.create({
|
||||||
|
currency_code: "usd",
|
||||||
|
items: [
|
||||||
|
// Adjustment to add
|
||||||
|
{
|
||||||
|
id: "item-1",
|
||||||
|
unit_price: 2000,
|
||||||
|
quantity: 1,
|
||||||
|
title: "Test item",
|
||||||
|
product_id: "prod_mat",
|
||||||
|
} as any,
|
||||||
|
// This adjustment will be removed and recreated
|
||||||
|
{
|
||||||
|
id: "item-2",
|
||||||
|
unit_price: 1000,
|
||||||
|
quantity: 1,
|
||||||
|
title: "Test item",
|
||||||
|
product_id: "prod_tshirt",
|
||||||
|
} as any,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
// Adjustment to keep
|
||||||
|
const [lineItemAdjustment] =
|
||||||
|
await cartModuleService.addLineItemAdjustments([
|
||||||
|
{
|
||||||
|
code: appliedPromotion.code!,
|
||||||
|
amount: 300,
|
||||||
|
item_id: "item-2",
|
||||||
|
promotion_id: appliedPromotion.id,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const api = useApi() as any
|
||||||
|
|
||||||
|
const created = await api.post(`/store/carts/${cart.id}/promotions`, {
|
||||||
|
promo_codes: [createdPromotion.code],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(created.status).toEqual(200)
|
||||||
|
expect(created.data.cart).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
items: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: "item-1",
|
||||||
|
adjustments: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
promotion_id: createdPromotion.id,
|
||||||
|
code: createdPromotion.code,
|
||||||
|
amount: 1000,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
adjustments: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.not.stringContaining(lineItemAdjustment.id),
|
||||||
|
promotion_id: appliedPromotion.id,
|
||||||
|
code: appliedPromotion.code,
|
||||||
|
amount: 300,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should add shipping method adjustments to a cart based on promotions", async () => {
|
||||||
|
const [appliedPromotion] = await promotionModuleService.create([
|
||||||
|
{
|
||||||
|
code: "PROMOTION_APPLIED",
|
||||||
|
type: PromotionType.STANDARD,
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
attribute: "customer_id",
|
||||||
|
operator: "in",
|
||||||
|
values: ["cus_test"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attribute: "currency_code",
|
||||||
|
operator: "in",
|
||||||
|
values: ["eur"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
application_method: {
|
||||||
|
type: "fixed",
|
||||||
|
target_type: "shipping_methods",
|
||||||
|
allocation: "each",
|
||||||
|
value: "100",
|
||||||
|
max_quantity: 1,
|
||||||
|
target_rules: [
|
||||||
|
{
|
||||||
|
attribute: "name",
|
||||||
|
operator: "in",
|
||||||
|
values: ["express"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const [newPromotion] = await promotionModuleService.create([
|
||||||
|
{
|
||||||
|
code: "PROMOTION_NEW",
|
||||||
|
type: PromotionType.STANDARD,
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
attribute: "customer_id",
|
||||||
|
operator: "in",
|
||||||
|
values: ["cus_test"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attribute: "currency_code",
|
||||||
|
operator: "in",
|
||||||
|
values: ["eur"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
application_method: {
|
||||||
|
type: "fixed",
|
||||||
|
target_type: "shipping_methods",
|
||||||
|
allocation: "each",
|
||||||
|
value: "200",
|
||||||
|
max_quantity: 1,
|
||||||
|
target_rules: [
|
||||||
|
{
|
||||||
|
attribute: "name",
|
||||||
|
operator: "in",
|
||||||
|
values: ["express", "standard"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const cart = await cartModuleService.create({
|
||||||
|
currency_code: "eur",
|
||||||
|
customer_id: "cus_test",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
unit_price: 2000,
|
||||||
|
quantity: 1,
|
||||||
|
title: "Test item",
|
||||||
|
product_id: "prod_mat",
|
||||||
|
} as any,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const [express, standard] = await cartModuleService.addShippingMethods(
|
||||||
|
cart.id,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
amount: 500,
|
||||||
|
name: "express",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
amount: 500,
|
||||||
|
name: "standard",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
const [adjustment] = await cartModuleService.addShippingMethodAdjustments(
|
||||||
|
cart.id,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
shipping_method_id: express.id,
|
||||||
|
amount: 100,
|
||||||
|
code: appliedPromotion.code!,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
const api = useApi() as any
|
||||||
|
|
||||||
|
const created = await api.post(`/store/carts/${cart.id}/promotions`, {
|
||||||
|
promo_codes: [newPromotion.code],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(created.status).toEqual(200)
|
||||||
|
expect(created.data.cart).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
items: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
shipping_methods: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: express.id,
|
||||||
|
adjustments: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
amount: 200,
|
||||||
|
code: newPromotion.code,
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.not.stringContaining(adjustment.id),
|
||||||
|
amount: 100,
|
||||||
|
code: appliedPromotion.code,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
id: standard.id,
|
||||||
|
adjustments: [
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
amount: 200,
|
||||||
|
code: newPromotion.code,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,300 @@
|
|||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import { ICartModuleService, IPromotionModuleService } from "@medusajs/types"
|
||||||
|
import { PromotionType } from "@medusajs/utils"
|
||||||
|
import path from "path"
|
||||||
|
import { startBootstrapApp } from "../../../../environment-helpers/bootstrap-app"
|
||||||
|
import { useApi } from "../../../../environment-helpers/use-api"
|
||||||
|
import { getContainer } from "../../../../environment-helpers/use-container"
|
||||||
|
import { initDb, useDb } from "../../../../environment-helpers/use-db"
|
||||||
|
import adminSeeder from "../../../../helpers/admin-seeder"
|
||||||
|
|
||||||
|
jest.setTimeout(50000)
|
||||||
|
|
||||||
|
const env = { MEDUSA_FF_MEDUSA_V2: true }
|
||||||
|
|
||||||
|
describe("Store Carts API: Remove promotions from cart", () => {
|
||||||
|
let dbConnection
|
||||||
|
let appContainer
|
||||||
|
let shutdownServer
|
||||||
|
let cartModuleService: ICartModuleService
|
||||||
|
let promotionModuleService: IPromotionModuleService
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
||||||
|
dbConnection = await initDb({ cwd, env } as any)
|
||||||
|
shutdownServer = await startBootstrapApp({ cwd, env })
|
||||||
|
appContainer = getContainer()
|
||||||
|
cartModuleService = appContainer.resolve(ModuleRegistrationName.CART)
|
||||||
|
promotionModuleService = appContainer.resolve(
|
||||||
|
ModuleRegistrationName.PROMOTION
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
const db = useDb()
|
||||||
|
await db.shutdown()
|
||||||
|
await shutdownServer()
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await adminSeeder(dbConnection)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
const db = useDb()
|
||||||
|
await db.teardown()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("DELETE /store/carts/:id/promotions", () => {
|
||||||
|
it("should remove line item adjustments from a cart based on promotions", async () => {
|
||||||
|
const appliedPromotion = await promotionModuleService.create({
|
||||||
|
code: "PROMOTION_APPLIED",
|
||||||
|
type: PromotionType.STANDARD,
|
||||||
|
application_method: {
|
||||||
|
type: "fixed",
|
||||||
|
target_type: "items",
|
||||||
|
allocation: "each",
|
||||||
|
value: "300",
|
||||||
|
apply_to_quantity: 1,
|
||||||
|
max_quantity: 1,
|
||||||
|
target_rules: [
|
||||||
|
{
|
||||||
|
attribute: "product_id",
|
||||||
|
operator: "eq",
|
||||||
|
values: "prod_tshirt",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const appliedPromotionToRemove = await promotionModuleService.create({
|
||||||
|
code: "PROMOTION_APPLIED_TO_REMOVE",
|
||||||
|
type: PromotionType.STANDARD,
|
||||||
|
application_method: {
|
||||||
|
type: "fixed",
|
||||||
|
target_type: "items",
|
||||||
|
allocation: "each",
|
||||||
|
value: "300",
|
||||||
|
apply_to_quantity: 1,
|
||||||
|
max_quantity: 1,
|
||||||
|
target_rules: [
|
||||||
|
{
|
||||||
|
attribute: "product_id",
|
||||||
|
operator: "eq",
|
||||||
|
values: "prod_tshirt",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const cart = await cartModuleService.create({
|
||||||
|
currency_code: "usd",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: "item-1",
|
||||||
|
unit_price: 2000,
|
||||||
|
quantity: 1,
|
||||||
|
title: "Test item",
|
||||||
|
product_id: "prod_mat",
|
||||||
|
} as any,
|
||||||
|
{
|
||||||
|
id: "item-2",
|
||||||
|
unit_price: 1000,
|
||||||
|
quantity: 1,
|
||||||
|
title: "Test item",
|
||||||
|
product_id: "prod_tshirt",
|
||||||
|
} as any,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const [adjustment1, adjustment2] =
|
||||||
|
await cartModuleService.addLineItemAdjustments([
|
||||||
|
{
|
||||||
|
code: appliedPromotion.code!,
|
||||||
|
amount: 300,
|
||||||
|
item_id: "item-2",
|
||||||
|
promotion_id: appliedPromotionToRemove.id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: appliedPromotionToRemove.code!,
|
||||||
|
amount: 150,
|
||||||
|
item_id: "item-1",
|
||||||
|
promotion_id: appliedPromotionToRemove.id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: appliedPromotionToRemove.code!,
|
||||||
|
amount: 150,
|
||||||
|
item_id: "item-2",
|
||||||
|
promotion_id: appliedPromotionToRemove.id,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const api = useApi() as any
|
||||||
|
|
||||||
|
const response = await api.delete(`/store/carts/${cart.id}/promotions`, {
|
||||||
|
data: {
|
||||||
|
promo_codes: [appliedPromotionToRemove.code],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.cart).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
items: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: "item-1",
|
||||||
|
adjustments: [],
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
id: "item-2",
|
||||||
|
adjustments: [
|
||||||
|
expect.objectContaining({
|
||||||
|
amount: 300,
|
||||||
|
code: appliedPromotion.code,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should add shipping method adjustments to a cart based on promotions", async () => {
|
||||||
|
const appliedPromotion = await promotionModuleService.create({
|
||||||
|
code: "PROMOTION_APPLIED",
|
||||||
|
type: PromotionType.STANDARD,
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
attribute: "customer_id",
|
||||||
|
operator: "in",
|
||||||
|
values: ["cus_test"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attribute: "currency_code",
|
||||||
|
operator: "in",
|
||||||
|
values: ["eur"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
application_method: {
|
||||||
|
type: "fixed",
|
||||||
|
target_type: "shipping_methods",
|
||||||
|
allocation: "each",
|
||||||
|
value: "100",
|
||||||
|
max_quantity: 1,
|
||||||
|
target_rules: [
|
||||||
|
{
|
||||||
|
attribute: "name",
|
||||||
|
operator: "in",
|
||||||
|
values: ["express"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const appliedPromotionToRemove = await promotionModuleService.create({
|
||||||
|
code: "PROMOTION_APPLIED_TO_REMOVE",
|
||||||
|
type: PromotionType.STANDARD,
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
attribute: "customer_id",
|
||||||
|
operator: "in",
|
||||||
|
values: ["cus_test"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attribute: "currency_code",
|
||||||
|
operator: "in",
|
||||||
|
values: ["eur"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
application_method: {
|
||||||
|
type: "fixed",
|
||||||
|
target_type: "shipping_methods",
|
||||||
|
allocation: "each",
|
||||||
|
value: "100",
|
||||||
|
max_quantity: 1,
|
||||||
|
target_rules: [
|
||||||
|
{
|
||||||
|
attribute: "name",
|
||||||
|
operator: "in",
|
||||||
|
values: ["express"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const cart = await cartModuleService.create({
|
||||||
|
currency_code: "eur",
|
||||||
|
customer_id: "cus_test",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
unit_price: 2000,
|
||||||
|
quantity: 1,
|
||||||
|
title: "Test item",
|
||||||
|
product_id: "prod_mat",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const [express, standard] = await cartModuleService.addShippingMethods(
|
||||||
|
cart.id,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
amount: 500,
|
||||||
|
name: "express",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
amount: 500,
|
||||||
|
name: "standard",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
await cartModuleService.addShippingMethodAdjustments(cart.id, [
|
||||||
|
{
|
||||||
|
shipping_method_id: express.id,
|
||||||
|
amount: 100,
|
||||||
|
code: appliedPromotion.code!,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shipping_method_id: express.id,
|
||||||
|
amount: 100,
|
||||||
|
code: appliedPromotionToRemove.code!,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const api = useApi() as any
|
||||||
|
|
||||||
|
const response = await api.delete(`/store/carts/${cart.id}/promotions`, {
|
||||||
|
data: { promo_codes: [appliedPromotionToRemove.code] },
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.cart).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
items: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
shipping_methods: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: express.id,
|
||||||
|
adjustments: [
|
||||||
|
expect.objectContaining({
|
||||||
|
amount: 100,
|
||||||
|
code: appliedPromotion.code,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
id: standard.id,
|
||||||
|
adjustments: [],
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -291,12 +291,9 @@ describe("Cart Module Service", () => {
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
const updatedCart = await service.update(
|
const updatedCart = await service.update(createdCart.id, {
|
||||||
createdCart.id,
|
|
||||||
{
|
|
||||||
email: "test@email.com",
|
email: "test@email.com",
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
const [cart] = await service.list({ id: [createdCart.id] })
|
const [cart] = await service.list({ id: [createdCart.id] })
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ import { BigNumberRawValue, DAL } from "@medusajs/types"
|
|||||||
import {
|
import {
|
||||||
BigNumber,
|
BigNumber,
|
||||||
DALUtils,
|
DALUtils,
|
||||||
|
MikroOrmBigNumberProperty,
|
||||||
createPsqlIndexStatementHelper,
|
createPsqlIndexStatementHelper,
|
||||||
generateEntityId,
|
generateEntityId,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
import {
|
import {
|
||||||
BeforeCreate,
|
BeforeCreate,
|
||||||
BeforeUpdate,
|
|
||||||
Cascade,
|
Cascade,
|
||||||
Collection,
|
Collection,
|
||||||
Entity,
|
Entity,
|
||||||
@@ -123,13 +123,13 @@ export default class LineItem {
|
|||||||
@Property({ columnType: "boolean" })
|
@Property({ columnType: "boolean" })
|
||||||
is_tax_inclusive = false
|
is_tax_inclusive = false
|
||||||
|
|
||||||
@Property({ columnType: "numeric", nullable: true })
|
@MikroOrmBigNumberProperty({ nullable: true })
|
||||||
compare_at_unit_price?: BigNumber | number | null = null
|
compare_at_unit_price?: BigNumber | number | null = null
|
||||||
|
|
||||||
@Property({ columnType: "jsonb", nullable: true })
|
@Property({ columnType: "jsonb", nullable: true })
|
||||||
raw_compare_at_unit_price: BigNumberRawValue | null = null
|
raw_compare_at_unit_price: BigNumberRawValue | null = null
|
||||||
|
|
||||||
@Property({ columnType: "numeric" })
|
@MikroOrmBigNumberProperty()
|
||||||
unit_price: BigNumber | number
|
unit_price: BigNumber | number
|
||||||
|
|
||||||
@Property({ columnType: "jsonb" })
|
@Property({ columnType: "jsonb" })
|
||||||
@@ -171,28 +171,10 @@ export default class LineItem {
|
|||||||
@BeforeCreate()
|
@BeforeCreate()
|
||||||
onCreate() {
|
onCreate() {
|
||||||
this.id = generateEntityId(this.id, "cali")
|
this.id = generateEntityId(this.id, "cali")
|
||||||
|
|
||||||
const val = new BigNumber(this.raw_unit_price ?? this.unit_price)
|
|
||||||
|
|
||||||
this.unit_price = val.numeric
|
|
||||||
this.raw_unit_price = val.raw!
|
|
||||||
}
|
|
||||||
|
|
||||||
@BeforeUpdate()
|
|
||||||
onUpdate() {
|
|
||||||
const val = new BigNumber(this.raw_unit_price ?? this.unit_price)
|
|
||||||
|
|
||||||
this.unit_price = val.numeric
|
|
||||||
this.raw_unit_price = val.raw as BigNumberRawValue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnInit()
|
@OnInit()
|
||||||
onInit() {
|
onInit() {
|
||||||
this.id = generateEntityId(this.id, "cali")
|
this.id = generateEntityId(this.id, "cali")
|
||||||
|
|
||||||
const val = new BigNumber(this.raw_unit_price ?? this.unit_price)
|
|
||||||
|
|
||||||
this.unit_price = val.numeric
|
|
||||||
this.raw_unit_price = val.raw!
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@ import { BigNumberRawValue, DAL } from "@medusajs/types"
|
|||||||
import {
|
import {
|
||||||
BigNumber,
|
BigNumber,
|
||||||
DALUtils,
|
DALUtils,
|
||||||
|
MikroOrmBigNumberProperty,
|
||||||
createPsqlIndexStatementHelper,
|
createPsqlIndexStatementHelper,
|
||||||
generateEntityId,
|
generateEntityId,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
@@ -19,7 +20,6 @@ import {
|
|||||||
PrimaryKey,
|
PrimaryKey,
|
||||||
Property,
|
Property,
|
||||||
} from "@mikro-orm/core"
|
} from "@mikro-orm/core"
|
||||||
import { BeforeUpdate } from "typeorm"
|
|
||||||
import Cart from "./cart"
|
import Cart from "./cart"
|
||||||
import ShippingMethodAdjustment from "./shipping-method-adjustment"
|
import ShippingMethodAdjustment from "./shipping-method-adjustment"
|
||||||
import ShippingMethodTaxLine from "./shipping-method-tax-line"
|
import ShippingMethodTaxLine from "./shipping-method-tax-line"
|
||||||
@@ -49,7 +49,7 @@ export default class ShippingMethod {
|
|||||||
|
|
||||||
@ManyToOne({
|
@ManyToOne({
|
||||||
entity: () => Cart,
|
entity: () => Cart,
|
||||||
cascade: [Cascade.REMOVE, Cascade.PERSIST, "soft-remove"] as any,
|
cascade: [Cascade.REMOVE, Cascade.PERSIST],
|
||||||
})
|
})
|
||||||
cart: Cart
|
cart: Cart
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ export default class ShippingMethod {
|
|||||||
@Property({ columnType: "jsonb", nullable: true })
|
@Property({ columnType: "jsonb", nullable: true })
|
||||||
description: string | null = null
|
description: string | null = null
|
||||||
|
|
||||||
@Property({ columnType: "numeric" })
|
@MikroOrmBigNumberProperty()
|
||||||
amount: BigNumber | number
|
amount: BigNumber | number
|
||||||
|
|
||||||
@Property({ columnType: "jsonb" })
|
@Property({ columnType: "jsonb" })
|
||||||
@@ -127,28 +127,10 @@ export default class ShippingMethod {
|
|||||||
@BeforeCreate()
|
@BeforeCreate()
|
||||||
onCreate() {
|
onCreate() {
|
||||||
this.id = generateEntityId(this.id, "casm")
|
this.id = generateEntityId(this.id, "casm")
|
||||||
|
|
||||||
const val = new BigNumber(this.raw_amount ?? this.amount)
|
|
||||||
|
|
||||||
this.amount = val.numeric
|
|
||||||
this.raw_amount = val.raw!
|
|
||||||
}
|
|
||||||
|
|
||||||
@BeforeUpdate()
|
|
||||||
onUpdate() {
|
|
||||||
const val = new BigNumber(this.raw_amount ?? this.amount)
|
|
||||||
|
|
||||||
this.amount = val.numeric
|
|
||||||
this.raw_amount = val.raw as BigNumberRawValue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnInit()
|
@OnInit()
|
||||||
onInit() {
|
onInit() {
|
||||||
this.id = generateEntityId(this.id, "casm")
|
this.id = generateEntityId(this.id, "casm")
|
||||||
|
|
||||||
const val = new BigNumber(this.raw_amount ?? this.amount)
|
|
||||||
|
|
||||||
this.amount = val.numeric
|
|
||||||
this.raw_amount = val.raw!
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import {
|
||||||
|
CreateLineItemAdjustmentDTO,
|
||||||
|
ICartModuleService,
|
||||||
|
} from "@medusajs/types"
|
||||||
|
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||||
|
|
||||||
|
interface StepInput {
|
||||||
|
lineItemAdjustmentsToCreate: CreateLineItemAdjustmentDTO[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createLineItemAdjustmentsStepId = "create-line-item-adjustments"
|
||||||
|
export const createLineItemAdjustmentsStep = createStep(
|
||||||
|
createLineItemAdjustmentsStepId,
|
||||||
|
async (data: StepInput, { container }) => {
|
||||||
|
const { lineItemAdjustmentsToCreate = [] } = data
|
||||||
|
const cartModuleService: ICartModuleService = container.resolve(
|
||||||
|
ModuleRegistrationName.CART
|
||||||
|
)
|
||||||
|
|
||||||
|
const createdLineItemAdjustments =
|
||||||
|
await cartModuleService.addLineItemAdjustments(
|
||||||
|
lineItemAdjustmentsToCreate
|
||||||
|
)
|
||||||
|
|
||||||
|
return new StepResponse(void 0, createdLineItemAdjustments)
|
||||||
|
},
|
||||||
|
async (createdLineItemAdjustments, { container }) => {
|
||||||
|
const cartModuleService: ICartModuleService = container.resolve(
|
||||||
|
ModuleRegistrationName.CART
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!createdLineItemAdjustments?.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await cartModuleService.softDeleteLineItemAdjustments(
|
||||||
|
createdLineItemAdjustments.map((c) => c.id)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import {
|
||||||
|
CreateShippingMethodAdjustmentDTO,
|
||||||
|
ICartModuleService,
|
||||||
|
} from "@medusajs/types"
|
||||||
|
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||||
|
|
||||||
|
interface StepInput {
|
||||||
|
shippingMethodAdjustmentsToCreate: CreateShippingMethodAdjustmentDTO[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createShippingMethodAdjustmentsStepId =
|
||||||
|
"create-shipping-method-adjustments"
|
||||||
|
export const createShippingMethodAdjustmentsStep = createStep(
|
||||||
|
createShippingMethodAdjustmentsStepId,
|
||||||
|
async (data: StepInput, { container }) => {
|
||||||
|
const { shippingMethodAdjustmentsToCreate = [] } = data
|
||||||
|
const cartModuleService: ICartModuleService = container.resolve(
|
||||||
|
ModuleRegistrationName.CART
|
||||||
|
)
|
||||||
|
|
||||||
|
const createdShippingMethodAdjustments =
|
||||||
|
await cartModuleService.addShippingMethodAdjustments(
|
||||||
|
shippingMethodAdjustmentsToCreate
|
||||||
|
)
|
||||||
|
|
||||||
|
return new StepResponse(void 0, createdShippingMethodAdjustments)
|
||||||
|
},
|
||||||
|
async (createdShippingMethodAdjustments, { container }) => {
|
||||||
|
const cartModuleService: ICartModuleService = container.resolve(
|
||||||
|
ModuleRegistrationName.CART
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!createdShippingMethodAdjustments?.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await cartModuleService.softDeleteShippingMethodAdjustments(
|
||||||
|
createdShippingMethodAdjustments.map((c) => c.id)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import { CartDTO, IPromotionModuleService } from "@medusajs/types"
|
||||||
|
import { deduplicate, isString } from "@medusajs/utils"
|
||||||
|
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||||
|
|
||||||
|
interface StepInput {
|
||||||
|
cart: CartDTO
|
||||||
|
promoCodes: string[]
|
||||||
|
removePromotions: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getActionsToComputeFromPromotionsStepId =
|
||||||
|
"get-actions-to-compute-from-promotions"
|
||||||
|
export const getActionsToComputeFromPromotionsStep = createStep(
|
||||||
|
getActionsToComputeFromPromotionsStepId,
|
||||||
|
async (data: StepInput, { container }) => {
|
||||||
|
const promotionModuleService: IPromotionModuleService = container.resolve(
|
||||||
|
ModuleRegistrationName.PROMOTION
|
||||||
|
)
|
||||||
|
|
||||||
|
const { removePromotions = false, promoCodes = [], cart } = data
|
||||||
|
|
||||||
|
const appliedItemPromoCodes = cart.items
|
||||||
|
?.map((item) => item.adjustments?.map((adjustment) => adjustment.code))
|
||||||
|
.flat(1)
|
||||||
|
.filter(isString) as string[]
|
||||||
|
|
||||||
|
const appliedShippingMethodPromoCodes = cart.shipping_methods
|
||||||
|
?.map((shippingMethod) =>
|
||||||
|
shippingMethod.adjustments?.map((adjustment) => adjustment.code)
|
||||||
|
)
|
||||||
|
.flat(1)
|
||||||
|
.filter(isString) as string[]
|
||||||
|
|
||||||
|
let promotionCodesToApply = deduplicate([
|
||||||
|
...promoCodes,
|
||||||
|
...appliedItemPromoCodes,
|
||||||
|
...appliedShippingMethodPromoCodes,
|
||||||
|
])
|
||||||
|
|
||||||
|
if (removePromotions) {
|
||||||
|
promotionCodesToApply = promotionCodesToApply.filter(
|
||||||
|
(code) => !promoCodes.includes(code)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const actionsToCompute = await promotionModuleService.computeActions(
|
||||||
|
promotionCodesToApply,
|
||||||
|
cart as any
|
||||||
|
)
|
||||||
|
|
||||||
|
return new StepResponse(actionsToCompute)
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -1,5 +1,12 @@
|
|||||||
export * from "./create-carts"
|
export * from "./create-carts"
|
||||||
|
export * from "./create-line-item-adjustments"
|
||||||
|
export * from "./create-shipping-method-adjustments"
|
||||||
export * from "./find-one-or-any-region"
|
export * from "./find-one-or-any-region"
|
||||||
export * from "./find-or-create-customer"
|
export * from "./find-or-create-customer"
|
||||||
export * from "./find-sales-channel"
|
export * from "./find-sales-channel"
|
||||||
|
export * from "./get-actions-to-compute-from-promotions"
|
||||||
|
export * from "./prepare-adjustments-from-promotion-actions"
|
||||||
|
export * from "./remove-line-item-adjustments"
|
||||||
|
export * from "./remove-shipping-method-adjustments"
|
||||||
|
export * from "./retrieve-cart"
|
||||||
export * from "./update-carts"
|
export * from "./update-carts"
|
||||||
+75
@@ -0,0 +1,75 @@
|
|||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import {
|
||||||
|
AddItemAdjustmentAction,
|
||||||
|
AddShippingMethodAdjustment,
|
||||||
|
ComputeActions,
|
||||||
|
IPromotionModuleService,
|
||||||
|
PromotionDTO,
|
||||||
|
RemoveItemAdjustmentAction,
|
||||||
|
RemoveShippingMethodAdjustment,
|
||||||
|
} from "@medusajs/types"
|
||||||
|
import { ComputedActions } from "@medusajs/utils"
|
||||||
|
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||||
|
|
||||||
|
interface StepInput {
|
||||||
|
actions: ComputeActions[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const prepareAdjustmentsFromPromotionActionsStepId =
|
||||||
|
"prepare-adjustments-from-promotion-actions"
|
||||||
|
export const prepareAdjustmentsFromPromotionActionsStep = createStep(
|
||||||
|
prepareAdjustmentsFromPromotionActionsStepId,
|
||||||
|
async (data: StepInput, { container }) => {
|
||||||
|
const promotionModuleService: IPromotionModuleService = container.resolve(
|
||||||
|
ModuleRegistrationName.PROMOTION
|
||||||
|
)
|
||||||
|
|
||||||
|
const { actions = [] } = data
|
||||||
|
const promotions = await promotionModuleService.list(
|
||||||
|
{ code: actions.map((a) => a.code) },
|
||||||
|
{ select: ["id", "code"] }
|
||||||
|
)
|
||||||
|
|
||||||
|
const promotionsMap = new Map<string, PromotionDTO>(
|
||||||
|
promotions.map((promotion) => [promotion.code!, promotion])
|
||||||
|
)
|
||||||
|
|
||||||
|
const lineItemAdjustmentsToCreate = actions
|
||||||
|
.filter((a) => a.action === ComputedActions.ADD_ITEM_ADJUSTMENT)
|
||||||
|
.map((action) => ({
|
||||||
|
code: action.code,
|
||||||
|
amount: (action as AddItemAdjustmentAction).amount,
|
||||||
|
item_id: (action as AddItemAdjustmentAction).item_id,
|
||||||
|
promotion_id: promotionsMap.get(action.code)?.id,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const lineItemAdjustmentIdsToRemove = actions
|
||||||
|
.filter((a) => a.action === ComputedActions.REMOVE_ITEM_ADJUSTMENT)
|
||||||
|
.map((a) => (a as RemoveItemAdjustmentAction).adjustment_id)
|
||||||
|
|
||||||
|
const shippingMethodAdjustmentsToCreate = actions
|
||||||
|
.filter(
|
||||||
|
(a) => a.action === ComputedActions.ADD_SHIPPING_METHOD_ADJUSTMENT
|
||||||
|
)
|
||||||
|
.map((action) => ({
|
||||||
|
code: action.code,
|
||||||
|
amount: (action as AddShippingMethodAdjustment).amount,
|
||||||
|
shipping_method_id: (action as AddShippingMethodAdjustment)
|
||||||
|
.shipping_method_id,
|
||||||
|
promotion_id: promotionsMap.get(action.code)?.id,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const shippingMethodAdjustmentIdsToRemove = actions
|
||||||
|
.filter(
|
||||||
|
(a) => a.action === ComputedActions.REMOVE_SHIPPING_METHOD_ADJUSTMENT
|
||||||
|
)
|
||||||
|
.map((a) => (a as RemoveShippingMethodAdjustment).adjustment_id)
|
||||||
|
|
||||||
|
return new StepResponse({
|
||||||
|
lineItemAdjustmentsToCreate,
|
||||||
|
lineItemAdjustmentIdsToRemove,
|
||||||
|
shippingMethodAdjustmentsToCreate,
|
||||||
|
shippingMethodAdjustmentIdsToRemove,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import { ICartModuleService } from "@medusajs/types"
|
||||||
|
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||||
|
|
||||||
|
interface StepInput {
|
||||||
|
lineItemAdjustmentIdsToRemove: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const removeLineItemAdjustmentsStepId = "remove-line-item-adjustments"
|
||||||
|
export const removeLineItemAdjustmentsStep = createStep(
|
||||||
|
removeLineItemAdjustmentsStepId,
|
||||||
|
async (data: StepInput, { container }) => {
|
||||||
|
const { lineItemAdjustmentIdsToRemove = [] } = data
|
||||||
|
const cartModuleService: ICartModuleService = container.resolve(
|
||||||
|
ModuleRegistrationName.CART
|
||||||
|
)
|
||||||
|
|
||||||
|
await cartModuleService.softDeleteLineItemAdjustments(
|
||||||
|
lineItemAdjustmentIdsToRemove
|
||||||
|
)
|
||||||
|
|
||||||
|
return new StepResponse(void 0, lineItemAdjustmentIdsToRemove)
|
||||||
|
},
|
||||||
|
async (lineItemAdjustmentIdsToRemove, { container }) => {
|
||||||
|
const cartModuleService: ICartModuleService = container.resolve(
|
||||||
|
ModuleRegistrationName.CART
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!lineItemAdjustmentIdsToRemove?.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await cartModuleService.restoreLineItemAdjustments(
|
||||||
|
lineItemAdjustmentIdsToRemove
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import { ICartModuleService } from "@medusajs/types"
|
||||||
|
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||||
|
|
||||||
|
interface StepInput {
|
||||||
|
shippingMethodAdjustmentIdsToRemove: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const removeShippingMethodAdjustmentsStepId =
|
||||||
|
"remove-shipping-method-adjustments"
|
||||||
|
export const removeShippingMethodAdjustmentsStep = createStep(
|
||||||
|
removeShippingMethodAdjustmentsStepId,
|
||||||
|
async (data: StepInput, { container }) => {
|
||||||
|
const { shippingMethodAdjustmentIdsToRemove = [] } = data
|
||||||
|
const cartModuleService: ICartModuleService = container.resolve(
|
||||||
|
ModuleRegistrationName.CART
|
||||||
|
)
|
||||||
|
|
||||||
|
await cartModuleService.softDeleteShippingMethodAdjustments(
|
||||||
|
shippingMethodAdjustmentIdsToRemove
|
||||||
|
)
|
||||||
|
|
||||||
|
return new StepResponse(void 0, shippingMethodAdjustmentIdsToRemove)
|
||||||
|
},
|
||||||
|
async (shippingMethodAdjustmentIdsToRemove, { container }) => {
|
||||||
|
const cartModuleService: ICartModuleService = container.resolve(
|
||||||
|
ModuleRegistrationName.CART
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!shippingMethodAdjustmentIdsToRemove?.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await cartModuleService.restoreShippingMethodAdjustments(
|
||||||
|
shippingMethodAdjustmentIdsToRemove
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import { CartDTO, FindConfig, ICartModuleService } from "@medusajs/types"
|
||||||
|
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||||
|
|
||||||
|
interface StepInput {
|
||||||
|
cartId: string
|
||||||
|
config: FindConfig<CartDTO>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const retrieveCartStepId = "retrieve-cart"
|
||||||
|
export const retrieveCartStep = createStep(
|
||||||
|
retrieveCartStepId,
|
||||||
|
async (data: StepInput, { container }) => {
|
||||||
|
const cartModuleService = container.resolve<ICartModuleService>(
|
||||||
|
ModuleRegistrationName.CART
|
||||||
|
)
|
||||||
|
|
||||||
|
const cart = await cartModuleService.retrieve(data.cartId, data.config)
|
||||||
|
|
||||||
|
// TODO: remove this when cart handles totals calculation
|
||||||
|
cart.items = cart.items?.map((item) => {
|
||||||
|
item.subtotal = item.unit_price
|
||||||
|
|
||||||
|
return item
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO: remove this when cart handles totals calculation
|
||||||
|
cart.shipping_methods = cart.shipping_methods?.map((shipping_method) => {
|
||||||
|
// TODO: should we align all amounts/prices fields to be unit_price?
|
||||||
|
shipping_method.subtotal = shipping_method.amount
|
||||||
|
|
||||||
|
return shipping_method
|
||||||
|
})
|
||||||
|
|
||||||
|
return new StepResponse(cart)
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
export * from "./create-carts"
|
export * from "./create-carts"
|
||||||
|
export * from "./update-cart-promotions"
|
||||||
export * from "./update-carts"
|
export * from "./update-carts"
|
||||||
|
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import { CartDTO } from "@medusajs/types"
|
||||||
|
import {
|
||||||
|
WorkflowData,
|
||||||
|
createWorkflow,
|
||||||
|
parallelize,
|
||||||
|
} from "@medusajs/workflows-sdk"
|
||||||
|
import {
|
||||||
|
createLineItemAdjustmentsStep,
|
||||||
|
createShippingMethodAdjustmentsStep,
|
||||||
|
getActionsToComputeFromPromotionsStep,
|
||||||
|
prepareAdjustmentsFromPromotionActionsStep,
|
||||||
|
removeLineItemAdjustmentsStep,
|
||||||
|
removeShippingMethodAdjustmentsStep,
|
||||||
|
retrieveCartStep,
|
||||||
|
} from "../steps"
|
||||||
|
|
||||||
|
type WorkflowInput = {
|
||||||
|
promoCodes: string[]
|
||||||
|
cartId: string
|
||||||
|
removePromotions?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateCartPromotionsWorkflowId = "update-cart-promotions"
|
||||||
|
export const updateCartPromotionsWorkflow = createWorkflow(
|
||||||
|
updateCartPromotionsWorkflowId,
|
||||||
|
(input: WorkflowData<WorkflowInput>): WorkflowData<CartDTO> => {
|
||||||
|
const retrieveCartInput = {
|
||||||
|
cartId: input.cartId,
|
||||||
|
config: {
|
||||||
|
relations: [
|
||||||
|
"items",
|
||||||
|
"items.adjustments",
|
||||||
|
"shipping_methods",
|
||||||
|
"shipping_methods.adjustments",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const cart = retrieveCartStep(retrieveCartInput)
|
||||||
|
const actions = getActionsToComputeFromPromotionsStep({
|
||||||
|
cart,
|
||||||
|
promoCodes: input.promoCodes,
|
||||||
|
removePromotions: input.removePromotions || false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const {
|
||||||
|
lineItemAdjustmentsToCreate,
|
||||||
|
lineItemAdjustmentIdsToRemove,
|
||||||
|
shippingMethodAdjustmentsToCreate,
|
||||||
|
shippingMethodAdjustmentIdsToRemove,
|
||||||
|
} = prepareAdjustmentsFromPromotionActionsStep({ actions })
|
||||||
|
|
||||||
|
parallelize(
|
||||||
|
removeLineItemAdjustmentsStep({ lineItemAdjustmentIdsToRemove }),
|
||||||
|
removeShippingMethodAdjustmentsStep({
|
||||||
|
shippingMethodAdjustmentIdsToRemove,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
parallelize(
|
||||||
|
createLineItemAdjustmentsStep({ lineItemAdjustmentsToCreate }),
|
||||||
|
createShippingMethodAdjustmentsStep({ shippingMethodAdjustmentsToCreate })
|
||||||
|
)
|
||||||
|
|
||||||
|
return retrieveCartStep(retrieveCartInput).config({
|
||||||
|
name: "retrieve-cart-result-step",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { updateCartPromotionsWorkflow } from "@medusajs/core-flows"
|
||||||
|
import { MedusaRequest, MedusaResponse } from "../../../../../types/routing"
|
||||||
|
import { StorePostCartsCartPromotionsReq } from "../../validators"
|
||||||
|
|
||||||
|
export const POST = async (req: MedusaRequest, res: MedusaResponse) => {
|
||||||
|
const workflow = updateCartPromotionsWorkflow(req.scope)
|
||||||
|
const payload = req.validatedBody as StorePostCartsCartPromotionsReq
|
||||||
|
|
||||||
|
const { result, errors } = await workflow.run({
|
||||||
|
input: {
|
||||||
|
promoCodes: payload.promo_codes,
|
||||||
|
cartId: req.params.id,
|
||||||
|
},
|
||||||
|
throwOnError: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (Array.isArray(errors) && errors[0]) {
|
||||||
|
throw errors[0].error
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({ cart: result })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DELETE = async (req: MedusaRequest, res: MedusaResponse) => {
|
||||||
|
const workflow = updateCartPromotionsWorkflow(req.scope)
|
||||||
|
const payload = req.validatedBody as StorePostCartsCartPromotionsReq
|
||||||
|
|
||||||
|
const { result, errors } = await workflow.run({
|
||||||
|
input: {
|
||||||
|
promoCodes: payload.promo_codes,
|
||||||
|
cartId: req.params.id,
|
||||||
|
removePromotions: true,
|
||||||
|
},
|
||||||
|
throwOnError: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (Array.isArray(errors) && errors[0]) {
|
||||||
|
throw errors[0].error
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({ cart: result })
|
||||||
|
}
|
||||||
@@ -3,8 +3,10 @@ import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
|
|||||||
import { authenticate } from "../../../utils/authenticate-middleware"
|
import { authenticate } from "../../../utils/authenticate-middleware"
|
||||||
import * as QueryConfig from "./query-config"
|
import * as QueryConfig from "./query-config"
|
||||||
import {
|
import {
|
||||||
|
StoreDeleteCartsCartPromotionsReq,
|
||||||
StoreGetCartsCartParams,
|
StoreGetCartsCartParams,
|
||||||
StorePostCartReq,
|
StorePostCartReq,
|
||||||
|
StorePostCartsCartPromotionsReq,
|
||||||
StorePostCartsCartReq,
|
StorePostCartsCartReq,
|
||||||
} from "./validators"
|
} from "./validators"
|
||||||
|
|
||||||
@@ -38,4 +40,14 @@ export const storeCartRoutesMiddlewares: MiddlewareRoute[] = [
|
|||||||
matcher: "/store/carts/:id",
|
matcher: "/store/carts/:id",
|
||||||
middlewares: [transformBody(StorePostCartsCartReq)],
|
middlewares: [transformBody(StorePostCartsCartReq)],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
method: ["POST"],
|
||||||
|
matcher: "/store/carts/:id/promotions",
|
||||||
|
middlewares: [transformBody(StorePostCartsCartPromotionsReq)],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: ["DELETE"],
|
||||||
|
matcher: "/store/carts/:id/promotions",
|
||||||
|
middlewares: [transformBody(StoreDeleteCartsCartPromotionsReq)],
|
||||||
|
},
|
||||||
]
|
]
|
||||||
@@ -52,6 +52,18 @@ export class StorePostCartReq {
|
|||||||
metadata?: Record<string, unknown>
|
metadata?: Record<string, unknown>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class StorePostCartsCartPromotionsReq {
|
||||||
|
@IsArray()
|
||||||
|
@Type(() => String)
|
||||||
|
promo_codes: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export class StoreDeleteCartsCartPromotionsReq {
|
||||||
|
@IsArray()
|
||||||
|
@Type(() => String)
|
||||||
|
promo_codes: string[]
|
||||||
|
}
|
||||||
|
|
||||||
export class StorePostCartsCartReq {
|
export class StorePostCartsCartReq {
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ import {
|
|||||||
CampaignBudgetType,
|
CampaignBudgetType,
|
||||||
InjectManager,
|
InjectManager,
|
||||||
InjectTransactionManager,
|
InjectTransactionManager,
|
||||||
isString,
|
|
||||||
MedusaContext,
|
MedusaContext,
|
||||||
MedusaError,
|
MedusaError,
|
||||||
ModulesSdkUtils,
|
ModulesSdkUtils,
|
||||||
PromotionType,
|
PromotionType,
|
||||||
|
isString,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
import {
|
import {
|
||||||
ApplicationMethod,
|
ApplicationMethod,
|
||||||
@@ -38,9 +38,9 @@ import {
|
|||||||
UpdatePromotionDTO,
|
UpdatePromotionDTO,
|
||||||
} from "@types"
|
} from "@types"
|
||||||
import {
|
import {
|
||||||
|
ComputeActionUtils,
|
||||||
allowedAllocationForQuantity,
|
allowedAllocationForQuantity,
|
||||||
areRulesValidForContext,
|
areRulesValidForContext,
|
||||||
ComputeActionUtils,
|
|
||||||
validateApplicationMethodAttributes,
|
validateApplicationMethodAttributes,
|
||||||
validatePromotionRuleAttributes,
|
validatePromotionRuleAttributes,
|
||||||
} from "@utils"
|
} from "@utils"
|
||||||
@@ -166,10 +166,10 @@ export default class PromotionModuleService<
|
|||||||
if (campaignBudget.type === CampaignBudgetType.SPEND) {
|
if (campaignBudget.type === CampaignBudgetType.SPEND) {
|
||||||
const campaignBudgetData = promotionCodeCampaignBudgetMap.get(
|
const campaignBudgetData = promotionCodeCampaignBudgetMap.get(
|
||||||
campaignBudget.id
|
campaignBudget.id
|
||||||
) || { id: campaignBudget.id, used: campaignBudget.used || 0 }
|
) || { id: campaignBudget.id, used: campaignBudget.used ?? 0 }
|
||||||
|
|
||||||
campaignBudgetData.used =
|
campaignBudgetData.used =
|
||||||
(campaignBudgetData.used || 0) + computedAction.amount
|
(campaignBudgetData.used ?? 0) + computedAction.amount
|
||||||
|
|
||||||
if (
|
if (
|
||||||
campaignBudget.limit &&
|
campaignBudget.limit &&
|
||||||
@@ -194,7 +194,7 @@ export default class PromotionModuleService<
|
|||||||
|
|
||||||
const campaignBudgetData = {
|
const campaignBudgetData = {
|
||||||
id: campaignBudget.id,
|
id: campaignBudget.id,
|
||||||
used: (campaignBudget.used || 0) + 1,
|
used: (campaignBudget.used ?? 0) + 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -225,10 +225,12 @@ export default class PromotionModuleService<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@InjectManager("baseRepository_")
|
||||||
async computeActions(
|
async computeActions(
|
||||||
promotionCodes: string[],
|
promotionCodes: string[],
|
||||||
applicationContext: PromotionTypes.ComputeActionContext,
|
applicationContext: PromotionTypes.ComputeActionContext,
|
||||||
options: PromotionTypes.ComputeActionOptions = {}
|
options: PromotionTypes.ComputeActionOptions = {},
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
): Promise<PromotionTypes.ComputeActions[]> {
|
): Promise<PromotionTypes.ComputeActions[]> {
|
||||||
const { prevent_auto_promotions: preventAutoPromotions } = options
|
const { prevent_auto_promotions: preventAutoPromotions } = options
|
||||||
const computedActions: PromotionTypes.ComputeActions[] = []
|
const computedActions: PromotionTypes.ComputeActions[] = []
|
||||||
@@ -238,12 +240,16 @@ export default class PromotionModuleService<
|
|||||||
const appliedShippingCodes: string[] = []
|
const appliedShippingCodes: string[] = []
|
||||||
const codeAdjustmentMap = new Map<
|
const codeAdjustmentMap = new Map<
|
||||||
string,
|
string,
|
||||||
PromotionTypes.ComputeActionAdjustmentLine
|
PromotionTypes.ComputeActionAdjustmentLine[]
|
||||||
>()
|
>()
|
||||||
const methodIdPromoValueMap = new Map<string, number>()
|
const methodIdPromoValueMap = new Map<string, number>()
|
||||||
const automaticPromotions = preventAutoPromotions
|
const automaticPromotions = preventAutoPromotions
|
||||||
? []
|
? []
|
||||||
: await this.list({ is_automatic: true }, { select: ["code"] })
|
: await this.list(
|
||||||
|
{ is_automatic: true },
|
||||||
|
{ select: ["code"] },
|
||||||
|
sharedContext
|
||||||
|
)
|
||||||
|
|
||||||
const automaticPromotionCodes = automaticPromotions.map((p) => p.code!)
|
const automaticPromotionCodes = automaticPromotions.map((p) => p.code!)
|
||||||
const promotionCodesToApply = [
|
const promotionCodesToApply = [
|
||||||
@@ -254,7 +260,11 @@ export default class PromotionModuleService<
|
|||||||
items.forEach((item) => {
|
items.forEach((item) => {
|
||||||
item.adjustments?.forEach((adjustment) => {
|
item.adjustments?.forEach((adjustment) => {
|
||||||
if (isString(adjustment.code)) {
|
if (isString(adjustment.code)) {
|
||||||
codeAdjustmentMap.set(adjustment.code, adjustment)
|
const adjustments = codeAdjustmentMap.get(adjustment.code) || []
|
||||||
|
|
||||||
|
adjustments.push(adjustment)
|
||||||
|
|
||||||
|
codeAdjustmentMap.set(adjustment.code, adjustments)
|
||||||
appliedItemCodes.push(adjustment.code)
|
appliedItemCodes.push(adjustment.code)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -263,7 +273,11 @@ export default class PromotionModuleService<
|
|||||||
shippingMethods.forEach((shippingMethod) => {
|
shippingMethods.forEach((shippingMethod) => {
|
||||||
shippingMethod.adjustments?.forEach((adjustment) => {
|
shippingMethod.adjustments?.forEach((adjustment) => {
|
||||||
if (isString(adjustment.code)) {
|
if (isString(adjustment.code)) {
|
||||||
codeAdjustmentMap.set(adjustment.code, adjustment)
|
const adjustments = codeAdjustmentMap.get(adjustment.code) || []
|
||||||
|
|
||||||
|
adjustments.push(adjustment)
|
||||||
|
|
||||||
|
codeAdjustmentMap.set(adjustment.code, adjustments)
|
||||||
appliedShippingCodes.push(adjustment.code)
|
appliedShippingCodes.push(adjustment.code)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -292,6 +306,7 @@ export default class PromotionModuleService<
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const appliedCodes = [...appliedShippingCodes, ...appliedItemCodes]
|
||||||
const sortedPermissionsToApply = promotions
|
const sortedPermissionsToApply = promotions
|
||||||
.filter((p) => promotionCodesToApply.includes(p.code!))
|
.filter((p) => promotionCodesToApply.includes(p.code!))
|
||||||
.sort(ComputeActionUtils.sortByBuyGetType)
|
.sort(ComputeActionUtils.sortByBuyGetType)
|
||||||
@@ -300,7 +315,7 @@ export default class PromotionModuleService<
|
|||||||
promotions.map((promotion) => [promotion.code!, promotion])
|
promotions.map((promotion) => [promotion.code!, promotion])
|
||||||
)
|
)
|
||||||
|
|
||||||
for (const appliedCode of [...appliedShippingCodes, ...appliedItemCodes]) {
|
for (const appliedCode of appliedCodes) {
|
||||||
const promotion = existingPromotionsMap.get(appliedCode)
|
const promotion = existingPromotionsMap.get(appliedCode)
|
||||||
|
|
||||||
if (!promotion) {
|
if (!promotion) {
|
||||||
@@ -310,24 +325,28 @@ export default class PromotionModuleService<
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (promotionCodes.includes(appliedCode)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (appliedItemCodes.includes(appliedCode)) {
|
if (appliedItemCodes.includes(appliedCode)) {
|
||||||
|
const adjustments = codeAdjustmentMap.get(appliedCode) || []
|
||||||
|
|
||||||
|
adjustments.forEach((adjustment) =>
|
||||||
computedActions.push({
|
computedActions.push({
|
||||||
action: "removeItemAdjustment",
|
action: "removeItemAdjustment",
|
||||||
adjustment_id: codeAdjustmentMap.get(appliedCode)!.id,
|
adjustment_id: adjustment.id,
|
||||||
code: appliedCode,
|
code: appliedCode,
|
||||||
})
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appliedShippingCodes.includes(appliedCode)) {
|
if (appliedShippingCodes.includes(appliedCode)) {
|
||||||
|
const adjustments = codeAdjustmentMap.get(appliedCode) || []
|
||||||
|
|
||||||
|
adjustments.forEach((adjustment) =>
|
||||||
computedActions.push({
|
computedActions.push({
|
||||||
action: "removeShippingMethodAdjustment",
|
action: "removeShippingMethodAdjustment",
|
||||||
adjustment_id: codeAdjustmentMap.get(appliedCode)!.id,
|
adjustment_id: adjustment.id,
|
||||||
code: appliedCode,
|
code: appliedCode,
|
||||||
})
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ import {
|
|||||||
ComputedActions,
|
ComputedActions,
|
||||||
MedusaError,
|
MedusaError,
|
||||||
PromotionType,
|
PromotionType,
|
||||||
|
isPresent,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
import { areRulesValidForContext } from "../validations"
|
import { areRulesValidForContext } from "../validations"
|
||||||
import { computeActionForBudgetExceeded } from "./usage"
|
import { computeActionForBudgetExceeded } from "./usage"
|
||||||
|
|
||||||
|
// TODO: calculations should eventually move to a totals util outside of the module
|
||||||
export function getComputedActionsForBuyGet(
|
export function getComputedActionsForBuyGet(
|
||||||
promotion: PromotionTypes.PromotionDTO,
|
promotion: PromotionTypes.PromotionDTO,
|
||||||
itemsContext: PromotionTypes.ComputeActionContext[ApplicationMethodTargetType.ITEMS],
|
itemsContext: PromotionTypes.ComputeActionContext[ApplicationMethodTargetType.ITEMS],
|
||||||
@@ -45,6 +47,7 @@ export function getComputedActionsForBuyGet(
|
|||||||
|
|
||||||
const validItemsForTargetRules = itemsContext
|
const validItemsForTargetRules = itemsContext
|
||||||
.filter((item) => areRulesValidForContext(targetRules, item))
|
.filter((item) => areRulesValidForContext(targetRules, item))
|
||||||
|
.filter((item) => isPresent(item.subtotal) && isPresent(item.quantity))
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
const aPrice = a.subtotal / a.quantity
|
const aPrice = a.subtotal / a.quantity
|
||||||
const bPrice = b.subtotal / b.quantity
|
const bPrice = b.subtotal / b.quantity
|
||||||
@@ -55,7 +58,7 @@ export function getComputedActionsForBuyGet(
|
|||||||
let remainingQtyToApply = applyToQuantity
|
let remainingQtyToApply = applyToQuantity
|
||||||
|
|
||||||
for (const method of validItemsForTargetRules) {
|
for (const method of validItemsForTargetRules) {
|
||||||
const appliedPromoValue = methodIdPromoValueMap.get(method.id) || 0
|
const appliedPromoValue = methodIdPromoValueMap.get(method.id) ?? 0
|
||||||
const multiplier = Math.min(method.quantity, remainingQtyToApply)
|
const multiplier = Math.min(method.quantity, remainingQtyToApply)
|
||||||
const amount = (method.subtotal / method.quantity) * multiplier
|
const amount = (method.subtotal / method.quantity) * multiplier
|
||||||
const newRemainingQtyToApply = remainingQtyToApply - multiplier
|
const newRemainingQtyToApply = remainingQtyToApply - multiplier
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ export function getComputedActionsForItems(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: calculations should eventually move to a totals util outside of the module
|
||||||
export function applyPromotionToItems(
|
export function applyPromotionToItems(
|
||||||
promotion: PromotionTypes.PromotionDTO,
|
promotion: PromotionTypes.PromotionDTO,
|
||||||
items: PromotionTypes.ComputeActionContext[ApplicationMethodTargetType.ITEMS],
|
items: PromotionTypes.ComputeActionContext[ApplicationMethodTargetType.ITEMS],
|
||||||
@@ -63,7 +64,11 @@ export function applyPromotionToItems(
|
|||||||
[allocation, allocationOverride].includes(ApplicationMethodAllocation.EACH)
|
[allocation, allocationOverride].includes(ApplicationMethodAllocation.EACH)
|
||||||
) {
|
) {
|
||||||
for (const method of items!) {
|
for (const method of items!) {
|
||||||
const appliedPromoValue = methodIdPromoValueMap.get(method.id) || 0
|
if (!method.subtotal || !method.quantity) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const appliedPromoValue = methodIdPromoValueMap.get(method.id) ?? 0
|
||||||
const quantityMultiplier = Math.min(
|
const quantityMultiplier = Math.min(
|
||||||
method.quantity,
|
method.quantity,
|
||||||
applicationMethod?.max_quantity!
|
applicationMethod?.max_quantity!
|
||||||
@@ -111,16 +116,20 @@ export function applyPromotionToItems(
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
const totalApplicableValue = items!.reduce((acc, method) => {
|
const totalApplicableValue = items!.reduce((acc, method) => {
|
||||||
const appliedPromoValue = methodIdPromoValueMap.get(method.id) || 0
|
const appliedPromoValue = methodIdPromoValueMap.get(method.id) ?? 0
|
||||||
return (
|
const perItemCost = method.subtotal
|
||||||
acc +
|
? method.subtotal / method.quantity
|
||||||
(method.subtotal / method.quantity) * method.quantity -
|
: 0
|
||||||
appliedPromoValue
|
|
||||||
)
|
return acc + perItemCost * method.quantity - appliedPromoValue
|
||||||
}, 0)
|
}, 0)
|
||||||
|
|
||||||
for (const method of items!) {
|
for (const method of items!) {
|
||||||
const appliedPromoValue = methodIdPromoValueMap.get(method.id) || 0
|
if (!method.subtotal || !method.quantity) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const appliedPromoValue = methodIdPromoValueMap.get(method.id) ?? 0
|
||||||
const promotionValue = parseFloat(applicationMethod!.value!)
|
const promotionValue = parseFloat(applicationMethod!.value!)
|
||||||
const applicableTotal =
|
const applicableTotal =
|
||||||
(method.subtotal / method.quantity) * method.quantity -
|
(method.subtotal / method.quantity) * method.quantity -
|
||||||
|
|||||||
@@ -55,7 +55,11 @@ export function applyPromotionToShippingMethods(
|
|||||||
|
|
||||||
if (allocation === ApplicationMethodAllocation.EACH) {
|
if (allocation === ApplicationMethodAllocation.EACH) {
|
||||||
for (const method of shippingMethods!) {
|
for (const method of shippingMethods!) {
|
||||||
const appliedPromoValue = methodIdPromoValueMap.get(method.id) || 0
|
if (!method.subtotal) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const appliedPromoValue = methodIdPromoValueMap.get(method.id) ?? 0
|
||||||
let promotionValue = parseFloat(applicationMethod!.value!)
|
let promotionValue = parseFloat(applicationMethod!.value!)
|
||||||
const applicableTotal = method.subtotal - appliedPromoValue
|
const applicableTotal = method.subtotal - appliedPromoValue
|
||||||
|
|
||||||
@@ -93,9 +97,9 @@ export function applyPromotionToShippingMethods(
|
|||||||
|
|
||||||
if (allocation === ApplicationMethodAllocation.ACROSS) {
|
if (allocation === ApplicationMethodAllocation.ACROSS) {
|
||||||
const totalApplicableValue = shippingMethods!.reduce((acc, method) => {
|
const totalApplicableValue = shippingMethods!.reduce((acc, method) => {
|
||||||
const appliedPromoValue = methodIdPromoValueMap.get(method.id) || 0
|
const appliedPromoValue = methodIdPromoValueMap.get(method.id) ?? 0
|
||||||
|
|
||||||
return acc + method.subtotal - appliedPromoValue
|
return acc + (method.subtotal ?? 0) - appliedPromoValue
|
||||||
}, 0)
|
}, 0)
|
||||||
|
|
||||||
if (totalApplicableValue <= 0) {
|
if (totalApplicableValue <= 0) {
|
||||||
@@ -103,9 +107,13 @@ export function applyPromotionToShippingMethods(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const method of shippingMethods!) {
|
for (const method of shippingMethods!) {
|
||||||
|
if (!method.subtotal) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
const promotionValue = parseFloat(applicationMethod!.value!)
|
const promotionValue = parseFloat(applicationMethod!.value!)
|
||||||
const applicableTotal = method.subtotal
|
const applicableTotal = method.subtotal
|
||||||
const appliedPromoValue = methodIdPromoValueMap.get(method.id) || 0
|
const appliedPromoValue = methodIdPromoValueMap.get(method.id) ?? 0
|
||||||
|
|
||||||
// TODO: should we worry about precision here?
|
// TODO: should we worry about precision here?
|
||||||
let applicablePromotionValue =
|
let applicablePromotionValue =
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export function computeActionForBudgetExceeded(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const campaignBudgetUsed = campaignBudget.used || 0
|
const campaignBudgetUsed = campaignBudget.used ?? 0
|
||||||
const totalUsed =
|
const totalUsed =
|
||||||
campaignBudget.type === CampaignBudgetType.SPEND
|
campaignBudget.type === CampaignBudgetType.SPEND
|
||||||
? campaignBudgetUsed + amount
|
? campaignBudgetUsed + amount
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ export interface CartShippingMethodDTO {
|
|||||||
/**
|
/**
|
||||||
* The price of the shipping method
|
* The price of the shipping method
|
||||||
*/
|
*/
|
||||||
unit_price: number
|
amount: number
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the shipping method price is tax inclusive or not
|
* Whether the shipping method price is tax inclusive or not
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ import {
|
|||||||
} from "./common"
|
} from "./common"
|
||||||
import {
|
import {
|
||||||
CreateAddressDTO,
|
CreateAddressDTO,
|
||||||
CreateAdjustmentDTO,
|
|
||||||
CreateCartDTO,
|
CreateCartDTO,
|
||||||
|
CreateLineItemAdjustmentDTO,
|
||||||
CreateLineItemDTO,
|
CreateLineItemDTO,
|
||||||
CreateLineItemForCartDTO,
|
CreateLineItemForCartDTO,
|
||||||
CreateLineItemTaxLineDTO,
|
CreateLineItemTaxLineDTO,
|
||||||
@@ -185,14 +185,14 @@ export interface ICartModuleService extends IModuleService {
|
|||||||
): Promise<LineItemAdjustmentDTO[]>
|
): Promise<LineItemAdjustmentDTO[]>
|
||||||
|
|
||||||
addLineItemAdjustments(
|
addLineItemAdjustments(
|
||||||
data: CreateAdjustmentDTO[]
|
data: CreateLineItemAdjustmentDTO[]
|
||||||
): Promise<LineItemAdjustmentDTO[]>
|
): Promise<LineItemAdjustmentDTO[]>
|
||||||
addLineItemAdjustments(
|
addLineItemAdjustments(
|
||||||
data: CreateAdjustmentDTO
|
data: CreateLineItemAdjustmentDTO
|
||||||
): Promise<LineItemAdjustmentDTO[]>
|
): Promise<LineItemAdjustmentDTO[]>
|
||||||
addLineItemAdjustments(
|
addLineItemAdjustments(
|
||||||
cartId: string,
|
cartId: string,
|
||||||
data: CreateAdjustmentDTO[]
|
data: CreateLineItemAdjustmentDTO[]
|
||||||
): Promise<LineItemAdjustmentDTO[]>
|
): Promise<LineItemAdjustmentDTO[]>
|
||||||
|
|
||||||
setLineItemAdjustments(
|
setLineItemAdjustments(
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ import {
|
|||||||
SoftDeleteReturn,
|
SoftDeleteReturn,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
import {
|
import {
|
||||||
|
MapToConfig,
|
||||||
isString,
|
isString,
|
||||||
kebabCase,
|
kebabCase,
|
||||||
lowerCaseFirst,
|
lowerCaseFirst,
|
||||||
mapObjectTo,
|
mapObjectTo,
|
||||||
MapToConfig,
|
|
||||||
pluralize,
|
pluralize,
|
||||||
upperCaseFirst,
|
upperCaseFirst,
|
||||||
} from "../common"
|
} from "../common"
|
||||||
@@ -311,9 +311,8 @@ export function abstractModuleServiceFactory<
|
|||||||
config: FindConfig<any> = {},
|
config: FindConfig<any> = {},
|
||||||
sharedContext: Context = {}
|
sharedContext: Context = {}
|
||||||
): Promise<T[]> {
|
): Promise<T[]> {
|
||||||
const entities = await this.__container__[
|
const service = this.__container__[serviceRegistrationName]
|
||||||
serviceRegistrationName
|
const entities = await service.list(filters, config, sharedContext)
|
||||||
].list(filters, config, sharedContext)
|
|
||||||
|
|
||||||
return await this.baseRepository_.serialize<T[]>(entities, {
|
return await this.baseRepository_.serialize<T[]>(entities, {
|
||||||
populate: true,
|
populate: true,
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import {
|
|||||||
ModuleServiceInitializeOptions,
|
ModuleServiceInitializeOptions,
|
||||||
RepositoryService,
|
RepositoryService,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
import { lowerCaseFirst } from "../../common"
|
|
||||||
import { asClass } from "awilix"
|
import { asClass } from "awilix"
|
||||||
import { internalModuleServiceFactory } from "../internal-module-service-factory"
|
import { lowerCaseFirst } from "../../common"
|
||||||
import { mikroOrmBaseRepositoryFactory } from "../../dal"
|
import { mikroOrmBaseRepositoryFactory } from "../../dal"
|
||||||
|
import { internalModuleServiceFactory } from "../internal-module-service-factory"
|
||||||
|
|
||||||
type RepositoryLoaderOptions = {
|
type RepositoryLoaderOptions = {
|
||||||
moduleModels: Record<string, any>
|
moduleModels: Record<string, any>
|
||||||
@@ -96,7 +96,10 @@ export function loadModuleServices({
|
|||||||
const finalService = moduleServicesMap.get(mappedServiceName)
|
const finalService = moduleServicesMap.get(mappedServiceName)
|
||||||
|
|
||||||
if (!finalService) {
|
if (!finalService) {
|
||||||
moduleServicesMap.set(mappedServiceName, internalModuleServiceFactory(Model))
|
moduleServicesMap.set(
|
||||||
|
mappedServiceName,
|
||||||
|
internalModuleServiceFactory(Model)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
WorkflowManager,
|
WorkflowManager,
|
||||||
} from "@medusajs/orchestration"
|
} from "@medusajs/orchestration"
|
||||||
import { LoadedModule, MedusaContainer } from "@medusajs/types"
|
import { LoadedModule, MedusaContainer } from "@medusajs/types"
|
||||||
import { isString, OrchestrationUtils } from "@medusajs/utils"
|
import { OrchestrationUtils, isString } from "@medusajs/utils"
|
||||||
import { ExportedWorkflow, exportWorkflow } from "../../helper"
|
import { ExportedWorkflow, exportWorkflow } from "../../helper"
|
||||||
import { proxify } from "./helpers/proxy"
|
import { proxify } from "./helpers/proxy"
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ export type WorkflowData<T = unknown> = (T extends object
|
|||||||
[Key in keyof T]: WorkflowData<T[Key]>
|
[Key in keyof T]: WorkflowData<T[Key]>
|
||||||
}
|
}
|
||||||
: T & WorkflowDataProperties<T>) &
|
: T & WorkflowDataProperties<T>) &
|
||||||
|
T &
|
||||||
WorkflowDataProperties<T> & {
|
WorkflowDataProperties<T> & {
|
||||||
config(
|
config(
|
||||||
config: { name?: string } & Omit<
|
config: { name?: string } & Omit<
|
||||||
|
|||||||
Reference in new issue
Block a user