From 40625c82d6deb28ecb4e4c0f911c28fdd7356bf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Frane=20Poli=C4=87?= <16856471+fPolic@users.noreply.github.com> Date: Wed, 16 Jul 2025 21:16:29 +0200 Subject: [PATCH] fix(core-flows): properly delete variant inventory item (#12958) * fix(core-flows): properly delete variant inventory item * fix: rm unused code --- .changeset/cuddly-dragons-attend.md | 5 + .../__tests__/product/admin/product.spec.ts | 93 +++++++++++++++++++ .../workflows/create-product-variants.ts | 32 +++---- .../workflows/delete-product-variants.ts | 26 +++--- 4 files changed, 127 insertions(+), 29 deletions(-) create mode 100644 .changeset/cuddly-dragons-attend.md diff --git a/.changeset/cuddly-dragons-attend.md b/.changeset/cuddly-dragons-attend.md new file mode 100644 index 0000000000..06772ac1c8 --- /dev/null +++ b/.changeset/cuddly-dragons-attend.md @@ -0,0 +1,5 @@ +--- +"@medusajs/core-flows": patch +--- + +fix(core-flows): correctly delete related inventory item when deleting variant diff --git a/integration-tests/http/__tests__/product/admin/product.spec.ts b/integration-tests/http/__tests__/product/admin/product.spec.ts index 11322c4a9f..92f106d283 100644 --- a/integration-tests/http/__tests__/product/admin/product.spec.ts +++ b/integration-tests/http/__tests__/product/admin/product.spec.ts @@ -2159,6 +2159,99 @@ medusaIntegrationTestRunner({ }) }) + it("should recreate a variant with the same sku after deletion", async () => { + const payload = { + title: "Test sku", + shipping_profile_id: shippingProfile.id, + variants: [ + { + sku: "test-sku-should-persist", + manage_inventory: true, + title: "Recreate variant", + prices: [ + { + currency_code: "usd", + amount: 100, + }, + ], + }, + ], + } + + const created = ( + await api.post( + "/admin/products?fields=*variants.inventory_items.inventory", + getProductFixture(payload), + adminHeaders + ) + ).data.product + + const createdVariant = created.variants.find( + (v) => v.sku === "test-sku-should-persist" + ) + + const inventoryItem = createdVariant.inventory_items[0] + + expect(created).toEqual( + expect.objectContaining({ + variants: expect.arrayContaining([ + expect.objectContaining({ + sku: "test-sku-should-persist", + inventory_items: expect.arrayContaining([ + expect.objectContaining({ + inventory: expect.objectContaining({ + sku: "test-sku-should-persist", + }), + }), + ]), + title: "Recreate variant", + }), + ]), + }) + ) + + await api.delete( + `/admin/products/${created.id}/variants/${createdVariant.id}`, + adminHeaders + ) + + const recreated = ( + await api.post( + `/admin/products/${created.id}/variants?fields=+variants.inventory_items.inventory.sku`, + { + sku: "test-sku-should-persist", + manage_inventory: true, + title: "Recreate variant", + prices: [ + { + currency_code: "usd", + amount: 100, + }, + ], + }, + adminHeaders + ) + ).data.product + + expect(recreated).toEqual( + expect.objectContaining({ + variants: expect.arrayContaining([ + expect.objectContaining({ + sku: "test-sku-should-persist", + inventory_items: expect.arrayContaining([ + expect.objectContaining({ + inventory: expect.objectContaining({ + sku: "test-sku-should-persist", + }), + }), + ]), + title: "Recreate variant", + }), + ]), + }) + ) + }) + it("updates products sales channels", async () => { const salesChannel1 = ( await api.post( diff --git a/packages/core/core-flows/src/product/workflows/create-product-variants.ts b/packages/core/core-flows/src/product/workflows/create-product-variants.ts index e09d749179..6086c537f0 100644 --- a/packages/core/core-flows/src/product/workflows/create-product-variants.ts +++ b/packages/core/core-flows/src/product/workflows/create-product-variants.ts @@ -26,9 +26,9 @@ import { createProductVariantsStep } from "../steps/create-product-variants" import { createVariantPricingLinkStep } from "../steps/create-variant-pricing-link" /** - * + * * The data to create one or more product variants, along with custom data that's passed to the workflow's hooks. - * + * * @privateRemarks * TODO: Create separate typings for the workflow input */ @@ -51,9 +51,9 @@ export type CreateProductVariantsWorkflowInput = { */ inventory_item_id: string /** - * The number of units a single quantity is equivalent to. For example, if a customer orders one quantity of the variant, - * Medusa checks the availability of the quantity multiplied by the value set for `required_quantity`. - * When the customer orders the quantity, Medusa reserves the ordered quantity multiplied by the value + * The number of units a single quantity is equivalent to. For example, if a customer orders one quantity of the variant, + * Medusa checks the availability of the quantity multiplied by the value set for `required_quantity`. + * When the customer orders the quantity, Medusa reserves the ordered quantity multiplied by the value * set for `required_quantity`. */ required_quantity?: number @@ -201,19 +201,19 @@ const buildVariantItemCreateMap = (data: { export const createProductVariantsWorkflowId = "create-product-variants" /** * This workflow creates one or more product variants. It's used by the [Create Product Variant Admin API Route](https://docs.medusajs.com/api/admin#products_postproductsidvariants). - * - * This workflow has a hook that allows you to perform custom actions on the created product variants. For example, you can pass under `additional_data` custom data that + * + * This workflow has a hook that allows you to perform custom actions on the created product variants. For example, you can pass under `additional_data` custom data that * allows you to create 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 creation. - * + * * :::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 * const { result } = await createProductVariantsWorkflow(container) * .run({ @@ -239,11 +239,11 @@ export const createProductVariantsWorkflowId = "create-product-variants" * } * } * }) - * + * * @summary - * + * * Create one or more product variants. - * + * * @property hooks.productVariantsCreated - This hook is executed after the product variants are created. You can consume this hook to perform custom actions on the created product variants. */ export const createProductVariantsWorkflow = createWorkflow( diff --git a/packages/core/core-flows/src/product/workflows/delete-product-variants.ts b/packages/core/core-flows/src/product/workflows/delete-product-variants.ts index 2e0572b706..5e6d53c783 100644 --- a/packages/core/core-flows/src/product/workflows/delete-product-variants.ts +++ b/packages/core/core-flows/src/product/workflows/delete-product-variants.ts @@ -20,7 +20,7 @@ import { deleteInventoryItemWorkflow } from "../../inventory" /** * The data to delete one or more product variants. */ -export type DeleteProductVariantsWorkflowInput = { +export type DeleteProductVariantsWorkflowInput = { /** * The IDs of the variants to delete. */ @@ -29,14 +29,14 @@ export type DeleteProductVariantsWorkflowInput = { export const deleteProductVariantsWorkflowId = "delete-product-variants" /** - * This workflow deletes one or more product variants. It's used by the + * This workflow deletes one or more product variants. It's used by the * [Delete Product Variants Admin API Route](https://docs.medusajs.com/api/admin#products_deleteproductsidvariantsvariant_id). - * - * This workflow has a hook that allows you to perform custom actions after the product variants are deleted. For example, + * + * This workflow has a hook that allows you to perform custom actions after the product variants are deleted. For example, * you can delete custom records linked to the product variants. - * + * * You can also use this workflow within your own custom workflows, allowing you to wrap custom logic around product-variant deletion. - * + * * @example * const { result } = await deleteProductVariantsWorkflow(container) * .run({ @@ -44,20 +44,16 @@ export const deleteProductVariantsWorkflowId = "delete-product-variants" * ids: ["variant_123"], * } * }) - * + * * @summary - * + * * Delete one or more product variants. - * + * * @property hooks.productVariantsDeleted - This hook is executed after the variants are deleted. You can consume this hook to perform custom actions on the deleted variants. */ export const deleteProductVariantsWorkflow = createWorkflow( deleteProductVariantsWorkflowId, (input: WorkflowData) => { - removeRemoteLinkStep({ - [Modules.PRODUCT]: { variant_id: input.ids }, - }).config({ name: "remove-variant-link-step" }) - const variantsWithInventoryStepResponse = useQueryGraphStep({ entity: "variants", fields: [ @@ -71,6 +67,10 @@ export const deleteProductVariantsWorkflow = createWorkflow( }, }) + removeRemoteLinkStep({ + [Modules.PRODUCT]: { variant_id: input.ids }, + }).config({ name: "remove-variant-link-step" }) + const toDeleteInventoryItemIds = transform( { variants: variantsWithInventoryStepResponse.data }, (data) => {