fix(core-flows): Avoid checking inventory items on fulfillment cancel for unmanaged inventory variants (#14177)

* Avoid checking existent inventory item on fulfillment cancellation for variants without managed inventory

* Add changeset

* Dismiss existent variant inventory links when updating to unmanaged inventory

* Update input type and step name

* Dismiss inventory when variant is updated to unmanaged inventory

* Review changes

* Fix

* Fix

* Comments

* Include Map to avoid iterating unnecessarily
This commit is contained in:
Nicolas Gorga
2026-01-05 12:11:23 -03:00
committed by GitHub
parent 4bc15b4dc4
commit a464e9d907
8 changed files with 409 additions and 6 deletions

View File

@@ -160,7 +160,9 @@ function prepareCancelOrderFulfillmentData({
(i) => i.id === lineItemId
) as OrderItemWithVariantDTO
// find inventory items
const iitems = orderItem!.variant?.inventory_items
const iitems = orderItem!.variant?.manage_inventory
? orderItem!.variant?.inventory_items
: undefined
// find fulfillment item
const fitem = fulfillment.items.find(
(i) => i.line_item_id === lineItemId

View File

@@ -0,0 +1,100 @@
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
import { ContainerRegistrationKeys, Modules } from "@medusajs/framework/utils"
import { Link, Query } from "@medusajs/framework/modules-sdk"
import { LinkDefinition } from "@medusajs/types"
export const dismissProductVariantsInventoryStepId =
"dismiss-product-variants-inventory"
export type DismissProductVariantsInventoryStepInput = {
variantIds: string[]
}
async function dismissVariantsInventory(
variantIds: string[],
query: Query,
link: Link
): Promise<Record<string, string[]>> {
const dismissedVariantInventoryItems: Record<string, string[]> = {}
if (!variantIds.length) {
return dismissedVariantInventoryItems
}
const { data: variantInventoryItems } = await query.graph({
entity: "product_variant_inventory_item",
fields: ["inventory_item_id", "variant_id"],
filters: {
variant_id: variantIds,
},
})
const variantInventoryItemsMap = new Map<string, string[]>()
for (const item of variantInventoryItems) {
variantInventoryItemsMap.set(item.variant_id, [
...(variantInventoryItemsMap.get(item.variant_id) ?? []),
item.inventory_item_id,
])
}
const dismissLinks: LinkDefinition[] = []
for (const variantId of variantIds) {
if (!variantId) {
continue
}
dismissedVariantInventoryItems[variantId] =
variantInventoryItemsMap.get(variantId) ?? []
for (const inventoryItemId of variantInventoryItemsMap.get(variantId) ??
[]) {
dismissLinks.push({
[Modules.PRODUCT]: { variant_id: variantId },
[Modules.INVENTORY]: { inventory_item_id: inventoryItemId },
})
}
}
await link.dismiss(dismissLinks)
return dismissedVariantInventoryItems
}
export const dismissProductVariantsInventoryStep = createStep(
dismissProductVariantsInventoryStepId,
async (data: DismissProductVariantsInventoryStepInput, { container }) => {
const query = container.resolve(ContainerRegistrationKeys.QUERY)
const link = container.resolve(ContainerRegistrationKeys.LINK)
const variantIds = data.variantIds || []
if (!variantIds.length) {
return new StepResponse(void 0)
}
const dismissedVariantInventoryItems = await dismissVariantsInventory(
variantIds,
query as Query,
link
)
return new StepResponse(void 0, dismissedVariantInventoryItems)
},
async (dismissedVariantInventoryItems, { container }) => {
if (!dismissedVariantInventoryItems) {
return
}
const linksToCreate: LinkDefinition[] = []
for (const [variantId, inventoryItemIds] of Object.entries(
dismissedVariantInventoryItems
)) {
for (const inventoryItemId of inventoryItemIds) {
linksToCreate.push({
[Modules.PRODUCT]: { variant_id: variantId },
[Modules.INVENTORY]: { inventory_item_id: inventoryItemId },
})
}
}
const link = container.resolve(ContainerRegistrationKeys.LINK)
await link.create(linksToCreate)
}
)

View File

@@ -32,3 +32,4 @@ export * from "./get-variant-availability"
export * from "./normalize-products"
export * from "./normalize-products-to-chunks"
export * from "./process-import-chunks"
export * from "./dismiss-product-variants-inventory"

View File

@@ -13,7 +13,10 @@ import {
} from "@medusajs/framework/workflows-sdk"
import { emitEventStep } from "../../common"
import { updatePriceSetsStep } from "../../pricing"
import { updateProductVariantsStep } from "../steps"
import {
dismissProductVariantsInventoryStep,
updateProductVariantsStep,
} from "../steps"
import { getVariantPricingLinkStep } from "../steps/get-variant-pricing-link"
/**
@@ -57,12 +60,12 @@ export const updateProductVariantsWorkflowId = "update-product-variants"
* allows you to update custom data models linked to the product variants.
*
* You can also use this workflow within your customizations or your own custom workflows, allowing you to wrap custom logic around product-variant update.
*
*
* :::note
*
* Learn more about adding rules to the product variant's prices in the Pricing Module's
*
* Learn more about adding rules to the product variant's prices in the Pricing Module's
* [Price Rules](https://docs.medusajs.com/resources/commerce-modules/pricing/price-rules) documentation.
*
*
* :::
*
* @example
@@ -151,6 +154,32 @@ export const updateProductVariantsWorkflow = createWorkflow(
const updatedVariants = updateProductVariantsStep(updateWithoutPrices)
const variantsToDismissInventory = transform(
{ input, updatedVariants },
(data) => {
const variantIds: string[] = []
if ("product_variants" in data.input) {
for (const variant of data.input.product_variants) {
if (variant.id && variant.manage_inventory === false) {
variantIds.push(variant.id)
}
}
} else if (
data.input.update &&
data.input.update?.manage_inventory === false
) {
variantIds.push(...data.updatedVariants.map((v) => v.id))
}
return variantIds
}
)
dismissProductVariantsInventoryStep({
variantIds: variantsToDismissInventory,
})
// We don't want to do any pricing updates if the prices didn't change
const variantIds = transform({ input, updatedVariants }, (data) => {
if ("product_variants" in data.input) {

View File

@@ -27,6 +27,7 @@ import {
useRemoteQueryStep,
} from "../../common"
import { upsertVariantPricesWorkflow } from "./upsert-variant-prices"
import { dismissProductVariantsInventoryStep } from "../steps/dismiss-product-variants-inventory"
/**
* Update products that match a specified selector, along with custom data that's passed to the workflow's hooks.
@@ -444,6 +445,35 @@ export const updateProductsWorkflow = createWorkflow(
const toUpdateInput = transform({ input }, prepareUpdateProductInput)
const updatedProducts = updateProductsStep(toUpdateInput)
const variantsToDismissInventory = transform(
{ input, updatedProducts },
(data) => {
const variantIds: string[] = []
if ("products" in data.input) {
for (const product of data.input.products) {
for (const variant of product.variants ?? []) {
if (variant.id && variant.manage_inventory === false) {
variantIds.push(variant.id)
}
}
}
} else if (data.input.update?.variants?.length) {
for (const variant of data.input.update.variants) {
if (variant.id && variant.manage_inventory === false) {
variantIds.push(variant.id)
}
}
}
return variantIds
}
)
dismissProductVariantsInventoryStep({
variantIds: variantsToDismissInventory,
})
const salesChannelLinks = transform(
{ input, updatedProducts },
prepareSalesChannelLinks