fix(core-flows, types): reservation of shared inventory item (#11403)

**What**
- if a cart contains variants that share inventory items, reservation of the item would fail also causing complete cart to fail
- include `completed_at` when compensating cart update
- account for multiple reservations of the same item when creating the locking key

---

CLOSES SUP-587
This commit is contained in:
Frane Polić
2025-02-13 09:03:53 +01:00
committed by GitHub
parent 90815793df
commit bbef0da5dd
7 changed files with 272 additions and 21 deletions

View File

@@ -2,7 +2,6 @@ import { MathBN, Modules } from "@medusajs/framework/utils"
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
import { BigNumberInput } from "@medusajs/types"
/**
* The details of the items and their quantity to reserve.
*/
@@ -45,7 +44,7 @@ export const reserveInventoryStepId = "reserve-inventory-step"
/**
* This step reserves the quantity of line items from the associated
* variant's inventory.
*
*
* @example
* const data = reserveInventoryStep({
* "items": [{
@@ -80,7 +79,9 @@ export const reserveInventoryStep = createStep(
}
})
const reservations = await locking.execute(inventoryItemIds, async () => {
const lockingKeys = Array.from(new Set(inventoryItemIds))
const reservations = await locking.execute(lockingKeys, async () => {
return await inventoryService.createReservationItems(items)
})
@@ -98,7 +99,9 @@ export const reserveInventoryStep = createStep(
const locking = container.resolve(Modules.LOCKING)
const inventoryItemIds = data.inventoryItemIds
await locking.execute(inventoryItemIds, async () => {
const lockingKeys = Array.from(new Set(inventoryItemIds))
await locking.execute(lockingKeys, async () => {
await inventoryService.deleteReservationItems(data.reservations)
})

View File

@@ -17,7 +17,7 @@ export type UpdateCartsStepInput = UpdateCartWorkflowInputDTO[]
export const updateCartsStepId = "update-carts"
/**
* This step updates a cart.
*
*
* @example
* const data = updateCartsStep([{
* id: "cart_123",
@@ -57,6 +57,7 @@ export const updateCartsStep = createStep(
email: cart.email,
currency_code: cart.currency_code,
metadata: cart.metadata,
completed_at: cart.completed_at,
})
}

View File

@@ -73,8 +73,10 @@ export const prepareConfirmInventoryInput = (data: {
if (inventory_items) {
const inventoryItemId = inventory_items.inventory_item_id
if (!productVariantInventoryItems.has(inventoryItemId)) {
productVariantInventoryItems.set(inventoryItemId, {
const mapKey = `${inventoryItemId}-${inventory_items.variant_id}`
if (!productVariantInventoryItems.has(mapKey)) {
productVariantInventoryItems.set(mapKey, {
variant_id: inventory_items.variant_id,
inventory_item_id: inventoryItemId,
required_quantity: inventory_items.required_quantity,

View File

@@ -6,14 +6,15 @@ import { MathBN, Modules } from "@medusajs/framework/utils"
/**
* The data to adjust the inventory levels.
*/
export type AdjustInventoryLevelsStepInput = InventoryTypes.BulkAdjustInventoryLevelInput[]
export type AdjustInventoryLevelsStepInput =
InventoryTypes.BulkAdjustInventoryLevelInput[]
export const adjustInventoryLevelsStepId = "adjust-inventory-levels-step"
/**
* This step adjusts the stocked quantity of one or more inventory levels. You can
* This step adjusts the stocked quantity of one or more inventory levels. You can
* pass a positive value in `adjustment` to add to the stocked quantity, or a negative value to
* subtract from the stocked quantity.
*
*
* @example
* const data = adjustInventoryLevelsStep([
* {
@@ -25,16 +26,15 @@ export const adjustInventoryLevelsStepId = "adjust-inventory-levels-step"
*/
export const adjustInventoryLevelsStep = createStep(
adjustInventoryLevelsStepId,
async (
input: AdjustInventoryLevelsStepInput,
{ container }
) => {
async (input: AdjustInventoryLevelsStepInput, { container }) => {
const inventoryService = container.resolve(Modules.INVENTORY)
const locking = container.resolve(Modules.LOCKING)
const inventoryItemIds = input.map((item) => item.inventory_item_id)
const lockingKeys = Array.from(new Set(inventoryItemIds))
const adjustedLevels: InventoryTypes.InventoryLevelDTO[] =
await locking.execute(inventoryItemIds, async () => {
await locking.execute(lockingKeys, async () => {
return await inventoryService.adjustInventory(
input.map((item) => {
return {
@@ -67,13 +67,15 @@ export const adjustInventoryLevelsStep = createStep(
(item) => item.inventory_item_id
)
const lockingKeys = Array.from(new Set(inventoryItemIds))
/**
* @todo
* The method "adjustInventory" was broken, it was receiving the
* "inventoryItemId" and "locationId" as snake case, whereas
* the expected object needed these properties as camelCase
*/
await locking.execute(inventoryItemIds, async () => {
await locking.execute(lockingKeys, async () => {
await inventoryService.adjustInventory(
adjustedLevels.map((level) => {
return {

View File

@@ -6,12 +6,13 @@ import { Modules } from "@medusajs/framework/utils"
/**
* The data to create reservation items.
*/
export type CreateReservationsStepInput = InventoryTypes.CreateReservationItemInput[]
export type CreateReservationsStepInput =
InventoryTypes.CreateReservationItemInput[]
export const createReservationsStepId = "create-reservations-step"
/**
* This step creates one or more reservations.
*
*
* @example
* const data = createReservationsStep([
* {
@@ -29,7 +30,9 @@ export const createReservationsStep = createStep(
const inventoryItemIds = data.map((item) => item.inventory_item_id)
const created = await locking.execute(inventoryItemIds, async () => {
const lockingKeys = Array.from(new Set(inventoryItemIds))
const created = await locking.execute(lockingKeys, async () => {
return await service.createReservationItems(data)
})
@@ -47,7 +50,9 @@ export const createReservationsStep = createStep(
const locking = container.resolve(Modules.LOCKING)
const inventoryItemIds = data.inventoryItemIds
await locking.execute(inventoryItemIds, async () => {
const lockingKeys = Array.from(new Set(inventoryItemIds))
await locking.execute(lockingKeys, async () => {
await service.deleteReservationItems(data.reservations)
})
}