chore(core-flows): adjust inventory when return is received (#8222)

This commit is contained in:
Carlos R. L. Rodrigues
2024-07-23 13:36:46 -03:00
committed by GitHub
parent f38f6d53b4
commit 7a5349c0ae
20 changed files with 579 additions and 286 deletions
@@ -1,5 +1,6 @@
import { IInventoryService } from "@medusajs/types"
import {
MathBN,
MedusaError,
ModuleRegistrationName,
promiseAll,
@@ -30,7 +31,7 @@ export const confirmInventoryStep = createStep(
return true
}
const itemQuantity = item.required_quantity * item.quantity
const itemQuantity = MathBN.mult(item.quantity, item.required_quantity)
return await inventoryService.confirmInventory(
item.inventory_item_id,
@@ -51,7 +51,11 @@ export const getLineItemActionsStep = createStep(
itemsToUpdate.push({
selector: { id: existingItem.id },
data: { id: existingItem.id, quantity: quantity },
data: {
id: existingItem.id,
quantity: quantity,
variant_id: item.variant_id!,
},
})
} else {
itemsToCreate.push(item)
@@ -26,9 +26,6 @@ import { prepareLineItemData } from "../utils/prepare-line-item-data"
import { confirmVariantInventoryWorkflow } from "./confirm-variant-inventory"
import { refreshPaymentCollectionForCartStep } from "./refresh-payment-collection"
// TODO: The AddToCartWorkflow are missing the following steps:
// - Refresh/delete shipping methods (fulfillment module)
export const addToCartWorkflowId = "add-to-cart"
export const addToCartWorkflow = createWorkflow(
addToCartWorkflowId,
@@ -60,14 +57,6 @@ export const addToCartWorkflow = createWorkflow(
validateVariantPricesStep({ variants })
confirmVariantInventoryWorkflow.runAsStep({
input: {
sales_channel_id: input.cart.sales_channel_id as string,
variants,
items: input.items,
},
})
const lineItems = transform({ input, variants }, (data) => {
const items = (data.input.items ?? []).map((item) => {
const variant = data.variants.find((v) => v.id === item.variant_id)!
@@ -91,6 +80,15 @@ export const addToCartWorkflow = createWorkflow(
items: lineItems,
})
confirmVariantInventoryWorkflow.runAsStep({
input: {
sales_channel_id: input.cart.sales_channel_id as string,
variants,
items: input.items,
itemsToUpdate,
},
})
const [createdItems, updatedItems] = parallelize(
createLineItemsStep({
id: input.cart.id,
@@ -34,6 +34,15 @@ export const confirmVariantInventoryWorkflow = createWorkflow(
const salesChannelId = data.input.sales_channel_id
for (const updateItem of data.input.itemsToUpdate ?? []) {
const item = data.input.items.find(
(item) => item.variant_id === updateItem.data.variant_id
)
if (item && updateItem.data.quantity) {
item.quantity = updateItem.data.quantity!
}
}
deepFlatMap(
data.input,
"variants.inventory_items.inventory.location_levels.stock_locations.sales_channels",
@@ -89,7 +89,6 @@ function prepareInventoryUpdate({
location_id: string
adjustment: BigNumberInput
}[] = []
for (const item of fulfillment.items) {
// if this is `null` this means that item is from variant that has `manage_inventory` false
if (item.inventory_item_id) {
@@ -35,6 +35,7 @@ export const beginReturnOrderWorkflow = createWorkflow(
const created = createReturnsStep([
{
order_id: input.order_id,
location_id: input.location_id,
metadata: input.metadata,
},
])
@@ -1,17 +1,25 @@
import {
BigNumberInput,
OrderChangeActionDTO,
OrderChangeDTO,
OrderDTO,
ReturnDTO,
} from "@medusajs/types"
import { ChangeActionType, MathBN, OrderChangeStatus } from "@medusajs/utils"
import {
ChangeActionType,
MathBN,
OrderChangeStatus,
deepFlatMap,
} from "@medusajs/utils"
import {
WorkflowData,
createStep,
createWorkflow,
parallelize,
transform,
} from "@medusajs/workflows-sdk"
import { useRemoteQueryStep } from "../../../common"
import { adjustInventoryLevelsStep } from "../../../inventory/steps"
import { previewOrderChangeStep, updateReturnItemsStep } from "../../steps"
import { confirmOrderChanges } from "../../steps/confirm-order-changes"
import {
@@ -40,13 +48,97 @@ const validationStep = createStep(
}
)
// Loop through the items in the return and prepare the inventory adjustment of items associated with each variant
function prepareInventoryUpdate({ orderReturn, returnedQuantityMap }) {
const inventoryAdjustment: {
inventory_item_id: string
location_id: string
adjustment: BigNumberInput
}[] = []
let hasManagedInventory = false
let hasStockLocation = false
const productVariantInventoryItems = new Map<string, any>()
// Create the map of inventory item ids associated with each variant that have inventory management
deepFlatMap(
orderReturn.items,
"item.variant.inventory_items.inventory.location_levels",
({ variant, inventory_items, location_levels }) => {
if (!variant?.manage_inventory) {
return
}
hasManagedInventory = true
if (location_levels?.location_id !== orderReturn.location_id) {
return
}
hasStockLocation = true
if (!inventory_items) {
return
}
const inventoryItemId = inventory_items.inventory_item_id
if (!productVariantInventoryItems.has(inventoryItemId)) {
productVariantInventoryItems.set(inventoryItemId, {
variant_id: inventory_items.variant_id,
inventory_item_id: inventoryItemId,
required_quantity: inventory_items.required_quantity,
})
}
}
)
if (hasManagedInventory && !hasStockLocation) {
throw new Error(
`Cannot receive the Return at location ${orderReturn.location_id}`
)
}
// Adjust the inventory of all inventory items of each variant in the return
for (const [variantId, quantity] of Object.entries(returnedQuantityMap)) {
const inventoryItemsByVariant = Array.from(
productVariantInventoryItems.values()
).filter((i) => i.variant_id === variantId)
for (const inventoryItem of inventoryItemsByVariant) {
inventoryAdjustment.push({
inventory_item_id: inventoryItem.inventory_item_id,
location_id: orderReturn.location_id,
adjustment: MathBN.mult(
quantity as number,
inventoryItem.required_quantity
),
})
}
}
return inventoryAdjustment
}
export const confirmReturnReceiveWorkflowId = "confirm-return-receive"
export const confirmReturnReceiveWorkflow = createWorkflow(
confirmReturnReceiveWorkflowId,
function (input: WorkflowInput): WorkflowData<OrderDTO> {
const orderReturn: ReturnDTO = useRemoteQueryStep({
entry_point: "return",
fields: ["id", "status", "order_id", "canceled_at", "items.*"],
fields: [
"id",
"status",
"order_id",
"location_id",
"canceled_at",
"items.*",
"items.item.variant_id",
"items.item.variant.id",
"items.item.variant.manage_inventory",
"items.item.variant.inventory_items.inventory_item_id",
"items.item.variant.inventory_items.required_quantity",
"items.item.variant.inventory_items.inventory.location_levels.location_id",
],
variables: { id: input.return_id },
list: false,
throw_if_key_not_found: true,
@@ -81,45 +173,84 @@ export const confirmReturnReceiveWorkflow = createWorkflow(
list: false,
}).config({ name: "order-change-query" })
const updateReturnItem = transform({ orderChange, orderReturn }, (data) => {
const retItems = data.orderReturn.items!
const received = data.orderChange.actions.filter((act) =>
[
ChangeActionType.RECEIVE_RETURN_ITEM,
ChangeActionType.RECEIVE_DAMAGED_RETURN_ITEM,
].includes(act.action as ChangeActionType)
)
const { updateReturnItem, returnedQuantityMap } = transform(
{ orderChange, orderReturn },
(data) => {
const returnedQuantityMap: Record<string, BigNumberInput> = {}
const itemMap = retItems.reduce((acc, item: any) => {
acc[item.item_id] = item.id
return acc
}, {})
const retItems = data.orderReturn.items ?? []
const received: OrderChangeActionDTO[] = []
const itemUpdates = {}
received.forEach((act) => {
const itemId = act.details!.reference_id as string
if (itemUpdates[itemId]) {
itemUpdates[itemId].received_quantity = MathBN.add(
itemUpdates[itemId].received_quantity,
act.details!.quantity as BigNumberInput
)
return
data.orderChange.actions.forEach((act) => {
if (
[
ChangeActionType.RECEIVE_RETURN_ITEM,
ChangeActionType.RECEIVE_DAMAGED_RETURN_ITEM,
].includes(act.action as ChangeActionType)
) {
received.push(act)
if (act.action === ChangeActionType.RECEIVE_RETURN_ITEM) {
const itemId = act.details!.reference_id as string
const variantId = (retItems as any).find(
(i: any) => i.item_id === itemId
)?.item?.variant_id
if (!variantId) {
return
}
const currentQuantity = returnedQuantityMap[variantId] ?? 0
returnedQuantityMap[variantId] = MathBN.add(
currentQuantity,
act.details!.quantity as number
)
}
}
})
const itemMap = retItems.reduce((acc, item: any) => {
acc[item.item_id] = item.id
return acc
}, {})
const itemUpdates = {}
received.forEach((act) => {
const itemId = act.details!.reference_id as string
if (itemUpdates[itemId]) {
itemUpdates[itemId].received_quantity = MathBN.add(
itemUpdates[itemId].received_quantity,
act.details!.quantity as BigNumberInput
)
return
}
itemUpdates[itemId] = {
id: itemMap[itemId],
received_quantity: act.details!.quantity,
}
})
return {
updateReturnItem: Object.values(itemUpdates) as any,
returnedQuantityMap,
}
}
)
itemUpdates[itemId] = {
id: itemMap[itemId],
received_quantity: act.details!.quantity,
}
})
return Object.values(itemUpdates) as any
})
const inventoryAdjustment = transform(
{ orderReturn, input, returnedQuantityMap },
prepareInventoryUpdate
)
validationStep({ order, orderReturn, orderChange })
updateReturnItemsStep(updateReturnItem)
confirmOrderChanges({ changes: [orderChange], orderId: order.id })
parallelize(
updateReturnItemsStep(updateReturnItem),
confirmOrderChanges({ changes: [orderChange], orderId: order.id }),
adjustInventoryLevelsStep(inventoryAdjustment)
)
return previewOrderChangeStep(order.id)
}