refactor: rename Recipient to Person, remove username, add userId FK

This commit is contained in:
2026-06-16 10:04:24 +02:00
parent befe1f3f82
commit d67f31cf54
27 changed files with 751 additions and 628 deletions
@@ -2,7 +2,7 @@ import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest"
import type { PrismaClient } from "@/generated/prisma/client"
import {
createTestItem,
createTestRecipient,
createTestPerson,
createTestUser,
} from "../helpers/factories"
import {
@@ -80,7 +80,7 @@ describe("asset use-cases", () => {
it("creates an assigned asset with assignment and ASSIGNMENT movement", async () => {
const actor = await createTestUser(prisma)
const recipient = await createTestRecipient(prisma)
const recipient = await createTestPerson(prisma)
const item = await createTestItem(prisma, { stock: 0 })
const result = await createAssetUseCase({
@@ -133,7 +133,7 @@ describe("asset use-cases", () => {
it("moves an available asset to assigned and back to available", async () => {
const actor = await createTestUser(prisma)
const recipient = await createTestRecipient(prisma)
const recipient = await createTestPerson(prisma)
const item = await createTestItem(prisma, { stock: 0 })
const created = await createAssetUseCase({
@@ -230,7 +230,7 @@ describe("asset use-cases", () => {
it("returns an active assignment without restoring stock when an assigned asset moves to a terminal status", async () => {
const actor = await createTestUser(prisma)
const recipient = await createTestRecipient(prisma)
const recipient = await createTestPerson(prisma)
const item = await createTestItem(prisma, { stock: 0 })
const created = await createAssetUseCase({
@@ -2,7 +2,7 @@ import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest"
import type { PrismaClient } from "@/generated/prisma/client"
import {
createTestItem,
createTestRecipient,
createTestPerson,
createTestUser,
} from "../helpers/factories"
import {
@@ -38,7 +38,7 @@ afterAll(async () => {
describe("assignment use-cases", () => {
it("creates an assignment, decrements stock, and records an ASSIGNMENT movement", async () => {
const actor = await createTestUser(prisma)
const recipient = await createTestRecipient(prisma)
const recipient = await createTestPerson(prisma)
const item = await createTestItem(prisma, { stock: 5 })
const assignmentDate = new Date("2026-01-01T00:00:00.000Z")
@@ -88,7 +88,7 @@ describe("assignment use-cases", () => {
it("rejects assignment creation when item stock is insufficient", async () => {
const actor = await createTestUser(prisma)
const recipient = await createTestRecipient(prisma)
const recipient = await createTestPerson(prisma)
const item = await createTestItem(prisma, { stock: 1 })
const result = await createAssignmentUseCase({
@@ -114,7 +114,7 @@ describe("assignment use-cases", () => {
it("returns an assignment, restores stock, closes it, and records a RETURN movement", async () => {
const actor = await createTestUser(prisma)
const recipient = await createTestRecipient(prisma)
const recipient = await createTestPerson(prisma)
const item = await createTestItem(prisma, { stock: 4 })
const created = await createAssignmentUseCase({
@@ -172,7 +172,7 @@ describe("assignment use-cases", () => {
it("rejects returning the same assignment twice", async () => {
const actor = await createTestUser(prisma)
const recipient = await createTestRecipient(prisma)
const recipient = await createTestPerson(prisma)
const item = await createTestItem(prisma, { stock: 2 })
const created = await createAssignmentUseCase({
@@ -0,0 +1,190 @@
import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest"
import type { PrismaClient } from "@/generated/prisma/client"
import { createTestPerson, createTestUser } from "../helpers/factories"
import {
resetIntegrationTestDatabase,
startIntegrationTestDatabase,
stopIntegrationTestDatabase,
} from "../helpers/test-db"
let prisma: PrismaClient
let createPersonUseCase: typeof import("@/use-cases/person.use-cases").createPersonUseCase
let updatePersonUseCase: typeof import("@/use-cases/person.use-cases").updatePersonUseCase
beforeAll(async () => {
await startIntegrationTestDatabase()
const prismaModule = await import("@/lib/prisma")
const personUseCases = await import("@/use-cases/person.use-cases")
prisma = prismaModule.prisma
createPersonUseCase = personUseCases.createPersonUseCase
updatePersonUseCase = personUseCases.updatePersonUseCase
})
beforeEach(async () => {
await resetIntegrationTestDatabase(prisma)
})
afterAll(async () => {
await prisma?.$disconnect()
await stopIntegrationTestDatabase()
})
describe("person use-cases", () => {
it("creates a person and normalizes empty optional contact fields to null", async () => {
await expect(
createPersonUseCase({
firstName: "Person",
lastName: "One",
department: "IT",
email: "",
phone: "",
}),
).resolves.toEqual({ success: true })
await expect(
prisma.person.findFirstOrThrow({
where: { firstName: "Person", lastName: "One" },
}),
).resolves.toMatchObject({
firstName: "Person",
lastName: "One",
department: "IT",
email: null,
phone: null,
userId: null,
})
})
it("creates a person with linked userId", async () => {
const user = await createTestUser(prisma)
await expect(
createPersonUseCase({
firstName: "Linked",
lastName: "Person",
department: "ENGINEERING",
email: "linked@example.test",
phone: null,
userId: user.id,
}),
).resolves.toEqual({ success: true })
await expect(
prisma.person.findFirstOrThrow({
where: { firstName: "Linked" },
}),
).resolves.toMatchObject({
firstName: "Linked",
lastName: "Person",
department: "ENGINEERING",
email: "linked@example.test",
userId: user.id,
})
})
it("rejects duplicate emails on create", async () => {
await createTestPerson(prisma, {
email: "existing@example.test",
})
await expect(
createPersonUseCase({
firstName: "Duplicate",
lastName: "Email",
department: "OTHER",
email: "existing@example.test",
phone: null,
}),
).resolves.toEqual({
success: false,
errors: { email: ["Email already exists"] },
})
await expect(prisma.person.count()).resolves.toBe(1)
})
it("updates a person and rejects duplicate emails", async () => {
const person = await createTestPerson(prisma, {
email: "person@example.test",
phone: "111111111",
})
const other = await createTestPerson(prisma, {
email: "other@example.test",
})
await expect(
updatePersonUseCase({
id: person.id,
firstName: "Edited",
lastName: "Person",
department: "ENGINEERING",
email: "edited@example.test",
phone: "222222222",
}),
).resolves.toEqual({ success: true })
await expect(
prisma.person.findUniqueOrThrow({ where: { id: person.id } }),
).resolves.toMatchObject({
firstName: "Edited",
lastName: "Person",
department: "ENGINEERING",
email: "edited@example.test",
phone: "222222222",
})
await expect(
updatePersonUseCase({
id: person.id,
firstName: "Edited",
lastName: "Person",
department: "ENGINEERING",
email: other.email,
phone: "222222222",
}),
).resolves.toEqual({
success: false,
errors: { email: ["Email already exists"] },
})
await expect(
prisma.person.findUniqueOrThrow({ where: { id: person.id } }),
).resolves.toMatchObject({
email: "edited@example.test",
})
await expect(prisma.person.count()).resolves.toBe(2)
})
it("searches by email and name in paginated results", async () => {
await createTestPerson(prisma, {
firstName: "Alice",
lastName: "Smith",
email: "alice@company.com",
})
await createTestPerson(prisma, {
firstName: "Bob",
lastName: "Jones",
email: "bob@other.com",
})
const { PersonService } = await import("@/services/person.service")
const emailResults = await PersonService.findAllPaginated({
search: "company",
page: 1,
pageSize: 10,
})
expect(emailResults.data).toHaveLength(1)
expect(emailResults.data[0].firstName).toBe("Alice")
const nameResults = await PersonService.findAllPaginated({
search: "Bob",
page: 1,
pageSize: 10,
})
expect(nameResults.data).toHaveLength(1)
expect(nameResults.data[0].firstName).toBe("Bob")
})
})
@@ -1,170 +0,0 @@
import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest"
import type { PrismaClient } from "@/generated/prisma/client"
import { createTestRecipient } from "../helpers/factories"
import {
resetIntegrationTestDatabase,
startIntegrationTestDatabase,
stopIntegrationTestDatabase,
} from "../helpers/test-db"
let prisma: PrismaClient
let createRecipientUseCase: typeof import("@/use-cases/recipient.use-cases").createRecipientUseCase
let updateRecipientUseCase: typeof import("@/use-cases/recipient.use-cases").updateRecipientUseCase
beforeAll(async () => {
await startIntegrationTestDatabase()
const prismaModule = await import("@/lib/prisma")
const recipientUseCases = await import("@/use-cases/recipient.use-cases")
prisma = prismaModule.prisma
createRecipientUseCase = recipientUseCases.createRecipientUseCase
updateRecipientUseCase = recipientUseCases.updateRecipientUseCase
})
beforeEach(async () => {
await resetIntegrationTestDatabase(prisma)
})
afterAll(async () => {
await prisma?.$disconnect()
await stopIntegrationTestDatabase()
})
describe("recipient use-cases", () => {
it("creates a recipient and normalizes empty optional contact fields to null", async () => {
await expect(
createRecipientUseCase({
username: "recipient-one",
firstName: "Recipient",
lastName: "One",
department: "IT",
email: "",
phone: "",
}),
).resolves.toEqual({ success: true })
await expect(
prisma.recipient.findUniqueOrThrow({
where: { username: "recipient-one" },
}),
).resolves.toMatchObject({
username: "recipient-one",
firstName: "Recipient",
lastName: "One",
department: "IT",
email: null,
phone: null,
})
})
it("rejects duplicate usernames and duplicate emails on create", async () => {
await createTestRecipient(prisma, {
username: "existing-recipient",
email: "existing-recipient@example.test",
})
await expect(
createRecipientUseCase({
username: "existing-recipient",
firstName: "Duplicate",
lastName: "Username",
department: "OTHER",
email: "unique-recipient@example.test",
phone: null,
}),
).resolves.toEqual({
success: false,
errors: { username: ["Username already exists"] },
})
await expect(
createRecipientUseCase({
username: "unique-recipient",
firstName: "Duplicate",
lastName: "Email",
department: "OTHER",
email: "existing-recipient@example.test",
phone: null,
}),
).resolves.toEqual({
success: false,
errors: { email: ["Email already exists"] },
})
await expect(prisma.recipient.count()).resolves.toBe(1)
})
it("updates a recipient and rejects duplicate usernames or emails", async () => {
const recipient = await createTestRecipient(prisma, {
username: "editable-recipient",
email: "editable-recipient@example.test",
phone: "111111111",
})
const other = await createTestRecipient(prisma, {
username: "other-recipient",
email: "other-recipient@example.test",
})
await expect(
updateRecipientUseCase({
id: recipient.id,
username: "edited-recipient",
firstName: "Edited",
lastName: "Recipient",
department: "ENGINEERING",
email: "edited-recipient@example.test",
phone: "222222222",
}),
).resolves.toEqual({ success: true })
await expect(
prisma.recipient.findUniqueOrThrow({ where: { id: recipient.id } }),
).resolves.toMatchObject({
username: "edited-recipient",
firstName: "Edited",
lastName: "Recipient",
department: "ENGINEERING",
email: "edited-recipient@example.test",
phone: "222222222",
})
await expect(
updateRecipientUseCase({
id: recipient.id,
username: other.username,
firstName: "Edited",
lastName: "Recipient",
department: "ENGINEERING",
email: "new-recipient@example.test",
phone: "222222222",
}),
).resolves.toEqual({
success: false,
errors: { username: ["Username already exists"] },
})
await expect(
updateRecipientUseCase({
id: recipient.id,
username: "edited-recipient",
firstName: "Edited",
lastName: "Recipient",
department: "ENGINEERING",
email: other.email,
phone: "222222222",
}),
).resolves.toEqual({
success: false,
errors: { email: ["Email already exists"] },
})
await expect(
prisma.recipient.findUniqueOrThrow({ where: { id: recipient.id } }),
).resolves.toMatchObject({
username: "edited-recipient",
email: "edited-recipient@example.test",
})
await expect(prisma.recipient.count()).resolves.toBe(2)
})
})