feat(items): emit ISSUE/ADJUSTMENT movement on stock decrease

This commit is contained in:
2026-06-25 03:03:26 +02:00
parent 0d38626f3a
commit 575cd2d9a0
4 changed files with 153 additions and 2 deletions
@@ -179,6 +179,112 @@ describe("item use-cases", () => {
})
})
it("writes an ISSUE movement when a QUANTITY item stock decreases to zero", async () => {
const actor = await createTestUser(prisma)
const category = await createTestCategory(prisma)
const created = await createItemUseCase({
actorId: actor.id,
name: "Cable",
categoryId: category.id,
stock: 1,
})
expect(created).toEqual({ success: true })
const item = await prisma.item.findUniqueOrThrow({
where: { sku: "CABLE" },
})
const updated = await updateItemUseCase({
actorId: actor.id,
id: item.id,
name: "Cable",
categoryId: category.id,
stock: 0,
})
expect(updated).toEqual({ success: true })
const refreshedItem = await prisma.item.findUniqueOrThrow({
where: { id: item.id },
})
expect(refreshedItem.stock).toBe(0)
const movements = await prisma.inventoryMovement.findMany({
where: { stockLines: { some: { itemId: item.id } } },
include: { stockLines: true },
orderBy: [{ createdAt: "asc" }, { id: "asc" }],
})
expect(movements).toHaveLength(2)
expect(movements[1]).toMatchObject({
type: "ISSUE",
performedById: actor.id,
})
expect(movements[1].stockLines[0]).toMatchObject({
itemId: item.id,
stockDelta: -1,
previousStock: 1,
newStock: 0,
})
})
it("writes an ADJUSTMENT movement with INVENTORY_CORRECTION reason when QUANTITY item decreases with reason", async () => {
const actor = await createTestUser(prisma)
const category = await createTestCategory(prisma)
const created = await createItemUseCase({
actorId: actor.id,
name: "Mouse",
categoryId: category.id,
stock: 5,
})
expect(created).toEqual({ success: true })
const item = await prisma.item.findUniqueOrThrow({
where: { sku: "MOUSE" },
})
const updated = await updateItemUseCase({
actorId: actor.id,
id: item.id,
name: "Mouse",
categoryId: category.id,
stock: 4,
reason: "INVENTORY_CORRECTION",
})
expect(updated).toEqual({ success: true })
const refreshedItem = await prisma.item.findUniqueOrThrow({
where: { id: item.id },
})
expect(refreshedItem.stock).toBe(4)
const movements = await prisma.inventoryMovement.findMany({
where: { stockLines: { some: { itemId: item.id } } },
include: { stockLines: true },
orderBy: [{ createdAt: "asc" }, { id: "asc" }],
})
expect(movements).toHaveLength(2)
expect(movements[1]).toMatchObject({
type: "ADJUSTMENT",
reason: "INVENTORY_CORRECTION",
performedById: actor.id,
})
expect(movements[1].stockLines[0]).toMatchObject({
itemId: item.id,
stockDelta: -1,
previousStock: 5,
newStock: 4,
})
})
it("blocks deleting items with stock and soft deletes empty items", async () => {
const actor = await createTestUser(prisma)
const category = await createTestCategory(prisma)