fix(core-flows, link-module): product <> inventory delete cascades (#9528)

**What**
- remove cascade delete of inventory items on product delete
- implement inventory deletion in product/variant delete workflows with checks:
  - product/variant cannot be deleted if there are reservations associated with their inventory items
  - inventory item will be cascade deleted if it's not used by other variants (that are not being deleted in the current flow)

---

FIXES CC-581 CC-582
This commit is contained in:
Frane Polić
2024-10-14 18:22:31 +02:00
committed by GitHub
parent 86f744cf3b
commit 809c851865
7 changed files with 355 additions and 31 deletions

View File

@@ -1,6 +1,30 @@
import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk"
import { Modules } from "@medusajs/framework/utils"
import { MathBN, MedusaError, Modules } from "@medusajs/framework/utils"
import { BigNumberInput } from "@medusajs/types"
export interface ValidateInventoryDeleteStepInput {
inventory_items: { id: string; reserved_quantity: BigNumberInput }[]
}
export const validateVariantInventoryStepId = "validate-inventory-item-delete"
export const validateInventoryDeleteStep = createStep(
validateVariantInventoryStepId,
async (data: ValidateInventoryDeleteStepInput) => {
const nonDeletable = data.inventory_items.filter((inventoryItem) => {
return MathBN.gt(inventoryItem.reserved_quantity, 0)
})
if (nonDeletable.length) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Cannot remove following inventory item(s) since they have reservations: [${nonDeletable
.map((inventoryItem) => inventoryItem.id)
.join(", ")}].`
)
}
}
)
export const deleteInventoryItemStepId = "delete-inventory-item-step"
/**

View File

@@ -3,11 +3,12 @@ import {
WorkflowData,
WorkflowResponse,
} from "@medusajs/framework/workflows-sdk"
import { deleteInventoryItemStep } from "../steps"
import { removeRemoteLinkStep } from "../../common/steps/remove-remote-links"
import { Modules } from "@medusajs/framework/utils"
import { deleteInventoryItemStep, validateInventoryDeleteStep } from "../steps"
import { removeRemoteLinkStep } from "../../common/steps/remove-remote-links"
import { useQueryGraphStep } from "../../common"
export const deleteInventoryItemWorkflowId = "delete-inventory-item-workflow"
/**
* This workflow deletes one or more inventory items.
@@ -15,6 +16,16 @@ export const deleteInventoryItemWorkflowId = "delete-inventory-item-workflow"
export const deleteInventoryItemWorkflow = createWorkflow(
deleteInventoryItemWorkflowId,
(input: WorkflowData<string[]>): WorkflowResponse<string[]> => {
const { data: inventoryItemsToDelete } = useQueryGraphStep({
entity: "inventory",
fields: ["id", "reserved_quantity"],
filters: {
id: input,
},
})
validateInventoryDeleteStep({ inventory_items: inventoryItemsToDelete })
deleteInventoryItemStep(input)
removeRemoteLinkStep({
[Modules.INVENTORY]: { inventory_item_id: input },

View File

@@ -9,8 +9,13 @@ import {
createWorkflow,
transform,
} from "@medusajs/framework/workflows-sdk"
import { emitEventStep, removeRemoteLinkStep } from "../../common"
import {
emitEventStep,
removeRemoteLinkStep,
useQueryGraphStep,
} from "../../common"
import { deleteProductVariantsStep } from "../steps"
import { deleteInventoryItemWorkflow } from "../../inventory"
export type DeleteProductVariantsWorkflowInput = { ids: string[] }
@@ -25,6 +30,47 @@ export const deleteProductVariantsWorkflow = createWorkflow(
[Modules.PRODUCT]: { variant_id: input.ids },
}).config({ name: "remove-variant-link-step" })
const variantsWithInventoryStepResponse = useQueryGraphStep({
entity: "variants",
fields: [
"id",
"manage_inventory",
"inventory.id",
"inventory.variants.id",
],
filters: {
id: input.ids,
},
})
const toDeleteInventoryItemIds = transform(
{ variants: variantsWithInventoryStepResponse.data },
(data) => {
const variants = data.variants || []
const variantsMap = new Map(variants.map((v) => [v.id, true]))
const toDeleteIds: Set<string> = new Set()
variants.forEach((variant) => {
if (!variant.manage_inventory) {
return
}
for (const inventoryItem of variant.inventory) {
if (inventoryItem.variants.every((v) => variantsMap.has(v.id))) {
toDeleteIds.add(inventoryItem.id)
}
}
})
return Array.from(toDeleteIds)
}
)
deleteInventoryItemWorkflow.runAsStep({
input: toDeleteInventoryItemIds,
})
const deletedProductVariants = deleteProductVariantsStep(input.ids)
const variantIdEvents = transform({ input }, ({ input }) => {

View File

@@ -7,9 +7,14 @@ import {
parallelize,
transform,
} from "@medusajs/framework/workflows-sdk"
import { emitEventStep, removeRemoteLinkStep } from "../../common"
import {
emitEventStep,
removeRemoteLinkStep,
useQueryGraphStep,
} from "../../common"
import { deleteProductsStep } from "../steps/delete-products"
import { getProductsStep } from "../steps/get-products"
import { deleteInventoryItemWorkflow } from "../../inventory"
export type DeleteProductsWorkflowInput = { ids: string[] }
@@ -27,6 +32,47 @@ export const deleteProductsWorkflow = createWorkflow(
.map((variant) => variant.id)
})
const variantsWithInventoryStepResponse = useQueryGraphStep({
entity: "variants",
fields: [
"id",
"manage_inventory",
"inventory.id",
"inventory.variants.id",
],
filters: {
id: variantsToBeDeleted,
},
})
const toDeleteInventoryItemIds = transform(
{ variants: variantsWithInventoryStepResponse.data },
(data) => {
const variants = data.variants || []
const variantsMap = new Map(variants.map((v) => [v.id, true]))
const toDeleteIds: Set<string> = new Set()
variants.forEach((variant) => {
if (!variant.manage_inventory) {
return
}
for (const inventoryItem of variant.inventory) {
if (inventoryItem.variants.every((v) => variantsMap.has(v.id))) {
toDeleteIds.add(inventoryItem.id)
}
}
})
return Array.from(toDeleteIds)
}
)
deleteInventoryItemWorkflow.runAsStep({
input: toDeleteInventoryItemIds,
})
const [, deletedProduct] = parallelize(
removeRemoteLinkStep({
[Modules.PRODUCT]: {