From 0d1b63d773ad91846757a6f0b81b78b0b97b3f2b Mon Sep 17 00:00:00 2001 From: Philip Korsholm <88927411+pKorsholm@users.noreply.github.com> Date: Thu, 16 Mar 2023 19:03:36 +0100 Subject: [PATCH] fix(medusa): Use get for creating fulfillments (#3498) * use get * changeset * use set * add tests --------- Co-authored-by: Oliver Windall Juhl <59018053+olivermrbl@users.noreply.github.com> --- .changeset/olive-penguins-sin.md | 5 + .../__tests__/inventory/order/order.js | 187 ++++++++++++++++-- .../routes/admin/orders/create-fulfillment.ts | 6 +- 3 files changed, 176 insertions(+), 22 deletions(-) create mode 100644 .changeset/olive-penguins-sin.md diff --git a/.changeset/olive-penguins-sin.md b/.changeset/olive-penguins-sin.md new file mode 100644 index 0000000000..ce9eba7453 --- /dev/null +++ b/.changeset/olive-penguins-sin.md @@ -0,0 +1,5 @@ +--- +"@medusajs/medusa": patch +--- + +fix(medusa): use get instead of indexing into map diff --git a/integration-tests/plugins/__tests__/inventory/order/order.js b/integration-tests/plugins/__tests__/inventory/order/order.js index 2f6da0ae67..04f194cfce 100644 --- a/integration-tests/plugins/__tests__/inventory/order/order.js +++ b/integration-tests/plugins/__tests__/inventory/order/order.js @@ -112,7 +112,7 @@ describe("/store/carts", () => { await inventoryService.createInventoryLevel({ inventory_item_id: invItem.id, location_id: locationId, - stocked_quantity: 1, + stocked_quantity: 100, }) const { id: orderId } = await simpleOrderFactory(dbConnection, { @@ -143,9 +143,9 @@ describe("/store/carts", () => { expect(inventoryItem.data.inventory_item.location_levels[0]).toEqual( expect.objectContaining({ - stocked_quantity: 1, + stocked_quantity: 100, reserved_quantity: 0, - available_quantity: 1, + available_quantity: 100, }) ) }) @@ -196,9 +196,9 @@ describe("/store/carts", () => { expect(inventoryItem.data.inventory_item.location_levels[0]).toEqual( expect.objectContaining({ - stocked_quantity: 1, + stocked_quantity: 100, reserved_quantity: 0, - available_quantity: 1, + available_quantity: 100, }) ) @@ -226,9 +226,9 @@ describe("/store/carts", () => { expect(inventoryItem.data.inventory_item.location_levels).toEqual( expect.arrayContaining([ expect.objectContaining({ - stocked_quantity: 1, + stocked_quantity: 100, reserved_quantity: 1, - available_quantity: 0, + available_quantity: 99, }), expect.objectContaining({ stocked_quantity: 1, @@ -281,9 +281,9 @@ describe("/store/carts", () => { expect.arrayContaining([ expect.objectContaining({ location_id: locationId, - stocked_quantity: 0, + stocked_quantity: 99, reserved_quantity: 0, - available_quantity: 0, + available_quantity: 99, }), expect.objectContaining({ location_id: loc.id, @@ -295,6 +295,130 @@ describe("/store/carts", () => { ) }) + it("Adjusts reservations on several subsequent fulfillments (being cancelled in-between), with a total quantity greater that the line item quantity", async () => { + const api = useApi() + + const lineItemId1 = "line-item-id-1" + + const customer = await simpleCustomerFactory(dbConnection, {}) + + const cart = await simpleCartFactory(dbConnection, { + email: "adrien@test.com", + region: regionId, + line_items: [ + { + id: lineItemId1, + variant_id: variantId, + quantity: 3, + unit_price: 1000, + }, + ], + sales_channel_id: "test-channel", + shipping_address: {}, + shipping_methods: [ + { + shipping_option: { + region_id: regionId, + }, + }, + ], + }) + + await appContainer + .resolve("cartService") + .update(cart.id, { customer_id: customer.id }) + + let inventoryItem = await api.get( + `/admin/inventory-items/${invItemId}`, + adminHeaders + ) + + expect(inventoryItem.data.inventory_item.location_levels[0]).toEqual( + expect.objectContaining({ + stocked_quantity: 100, + reserved_quantity: 0, + available_quantity: 100, + }) + ) + + await api.post(`/store/carts/${cart.id}/payment-sessions`) + + const completeRes = await api.post(`/store/carts/${cart.id}/complete`) + + const orderId = completeRes.data.data.id + + let fulfillmentRes = await api.post( + `/admin/orders/${orderId}/fulfillment`, + { + items: [{ item_id: lineItemId1, quantity: 2 }], + location_id: locationId, + }, + adminHeaders + ) + + expect(fulfillmentRes.status).toBe(200) + expect( + fulfillmentRes.data.order.fulfillments[0].location_id + ).toBeTruthy() + expect(fulfillmentRes.data.order.fulfillment_status).toEqual( + "partially_fulfilled" + ) + + await api.post( + `/admin/orders/${orderId}/fulfillments/${fulfillmentRes.data.order.fulfillments[0].id}/cancel`, + {}, + adminHeaders + ) + + fulfillmentRes = await api.post( + `/admin/orders/${orderId}/fulfillment`, + { + items: [{ item_id: lineItemId1, quantity: 2 }], + location_id: locationId, + }, + adminHeaders + ) + + expect(fulfillmentRes.status).toBe(200) + expect( + fulfillmentRes.data.order.fulfillments[1].location_id + ).toBeTruthy() + expect(fulfillmentRes.data.order.fulfillment_status).toEqual( + "partially_fulfilled" + ) + + inventoryItem = await api.get( + `/admin/inventory-items/${invItemId}`, + adminHeaders + ) + + const reservations = await api.get( + `/admin/reservations?inventory_item_id[]=${invItemId}`, + adminHeaders + ) + + expect(reservations.data.reservations.length).toBe(0) + expect(inventoryItem.data.inventory_item.location_levels).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + location_id: locationId, + stocked_quantity: 98, + reserved_quantity: 0, + available_quantity: 98, + }), + ]) + ) + + const orderRes = await api.get(`/admin/orders/${orderId}`, adminHeaders) + expect(orderRes.data.order.items).toEqual([ + expect.objectContaining({ + id: lineItemId1, + fulfilled_quantity: 2, + quantity: 3, + }), + ]) + }) + it("Adjusts reservation on successful fulfillment with reservation", async () => { const api = useApi() @@ -310,9 +434,9 @@ describe("/store/carts", () => { expect(inventoryItem.data.inventory_item.location_levels[0]).toEqual( expect.objectContaining({ - stocked_quantity: 1, + stocked_quantity: 100, reserved_quantity: 1, - available_quantity: 0, + available_quantity: 99, }) ) @@ -343,9 +467,9 @@ describe("/store/carts", () => { expect(reservations.data.reservations.length).toBe(0) expect(inventoryItem.data.inventory_item.location_levels[0]).toEqual( expect.objectContaining({ - stocked_quantity: 0, + stocked_quantity: 99, reserved_quantity: 0, - available_quantity: 0, + available_quantity: 99, }) ) }) @@ -646,9 +770,9 @@ describe("/store/carts", () => { expect(inventoryItem.data.inventory_item.location_levels[0]).toEqual( expect.objectContaining({ - stocked_quantity: 0, + stocked_quantity: 99, reserved_quantity: 0, - available_quantity: 0, + available_quantity: 99, }) ) @@ -669,9 +793,9 @@ describe("/store/carts", () => { ) expect(inventoryItem.data.inventory_item.location_levels[0]).toEqual( expect.objectContaining({ - stocked_quantity: 1, + stocked_quantity: 100, reserved_quantity: 0, - available_quantity: 1, + available_quantity: 100, }) ) }) @@ -699,9 +823,9 @@ describe("/store/carts", () => { expect(inventoryItem.data.inventory_item.location_levels[0]).toEqual( expect.objectContaining({ - stocked_quantity: 0, + stocked_quantity: 99, reserved_quantity: 0, - available_quantity: 0, + available_quantity: 99, }) ) }) @@ -709,6 +833,31 @@ describe("/store/carts", () => { it("Fails to create fulfillment if there is not enough inventory at the fulfillment location", async () => { const api = useApi() + const updateRes = await api.post( + `/admin/inventory-items/${invItemId}/location-levels/${locationId}`, + { + stocked_quantity: 1, + }, + adminHeaders + ) + + expect(updateRes.status).toBe(200) + + const locationLevels = await api.get( + `/admin/variants/${variantId}/inventory`, + adminHeaders + ) + + expect( + locationLevels.data.variant.inventory[0].location_levels[0] + ).toEqual( + expect.objectContaining({ + stocked_quantity: 1, + reserved_quantity: 0, + available_quantity: 1, + }) + ) + const err = await api .post( `/admin/orders/${order.id}/fulfillment`, diff --git a/packages/medusa/src/api/routes/admin/orders/create-fulfillment.ts b/packages/medusa/src/api/routes/admin/orders/create-fulfillment.ts index 8582419981..2c10ed6dbd 100644 --- a/packages/medusa/src/api/routes/admin/orders/create-fulfillment.ts +++ b/packages/medusa/src/api/routes/admin/orders/create-fulfillment.ts @@ -114,8 +114,8 @@ export default async (req, res) => { await orderServiceTx.retrieve(id, { relations: ["fulfillments"], }) - const existingFulfillmentMap = new Map( - existingFulfillments.map((fulfillment) => [fulfillment.id, fulfillment]) + const existingFulfillmentSet = new Set( + existingFulfillments.map((fulfillment) => fulfillment.id) ) await orderServiceTx.createFulfillment(id, validatedBody.items, { @@ -137,7 +137,7 @@ export default async (req, res) => { pvInventoryService.withTransaction(transactionManager) await updateInventoryAndReservations( - fulfillments.filter((f) => !existingFulfillmentMap[f.id]), + fulfillments.filter((f) => !existingFulfillmentSet.has(f.id)), { inventoryService: pvInventoryServiceTx, locationId: validatedBody.location_id,