diff --git a/.changeset/many-cycles-count.md b/.changeset/many-cycles-count.md new file mode 100644 index 0000000000..f4fbcd8bde --- /dev/null +++ b/.changeset/many-cycles-count.md @@ -0,0 +1,5 @@ +--- +"@medusajs/core-flows": patch +--- + +fix(core-flows): fulfillment reservation check diff --git a/integration-tests/http/__tests__/claims/claims.spec.ts b/integration-tests/http/__tests__/claims/claims.spec.ts index 91cac0ba73..266f1c9b16 100644 --- a/integration-tests/http/__tests__/claims/claims.spec.ts +++ b/integration-tests/http/__tests__/claims/claims.spec.ts @@ -331,6 +331,17 @@ medusaIntegrationTestRunner({ }, ]) + // create reservation for inventory item that is initially on the order + const inventoryModule = container.resolve(Modules.INVENTORY) + await inventoryModule.createReservationItems([ + { + inventory_item_id: inventoryItem.id, + location_id: location.id, + quantity: 2, + line_item_id: order.items[0].id, + }, + ]) + const shippingOptionPayload = { name: "Return shipping", service_zone_id: fulfillmentSet.service_zones[0].id, diff --git a/integration-tests/http/__tests__/exchanges/exchanges.spec.ts b/integration-tests/http/__tests__/exchanges/exchanges.spec.ts index afd731faef..b9d9aa100f 100644 --- a/integration-tests/http/__tests__/exchanges/exchanges.spec.ts +++ b/integration-tests/http/__tests__/exchanges/exchanges.spec.ts @@ -325,6 +325,17 @@ medusaIntegrationTestRunner({ }, ]) + // create reservation for inventory item that is initially on the order + const inventoryModule = container.resolve(Modules.INVENTORY) + await inventoryModule.createReservationItems([ + { + inventory_item_id: inventoryItem.id, + location_id: location.id, + quantity: 2, + line_item_id: order.items[0].id, + }, + ]) + const shippingOptionPayload = { name: "Return shipping", service_zone_id: fulfillmentSet.service_zones[0].id, diff --git a/integration-tests/http/__tests__/order/admin/rma-flows.spec.ts b/integration-tests/http/__tests__/order/admin/rma-flows.spec.ts index 4cbd0e3a7e..9b1c124ca6 100644 --- a/integration-tests/http/__tests__/order/admin/rma-flows.spec.ts +++ b/integration-tests/http/__tests__/order/admin/rma-flows.spec.ts @@ -17,6 +17,7 @@ medusaIntegrationTestRunner({ let location let item let returnShippingOption + let outboundShippingOption const shippingProviderId = "manual_test-provider" beforeEach(async () => { @@ -135,7 +136,7 @@ medusaIntegrationTestRunner({ prices: [ { currency_code: "usd", - amount: 20, + amount: 0, // Free shipping }, ], rules: [ @@ -160,7 +161,7 @@ medusaIntegrationTestRunner({ ) ).data.shipping_option - const outboundShippingOption = ( + outboundShippingOption = ( await api.post( "/admin/shipping-options", outboundShippingOptionPayload, @@ -289,6 +290,15 @@ medusaIntegrationTestRunner({ adminHeaders ) + // If claim has outbound items, we need to set the outbound shipping method for reservation to be created + // TODO: change condition in confirm workfow to crate reservation depending on shipping since we can have + // reservations for inventory items that don't require shipping + await api.post( + `/admin/claims/${singleOutboundClaim.id}/outbound/shipping-method`, + { shipping_option_id: outboundShippingOption.id }, + adminHeaders + ) + await api.post( `/admin/claims/${singleOutboundClaim.id}/request`, {}, diff --git a/integration-tests/http/__tests__/returns/returns.spec.ts b/integration-tests/http/__tests__/returns/returns.spec.ts index dda188fbf7..24896a7eb8 100644 --- a/integration-tests/http/__tests__/returns/returns.spec.ts +++ b/integration-tests/http/__tests__/returns/returns.spec.ts @@ -239,6 +239,17 @@ medusaIntegrationTestRunner({ }, ]) + // create reservation for inventory item that is initially on the order + const inventoryModule = container.resolve(Modules.INVENTORY) + await inventoryModule.createReservationItems([ + { + inventory_item_id: inventoryItem.id, + location_id: location.id, + quantity: 2, + line_item_id: order.items[0].id, + }, + ]) + const shippingOptionPayload = { name: "Return shipping", service_zone_id: fulfillmentSet.service_zones[0].id, @@ -866,7 +877,9 @@ medusaIntegrationTestRunner({ adminHeaders ) ).data.inventory_levels - expect(inventoryLevel[0].stocked_quantity).toEqual(2) + + // we had 2 items in stock that were reserved for the order and then fulfilled + expect(inventoryLevel[0].stocked_quantity).toEqual(0) let result = await api.post( `/admin/returns/${returnId}/receive`, @@ -987,7 +1000,7 @@ medusaIntegrationTestRunner({ adminHeaders ) ).data.inventory_levels - expect(inventoryLevel[0].stocked_quantity).toEqual(3) + expect(inventoryLevel[0].stocked_quantity).toEqual(1) }) }) 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 6a9e025c6e..e84dbb4a79 100644 --- a/integration-tests/modules/__tests__/order/workflows/create-fulfillment.spec.ts +++ b/integration-tests/modules/__tests__/order/workflows/create-fulfillment.spec.ts @@ -486,6 +486,9 @@ medusaIntegrationTestRunner({ it("should revert an order fulfillment when it fails and recreate it when tried again", async () => { const order = await createOrderFixture({ container, product, location }) + const itemId = order.items?.find((i) => i.title === "Custom Item 2")! + .id as string + // Create a fulfillment const createOrderFulfillmentData: OrderWorkflow.CreateOrderFulfillmentWorkflowInput = { @@ -493,7 +496,7 @@ medusaIntegrationTestRunner({ created_by: "user_1", items: [ { - id: order.items![0].id, + id: itemId, quantity: 1, }, ], @@ -540,12 +543,14 @@ medusaIntegrationTestRunner({ const [orderFulfill] = await remoteQuery(remoteQueryObject) + const fulfilledItem = orderFulfill.items?.find((i) => i.id === itemId) + expect(orderFulfill.fulfillments).toHaveLength(1) - expect(orderFulfill.items[0].detail.fulfilled_quantity).toEqual(1) + expect(fulfilledItem?.detail.fulfilled_quantity).toEqual(1) const inventoryModule = container.resolve(Modules.INVENTORY) const reservation = await inventoryModule.listReservationItems({ - line_item_id: order.items![0].id, + line_item_id: itemId, }) expect(reservation).toHaveLength(0) diff --git a/packages/core/core-flows/src/order/workflows/claim/claim-add-new-item.ts b/packages/core/core-flows/src/order/workflows/claim/claim-add-new-item.ts index d46d366612..6d21deaa08 100644 --- a/packages/core/core-flows/src/order/workflows/claim/claim-add-new-item.ts +++ b/packages/core/core-flows/src/order/workflows/claim/claim-add-new-item.ts @@ -42,16 +42,16 @@ export type OrderClaimAddNewItemValidationStepInput = { } /** - * This step validates that new items can be added to the claim. If the + * This step validates that new items can be added to the claim. If the * order or claim is canceled, or the order change is not active, the step will throw an error. - * + * * :::note - * + * * You can retrieve an order, order claim, and order change details using [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query), * or [useQueryGraphStep](https://docs.medusajs.com/resources/references/medusa-workflows/steps/useQueryGraphStep). - * + * * ::: - * + * * @example * const data = orderClaimAddNewItemValidationStep({ * order: { @@ -85,10 +85,10 @@ export const orderClaimAddNewItemWorkflowId = "claim-add-new-item" /** * This workflow adds outbound (or new) items to a claim. It's used by the * [Add Outbound Items Admin API Route](https://docs.medusajs.com/api/admin#claims_postclaimsidoutbounditems). - * + * * You can use this workflow within your customizations or your own custom workflows, allowing you to add outbound items to a claim * in your custom flows. - * + * * @example * const { result } = await orderClaimAddNewItemWorkflow(container) * .run({ @@ -102,9 +102,9 @@ export const orderClaimAddNewItemWorkflowId = "claim-add-new-item" * ] * } * }) - * + * * @summary - * + * * Add outbound or new items to a claim. */ export const orderClaimAddNewItemWorkflow = createWorkflow( diff --git a/packages/core/core-flows/src/order/workflows/claim/confirm-claim-request.ts b/packages/core/core-flows/src/order/workflows/claim/confirm-claim-request.ts index 599b4f9a71..5a76267626 100644 --- a/packages/core/core-flows/src/order/workflows/claim/confirm-claim-request.ts +++ b/packages/core/core-flows/src/order/workflows/claim/confirm-claim-request.ts @@ -239,7 +239,7 @@ function extractShippingOption({ orderPreview, orderClaim, returnId }) { for (const action of modifiedShippingMethod_.actions) { if (action.action === ChangeActionType.SHIPPING_ADD) { - if (action.return?.id === returnId) { + if (!!action.return_id && action.return_id === returnId) { returnShippingMethod = shippingMethod } else if (action.claim_id === orderClaim.id) { claimShippingMethod = shippingMethod 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 4a764fa07d..bb71875344 100644 --- a/packages/core/core-flows/src/order/workflows/create-fulfillment.ts +++ b/packages/core/core-flows/src/order/workflows/create-fulfillment.ts @@ -241,10 +241,14 @@ function prepareInventoryUpdate({ }[] = [] const allItems = itemsList ?? order.items - for (const item of allItems) { + const itemsToFulfill = allItems.filter((i) => i.id in inputItemsMap) + + // iterate over items that are being fulfilled + for (const item of itemsToFulfill) { const reservation = reservationMap[item.id] + if (!reservation) { - if (item.manage_inventory) { + if (item.variant?.manage_inventory) { throw new Error( `No stock reservation found for item ${item.id} - ${item.title} (${item.variant_title})` ) @@ -345,6 +349,7 @@ export const createOrderFulfillmentWorkflow = createWorkflow( "items.variant.height", "items.variant.width", "items.variant.material", + "items.variant_title", "shipping_address.*", "shipping_methods.id", "shipping_methods.shipping_option_id", @@ -394,9 +399,11 @@ export const createOrderFulfillmentWorkflow = createWorkflow( }).config({ name: "get-shipping-option" }) const lineItemIds = transform( - { order, itemsList: input.items_list }, - ({ order, itemsList }) => { - return (itemsList ?? order.items)!.map((i) => i.id) + { order, itemsList: input.items_list, inputItemsMap }, + ({ order, itemsList, inputItemsMap }) => { + return (itemsList ?? order.items)! + .map((i) => i.id) + .filter((i) => i in inputItemsMap) } ) const reservations = useRemoteQueryStep({