feat(assignments): emit ADJUSTMENT movement on quantity change

This commit is contained in:
2026-06-25 16:58:03 +02:00
parent 1142855578
commit 91dc0220ae
2 changed files with 262 additions and 1 deletions
@@ -408,4 +408,240 @@ describe("assignment use-cases", () => {
expect(await prisma.inventoryMovement.count()).toBe(2)
})
it("writes an ADJUSTMENT movement with stockDelta=+1 when quantity decreases on an OPEN assignment", async () => {
const actor = await createTestUser(prisma)
const person = await createTestPerson(prisma)
const item = await createTestItem(prisma, { stock: 5 })
const created = await createAssignmentUseCase({
actorId: actor.id,
personId: person.id,
lines: [{ itemId: item.id, quantity: 2 }],
})
expect(created.success).toBe(true)
if (!created.success) throw new Error("Expected assignment creation success")
const updated = await updateAssignmentUseCase({
actorId: actor.id,
id: created.assignmentId,
personId: person.id,
lines: [{ itemId: item.id, quantity: 1 }],
})
expect(updated.success).toBe(true)
if (!updated.success) throw new Error("Expected assignment update success")
const [updatedItem, movements] = await Promise.all([
prisma.item.findUniqueOrThrow({ where: { id: item.id } }),
prisma.inventoryMovement.findMany({
include: { stockLines: true },
orderBy: [{ createdAt: "asc" }, { id: "asc" }],
}),
])
expect(updatedItem.stock).toBe(4)
const adjustment = movements.find((movement) => movement.type === "ADJUSTMENT")
expect(adjustment).toBeDefined()
expect(adjustment).toMatchObject({
type: "ADJUSTMENT",
reason: "INVENTORY_CORRECTION",
})
expect(adjustment?.stockLines[0]).toMatchObject({
itemId: item.id,
stockDelta: 1,
previousStock: 3,
newStock: 4,
})
})
it("writes an ADJUSTMENT movement with stockDelta=+2 when quantity decreases on a PARTIALLY_RETURNED assignment", async () => {
const actor = await createTestUser(prisma)
const person = await createTestPerson(prisma)
const item = await createTestItem(prisma, { stock: 5 })
const created = await createAssignmentUseCase({
actorId: actor.id,
personId: person.id,
lines: [{ itemId: item.id, quantity: 3 }],
})
expect(created.success).toBe(true)
if (!created.success) throw new Error("Expected assignment creation success")
const createdAssignment = await prisma.assignment.findUniqueOrThrow({
where: { id: created.assignmentId },
include: { stockLines: true },
})
const partialReturn = await returnAssignmentUseCase({
id: created.assignmentId,
actorId: actor.id,
returns: [
{
assignmentLineId: createdAssignment.stockLines[0].id,
quantity: 1,
},
],
})
expect(partialReturn).toEqual({ success: true })
const updated = await updateAssignmentUseCase({
actorId: actor.id,
id: created.assignmentId,
personId: person.id,
lines: [{ itemId: item.id, quantity: 1 }],
})
expect(updated.success).toBe(true)
if (!updated.success) throw new Error("Expected assignment update success")
const [updatedItem, movements] = await Promise.all([
prisma.item.findUniqueOrThrow({ where: { id: item.id } }),
prisma.inventoryMovement.findMany({
include: { stockLines: true },
orderBy: [{ createdAt: "asc" }, { id: "asc" }],
}),
])
expect(updatedItem.stock).toBe(5)
const adjustment = movements.find((movement) => movement.type === "ADJUSTMENT")
expect(adjustment).toBeDefined()
expect(adjustment).toMatchObject({
type: "ADJUSTMENT",
reason: "INVENTORY_CORRECTION",
})
expect(adjustment?.stockLines[0]).toMatchObject({
itemId: item.id,
stockDelta: 2,
previousStock: 3,
newStock: 5,
})
})
it("writes an ADJUSTMENT movement with stockDelta=-1 when quantity increases on an OPEN assignment", async () => {
const actor = await createTestUser(prisma)
const person = await createTestPerson(prisma)
const item = await createTestItem(prisma, { stock: 5 })
const created = await createAssignmentUseCase({
actorId: actor.id,
personId: person.id,
lines: [{ itemId: item.id, quantity: 2 }],
})
expect(created.success).toBe(true)
if (!created.success) throw new Error("Expected assignment creation success")
const updated = await updateAssignmentUseCase({
actorId: actor.id,
id: created.assignmentId,
personId: person.id,
lines: [{ itemId: item.id, quantity: 3 }],
})
expect(updated.success).toBe(true)
if (!updated.success) throw new Error("Expected assignment update success")
const [updatedItem, movements] = await Promise.all([
prisma.item.findUniqueOrThrow({ where: { id: item.id } }),
prisma.inventoryMovement.findMany({
include: { stockLines: true },
orderBy: [{ createdAt: "asc" }, { id: "asc" }],
}),
])
expect(updatedItem.stock).toBe(2)
const adjustment = movements.find((movement) => movement.type === "ADJUSTMENT")
expect(adjustment).toBeDefined()
expect(adjustment).toMatchObject({
type: "ADJUSTMENT",
reason: "INVENTORY_CORRECTION",
})
expect(adjustment?.stockLines[0]).toMatchObject({
itemId: item.id,
stockDelta: -1,
previousStock: 3,
newStock: 2,
})
})
it("rejects a quantity change on a RETURNED assignment and writes no movement", async () => {
const actor = await createTestUser(prisma)
const person = await createTestPerson(prisma)
const item = await createTestItem(prisma, { stock: 5 })
const created = await createAssignmentUseCase({
actorId: actor.id,
personId: person.id,
lines: [{ itemId: item.id, quantity: 2 }],
})
expect(created.success).toBe(true)
if (!created.success) throw new Error("Expected assignment creation success")
const returned = await returnAssignmentUseCase({
id: created.assignmentId,
actorId: actor.id,
})
expect(returned).toEqual({ success: true })
const movementsBefore = await prisma.inventoryMovement.count()
const updated = await updateAssignmentUseCase({
actorId: actor.id,
id: created.assignmentId,
personId: person.id,
lines: [{ itemId: item.id, quantity: 3 }],
})
expect(updated).toEqual({
success: false,
errors: { id: ["Assignment is closed"] },
})
const [assignment, movements] = await Promise.all([
prisma.assignment.findUniqueOrThrow({
where: { id: created.assignmentId },
include: { stockLines: true },
}),
prisma.inventoryMovement.findMany({
include: { stockLines: true },
}),
])
expect(assignment.stockLines[0].quantity).toBe(2)
expect(movements).toHaveLength(movementsBefore)
expect(movements.some((movement) => movement.type === "ADJUSTMENT")).toBe(false)
})
it("writes no movement when quantity is unchanged on an OPEN assignment", async () => {
const actor = await createTestUser(prisma)
const person = await createTestPerson(prisma)
const item = await createTestItem(prisma, { stock: 5 })
const created = await createAssignmentUseCase({
actorId: actor.id,
personId: person.id,
lines: [{ itemId: item.id, quantity: 2 }],
})
expect(created.success).toBe(true)
if (!created.success) throw new Error("Expected assignment creation success")
const movementsBefore = await prisma.inventoryMovement.count()
const updated = await updateAssignmentUseCase({
actorId: actor.id,
id: created.assignmentId,
personId: person.id,
lines: [{ itemId: item.id, quantity: 2 }],
})
expect(updated).toEqual({ success: true })
const [updatedItem, movements] = await Promise.all([
prisma.item.findUniqueOrThrow({ where: { id: item.id } }),
prisma.inventoryMovement.findMany({
include: { stockLines: true },
orderBy: [{ createdAt: "asc" }, { id: "asc" }],
}),
])
expect(updatedItem.stock).toBe(3)
expect(movements).toHaveLength(movementsBefore)
expect(movements.some((movement) => movement.type === "ADJUSTMENT")).toBe(false)
})
})