chore(): Improve cart update line items (#11666)

**What**
Currently, we are potentially providing an array of selector/data leading to fetching data sequentially before running on update which will fetch data again in batch and perform the update. Now we can pass the data directly which includes the id already and only perform one bulk fetch + one bulk update.

This pr also include a fix on the inventory validation, currently, only the item to update inventory is being checked, with this pr we also check the inventory for the items that needs to be created
This commit is contained in:
Adrien de Peretti
2025-03-03 11:06:40 +01:00
committed by GitHub
parent 39597b6b52
commit d1efad9bf0
10 changed files with 188 additions and 60 deletions
@@ -2,6 +2,7 @@ import {
CartLineItemDTO,
CreateLineItemForCartDTO,
ICartModuleService,
UpdateLineItemWithoutSelectorDTO,
UpdateLineItemWithSelectorDTO,
} from "@medusajs/framework/types"
import {
@@ -34,7 +35,9 @@ export interface GetLineItemActionsStepOutput {
/**
* The line items to update.
*/
itemsToUpdate: UpdateLineItemWithSelectorDTO[]
itemsToUpdate:
| UpdateLineItemWithSelectorDTO[]
| UpdateLineItemWithoutSelectorDTO[]
}
export const getLineItemActionsStepId = "get-line-item-actions-step"
@@ -63,17 +66,29 @@ export const getLineItemActionsStep = createStep(
const cartModule = container.resolve<ICartModuleService>(Modules.CART)
const variantIds = data.items.map((d) => d.variant_id!)
const existingVariantItems = await cartModule.listLineItems({
cart_id: data.id,
variant_id: variantIds,
})
const existingVariantItems = await cartModule.listLineItems(
{
cart_id: data.id,
variant_id: variantIds,
},
{
select: [
"id",
"metadata",
"variant_id",
"quantity",
"unit_price",
"compare_at_unit_price",
],
}
)
const variantItemMap = new Map<string, CartLineItemDTO>(
existingVariantItems.map((item) => [item.variant_id!, item])
)
const itemsToCreate: CreateLineItemForCartDTO[] = []
const itemsToUpdate: UpdateLineItemWithSelectorDTO[] = []
const itemsToUpdate: UpdateLineItemWithSelectorDTO["data"][] = []
for (const item of data.items) {
const existingItem = variantItemMap.get(item.variant_id!)
@@ -88,15 +103,12 @@ export const getLineItemActionsStep = createStep(
)
itemsToUpdate.push({
selector: { id: existingItem.id },
data: {
id: existingItem.id,
quantity: quantity,
variant_id: item.variant_id!,
unit_price: item.unit_price ?? existingItem.unit_price,
compare_at_unit_price:
item.compare_at_unit_price ?? existingItem.compare_at_unit_price,
},
id: existingItem.id,
quantity: quantity,
variant_id: item.variant_id!,
unit_price: item.unit_price ?? existingItem.unit_price,
compare_at_unit_price:
item.compare_at_unit_price ?? existingItem.compare_at_unit_price,
})
} else {
itemsToCreate.push(item)
@@ -1,5 +1,6 @@
import {
ICartModuleService,
UpdateLineItemWithoutSelectorDTO,
UpdateLineItemWithSelectorDTO,
} from "@medusajs/framework/types"
import {
@@ -19,13 +20,13 @@ export interface UpdateLineItemsStepInput {
/**
* The line items to update.
*/
items: UpdateLineItemWithSelectorDTO[]
items: (UpdateLineItemWithSelectorDTO | UpdateLineItemWithoutSelectorDTO)[]
}
export const updateLineItemsStepId = "update-line-items-step"
/**
* This step updates a cart's line items.
*
*
* @example
* const data = updateLineItemsStep({
* id: "cart_123",
@@ -53,16 +54,18 @@ export const updateLineItemsStep = createStep(
const cartModule = container.resolve<ICartModuleService>(Modules.CART)
const { selects, relations } = getSelectsAndRelationsFromObjectArray(
items.map((item) => item.data)
items.map((item) => ("data" in item ? item.data : item))
)
const itemsBeforeUpdate = await cartModule.listLineItems(
{ id: items.map((d) => d.selector.id!) },
{ id: items.map((d) => ("selector" in d ? d.selector.id! : d.id!)) },
{ select: selects, relations }
)
const updatedItems = items.length
? await cartModule.updateLineItems(items)
? await cartModule.updateLineItems(
items as UpdateLineItemWithoutSelectorDTO[]
)
: []
return new StepResponse(updatedItems, itemsBeforeUpdate)
@@ -51,11 +51,13 @@ export const prepareConfirmInventoryInput = (data: {
const salesChannelId = data.input.sales_channel_id
for (const updateItem of data.input.itemsToUpdate ?? []) {
const updateItem_ = "data" in updateItem ? updateItem.data : updateItem
const item = data.input.items.find(
(item) => item.variant_id === updateItem.data.variant_id
(item) => item.variant_id === updateItem_.variant_id
)
if (item && updateItem.data.quantity) {
item.quantity = updateItem.data.quantity!
if (item && updateItem_.quantity) {
item.quantity = updateItem_.quantity!
}
}
@@ -1,4 +1,7 @@
import { AddToCartWorkflowInputDTO } from "@medusajs/framework/types"
import {
AddToCartWorkflowInputDTO,
ConfirmVariantInventoryWorkflowInputDTO,
} from "@medusajs/framework/types"
import { CartWorkflowEvents, isDefined } from "@medusajs/framework/utils"
import {
createHook,
@@ -141,12 +144,32 @@ export const addToCartWorkflow = createWorkflow(
items: lineItems,
})
const itemsToConfirmInventory = transform(
{ itemsToUpdate, itemsToCreate },
(data) => {
return (data.itemsToUpdate as [])
.concat(data.itemsToCreate as [])
.filter(
(
item:
| {
data: { variant_id: string }
}
| { variant_id?: string }
) =>
isDefined(
"data" in item ? item.data?.variant_id : item.variant_id
)
) as unknown as ConfirmVariantInventoryWorkflowInputDTO["itemsToUpdate"]
}
)
confirmVariantInventoryWorkflow.runAsStep({
input: {
sales_channel_id: cart.sales_channel_id,
variants,
items: input.items,
itemsToUpdate,
itemsToUpdate: itemsToConfirmInventory,
},
})