Skip to content

Commit

Permalink
Merge branch 'develop' into feat/admin-extended-stores-res
Browse files Browse the repository at this point in the history
  • Loading branch information
patrick-medusajs authored Mar 15, 2023
2 parents d1a2421 + 10bf05c commit 72cde2f
Show file tree
Hide file tree
Showing 8 changed files with 155 additions and 9 deletions.
6 changes: 6 additions & 0 deletions .changeset/many-papayas-judge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@medusajs/inventory": patch
"@medusajs/medusa": patch
---

Fix(inventory, medusa): ensure no orphaned reservations and invenotry levels on location removal
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,71 @@ describe("Inventory Items endpoints", () => {
})
})

it("When deleting an inventory item it removes associated levels and reservations", async () => {
const api = useApi()
const inventoryService = appContainer.resolve("inventoryService")

const invItem2 = await inventoryService.createInventoryItem({
sku: "1234567",
})

const stockRes = await api.post(
`/admin/stock-locations`,
{
name: "Fake Warehouse 1",
},
adminHeaders
)

locationId = stockRes.data.stock_location.id

const level = await inventoryService.createInventoryLevel({
inventory_item_id: invItem2.id,
location_id: locationId,
stocked_quantity: 10,
})

const reservation = await inventoryService.createReservationItem({
inventory_item_id: invItem2.id,
location_id: locationId,
quantity: 5,
})

const [, reservationCount] = await inventoryService.listReservationItems({
location_id: locationId,
})

expect(reservationCount).toEqual(1)

const [, inventoryLevelCount] =
await inventoryService.listInventoryLevels({
location_id: locationId,
})

expect(inventoryLevelCount).toEqual(1)

const res = await api.delete(
`/admin/stock-locations/${locationId}`,
adminHeaders
)

expect(res.status).toEqual(200)

const [, reservationCountPostDelete] =
await inventoryService.listReservationItems({
location_id: locationId,
})

expect(reservationCountPostDelete).toEqual(0)

const [, inventoryLevelCountPostDelete] =
await inventoryService.listInventoryLevels({
location_id: locationId,
})

expect(inventoryLevelCountPostDelete).toEqual(0)
})

it("When deleting an inventory item it removes the product variants associated to it", async () => {
const api = useApi()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const LocationCard: React.FC<Props> = ({ location }) => {
const onDelete = async () => {
const shouldDelete = await dialog({
heading: "Delete Location",
text: "Are you sure you want to delete this location",
text: "Are you sure you want to delete this location. This will also delete all inventory levels and reservations associated with this location.",
extraConfirmation: true,
entityName: location.name,
})
Expand Down
18 changes: 18 additions & 0 deletions packages/inventory/src/services/inventory-level.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,24 @@ export default class InventoryLevelService extends TransactionBaseService {
})
}

/**
* Deletes inventory levels by location ID.
* @param locationId - The ID of the location to delete inventory levels for.
*/
async deleteByLocationId(locationId: string): Promise<void> {
return await this.atomicPhase_(async (manager) => {
const levelRepository = manager.getRepository(InventoryLevel)

await levelRepository.delete({ location_id: locationId })

await this.eventBusService_
.withTransaction(manager)
.emit(InventoryLevelService.Events.DELETED, {
location_id: locationId,
})
})
}

/**
* Gets the total stocked quantity for a specific inventory item at multiple locations.
* @param inventoryItemId - The ID of the inventory item.
Expand Down
12 changes: 12 additions & 0 deletions packages/inventory/src/services/inventory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,18 @@ export default class InventoryService
.delete(inventoryItemId)
}

async deleteInventoryItemLevelByLocationId(locationId: string): Promise<void> {
return await this.inventoryLevelService_
.withTransaction(this.activeManager_)
.deleteByLocationId(locationId)
}

async deleteReservationItemByLocationId(locationId: string): Promise<void> {
return await this.reservationItemService_
.withTransaction(this.activeManager_)
.deleteByLocationId(locationId)
}

/**
* Deletes an inventory level
* @param inventoryItemId - the id of the inventory item associated with the level
Expand Down
38 changes: 31 additions & 7 deletions packages/inventory/src/services/reservation-item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ export default class ReservationItemService extends TransactionBaseService {
CREATED: "reservation-item.created",
UPDATED: "reservation-item.updated",
DELETED: "reservation-item.deleted",
DELETED_BY_LINE_ITEM: "reservation-item.deleted-by-line-item",
}

protected readonly eventBusService_: IEventBusService
Expand Down Expand Up @@ -95,7 +94,10 @@ export default class ReservationItemService extends TransactionBaseService {
const manager = this.activeManager_
const reservationItemRepository = manager.getRepository(ReservationItem)

const query = buildQuery({ id: reservationItemId }, config) as FindManyOptions
const query = buildQuery(
{ id: reservationItemId },
config
) as FindManyOptions
const [reservationItem] = await reservationItemRepository.find(query)

if (!reservationItem) {
Expand Down Expand Up @@ -165,8 +167,7 @@ export default class ReservationItemService extends TransactionBaseService {
isDefined(data.quantity) && data.quantity !== item.quantity

const shouldUpdateLocation =
isDefined(data.location_id) &&
data.location_id !== item.location_id
isDefined(data.location_id) && data.location_id !== item.location_id

const ops: Promise<unknown>[] = []

Expand Down Expand Up @@ -243,12 +244,35 @@ export default class ReservationItemService extends TransactionBaseService {

await this.eventBusService_
.withTransaction(manager)
.emit(ReservationItemService.Events.DELETED_BY_LINE_ITEM, {
.emit(ReservationItemService.Events.DELETED, {
line_item_id: lineItemId,
})
})
}

/**
* Deletes reservation items by location ID.
* @param locationId - The ID of the location to delete reservations for.
*/
async deleteByLocationId(locationId: string): Promise<void> {
return await this.atomicPhase_(async (manager) => {
const itemRepository = manager.getRepository(ReservationItem)

await itemRepository
.createQueryBuilder("reservation_item")
.softDelete()
.where("location_id = :locationId", { locationId })
.andWhere("deleted_at IS NULL")
.execute()

await this.eventBusService_
.withTransaction(manager)
.emit(ReservationItemService.Events.DELETED, {
location_id: locationId,
})
})
}

/**
* Deletes a reservation item by id.
* @param reservationItemId - the id of the reservation item to delete.
Expand All @@ -272,8 +296,8 @@ export default class ReservationItemService extends TransactionBaseService {
await this.eventBusService_
.withTransaction(manager)
.emit(ReservationItemService.Events.DELETED, {
id: reservationItemId,
})
id: reservationItemId,
})
})
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { EntityManager } from "typeorm"
import { IStockLocationService } from "../../../../interfaces"
import {
IInventoryService,
IStockLocationService,
} from "../../../../interfaces"
import { SalesChannelLocationService } from "../../../../services"

/**
Expand Down Expand Up @@ -60,6 +63,9 @@ export default async (req, res) => {
"stockLocationService"
)

const inventoryService: IInventoryService =
req.scope.resolve("inventoryService")

const salesChannelLocationService: SalesChannelLocationService =
req.scope.resolve("salesChannelLocationService")

Expand All @@ -70,6 +76,17 @@ export default async (req, res) => {
.removeLocation(id)

await stockLocationService.withTransaction(transactionManager).delete(id)

if (inventoryService) {
await Promise.all([
inventoryService
.withTransaction(transactionManager)
.deleteInventoryItemLevelByLocationId(id),
inventoryService
.withTransaction(transactionManager)
.deleteReservationItemByLocationId(id),
])
}
})

res.status(200).send({
Expand Down
4 changes: 4 additions & 0 deletions packages/medusa/src/interfaces/services/inventory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ export interface IInventoryService {

deleteInventoryItem(inventoryItemId: string): Promise<void>

deleteInventoryItemLevelByLocationId(locationId: string): Promise<void>

deleteReservationItemByLocationId(locationId: string): Promise<void>

deleteInventoryLevel(
inventoryLevelId: string,
locationId: string
Expand Down

0 comments on commit 72cde2f

Please sign in to comment.