From ca334b7cc1eef297cbde2356746adfd0121d348f Mon Sep 17 00:00:00 2001 From: Nicolas Gorga <62995075+NicolasGorga@users.noreply.github.com> Date: Thu, 2 Oct 2025 09:59:13 -0300 Subject: [PATCH] fix(inventory): delete reservation item inventory level required quantity (#13633) Fixes #13625 --- > [!NOTE] > Ensure `reserved_quantity` is adjusted when deleting reservation items via `deleteReservationItems`, with new integration tests covering deletion and inventory updates. > > - **Inventory Module Service (`packages/modules/inventory/src/services/inventory-module.ts`)**: > - Add `deleteReservationItems` API (and transactional `_` variant) that hard-deletes reservations, then updates related inventory levels via `adjustInventoryLevelsForReservationsDeletion`. > - Wire through event emission/manager decorators consistent with existing patterns. > - **Integration Tests (`packages/modules/inventory/integration-tests/__tests__/inventory-module-service.spec.ts`)**: > - Add tests for `deleteReservationItems` to verify deletion by id and `reserved_quantity` adjustments on inventory levels. > - Minor import update to include `ReservationItemDTO`. > - **Changeset**: > - Patch release note for `@medusajs/inventory` documenting the fix. > > Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit ac6641a9ec9543115504407f708f81bd427c3444. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot). --- .changeset/tiny-trains-drum.md | 5 + .../inventory-module-service.spec.ts | 101 +++++++++++++++++- .../src/services/inventory-module.ts | 26 +++++ 3 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 .changeset/tiny-trains-drum.md diff --git a/.changeset/tiny-trains-drum.md b/.changeset/tiny-trains-drum.md new file mode 100644 index 0000000000..5a691ec50a --- /dev/null +++ b/.changeset/tiny-trains-drum.md @@ -0,0 +1,5 @@ +--- +"@medusajs/inventory": patch +--- + +fix(inventory): Adjust inventory level required_quantity when reservation item is deleted through deleteReservationItems from module service diff --git a/packages/modules/inventory/integration-tests/__tests__/inventory-module-service.spec.ts b/packages/modules/inventory/integration-tests/__tests__/inventory-module-service.spec.ts index 6a229bcec2..209da6c513 100644 --- a/packages/modules/inventory/integration-tests/__tests__/inventory-module-service.spec.ts +++ b/packages/modules/inventory/integration-tests/__tests__/inventory-module-service.spec.ts @@ -1,4 +1,4 @@ -import { IInventoryService, InventoryItemDTO } from "@medusajs/framework/types" +import { IInventoryService, InventoryItemDTO, ReservationItemDTO } from "@medusajs/framework/types" import { BigNumber, CommonEvents, @@ -552,6 +552,105 @@ moduleIntegrationTestRunner({ }) }) + describe("deleteReservationItems", () => { + let inventoryItem: InventoryItemDTO + let reservationItems: ReservationItemDTO[] + + beforeEach(async () => { + inventoryItem = await service.createInventoryItems({ + sku: "test-sku", + origin_country: "test-country", + }) + + await service.createInventoryLevels([ + { + inventory_item_id: inventoryItem.id, + location_id: "location-1", + stocked_quantity: 2, + }, + { + inventory_item_id: inventoryItem.id, + location_id: "location-2", + stocked_quantity: 2, + }, + ]) + + reservationItems = await service.createReservationItems([ + { + inventory_item_id: inventoryItem.id, + location_id: "location-1", + quantity: 2, + }, + { + inventory_item_id: inventoryItem.id, + location_id: "location-2", + quantity: 2, + }, + ]) + }) + + it("should delete reservation items by id", async () => { + const reservationItemsPreDeleted = await service.listReservationItems( + { + id: reservationItems.map((item) => item.id), + } + ) + + expect(reservationItemsPreDeleted).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + inventory_item_id: inventoryItem.id, + location_id: "location-1", + quantity: 2, + }), + expect.objectContaining({ + inventory_item_id: inventoryItem.id, + location_id: "location-2", + quantity: 2, + }), + ]) + ) + expect(reservationItemsPreDeleted).toHaveLength(2) + + await service.deleteReservationItems( + reservationItems.map((item) => item.id) + ) + + const reservationItemsPostDeleted = + await service.listReservationItems({ + id: reservationItems.map((item) => item.id), + }) + + expect(reservationItemsPostDeleted).toEqual([]) + }) + + it("should adjust inventory levels accordingly when removing reservations by id", async () => { + const reservationItem = (await service.listReservationItems({ location_id: "location-1"}))[0] + + const inventoryLevelBefore = + await service.retrieveInventoryLevelByItemAndLocation( + inventoryItem.id, + "location-1" + ) + + expect(inventoryLevelBefore).toEqual( + expect.objectContaining({ reserved_quantity: 2 }) + ) + + await service.deleteReservationItems(reservationItem.id) + + const inventoryLevelAfter = + await service.retrieveInventoryLevelByItemAndLocation( + inventoryItem.id, + "location-1" + ) + + expect(inventoryLevelAfter).toEqual( + expect.objectContaining({ reserved_quantity: 0 }) + ) + }) + }) + describe("deleteReservationItemsByLineItem", () => { let inventoryItem: InventoryItemDTO diff --git a/packages/modules/inventory/src/services/inventory-module.ts b/packages/modules/inventory/src/services/inventory-module.ts index 19f9272934..befcb07f3f 100644 --- a/packages/modules/inventory/src/services/inventory-module.ts +++ b/packages/modules/inventory/src/services/inventory-module.ts @@ -711,6 +711,32 @@ export default class InventoryModuleService return result } + @InjectManager() + @EmitEvents() + // @ts-expect-error + async deleteReservationItems( + ids: string | string[], + @MedusaContext() context: Context = {} + ): Promise { + return await this.deleteReservationItems_(ids, context) + } + + @InjectTransactionManager() + protected async deleteReservationItems_( + ids: string | string[], + @MedusaContext() context: Context = {} + ): Promise { + const reservations: InventoryTypes.ReservationItemDTO[] = + await this.reservationItemService_.list({ id: ids }, {}, context) + + await super.deleteReservationItems(ids, context) + + await this.adjustInventoryLevelsForReservationsDeletion( + reservations, + context + ) + } + @InjectManager() @EmitEvents() // @ts-expect-error