feat(core-flows,medusa,types,utils): adds update cart API with promotions (#6514)
what: - adds update cart API - workflow - promotions - sales channel - region - customer
This commit is contained in:
1 parent
608c10383a
commit
f5c2256286
20 files changed
+435
-96
No files matched your search
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/core-flows": patch
|
||||||
|
"@medusajs/medusa": patch
|
||||||
|
"@medusajs/types": patch
|
||||||
|
"@medusajs/utils": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(core-flows,medusa,types,utils): adds update cart API with promotions
|
||||||
@@ -4,9 +4,11 @@ import {
|
|||||||
ICustomerModuleService,
|
ICustomerModuleService,
|
||||||
IPricingModuleService,
|
IPricingModuleService,
|
||||||
IProductModuleService,
|
IProductModuleService,
|
||||||
|
IPromotionModuleService,
|
||||||
IRegionModuleService,
|
IRegionModuleService,
|
||||||
ISalesChannelModuleService,
|
ISalesChannelModuleService,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
|
import { PromotionRuleOperator, PromotionType } from "@medusajs/utils"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { startBootstrapApp } from "../../../../environment-helpers/bootstrap-app"
|
import { startBootstrapApp } from "../../../../environment-helpers/bootstrap-app"
|
||||||
import { useApi } from "../../../../environment-helpers/use-api"
|
import { useApi } from "../../../../environment-helpers/use-api"
|
||||||
@@ -30,6 +32,7 @@ describe("Store Carts API", () => {
|
|||||||
let productModule: IProductModuleService
|
let productModule: IProductModuleService
|
||||||
let pricingModule: IPricingModuleService
|
let pricingModule: IPricingModuleService
|
||||||
let remoteLink
|
let remoteLink
|
||||||
|
let promotionModule: IPromotionModuleService
|
||||||
|
|
||||||
let defaultRegion
|
let defaultRegion
|
||||||
|
|
||||||
@@ -45,6 +48,7 @@ describe("Store Carts API", () => {
|
|||||||
productModule = appContainer.resolve(ModuleRegistrationName.PRODUCT)
|
productModule = appContainer.resolve(ModuleRegistrationName.PRODUCT)
|
||||||
pricingModule = appContainer.resolve(ModuleRegistrationName.PRICING)
|
pricingModule = appContainer.resolve(ModuleRegistrationName.PRICING)
|
||||||
remoteLink = appContainer.resolve("remoteLink")
|
remoteLink = appContainer.resolve("remoteLink")
|
||||||
|
promotionModule = appContainer.resolve(ModuleRegistrationName.PROMOTION)
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
@@ -288,6 +292,171 @@ describe("Store Carts API", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("POST /store/carts/:id", () => {
|
||||||
|
it("should update a cart with promo codes with a replace action", async () => {
|
||||||
|
const targetRules = [
|
||||||
|
{
|
||||||
|
attribute: "product_id",
|
||||||
|
operator: PromotionRuleOperator.IN,
|
||||||
|
values: ["prod_tshirt"],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const appliedPromotion = await promotionModule.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: targetRules,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const createdPromotion = await promotionModule.create({
|
||||||
|
code: "PROMOTION_TEST",
|
||||||
|
type: PromotionType.STANDARD,
|
||||||
|
application_method: {
|
||||||
|
type: "fixed",
|
||||||
|
target_type: "items",
|
||||||
|
allocation: "across",
|
||||||
|
value: "1000",
|
||||||
|
apply_to_quantity: 1,
|
||||||
|
target_rules: targetRules,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const cart = await cartModuleService.create({
|
||||||
|
currency_code: "usd",
|
||||||
|
email: "tony@stark.com",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: "item-1",
|
||||||
|
unit_price: 2000,
|
||||||
|
quantity: 1,
|
||||||
|
title: "Test item",
|
||||||
|
product_id: "prod_tshirt",
|
||||||
|
} as any,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const [adjustment] = await cartModuleService.addLineItemAdjustments([
|
||||||
|
{
|
||||||
|
code: appliedPromotion.code!,
|
||||||
|
amount: 300,
|
||||||
|
item_id: "item-1",
|
||||||
|
promotion_id: appliedPromotion.id,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const api = useApi() as any
|
||||||
|
|
||||||
|
// Should remove earlier adjustments from other promocodes
|
||||||
|
let updated = await api.post(`/store/carts/${cart.id}`, {
|
||||||
|
promo_codes: [createdPromotion.code],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(updated.status).toEqual(200)
|
||||||
|
expect(updated.data.cart).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: cart.id,
|
||||||
|
items: [
|
||||||
|
expect.objectContaining({
|
||||||
|
id: "item-1",
|
||||||
|
adjustments: [
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.not.stringContaining(adjustment.id),
|
||||||
|
code: createdPromotion.code,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
// Should remove all adjustments from other promo codes
|
||||||
|
updated = await api.post(`/store/carts/${cart.id}`, {
|
||||||
|
promo_codes: [],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(updated.status).toEqual(200)
|
||||||
|
expect(updated.data.cart).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: cart.id,
|
||||||
|
items: [
|
||||||
|
expect.objectContaining({
|
||||||
|
id: "item-1",
|
||||||
|
adjustments: [],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should update a cart's region, sales channel and customer data", async () => {
|
||||||
|
const region = await regionModuleService.create({
|
||||||
|
name: "US",
|
||||||
|
currency_code: "usd",
|
||||||
|
})
|
||||||
|
|
||||||
|
const salesChannel = await scModuleService.create({
|
||||||
|
name: "Webshop",
|
||||||
|
})
|
||||||
|
|
||||||
|
const cart = await cartModuleService.create({
|
||||||
|
currency_code: "eur",
|
||||||
|
})
|
||||||
|
|
||||||
|
const api = useApi() as any
|
||||||
|
|
||||||
|
let updated = await api.post(`/store/carts/${cart.id}`, {
|
||||||
|
region_id: region.id,
|
||||||
|
email: "tony@stark.com",
|
||||||
|
sales_channel_id: salesChannel.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(updated.status).toEqual(200)
|
||||||
|
expect(updated.data.cart).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: cart.id,
|
||||||
|
currency_code: "usd",
|
||||||
|
region: expect.objectContaining({
|
||||||
|
id: region.id,
|
||||||
|
currency_code: "usd",
|
||||||
|
}),
|
||||||
|
email: "tony@stark.com",
|
||||||
|
customer: expect.objectContaining({
|
||||||
|
email: "tony@stark.com",
|
||||||
|
}),
|
||||||
|
sales_channel_id: salesChannel.id,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
updated = await api.post(`/store/carts/${cart.id}`, {
|
||||||
|
email: null,
|
||||||
|
sales_channel_id: null,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(updated.status).toEqual(200)
|
||||||
|
expect(updated.data.cart).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: cart.id,
|
||||||
|
currency_code: "usd",
|
||||||
|
email: null,
|
||||||
|
customer_id: null,
|
||||||
|
region: expect.objectContaining({
|
||||||
|
id: region.id,
|
||||||
|
currency_code: "usd",
|
||||||
|
}),
|
||||||
|
sales_channel_id: null,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe("GET /store/carts/:id", () => {
|
describe("GET /store/carts/:id", () => {
|
||||||
it("should create and update a cart", async () => {
|
it("should create and update a cart", async () => {
|
||||||
const region = await regionModuleService.create({
|
const region = await regionModuleService.create({
|
||||||
|
|||||||
@@ -14,11 +14,11 @@ export interface CreateCartDTO {
|
|||||||
|
|
||||||
export interface UpdateCartDTO {
|
export interface UpdateCartDTO {
|
||||||
id: string
|
id: string
|
||||||
region_id?: string | null
|
region_id?: string
|
||||||
customer_id?: string | null
|
customer_id?: string | null
|
||||||
sales_channel_id?: string | null
|
sales_channel_id?: string | null
|
||||||
email?: string | null
|
email?: string | null
|
||||||
currency_code?: string | null
|
currency_code?: string
|
||||||
metadata?: Record<string, unknown> | null
|
metadata?: Record<string, unknown> | null
|
||||||
|
|
||||||
adjustments?: (CreateLineItemAdjustmentDTO | UpdateLineItemAdjustmentDTO)[]
|
adjustments?: (CreateLineItemAdjustmentDTO | UpdateLineItemAdjustmentDTO)[]
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ import { validateEmail } from "@medusajs/utils"
|
|||||||
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||||
|
|
||||||
interface StepInput {
|
interface StepInput {
|
||||||
customerId?: string
|
customerId?: string | null
|
||||||
email?: string
|
email?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
interface StepOutput {
|
interface StepOutput {
|
||||||
customer?: CustomerDTO
|
customer?: CustomerDTO | null
|
||||||
email?: string
|
email?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
interface StepCompensateInput {
|
interface StepCompensateInput {
|
||||||
@@ -22,11 +22,27 @@ export const findOrCreateCustomerStepId = "find-or-create-customer"
|
|||||||
export const findOrCreateCustomerStep = createStep(
|
export const findOrCreateCustomerStep = createStep(
|
||||||
findOrCreateCustomerStepId,
|
findOrCreateCustomerStepId,
|
||||||
async (data: StepInput, { container }) => {
|
async (data: StepInput, { container }) => {
|
||||||
|
if (
|
||||||
|
typeof data.customerId === undefined &&
|
||||||
|
typeof data.email === undefined
|
||||||
|
) {
|
||||||
|
return new StepResponse(
|
||||||
|
{
|
||||||
|
customer: undefined,
|
||||||
|
email: undefined,
|
||||||
|
},
|
||||||
|
{ customerWasCreated: false }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const service = container.resolve<ICustomerModuleService>(
|
const service = container.resolve<ICustomerModuleService>(
|
||||||
ModuleRegistrationName.CUSTOMER
|
ModuleRegistrationName.CUSTOMER
|
||||||
)
|
)
|
||||||
|
|
||||||
const customerData: StepOutput = {}
|
const customerData: StepOutput = {
|
||||||
|
customer: null,
|
||||||
|
email: null,
|
||||||
|
}
|
||||||
let customerWasCreated = false
|
let customerWasCreated = false
|
||||||
|
|
||||||
if (data.customerId) {
|
if (data.customerId) {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { MedusaError } from "@medusajs/utils"
|
|||||||
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||||
|
|
||||||
interface StepInput {
|
interface StepInput {
|
||||||
salesChannelId?: string
|
salesChannelId?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export const findSalesChannelStepId = "find-sales-channel"
|
export const findSalesChannelStepId = "find-sales-channel"
|
||||||
@@ -15,6 +15,10 @@ export const findSalesChannelStep = createStep(
|
|||||||
ModuleRegistrationName.SALES_CHANNEL
|
ModuleRegistrationName.SALES_CHANNEL
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (data.salesChannelId === null) {
|
||||||
|
return new StepResponse(null)
|
||||||
|
}
|
||||||
|
|
||||||
let salesChannel: SalesChannelDTO | undefined
|
let salesChannel: SalesChannelDTO | undefined
|
||||||
if (data.salesChannelId) {
|
if (data.salesChannelId) {
|
||||||
salesChannel = await salesChannelService.retrieve(data.salesChannelId)
|
salesChannel = await salesChannelService.retrieve(data.salesChannelId)
|
||||||
|
|||||||
+16
-5
@@ -1,12 +1,15 @@
|
|||||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
import { CartDTO, IPromotionModuleService } from "@medusajs/types"
|
import { CartDTO, IPromotionModuleService } from "@medusajs/types"
|
||||||
import { deduplicate, isString } from "@medusajs/utils"
|
import { PromotionActions, deduplicate, isString } from "@medusajs/utils"
|
||||||
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||||
|
|
||||||
interface StepInput {
|
interface StepInput {
|
||||||
cart: CartDTO
|
cart: CartDTO
|
||||||
promoCodes: string[]
|
promoCodes?: string[]
|
||||||
removePromotions: boolean
|
action:
|
||||||
|
| PromotionActions.ADD
|
||||||
|
| PromotionActions.REMOVE
|
||||||
|
| PromotionActions.REPLACE
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getActionsToComputeFromPromotionsStepId =
|
export const getActionsToComputeFromPromotionsStepId =
|
||||||
@@ -18,7 +21,11 @@ export const getActionsToComputeFromPromotionsStep = createStep(
|
|||||||
ModuleRegistrationName.PROMOTION
|
ModuleRegistrationName.PROMOTION
|
||||||
)
|
)
|
||||||
|
|
||||||
const { removePromotions = false, promoCodes = [], cart } = data
|
const { action = PromotionActions.ADD, promoCodes, cart } = data
|
||||||
|
|
||||||
|
if (!Array.isArray(promoCodes)) {
|
||||||
|
return new StepResponse([])
|
||||||
|
}
|
||||||
|
|
||||||
const appliedItemPromoCodes = cart.items
|
const appliedItemPromoCodes = cart.items
|
||||||
?.map((item) => item.adjustments?.map((adjustment) => adjustment.code))
|
?.map((item) => item.adjustments?.map((adjustment) => adjustment.code))
|
||||||
@@ -38,12 +45,16 @@ export const getActionsToComputeFromPromotionsStep = createStep(
|
|||||||
...appliedShippingMethodPromoCodes,
|
...appliedShippingMethodPromoCodes,
|
||||||
])
|
])
|
||||||
|
|
||||||
if (removePromotions) {
|
if (action === PromotionActions.REMOVE) {
|
||||||
promotionCodesToApply = promotionCodesToApply.filter(
|
promotionCodesToApply = promotionCodesToApply.filter(
|
||||||
(code) => !promoCodes.includes(code)
|
(code) => !promoCodes.includes(code)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (action === PromotionActions.REPLACE) {
|
||||||
|
promotionCodesToApply = promoCodes
|
||||||
|
}
|
||||||
|
|
||||||
const actionsToCompute = await promotionModuleService.computeActions(
|
const actionsToCompute = await promotionModuleService.computeActions(
|
||||||
promotionCodesToApply,
|
promotionCodesToApply,
|
||||||
cart as any
|
cart as any
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { CartDTO, FindConfig, ICartModuleService } from "@medusajs/types"
|
|||||||
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||||
|
|
||||||
interface StepInput {
|
interface StepInput {
|
||||||
cartId: string
|
id: string
|
||||||
config: FindConfig<CartDTO>
|
config: FindConfig<CartDTO>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ export const retrieveCartStep = createStep(
|
|||||||
ModuleRegistrationName.CART
|
ModuleRegistrationName.CART
|
||||||
)
|
)
|
||||||
|
|
||||||
const cart = await cartModuleService.retrieve(data.cartId, data.config)
|
const cart = await cartModuleService.retrieve(data.id, data.config)
|
||||||
|
|
||||||
// TODO: remove this when cart handles totals calculation
|
// TODO: remove this when cart handles totals calculation
|
||||||
cart.items = cart.items?.map((item) => {
|
cart.items = cart.items?.map((item) => {
|
||||||
|
|||||||
@@ -1,61 +1,53 @@
|
|||||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
import {
|
import {
|
||||||
CartDTO,
|
|
||||||
FilterableCartProps,
|
|
||||||
ICartModuleService,
|
ICartModuleService,
|
||||||
UpdateCartDataDTO,
|
UpdateCartDTO,
|
||||||
|
UpdateCartWorkflowInputDTO,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
import { getSelectsAndRelationsFromObjectArray } from "@medusajs/utils"
|
import { getSelectsAndRelationsFromObjectArray } from "@medusajs/utils"
|
||||||
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||||
|
|
||||||
type UpdateCartsStepInput = {
|
|
||||||
selector: FilterableCartProps
|
|
||||||
update: UpdateCartDataDTO
|
|
||||||
}
|
|
||||||
|
|
||||||
export const updateCartsStepId = "update-carts"
|
export const updateCartsStepId = "update-carts"
|
||||||
export const updateCartsStep = createStep(
|
export const updateCartsStep = createStep(
|
||||||
updateCartsStepId,
|
updateCartsStepId,
|
||||||
async (data: UpdateCartsStepInput, { container }) => {
|
async (data: UpdateCartWorkflowInputDTO[], { container }) => {
|
||||||
const service = container.resolve<ICartModuleService>(
|
const cartModule = container.resolve<ICartModuleService>(
|
||||||
ModuleRegistrationName.CART
|
ModuleRegistrationName.CART
|
||||||
)
|
)
|
||||||
|
|
||||||
const { selects, relations } = getSelectsAndRelationsFromObjectArray([
|
const { selects, relations } = getSelectsAndRelationsFromObjectArray(data)
|
||||||
data.update,
|
const cartsBeforeUpdate = await cartModule.list(
|
||||||
])
|
{ id: data.map((d) => d.id) },
|
||||||
|
{ select: selects, relations }
|
||||||
const prevCarts = await service.list(data.selector, {
|
|
||||||
select: selects,
|
|
||||||
relations,
|
|
||||||
})
|
|
||||||
|
|
||||||
const updatedCarts = await service.update(
|
|
||||||
data.selector as Partial<CartDTO>,
|
|
||||||
data.update
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return new StepResponse(updatedCarts, prevCarts)
|
const updatedCart = await cartModule.update(data)
|
||||||
|
|
||||||
|
return new StepResponse(updatedCart, cartsBeforeUpdate)
|
||||||
},
|
},
|
||||||
async (previousCarts, { container }) => {
|
async (cartsBeforeUpdate, { container }) => {
|
||||||
if (!previousCarts?.length) {
|
if (!cartsBeforeUpdate) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const service = container.resolve<ICartModuleService>(
|
const cartModule = container.resolve<ICartModuleService>(
|
||||||
ModuleRegistrationName.CART
|
ModuleRegistrationName.CART
|
||||||
)
|
)
|
||||||
|
|
||||||
const toRestore = previousCarts.map((c) => ({
|
const dataToUpdate: UpdateCartDTO[] = []
|
||||||
id: c.id,
|
|
||||||
region_id: c.region_id,
|
|
||||||
customer_id: c.customer_id,
|
|
||||||
sales_channel_id: c.sales_channel_id,
|
|
||||||
email: c.email,
|
|
||||||
currency_code: c.currency_code,
|
|
||||||
metadata: c.metadata,
|
|
||||||
}))
|
|
||||||
|
|
||||||
await service.update(toRestore)
|
for (const cart of cartsBeforeUpdate) {
|
||||||
|
dataToUpdate.push({
|
||||||
|
id: cart.id,
|
||||||
|
region_id: cart.region_id,
|
||||||
|
customer_id: cart.customer_id,
|
||||||
|
sales_channel_id: cart.sales_channel_id,
|
||||||
|
email: cart.email,
|
||||||
|
currency_code: cart.currency_code,
|
||||||
|
metadata: cart.metadata,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
await cartModule.update(dataToUpdate)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
export * from "./add-to-cart"
|
export * from "./add-to-cart"
|
||||||
export * from "./create-carts"
|
export * from "./create-carts"
|
||||||
|
export * from "./update-cart"
|
||||||
export * from "./update-cart-promotions"
|
export * from "./update-cart-promotions"
|
||||||
export * from "./update-carts"
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { CartDTO } from "@medusajs/types"
|
import { CartDTO } from "@medusajs/types"
|
||||||
|
import { PromotionActions } from "@medusajs/utils"
|
||||||
import {
|
import {
|
||||||
WorkflowData,
|
WorkflowData,
|
||||||
createWorkflow,
|
createWorkflow,
|
||||||
@@ -17,7 +18,10 @@ import {
|
|||||||
type WorkflowInput = {
|
type WorkflowInput = {
|
||||||
promoCodes: string[]
|
promoCodes: string[]
|
||||||
cartId: string
|
cartId: string
|
||||||
removePromotions?: boolean
|
action?:
|
||||||
|
| PromotionActions.ADD
|
||||||
|
| PromotionActions.REMOVE
|
||||||
|
| PromotionActions.REPLACE
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateCartPromotionsWorkflowId = "update-cart-promotions"
|
export const updateCartPromotionsWorkflowId = "update-cart-promotions"
|
||||||
@@ -25,7 +29,7 @@ export const updateCartPromotionsWorkflow = createWorkflow(
|
|||||||
updateCartPromotionsWorkflowId,
|
updateCartPromotionsWorkflowId,
|
||||||
(input: WorkflowData<WorkflowInput>): WorkflowData<CartDTO> => {
|
(input: WorkflowData<WorkflowInput>): WorkflowData<CartDTO> => {
|
||||||
const retrieveCartInput = {
|
const retrieveCartInput = {
|
||||||
cartId: input.cartId,
|
id: input.cartId,
|
||||||
config: {
|
config: {
|
||||||
relations: [
|
relations: [
|
||||||
"items",
|
"items",
|
||||||
@@ -40,7 +44,7 @@ export const updateCartPromotionsWorkflow = createWorkflow(
|
|||||||
const actions = getActionsToComputeFromPromotionsStep({
|
const actions = getActionsToComputeFromPromotionsStep({
|
||||||
cart,
|
cart,
|
||||||
promoCodes: input.promoCodes,
|
promoCodes: input.promoCodes,
|
||||||
removePromotions: input.removePromotions || false,
|
action: input.action || PromotionActions.ADD,
|
||||||
})
|
})
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
import { CartDTO, UpdateCartWorkflowInputDTO } from "@medusajs/types"
|
||||||
|
import { PromotionActions, isPresent } from "@medusajs/utils"
|
||||||
|
import {
|
||||||
|
WorkflowData,
|
||||||
|
createWorkflow,
|
||||||
|
parallelize,
|
||||||
|
transform,
|
||||||
|
} from "@medusajs/workflows-sdk"
|
||||||
|
import {
|
||||||
|
createLineItemAdjustmentsStep,
|
||||||
|
createShippingMethodAdjustmentsStep,
|
||||||
|
findOneOrAnyRegionStep,
|
||||||
|
findOrCreateCustomerStep,
|
||||||
|
findSalesChannelStep,
|
||||||
|
getActionsToComputeFromPromotionsStep,
|
||||||
|
prepareAdjustmentsFromPromotionActionsStep,
|
||||||
|
removeLineItemAdjustmentsStep,
|
||||||
|
removeShippingMethodAdjustmentsStep,
|
||||||
|
retrieveCartStep,
|
||||||
|
updateCartsStep,
|
||||||
|
} from "../steps"
|
||||||
|
|
||||||
|
export const updateCartWorkflowId = "update-cart"
|
||||||
|
export const updateCartWorkflow = createWorkflow(
|
||||||
|
updateCartWorkflowId,
|
||||||
|
(input: WorkflowData<UpdateCartWorkflowInputDTO>): WorkflowData<CartDTO> => {
|
||||||
|
const retrieveCartInput = {
|
||||||
|
id: input.id,
|
||||||
|
config: {
|
||||||
|
relations: [
|
||||||
|
"items",
|
||||||
|
"items.adjustments",
|
||||||
|
"shipping_methods",
|
||||||
|
"shipping_methods.adjustments",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const [salesChannel, region, customerData] = parallelize(
|
||||||
|
findSalesChannelStep({
|
||||||
|
salesChannelId: input.sales_channel_id,
|
||||||
|
}),
|
||||||
|
findOneOrAnyRegionStep({
|
||||||
|
regionId: input.region_id,
|
||||||
|
}),
|
||||||
|
findOrCreateCustomerStep({
|
||||||
|
customerId: input.customer_id,
|
||||||
|
email: input.email,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const cartInput = transform(
|
||||||
|
{ input, region, customerData, salesChannel },
|
||||||
|
(data) => {
|
||||||
|
const { promo_codes, ...updateCartData } = data.input
|
||||||
|
const data_ = { ...updateCartData }
|
||||||
|
|
||||||
|
if (isPresent(updateCartData.region_id)) {
|
||||||
|
data_.currency_code = data.region.currency_code
|
||||||
|
data_.region_id = data.region.id
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
updateCartData.customer_id !== undefined ||
|
||||||
|
updateCartData.email !== undefined
|
||||||
|
) {
|
||||||
|
data_.customer_id = data.customerData.customer?.id || null
|
||||||
|
data_.email =
|
||||||
|
data.input?.email ?? (data.customerData.customer?.email || null)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateCartData.sales_channel_id !== undefined) {
|
||||||
|
data_.sales_channel_id = data.salesChannel?.id || null
|
||||||
|
}
|
||||||
|
|
||||||
|
return data_
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
updateCartsStep([cartInput])
|
||||||
|
|
||||||
|
const cart = retrieveCartStep(retrieveCartInput)
|
||||||
|
const actions = getActionsToComputeFromPromotionsStep({
|
||||||
|
cart,
|
||||||
|
promoCodes: input.promo_codes,
|
||||||
|
action: PromotionActions.REPLACE,
|
||||||
|
})
|
||||||
|
|
||||||
|
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",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import {
|
|
||||||
CartDTO,
|
|
||||||
FilterableCartProps,
|
|
||||||
UpdateCartDataDTO,
|
|
||||||
} from "@medusajs/types"
|
|
||||||
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
|
|
||||||
import { updateCartsStep } from "../steps/update-carts"
|
|
||||||
|
|
||||||
type WorkflowInput = {
|
|
||||||
selector: FilterableCartProps
|
|
||||||
update: UpdateCartDataDTO
|
|
||||||
}
|
|
||||||
|
|
||||||
export const updateCartsWorkflowId = "update-carts"
|
|
||||||
export const updateCartsWorkflow = createWorkflow(
|
|
||||||
updateCartsWorkflowId,
|
|
||||||
(input: WorkflowData<WorkflowInput>): WorkflowData<CartDTO[]> => {
|
|
||||||
return updateCartsStep(input)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { updateCartPromotionsWorkflow } from "@medusajs/core-flows"
|
import { updateCartPromotionsWorkflow } from "@medusajs/core-flows"
|
||||||
|
import { PromotionActions } from "@medusajs/utils"
|
||||||
import { MedusaRequest, MedusaResponse } from "../../../../../types/routing"
|
import { MedusaRequest, MedusaResponse } from "../../../../../types/routing"
|
||||||
import { StorePostCartsCartPromotionsReq } from "../../validators"
|
import { StorePostCartsCartPromotionsReq } from "../../validators"
|
||||||
|
|
||||||
@@ -10,6 +11,7 @@ export const POST = async (req: MedusaRequest, res: MedusaResponse) => {
|
|||||||
input: {
|
input: {
|
||||||
promoCodes: payload.promo_codes,
|
promoCodes: payload.promo_codes,
|
||||||
cartId: req.params.id,
|
cartId: req.params.id,
|
||||||
|
action: PromotionActions.ADD,
|
||||||
},
|
},
|
||||||
throwOnError: false,
|
throwOnError: false,
|
||||||
})
|
})
|
||||||
@@ -29,7 +31,7 @@ export const DELETE = async (req: MedusaRequest, res: MedusaResponse) => {
|
|||||||
input: {
|
input: {
|
||||||
promoCodes: payload.promo_codes,
|
promoCodes: payload.promo_codes,
|
||||||
cartId: req.params.id,
|
cartId: req.params.id,
|
||||||
removePromotions: true,
|
action: PromotionActions.REMOVE,
|
||||||
},
|
},
|
||||||
throwOnError: false,
|
throwOnError: false,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { MedusaRequest, MedusaResponse } from "../../../../types/routing"
|
import { updateCartWorkflow } from "@medusajs/core-flows"
|
||||||
|
|
||||||
import { UpdateCartDataDTO } from "@medusajs/types"
|
import { UpdateCartDataDTO } from "@medusajs/types"
|
||||||
import { defaultStoreCartFields } from "../query-config"
|
|
||||||
import { remoteQueryObjectFromString } from "@medusajs/utils"
|
import { remoteQueryObjectFromString } from "@medusajs/utils"
|
||||||
import { updateCartsWorkflow } from "@medusajs/core-flows"
|
|
||||||
|
import { MedusaRequest, MedusaResponse } from "../../../../types/routing"
|
||||||
|
import { defaultStoreCartFields } from "../query-config"
|
||||||
|
|
||||||
export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
|
export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
|
||||||
const remoteQuery = req.scope.resolve("remoteQuery")
|
const remoteQuery = req.scope.resolve("remoteQuery")
|
||||||
@@ -24,15 +24,13 @@ export const POST = async (
|
|||||||
req: MedusaRequest<UpdateCartDataDTO>,
|
req: MedusaRequest<UpdateCartDataDTO>,
|
||||||
res: MedusaResponse
|
res: MedusaResponse
|
||||||
) => {
|
) => {
|
||||||
const updateCartWorkflow = updateCartsWorkflow(req.scope)
|
const workflow = updateCartWorkflow(req.scope)
|
||||||
|
|
||||||
const workflowInput = {
|
const { errors } = await workflow.run({
|
||||||
selector: { id: req.params.id },
|
input: {
|
||||||
update: req.validatedBody,
|
...(req.validatedBody as UpdateCartDataDTO),
|
||||||
}
|
id: req.params.id,
|
||||||
|
},
|
||||||
const { result, errors } = await updateCartWorkflow.run({
|
|
||||||
input: workflowInput,
|
|
||||||
throwOnError: false,
|
throwOnError: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -40,5 +38,16 @@ export const POST = async (
|
|||||||
throw errors[0].error
|
throw errors[0].error
|
||||||
}
|
}
|
||||||
|
|
||||||
res.status(200).json({ cart: result[0] })
|
const remoteQuery = req.scope.resolve("remoteQuery")
|
||||||
|
|
||||||
|
const query = remoteQueryObjectFromString({
|
||||||
|
entryPoint: "cart",
|
||||||
|
fields: defaultStoreCartFields,
|
||||||
|
})
|
||||||
|
|
||||||
|
const [updatedCart] = await remoteQuery(query, {
|
||||||
|
cart: { id: req.params.id },
|
||||||
|
})
|
||||||
|
|
||||||
|
res.status(200).json({ cart: updatedCart })
|
||||||
}
|
}
|
||||||
@@ -10,8 +10,14 @@ export const defaultStoreCartFields = [
|
|||||||
"items.title",
|
"items.title",
|
||||||
"items.quantity",
|
"items.quantity",
|
||||||
"items.unit_price",
|
"items.unit_price",
|
||||||
|
"items.adjustments.id",
|
||||||
|
"items.adjustments.code",
|
||||||
|
"items.adjustments.amount",
|
||||||
"customer.id",
|
"customer.id",
|
||||||
"customer.email",
|
"customer.email",
|
||||||
|
"shipping_methods.adjustments.id",
|
||||||
|
"shipping_methods.adjustments.code",
|
||||||
|
"shipping_methods.adjustments.amount",
|
||||||
"shipping_address.id",
|
"shipping_address.id",
|
||||||
"shipping_address.first_name",
|
"shipping_address.first_name",
|
||||||
"shipping_address.last_name",
|
"shipping_address.last_name",
|
||||||
@@ -40,20 +46,24 @@ export const defaultStoreCartFields = [
|
|||||||
|
|
||||||
export const defaultStoreCartRelations = [
|
export const defaultStoreCartRelations = [
|
||||||
"items",
|
"items",
|
||||||
|
"items.adjustments",
|
||||||
"region",
|
"region",
|
||||||
"customer",
|
"customer",
|
||||||
"shipping_address",
|
"shipping_address",
|
||||||
"billing_address",
|
"billing_address",
|
||||||
"shipping_methods",
|
"shipping_methods",
|
||||||
|
"shipping_methods.adjustments",
|
||||||
]
|
]
|
||||||
|
|
||||||
export const allowedRelations = [
|
export const allowedRelations = [
|
||||||
"items",
|
"items",
|
||||||
|
"items.adjustments",
|
||||||
"region",
|
"region",
|
||||||
"customer",
|
"customer",
|
||||||
"shipping_address",
|
"shipping_address",
|
||||||
"billing_address",
|
"billing_address",
|
||||||
"shipping_methods",
|
"shipping_methods",
|
||||||
|
"shipping_methods.adjustments",
|
||||||
"sales_channel",
|
"sales_channel",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -81,14 +81,19 @@ export class StorePostCartsCartReq {
|
|||||||
@IsType([AddressPayload, String])
|
@IsType([AddressPayload, String])
|
||||||
shipping_address?: AddressPayload | string
|
shipping_address?: AddressPayload | string
|
||||||
|
|
||||||
@IsEmail()
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
sales_channel_id?: string
|
sales_channel_id?: string
|
||||||
|
|
||||||
@IsObject()
|
@IsObject()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
metadata?: Record<string, unknown>
|
metadata?: Record<string, unknown>
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsArray()
|
||||||
|
@Type(() => String)
|
||||||
|
promo_codes?: string[]
|
||||||
|
|
||||||
// @IsOptional()
|
// @IsOptional()
|
||||||
// @IsArray()
|
// @IsArray()
|
||||||
// @ValidateNested({ each: true })
|
// @ValidateNested({ each: true })
|
||||||
|
|||||||
@@ -41,12 +41,12 @@ export interface CreateCartDTO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateCartDataDTO {
|
export interface UpdateCartDataDTO {
|
||||||
region_id?: string | null
|
region_id?: string
|
||||||
customer_id?: string | null
|
customer_id?: string | null
|
||||||
sales_channel_id?: string | null
|
sales_channel_id?: string | null
|
||||||
|
|
||||||
email?: string | null
|
email?: string | null
|
||||||
currency_code?: string | null
|
currency_code?: string
|
||||||
|
|
||||||
shipping_address_id?: string | null
|
shipping_address_id?: string | null
|
||||||
billing_address_id?: string | null
|
billing_address_id?: string | null
|
||||||
@@ -58,7 +58,7 @@ export interface UpdateCartDataDTO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateCartDTO extends UpdateCartDataDTO {
|
export interface UpdateCartDTO extends UpdateCartDataDTO {
|
||||||
id?: string
|
id: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/** CART END */
|
/** CART END */
|
||||||
@@ -176,7 +176,7 @@ export interface UpdateLineItemWithSelectorDTO {
|
|||||||
|
|
||||||
export interface UpdateCartWithSelectorDTO {
|
export interface UpdateCartWithSelectorDTO {
|
||||||
selector: Partial<CartDTO>
|
selector: Partial<CartDTO>
|
||||||
data: UpdateCartDTO
|
data: UpdateCartDataDTO
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateLineItemDTO
|
export interface UpdateLineItemDTO
|
||||||
|
|||||||
@@ -65,3 +65,14 @@ export interface AddToCartWorkflowInputDTO {
|
|||||||
items: CreateCartCreateLineItemDTO[]
|
items: CreateCartCreateLineItemDTO[]
|
||||||
cart: CartDTO
|
cart: CartDTO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UpdateCartWorkflowInputDTO {
|
||||||
|
id: string
|
||||||
|
promo_codes?: string[]
|
||||||
|
region_id?: string
|
||||||
|
customer_id?: string | null
|
||||||
|
sales_channel_id?: string | null
|
||||||
|
email?: string | null
|
||||||
|
currency_code?: string
|
||||||
|
metadata?: Record<string, unknown> | null
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
export * from "./address"
|
export * from "./address"
|
||||||
|
export * from "./api-key"
|
||||||
export * from "./auth"
|
export * from "./auth"
|
||||||
export * from "./bundles"
|
export * from "./bundles"
|
||||||
export * from "./cache"
|
export * from "./cache"
|
||||||
@@ -27,10 +28,9 @@ export * from "./sales-channel"
|
|||||||
export * from "./search"
|
export * from "./search"
|
||||||
export * from "./shared-context"
|
export * from "./shared-context"
|
||||||
export * from "./stock-location"
|
export * from "./stock-location"
|
||||||
|
export * from "./store"
|
||||||
export * from "./tax"
|
export * from "./tax"
|
||||||
export * from "./totals"
|
export * from "./totals"
|
||||||
export * from "./transaction-base"
|
export * from "./transaction-base"
|
||||||
export * from "./user"
|
export * from "./user"
|
||||||
export * from "./workflow"
|
export * from "./workflow"
|
||||||
export * from "./api-key"
|
|
||||||
export * from "./store"
|
|
||||||
@@ -41,3 +41,9 @@ export enum ComputedActions {
|
|||||||
REMOVE_SHIPPING_METHOD_ADJUSTMENT = "removeShippingMethodAdjustment",
|
REMOVE_SHIPPING_METHOD_ADJUSTMENT = "removeShippingMethodAdjustment",
|
||||||
CAMPAIGN_BUDGET_EXCEEDED = "campaignBudgetExceeded",
|
CAMPAIGN_BUDGET_EXCEEDED = "campaignBudgetExceeded",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum PromotionActions {
|
||||||
|
ADD = "add",
|
||||||
|
REMOVE = "remove",
|
||||||
|
REPLACE = "replace",
|
||||||
|
}
|
||||||
Reference in new issue
Block a user