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:
Riqwan Thamir
2024-02-27 12:09:30 +00:00
committed by GitHub
parent 608c10383a
commit f5c2256286
20 changed files with 435 additions and 96 deletions
@@ -4,13 +4,13 @@ import { validateEmail } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
interface StepInput {
customerId?: string
email?: string
customerId?: string | null
email?: string | null
}
interface StepOutput {
customer?: CustomerDTO
email?: string
customer?: CustomerDTO | null
email?: string | null
}
interface StepCompensateInput {
@@ -22,11 +22,27 @@ export const findOrCreateCustomerStepId = "find-or-create-customer"
export const findOrCreateCustomerStep = createStep(
findOrCreateCustomerStepId,
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>(
ModuleRegistrationName.CUSTOMER
)
const customerData: StepOutput = {}
const customerData: StepOutput = {
customer: null,
email: null,
}
let customerWasCreated = false
if (data.customerId) {
@@ -4,7 +4,7 @@ import { MedusaError } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
interface StepInput {
salesChannelId?: string
salesChannelId?: string | null
}
export const findSalesChannelStepId = "find-sales-channel"
@@ -15,6 +15,10 @@ export const findSalesChannelStep = createStep(
ModuleRegistrationName.SALES_CHANNEL
)
if (data.salesChannelId === null) {
return new StepResponse(null)
}
let salesChannel: SalesChannelDTO | undefined
if (data.salesChannelId) {
salesChannel = await salesChannelService.retrieve(data.salesChannelId)
@@ -1,12 +1,15 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
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"
interface StepInput {
cart: CartDTO
promoCodes: string[]
removePromotions: boolean
promoCodes?: string[]
action:
| PromotionActions.ADD
| PromotionActions.REMOVE
| PromotionActions.REPLACE
}
export const getActionsToComputeFromPromotionsStepId =
@@ -18,7 +21,11 @@ export const getActionsToComputeFromPromotionsStep = createStep(
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
?.map((item) => item.adjustments?.map((adjustment) => adjustment.code))
@@ -38,12 +45,16 @@ export const getActionsToComputeFromPromotionsStep = createStep(
...appliedShippingMethodPromoCodes,
])
if (removePromotions) {
if (action === PromotionActions.REMOVE) {
promotionCodesToApply = promotionCodesToApply.filter(
(code) => !promoCodes.includes(code)
)
}
if (action === PromotionActions.REPLACE) {
promotionCodesToApply = promoCodes
}
const actionsToCompute = await promotionModuleService.computeActions(
promotionCodesToApply,
cart as any
@@ -3,7 +3,7 @@ import { CartDTO, FindConfig, ICartModuleService } from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
interface StepInput {
cartId: string
id: string
config: FindConfig<CartDTO>
}
@@ -15,7 +15,7 @@ export const retrieveCartStep = createStep(
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
cart.items = cart.items?.map((item) => {
@@ -1,61 +1,53 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import {
CartDTO,
FilterableCartProps,
ICartModuleService,
UpdateCartDataDTO,
UpdateCartDTO,
UpdateCartWorkflowInputDTO,
} from "@medusajs/types"
import { getSelectsAndRelationsFromObjectArray } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
type UpdateCartsStepInput = {
selector: FilterableCartProps
update: UpdateCartDataDTO
}
export const updateCartsStepId = "update-carts"
export const updateCartsStep = createStep(
updateCartsStepId,
async (data: UpdateCartsStepInput, { container }) => {
const service = container.resolve<ICartModuleService>(
async (data: UpdateCartWorkflowInputDTO[], { container }) => {
const cartModule = container.resolve<ICartModuleService>(
ModuleRegistrationName.CART
)
const { selects, relations } = getSelectsAndRelationsFromObjectArray([
data.update,
])
const prevCarts = await service.list(data.selector, {
select: selects,
relations,
})
const updatedCarts = await service.update(
data.selector as Partial<CartDTO>,
data.update
const { selects, relations } = getSelectsAndRelationsFromObjectArray(data)
const cartsBeforeUpdate = await cartModule.list(
{ id: data.map((d) => d.id) },
{ select: selects, relations }
)
return new StepResponse(updatedCarts, prevCarts)
const updatedCart = await cartModule.update(data)
return new StepResponse(updatedCart, cartsBeforeUpdate)
},
async (previousCarts, { container }) => {
if (!previousCarts?.length) {
async (cartsBeforeUpdate, { container }) => {
if (!cartsBeforeUpdate) {
return
}
const service = container.resolve<ICartModuleService>(
const cartModule = container.resolve<ICartModuleService>(
ModuleRegistrationName.CART
)
const toRestore = previousCarts.map((c) => ({
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,
}))
const dataToUpdate: UpdateCartDTO[] = []
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 "./create-carts"
export * from "./update-cart"
export * from "./update-cart-promotions"
export * from "./update-carts"
@@ -1,4 +1,5 @@
import { CartDTO } from "@medusajs/types"
import { PromotionActions } from "@medusajs/utils"
import {
WorkflowData,
createWorkflow,
@@ -17,7 +18,10 @@ import {
type WorkflowInput = {
promoCodes: string[]
cartId: string
removePromotions?: boolean
action?:
| PromotionActions.ADD
| PromotionActions.REMOVE
| PromotionActions.REPLACE
}
export const updateCartPromotionsWorkflowId = "update-cart-promotions"
@@ -25,7 +29,7 @@ export const updateCartPromotionsWorkflow = createWorkflow(
updateCartPromotionsWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<CartDTO> => {
const retrieveCartInput = {
cartId: input.cartId,
id: input.cartId,
config: {
relations: [
"items",
@@ -40,7 +44,7 @@ export const updateCartPromotionsWorkflow = createWorkflow(
const actions = getActionsToComputeFromPromotionsStep({
cart,
promoCodes: input.promoCodes,
removePromotions: input.removePromotions || false,
action: input.action || PromotionActions.ADD,
})
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)
}
)