feat(core-flows,types,utils,medusa): Update existing line items when adding the same variant to cart (#7470)
* feat(core-flows,types,utils,medusa): Update existing line items when adding the same variant to cart * chore: split steps into 2 for add-to-cart * chore: split steps into 2 for add-to-cart * chore: iterate safely * chore: parallelize upsert
This commit is contained in:
@@ -1,31 +0,0 @@
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { CreateLineItemForCartDTO, ICartModuleService } from "@medusajs/types"
|
||||
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||
|
||||
interface StepInput {
|
||||
items: CreateLineItemForCartDTO[]
|
||||
}
|
||||
|
||||
export const addToCartStepId = "add-to-cart-step"
|
||||
export const addToCartStep = createStep(
|
||||
addToCartStepId,
|
||||
async (data: StepInput, { container }) => {
|
||||
const cartService = container.resolve<ICartModuleService>(
|
||||
ModuleRegistrationName.CART
|
||||
)
|
||||
|
||||
const items = await cartService.addLineItems(data.items)
|
||||
|
||||
return new StepResponse(items, items)
|
||||
},
|
||||
async (createdLineItems, { container }) => {
|
||||
const cartService: ICartModuleService = container.resolve(
|
||||
ModuleRegistrationName.CART
|
||||
)
|
||||
if (!createdLineItems?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
await cartService.deleteLineItems(createdLineItems.map((c) => c.id))
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,35 @@
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { CreateLineItemForCartDTO, ICartModuleService } from "@medusajs/types"
|
||||
import { createStep, StepResponse } from "@medusajs/workflows-sdk"
|
||||
|
||||
interface StepInput {
|
||||
id: string
|
||||
items: CreateLineItemForCartDTO[]
|
||||
}
|
||||
|
||||
export const createLineItemsStepId = "create-line-items-step"
|
||||
export const createLineItemsStep = createStep(
|
||||
createLineItemsStepId,
|
||||
async (data: StepInput, { container }) => {
|
||||
const cartModule = container.resolve<ICartModuleService>(
|
||||
ModuleRegistrationName.CART
|
||||
)
|
||||
|
||||
const createdItems = data.items.length
|
||||
? await cartModule.addLineItems(data.items)
|
||||
: []
|
||||
|
||||
return new StepResponse(createdItems, createdItems)
|
||||
},
|
||||
async (createdItems, { container }) => {
|
||||
if (!createdItems?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const cartModule: ICartModuleService = container.resolve(
|
||||
ModuleRegistrationName.CART
|
||||
)
|
||||
|
||||
await cartModule.deleteLineItems(createdItems.map((c) => c.id))
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,59 @@
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import {
|
||||
CartLineItemDTO,
|
||||
CreateLineItemForCartDTO,
|
||||
ICartModuleService,
|
||||
UpdateLineItemWithSelectorDTO,
|
||||
} from "@medusajs/types"
|
||||
import { deepEqualObj, isPresent, MathBN } from "@medusajs/utils"
|
||||
import { createStep, StepResponse } from "@medusajs/workflows-sdk"
|
||||
|
||||
interface StepInput {
|
||||
id: string
|
||||
items: CreateLineItemForCartDTO[]
|
||||
}
|
||||
|
||||
export const getLineItemActionsStepId = "get-line-item-actions-step"
|
||||
export const getLineItemActionsStep = createStep(
|
||||
getLineItemActionsStepId,
|
||||
async (data: StepInput, { container }) => {
|
||||
const cartModule = container.resolve<ICartModuleService>(
|
||||
ModuleRegistrationName.CART
|
||||
)
|
||||
|
||||
const existingVariantItems = await cartModule.listLineItems({
|
||||
cart_id: data.id,
|
||||
variant_id: data.items.map((d) => d.variant_id!),
|
||||
})
|
||||
|
||||
const variantItemMap = new Map<string, CartLineItemDTO>(
|
||||
existingVariantItems.map((item) => [item.variant_id!, item])
|
||||
)
|
||||
|
||||
const itemsToCreate: CreateLineItemForCartDTO[] = []
|
||||
const itemsToUpdate: UpdateLineItemWithSelectorDTO[] = []
|
||||
|
||||
for (const item of data.items) {
|
||||
const existingItem = variantItemMap.get(item.variant_id!)
|
||||
const metadataMatches =
|
||||
(!isPresent(existingItem?.metadata) && !isPresent(item.metadata)) ||
|
||||
deepEqualObj(existingItem?.metadata, item.metadata)
|
||||
|
||||
if (existingItem && metadataMatches) {
|
||||
const quantity = MathBN.sum(
|
||||
existingItem.quantity as number,
|
||||
item.quantity || 1
|
||||
).toNumber()
|
||||
|
||||
itemsToUpdate.push({
|
||||
selector: { id: existingItem.id },
|
||||
data: { id: existingItem.id, quantity: quantity },
|
||||
})
|
||||
} else {
|
||||
itemsToCreate.push(item)
|
||||
}
|
||||
}
|
||||
|
||||
return new StepResponse({ itemsToCreate, itemsToUpdate }, null)
|
||||
}
|
||||
)
|
||||
@@ -1,8 +1,8 @@
|
||||
export * from "./add-shipping-method-to-cart"
|
||||
export * from "./add-to-cart"
|
||||
export * from "./confirm-inventory"
|
||||
export * from "./create-carts"
|
||||
export * from "./create-line-item-adjustments"
|
||||
export * from "./create-line-items"
|
||||
export * from "./create-order-from-cart"
|
||||
export * from "./create-shipping-method-adjustments"
|
||||
export * from "./find-one-or-any-region"
|
||||
@@ -10,6 +10,7 @@ export * from "./find-or-create-customer"
|
||||
export * from "./find-sales-channel"
|
||||
export * from "./get-actions-to-compute-from-promotions"
|
||||
export * from "./get-item-tax-lines"
|
||||
export * from "./get-line-item-actions"
|
||||
export * from "./get-promotion-codes-to-apply"
|
||||
export * from "./get-variant-price-sets"
|
||||
export * from "./get-variants"
|
||||
@@ -22,6 +23,7 @@ export * from "./retrieve-cart-with-links"
|
||||
export * from "./set-tax-lines-for-items"
|
||||
export * from "./update-cart-promotions"
|
||||
export * from "./update-carts"
|
||||
export * from "./update-line-items"
|
||||
export * from "./validate-cart-payments"
|
||||
export * from "./validate-cart-shipping-options"
|
||||
export * from "./validate-variant-prices"
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import {
|
||||
ICartModuleService,
|
||||
UpdateLineItemWithSelectorDTO,
|
||||
} from "@medusajs/types"
|
||||
import { getSelectsAndRelationsFromObjectArray } from "@medusajs/utils"
|
||||
import { createStep, StepResponse } from "@medusajs/workflows-sdk"
|
||||
|
||||
interface StepInput {
|
||||
id: string
|
||||
items: UpdateLineItemWithSelectorDTO[]
|
||||
}
|
||||
|
||||
export const updateLineItemsStepId = "update-line-items-step"
|
||||
export const updateLineItemsStep = createStep(
|
||||
updateLineItemsStepId,
|
||||
async (input: StepInput, { container }) => {
|
||||
const { id, items = [] } = input
|
||||
|
||||
if (!items?.length) {
|
||||
return new StepResponse([], [])
|
||||
}
|
||||
|
||||
const cartModule = container.resolve<ICartModuleService>(
|
||||
ModuleRegistrationName.CART
|
||||
)
|
||||
|
||||
const { selects, relations } = getSelectsAndRelationsFromObjectArray(
|
||||
items.map((item) => item.data)
|
||||
)
|
||||
|
||||
const itemsBeforeUpdate = await cartModule.listLineItems(
|
||||
{ id: items.map((d) => d.selector.id!) },
|
||||
{ select: selects, relations }
|
||||
)
|
||||
|
||||
const updatedItems = items.length
|
||||
? await cartModule.updateLineItems(items)
|
||||
: []
|
||||
|
||||
return new StepResponse(updatedItems, itemsBeforeUpdate)
|
||||
},
|
||||
async (itemsBeforeUpdate, { container }) => {
|
||||
if (!itemsBeforeUpdate?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const cartModule: ICartModuleService = container.resolve(
|
||||
ModuleRegistrationName.CART
|
||||
)
|
||||
|
||||
if (itemsBeforeUpdate.length) {
|
||||
const itemsToUpdate: UpdateLineItemWithSelectorDTO[] = []
|
||||
|
||||
for (const item of itemsBeforeUpdate) {
|
||||
const { id, ...data } = item
|
||||
|
||||
itemsToUpdate.push({ selector: { id }, data })
|
||||
}
|
||||
|
||||
await cartModule.updateLineItems(itemsToUpdate)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -5,10 +5,16 @@ import {
|
||||
import {
|
||||
WorkflowData,
|
||||
createWorkflow,
|
||||
parallelize,
|
||||
transform,
|
||||
} from "@medusajs/workflows-sdk"
|
||||
import { useRemoteQueryStep } from "../../../common/steps/use-remote-query"
|
||||
import { addToCartStep, refreshCartShippingMethodsStep } from "../steps"
|
||||
import {
|
||||
createLineItemsStep,
|
||||
getLineItemActionsStep,
|
||||
refreshCartShippingMethodsStep,
|
||||
updateLineItemsStep,
|
||||
} from "../steps"
|
||||
import { refreshCartPromotionsStep } from "../steps/refresh-cart-promotions"
|
||||
import { updateTaxLinesStep } from "../steps/update-tax-lines"
|
||||
import { validateVariantPricesStep } from "../steps/validate-variant-prices"
|
||||
@@ -78,7 +84,25 @@ export const addToCartWorkflow = createWorkflow(
|
||||
return items
|
||||
})
|
||||
|
||||
const items = addToCartStep({ items: lineItems })
|
||||
const { itemsToCreate = [], itemsToUpdate = [] } = getLineItemActionsStep({
|
||||
id: input.cart.id,
|
||||
items: lineItems,
|
||||
})
|
||||
|
||||
const [createdItems, updatedItems] = parallelize(
|
||||
createLineItemsStep({
|
||||
id: input.cart.id,
|
||||
items: itemsToCreate,
|
||||
}),
|
||||
updateLineItemsStep({
|
||||
id: input.cart.id,
|
||||
items: itemsToUpdate,
|
||||
})
|
||||
)
|
||||
|
||||
const items = transform({ createdItems, updatedItems }, (data) => {
|
||||
return [...(data.createdItems || []), ...(data.updatedItems || [])]
|
||||
})
|
||||
|
||||
const cart = useRemoteQueryStep({
|
||||
entry_point: "cart",
|
||||
@@ -88,9 +112,7 @@ export const addToCartWorkflow = createWorkflow(
|
||||
}).config({ name: "refetch–cart" })
|
||||
|
||||
refreshCartShippingMethodsStep({ cart })
|
||||
// TODO: since refreshCartShippingMethodsStep potentially removes cart shipping methods, we need the updated cart here
|
||||
// for the following 2 steps as they act upon final cart shape
|
||||
updateTaxLinesStep({ cart_or_cart_id: cart, items })
|
||||
updateTaxLinesStep({ cart_or_cart_id: input.cart.id, items })
|
||||
refreshCartPromotionsStep({ id: input.cart.id })
|
||||
refreshPaymentCollectionForCartStep({ cart_id: input.cart.id })
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
transform,
|
||||
} from "@medusajs/workflows-sdk"
|
||||
import { useRemoteQueryStep } from "../../../common/steps/use-remote-query"
|
||||
import { updateLineItemsStep } from "../../line-item/steps"
|
||||
import { updateLineItemsStepWithSelector } from "../../line-item/steps"
|
||||
import { refreshCartShippingMethodsStep } from "../steps"
|
||||
import { refreshCartPromotionsStep } from "../steps/refresh-cart-promotions"
|
||||
import { validateVariantPricesStep } from "../steps/validate-variant-prices"
|
||||
@@ -77,7 +77,7 @@ export const updateLineItemInCartWorkflow = createWorkflow(
|
||||
}
|
||||
})
|
||||
|
||||
const result = updateLineItemsStep(lineItemUpdate)
|
||||
const result = updateLineItemsStepWithSelector(lineItemUpdate)
|
||||
|
||||
const cart = useRemoteQueryStep({
|
||||
entry_point: "cart",
|
||||
|
||||
@@ -10,9 +10,10 @@ import {
|
||||
} from "@medusajs/utils"
|
||||
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||
|
||||
export const updateLineItemsStepId = "update-line-items"
|
||||
export const updateLineItemsStep = createStep(
|
||||
updateLineItemsStepId,
|
||||
export const updateLineItemsStepWithSelectorId =
|
||||
"update-line-items-with-selector"
|
||||
export const updateLineItemsStepWithSelector = createStep(
|
||||
updateLineItemsStepWithSelectorId,
|
||||
async (input: UpdateLineItemWithSelectorDTO, { container }) => {
|
||||
const service = container.resolve<ICartModuleService>(
|
||||
ModuleRegistrationName.CART
|
||||
|
||||
Reference in New Issue
Block a user