From 68fb04b8498c4bae9998a3b67db3618c9d574286 Mon Sep 17 00:00:00 2001 From: "Carlos R. L. Rodrigues" <37986729+carlos-r-l-rodrigues@users.noreply.github.com> Date: Tue, 4 Jun 2024 07:08:54 -0300 Subject: [PATCH] fix(order): ignore reservation when manage_inventory is false (#7594) --- .../workflows/create-fulfillment.spec.ts | 91 ++++++++++++++++++- .../src/order/workflows/create-fulfillment.ts | 23 +++-- 2 files changed, 105 insertions(+), 9 deletions(-) diff --git a/integration-tests/modules/__tests__/order/workflows/create-fulfillment.spec.ts b/integration-tests/modules/__tests__/order/workflows/create-fulfillment.spec.ts index 384d46e5f9..39c869008c 100644 --- a/integration-tests/modules/__tests__/order/workflows/create-fulfillment.spec.ts +++ b/integration-tests/modules/__tests__/order/workflows/create-fulfillment.spec.ts @@ -96,6 +96,11 @@ async function prepareDataFixtures({ container }) { title: "Test variant", sku: "test-variant", }, + { + title: "Test variant no inventory management", + sku: "test-variant-no-inventory", + manage_inventory: false, + }, ], }, ]) @@ -228,7 +233,15 @@ async function createOrderFixture({ container, product, location }) { provider_id: "coupon_kings", }, ], - } as any, + }, + { + title: product.title, + variant_sku: product.variants[1].sku, + variant_title: product.variants[1].title, + variant_id: product.variants[1].id, + quantity: 1, + unit_price: 200, + }, ], transactions: [ { @@ -282,6 +295,7 @@ async function createOrderFixture({ container, product, location }) { }) const inventoryModule = container.resolve(ModuleRegistrationName.INVENTORY) + const reservation = await inventoryModule.createReservationItems([ { line_item_id: order.items![0].id, @@ -429,6 +443,81 @@ medusaIntegrationTestRunner({ ]) expect(stockAvailabilityAfterCancelled).toEqual(2) }) + + it("should revert an order fulfillment when it fails and recreate it when tried again", async () => { + const order = await createOrderFixture({ container, product, location }) + + // Create a fulfillment + const createOrderFulfillmentData: OrderWorkflow.CreateOrderFulfillmentWorkflowInput = + { + order_id: order.id, + created_by: "user_1", + items: [ + { + id: order.items![0].id, + quantity: 1, + }, + ], + no_notification: false, + location_id: undefined, + } + + const worflow = createOrderFulfillmentWorkflow(container) + worflow.addAction("fail", { + invoke: () => { + throw new Error("Fulfillment failed") + }, + }) + + await worflow + .run({ + input: createOrderFulfillmentData, + }) + .catch(() => void 0) + + worflow.deleteAction("fail") + + await worflow.run({ + input: createOrderFulfillmentData, + }) + + const remoteQuery = container.resolve( + ContainerRegistrationKeys.REMOTE_QUERY + ) + const remoteQueryObject = remoteQueryObjectFromString({ + entryPoint: "order", + variables: { + id: order.id, + }, + fields: [ + "*", + "items.*", + "shipping_methods.*", + "total", + "item_total", + "fulfillments.*", + ], + }) + + const [orderFulfill] = await remoteQuery(remoteQueryObject) + + expect(orderFulfill.fulfillments).toHaveLength(1) + expect(orderFulfill.items[0].detail.fulfilled_quantity).toEqual(1) + + const inventoryModule = container.resolve( + ModuleRegistrationName.INVENTORY + ) + const reservation = await inventoryModule.listReservationItems({ + line_item_id: order.items![0].id, + }) + expect(reservation).toHaveLength(0) + + const stockAvailability = await inventoryModule.retrieveStockedQuantity( + inventoryItem.id, + [location.id] + ) + expect(stockAvailability).toEqual(1) + }) }) }, }) diff --git a/packages/core/core-flows/src/order/workflows/create-fulfillment.ts b/packages/core/core-flows/src/order/workflows/create-fulfillment.ts index ebd8234a0e..d05375aaae 100644 --- a/packages/core/core-flows/src/order/workflows/create-fulfillment.ts +++ b/packages/core/core-flows/src/order/workflows/create-fulfillment.ts @@ -94,7 +94,7 @@ function prepareFulfillmentData({ const reservation = reservationItemMap.get(i.id)! return { line_item_id: i.id, - inventory_item_id: reservation.inventory_item_id, + inventory_item_id: reservation?.inventory_item_id, quantity: i.quantity, title: orderItem.variant_title ?? orderItem.title, sku: orderItem.variant_sku || "", @@ -114,6 +114,9 @@ function prepareFulfillmentData({ ) } + const shippingAddress = order.shipping_address ?? { id: undefined } + delete shippingAddress.id + return { input: { location_id: locationId, @@ -121,18 +124,12 @@ function prepareFulfillmentData({ shipping_option_id: shippingOption.id, items: fulfillmentItems, labels: [] as FulfillmentWorkflow.CreateFulfillmentLabelWorkflowDTO[], // TODO: shipping labels - delivery_address: order.shipping_address ?? ({} as any), + delivery_address: shippingAddress as any, }, } } function prepareInventoryUpdate({ reservations, order, input }) { - if (!reservations || !reservations.length) { - throw new Error( - `No stock reservation found for items ${input.items.map((i) => i.id)}` - ) - } - const reservationMap = reservations.reduce((acc, reservation) => { acc[reservation.line_item_id as string] = reservation return acc @@ -157,6 +154,15 @@ function prepareInventoryUpdate({ reservations, order, input }) { for (const item of order.items) { const reservation = reservationMap[item.id] + if (!reservation) { + if (item.manage_inventory) { + throw new Error( + `No stock reservation found for item ${item.id} - ${item.title} (${item.variant_title})` + ) + } + continue + } + const inputQuantity = inputItemsMap[item.id]?.quantity ?? item.quantity const quantity = reservation.quantity - inputQuantity @@ -199,6 +205,7 @@ export const createOrderFulfillmentWorkflow = createWorkflow( "region_id", "currency_code", "items.*", + "items.variant.manage_inventory", "shipping_address.*", "shipping_methods.shipping_option_id", // TODO: which shipping method to use when multiple? ],