import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest" import type { PrismaClient } from "@/generated/prisma/client" import { createTestCategory, createTestItem, createTestPerson, createTestUser, } from "../helpers/factories" import { resetIntegrationTestDatabase, startIntegrationTestDatabase, stopIntegrationTestDatabase, } from "../helpers/test-db" let prisma: PrismaClient let createAssignmentUseCase: typeof import("@/use-cases/assignment.use-cases").createAssignmentUseCase let updateAssignmentUseCase: typeof import("@/use-cases/assignment.use-cases").updateAssignmentUseCase let returnAssignmentUseCase: typeof import("@/use-cases/assignment.use-cases").returnAssignmentUseCase beforeAll(async () => { await startIntegrationTestDatabase() const prismaModule = await import("@/lib/prisma") const assignmentUseCases = await import("@/use-cases/assignment.use-cases") prisma = prismaModule.prisma createAssignmentUseCase = assignmentUseCases.createAssignmentUseCase updateAssignmentUseCase = assignmentUseCases.updateAssignmentUseCase returnAssignmentUseCase = assignmentUseCases.returnAssignmentUseCase }) beforeEach(async () => { await resetIntegrationTestDatabase(prisma) }) afterAll(async () => { await prisma?.$disconnect() await stopIntegrationTestDatabase() }) describe("assignment use-cases", () => { it("creates a SERIALIZED assignment without writing a stock line", async () => { const actor = await createTestUser(prisma) const person = await createTestPerson(prisma) const category = await createTestCategory(prisma) const item = await prisma.item.create({ data: { sku: "SERIALIZED-ASSIGNMENT-SKU", name: "Serial Item", trackingType: "SERIALIZED", stock: 0, category: { connect: { id: category.id } }, }, }) const asset = await prisma.asset.create({ data: { serialNumber: "SERIALIZED-ASSIGNMENT-ASSET-001", itemId: item.id, status: "AVAILABLE", }, }) const result = await createAssignmentUseCase({ actorId: actor.id, personId: person.id, assetId: asset.id, lines: [{ itemId: item.id, quantity: 1 }], }) expect(result.success).toBe(true) if (!result.success) throw new Error("Expected assignment creation success") const [updatedItem, updatedAsset, movements] = await Promise.all([ prisma.item.findUniqueOrThrow({ where: { id: item.id } }), prisma.asset.findUniqueOrThrow({ where: { id: asset.id } }), prisma.inventoryMovement.findMany({ include: { stockLines: true, assetLines: true }, orderBy: [{ createdAt: "asc" }, { id: "asc" }], }), ]) expect(updatedItem.stock).toBe(0) expect(updatedAsset.status).toBe("ASSIGNED") expect(movements).toHaveLength(1) expect(movements[0].stockLines).toEqual([]) expect(movements[0].assetLines).toHaveLength(1) expect(movements[0].assetLines[0]).toMatchObject({ assetId: asset.id, }) }) it("creates an assignment, decrements stock, and records an ASSIGNMENT movement", async () => { const actor = await createTestUser(prisma) const person = await createTestPerson(prisma) const item = await createTestItem(prisma, { stock: 5 }) const assignmentDate = new Date("2026-01-01T00:00:00.000Z") const result = await createAssignmentUseCase({ actorId: actor.id, personId: person.id, assignmentDate, notes: "Initial assignment", lines: [{ itemId: item.id, quantity: 2 }], }) expect(result.success).toBe(true) if (!result.success) throw new Error("Expected assignment creation success") const [updatedItem, assignment, movements] = await Promise.all([ prisma.item.findUniqueOrThrow({ where: { id: item.id } }), prisma.assignment.findUniqueOrThrow({ where: { id: result.assignmentId }, include: { stockLines: true }, }), prisma.inventoryMovement.findMany({ include: { stockLines: true }, orderBy: [{ createdAt: "asc" }, { id: "asc" }], }), ]) expect(updatedItem.stock).toBe(3) expect(assignment).toMatchObject({ personId: person.id, notes: "Initial assignment", createdById: actor.id, closedAt: null, }) expect(assignment.assignedAt).toEqual(assignmentDate) expect(assignment.stockLines[0]).toMatchObject({ itemId: item.id, quantity: 2, returnedQuantity: 0, }) expect(movements).toHaveLength(1) expect(movements[0]).toMatchObject({ type: "ASSIGNMENT", assignmentId: result.assignmentId, performedById: actor.id, }) expect(movements[0].stockLines[0]).toMatchObject({ itemId: item.id, stockDelta: -2, previousStock: 5, newStock: 3, }) }) it("updates an assignment from a quantity line DTO and keeps the edited line quantity", 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) const assignment = await prisma.assignment.findUniqueOrThrow({ where: { id: created.assignmentId }, include: { stockLines: true }, }) expect(assignment.stockLines).toHaveLength(1) expect(assignment.stockLines[0]).toMatchObject({ itemId: item.id, quantity: 1, }) }) it("rejects assignment creation when item stock is insufficient", async () => { const actor = await createTestUser(prisma) const person = await createTestPerson(prisma) const item = await createTestItem(prisma, { stock: 1 }) const result = await createAssignmentUseCase({ actorId: actor.id, itemId: item.id, personId: person.id, quantity: 2, }) expect(result).toEqual({ success: false, errors: { quantity: ["Item does not have enough stock"], }, }) expect( await prisma.item.findUniqueOrThrow({ where: { id: item.id } }), ).toMatchObject({ stock: 1, }) expect(await prisma.assignment.count()).toBe(0) expect(await prisma.inventoryMovement.count()).toBe(0) }) it("returns an assignment, restores stock, closes it, and records a RETURN movement", async () => { const actor = await createTestUser(prisma) const person = await createTestPerson(prisma) const item = await createTestItem(prisma, { stock: 4 }) const created = await createAssignmentUseCase({ actorId: actor.id, itemId: item.id, personId: person.id, quantity: 3, }) 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 [updatedItem, assignment, movements] = await Promise.all([ prisma.item.findUniqueOrThrow({ where: { id: item.id } }), prisma.assignment.findUniqueOrThrow({ where: { id: created.assignmentId }, include: { stockLines: { include: { returns: true } } }, }), prisma.inventoryMovement.findMany({ include: { stockLines: true }, orderBy: [{ createdAt: "asc" }, { id: "asc" }], }), ]) expect(updatedItem.stock).toBe(4) expect(assignment.closedAt).toBeInstanceOf(Date) expect(assignment).toMatchObject({ personId: person.id, status: "RETURNED", closedById: actor.id, }) expect(assignment.stockLines[0]).toMatchObject({ itemId: item.id, quantity: 3, returnedQuantity: 3, }) expect(assignment.stockLines[0].returns).toHaveLength(1) expect(assignment.stockLines[0].returns[0]).toMatchObject({ quantity: 3, receivedById: actor.id, }) expect(movements).toHaveLength(2) expect(movements[0]).toMatchObject({ type: "ASSIGNMENT", assignmentId: created.assignmentId, performedById: actor.id, }) expect(movements[0].stockLines[0]).toMatchObject({ itemId: item.id, stockDelta: -3, previousStock: 4, newStock: 1, }) expect(movements[1]).toMatchObject({ type: "RETURN", assignmentId: created.assignmentId, performedById: actor.id, }) expect(movements[1].stockLines[0]).toMatchObject({ itemId: item.id, stockDelta: 3, previousStock: 1, newStock: 4, }) }) it("records partial quantity returns before fully closing the 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, itemId: item.id, personId: person.id, quantity: 5, }) expect(created.success).toBe(true) if (!created.success) throw new Error("Expected assignment creation success") const assignment = await prisma.assignment.findUniqueOrThrow({ where: { id: created.assignmentId }, include: { stockLines: true }, }) const firstReturn = await returnAssignmentUseCase({ id: created.assignmentId, actorId: actor.id, returns: [ { assignmentLineId: assignment.stockLines[0].id, quantity: 2, notes: "First return batch", }, ], }) expect(firstReturn).toEqual({ success: true }) const partiallyReturned = await prisma.assignment.findUniqueOrThrow({ where: { id: created.assignmentId }, include: { stockLines: { include: { returns: true } } }, }) expect(partiallyReturned).toMatchObject({ status: "PARTIALLY_RETURNED", closedAt: null, closedById: null, }) expect(partiallyReturned.stockLines[0]).toMatchObject({ quantity: 5, returnedQuantity: 2, }) expect(partiallyReturned.stockLines[0].returns).toHaveLength(1) expect(partiallyReturned.stockLines[0].returns[0]).toMatchObject({ quantity: 2, receivedById: actor.id, notes: "First return batch", }) const secondReturn = await returnAssignmentUseCase({ id: created.assignmentId, actorId: actor.id, returns: [ { assignmentLineId: assignment.stockLines[0].id, quantity: 3, }, ], }) expect(secondReturn).toEqual({ success: true }) const fullyReturned = await prisma.assignment.findUniqueOrThrow({ where: { id: created.assignmentId }, include: { stockLines: { include: { returns: true } } }, }) expect(fullyReturned).toMatchObject({ status: "RETURNED", closedById: actor.id, }) expect(fullyReturned.closedAt).toBeInstanceOf(Date) expect(fullyReturned.stockLines[0]).toMatchObject({ quantity: 5, returnedQuantity: 5, }) expect(fullyReturned.stockLines[0].returns).toHaveLength(2) expect( await prisma.item.findUniqueOrThrow({ where: { id: item.id } }), ).toMatchObject({ stock: 5, }) }) it("rejects returning the same assignment twice", async () => { const actor = await createTestUser(prisma) const person = await createTestPerson(prisma) const item = await createTestItem(prisma, { stock: 2 }) const created = await createAssignmentUseCase({ actorId: actor.id, itemId: item.id, personId: person.id, quantity: 1, }) expect(created.success).toBe(true) if (!created.success) throw new Error("Expected assignment creation success") await expect( returnAssignmentUseCase({ id: created.assignmentId, actorId: actor.id }), ).resolves.toEqual({ success: true }) await expect( returnAssignmentUseCase({ id: created.assignmentId, actorId: actor.id }), ).resolves.toEqual({ success: false, errors: { id: ["Assignment already returned"] }, }) expect(await prisma.inventoryMovement.count()).toBe(2) }) })