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