refactor: rename Recipient to Person, remove username, add userId FK
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
CREATE TYPE "UserRole" AS ENUM ('ADMIN', 'MANAGER', 'STAFF', 'VIEWER');
|
CREATE TYPE "UserRole" AS ENUM ('ADMIN', 'MANAGER', 'STAFF', 'VIEWER');
|
||||||
|
|
||||||
-- CreateEnum
|
-- CreateEnum
|
||||||
CREATE TYPE "RecipientDepartment" AS ENUM ('IT', 'ENGINEERING', 'LOGISTICS', 'TRAFFIC', 'DRIVER', 'ADMINISTRATION', 'SALES', 'OTHER');
|
CREATE TYPE "PersonDepartment" AS ENUM ('IT', 'ENGINEERING', 'LOGISTICS', 'TRAFFIC', 'DRIVER', 'ADMINISTRATION', 'SALES', 'OTHER');
|
||||||
|
|
||||||
-- CreateEnum
|
-- CreateEnum
|
||||||
CREATE TYPE "ItemStatus" AS ENUM ('AVAILABLE', 'ASSIGNED', 'RESERVED', 'IN_REPAIR', 'BROKEN', 'STOLEN', 'DISPOSED');
|
CREATE TYPE "ItemStatus" AS ENUM ('AVAILABLE', 'ASSIGNED', 'RESERVED', 'IN_REPAIR', 'BROKEN', 'STOLEN', 'DISPOSED');
|
||||||
@@ -26,19 +26,19 @@ CREATE TABLE "User" (
|
|||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "Recipient" (
|
CREATE TABLE "Person" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"username" TEXT NOT NULL,
|
|
||||||
"firstName" TEXT NOT NULL,
|
"firstName" TEXT NOT NULL,
|
||||||
"lastName" TEXT NOT NULL,
|
"lastName" TEXT NOT NULL,
|
||||||
"department" "RecipientDepartment",
|
"department" "PersonDepartment",
|
||||||
"email" TEXT,
|
"email" TEXT,
|
||||||
"phone" TEXT,
|
"phone" TEXT,
|
||||||
|
"userId" TEXT,
|
||||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
CONSTRAINT "Recipient_pkey" PRIMARY KEY ("id")
|
CONSTRAINT "Person_pkey" PRIMARY KEY ("id")
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
@@ -126,16 +126,16 @@ CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
|
|||||||
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE UNIQUE INDEX "Recipient_username_key" ON "Recipient"("username");
|
CREATE UNIQUE INDEX "Person_email_key" ON "Person"("email");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE UNIQUE INDEX "Recipient_email_key" ON "Recipient"("email");
|
CREATE UNIQUE INDEX "Person_userId_key" ON "Person"("userId");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "Recipient_lastName_firstName_idx" ON "Recipient"("lastName", "firstName");
|
CREATE INDEX "Person_lastName_firstName_idx" ON "Person"("lastName", "firstName");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "Recipient_department_idx" ON "Recipient"("department");
|
CREATE INDEX "Person_department_idx" ON "Person"("department");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE UNIQUE INDEX "Category_name_key" ON "Category"("name");
|
CREATE UNIQUE INDEX "Category_name_key" ON "Category"("name");
|
||||||
@@ -191,6 +191,9 @@ CREATE INDEX "Movement_type_idx" ON "Movement"("type");
|
|||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "Movement_userId_idx" ON "Movement"("userId");
|
CREATE INDEX "Movement_userId_idx" ON "Movement"("userId");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Person" ADD CONSTRAINT "Person_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
ALTER TABLE "Item" ADD CONSTRAINT "Item_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "Category"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
ALTER TABLE "Item" ADD CONSTRAINT "Item_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "Category"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
@@ -204,7 +207,7 @@ ALTER TABLE "Assignment" ADD CONSTRAINT "Assignment_itemId_fkey" FOREIGN KEY ("i
|
|||||||
ALTER TABLE "Assignment" ADD CONSTRAINT "Assignment_assetId_fkey" FOREIGN KEY ("assetId") REFERENCES "Asset"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
ALTER TABLE "Assignment" ADD CONSTRAINT "Assignment_assetId_fkey" FOREIGN KEY ("assetId") REFERENCES "Asset"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
ALTER TABLE "Assignment" ADD CONSTRAINT "Assignment_recipientId_fkey" FOREIGN KEY ("recipientId") REFERENCES "Recipient"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
ALTER TABLE "Assignment" ADD CONSTRAINT "Assignment_recipientId_fkey" FOREIGN KEY ("recipientId") REFERENCES "Person"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
ALTER TABLE "Assignment" ADD CONSTRAINT "Assignment_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
ALTER TABLE "Assignment" ADD CONSTRAINT "Assignment_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
@@ -216,7 +219,7 @@ ALTER TABLE "Movement" ADD CONSTRAINT "Movement_itemId_fkey" FOREIGN KEY ("itemI
|
|||||||
ALTER TABLE "Movement" ADD CONSTRAINT "Movement_assetId_fkey" FOREIGN KEY ("assetId") REFERENCES "Asset"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
ALTER TABLE "Movement" ADD CONSTRAINT "Movement_assetId_fkey" FOREIGN KEY ("assetId") REFERENCES "Asset"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
ALTER TABLE "Movement" ADD CONSTRAINT "Movement_recipientId_fkey" FOREIGN KEY ("recipientId") REFERENCES "Recipient"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
ALTER TABLE "Movement" ADD CONSTRAINT "Movement_recipientId_fkey" FOREIGN KEY ("recipientId") REFERENCES "Person"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
ALTER TABLE "Movement" ADD CONSTRAINT "Movement_assignmentId_fkey" FOREIGN KEY ("assignmentId") REFERENCES "Assignment"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
ALTER TABLE "Movement" ADD CONSTRAINT "Movement_assignmentId_fkey" FOREIGN KEY ("assignmentId") REFERENCES "Assignment"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|||||||
+18
-16
@@ -33,9 +33,10 @@ model User {
|
|||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
movements Movement[]
|
movements Movement[]
|
||||||
assignments Assignment[]
|
assignments Assignment[]
|
||||||
|
person Person?
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RecipientDepartment {
|
enum PersonDepartment {
|
||||||
IT
|
IT
|
||||||
ENGINEERING
|
ENGINEERING
|
||||||
LOGISTICS
|
LOGISTICS
|
||||||
@@ -46,17 +47,18 @@ enum RecipientDepartment {
|
|||||||
OTHER
|
OTHER
|
||||||
}
|
}
|
||||||
|
|
||||||
model Recipient {
|
model Person {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
username String @unique
|
|
||||||
firstName String
|
firstName String
|
||||||
lastName String
|
lastName String
|
||||||
department RecipientDepartment?
|
department PersonDepartment?
|
||||||
email String? @unique
|
email String? @unique
|
||||||
phone String?
|
phone String?
|
||||||
isActive Boolean @default(true)
|
userId String? @unique
|
||||||
createdAt DateTime @default(now())
|
user User? @relation(fields: [userId], references: [id], onDelete: SetNull, onUpdate: Cascade)
|
||||||
updatedAt DateTime @updatedAt
|
isActive Boolean @default(true)
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
assignments Assignment[]
|
assignments Assignment[]
|
||||||
movements Movement[]
|
movements Movement[]
|
||||||
|
|
||||||
@@ -128,11 +130,11 @@ model Assignment {
|
|||||||
quantity Int?
|
quantity Int?
|
||||||
notes String?
|
notes String?
|
||||||
itemId String?
|
itemId String?
|
||||||
item Item? @relation(fields: [itemId], references: [id])
|
item Item? @relation(fields: [itemId], references: [id], onDelete: SetNull, onUpdate: Cascade)
|
||||||
assetId String? @unique
|
assetId String? @unique
|
||||||
asset Asset? @relation(fields: [assetId], references: [id])
|
asset Asset? @relation(fields: [assetId], references: [id], onDelete: SetNull, onUpdate: Cascade)
|
||||||
recipientId String?
|
recipientId String?
|
||||||
recipient Recipient? @relation(fields: [recipientId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
recipient Person? @relation(fields: [recipientId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||||
assignmentDate DateTime @default(now())
|
assignmentDate DateTime @default(now())
|
||||||
returnDate DateTime?
|
returnDate DateTime?
|
||||||
createdBy String
|
createdBy String
|
||||||
@@ -163,15 +165,15 @@ model Movement {
|
|||||||
details String?
|
details String?
|
||||||
notes String?
|
notes String?
|
||||||
itemId String?
|
itemId String?
|
||||||
item Item? @relation(fields: [itemId], references: [id])
|
item Item? @relation(fields: [itemId], references: [id], onDelete: SetNull, onUpdate: Cascade)
|
||||||
assetId String?
|
assetId String?
|
||||||
asset Asset? @relation(fields: [assetId], references: [id])
|
asset Asset? @relation(fields: [assetId], references: [id], onDelete: SetNull, onUpdate: Cascade)
|
||||||
previousStock Int?
|
previousStock Int?
|
||||||
newStock Int?
|
newStock Int?
|
||||||
recipientId String?
|
recipientId String?
|
||||||
recipient Recipient? @relation(fields: [recipientId], references: [id])
|
recipient Person? @relation(fields: [recipientId], references: [id], onDelete: SetNull, onUpdate: Cascade)
|
||||||
assignmentId String?
|
assignmentId String?
|
||||||
assignment Assignment? @relation(fields: [assignmentId], references: [id])
|
assignment Assignment? @relation(fields: [assignmentId], references: [id], onDelete: SetNull, onUpdate: Cascade)
|
||||||
userId String
|
userId String
|
||||||
user User @relation(fields: [userId], references: [id])
|
user User @relation(fields: [userId], references: [id])
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
|||||||
@@ -12,14 +12,14 @@ import { getAuthenticatedUserId } from "@/services/auth.service"
|
|||||||
import { CategoryService } from "@/services/category.service"
|
import { CategoryService } from "@/services/category.service"
|
||||||
import { ItemService } from "@/services/item.service"
|
import { ItemService } from "@/services/item.service"
|
||||||
import { MovementService } from "@/services/movement.service"
|
import { MovementService } from "@/services/movement.service"
|
||||||
import { RecipientService } from "@/services/recipient.service"
|
import { PersonService } from "@/services/person.service"
|
||||||
import type {
|
import type {
|
||||||
Asset,
|
Asset,
|
||||||
Assignment,
|
Assignment,
|
||||||
Category,
|
Category,
|
||||||
ImportItem,
|
ImportItem,
|
||||||
Item,
|
Item,
|
||||||
Recipient,
|
Person,
|
||||||
} from "@/types"
|
} from "@/types"
|
||||||
|
|
||||||
export async function importItems(formData: ImportFormType) {
|
export async function importItems(formData: ImportFormType) {
|
||||||
@@ -123,7 +123,7 @@ export async function importItems(formData: ImportFormType) {
|
|||||||
file: [
|
file: [
|
||||||
"Only one of category or categoryId is allowed, you must select one of them",
|
"Only one of category or categoryId is allowed, you must select one of them",
|
||||||
],
|
],
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,7 +153,6 @@ export async function importItems(formData: ImportFormType) {
|
|||||||
category,
|
category,
|
||||||
deliveryNote,
|
deliveryNote,
|
||||||
assigned,
|
assigned,
|
||||||
username,
|
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
} = row
|
} = row
|
||||||
@@ -178,10 +177,6 @@ export async function importItems(formData: ImportFormType) {
|
|||||||
importErrors.push(`Row ${index + 2}: Delivery note must be a string`)
|
importErrors.push(`Row ${index + 2}: Delivery note must be a string`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (username && typeof username !== "string") {
|
|
||||||
importErrors.push(`Row ${index + 2}: Username must be a string`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (firstName && typeof firstName !== "string") {
|
if (firstName && typeof firstName !== "string") {
|
||||||
importErrors.push(`Row ${index + 2}: First name must be a string`)
|
importErrors.push(`Row ${index + 2}: First name must be a string`)
|
||||||
}
|
}
|
||||||
@@ -214,7 +209,6 @@ export async function importItems(formData: ImportFormType) {
|
|||||||
category: row.category?.trim() || "",
|
category: row.category?.trim() || "",
|
||||||
deliveryNote: row.deliveryNote?.trim() || "",
|
deliveryNote: row.deliveryNote?.trim() || "",
|
||||||
assigned: row.assigned?.trim() === "true",
|
assigned: row.assigned?.trim() === "true",
|
||||||
username: row.username?.trim() || "",
|
|
||||||
firstName: row.firstName?.trim() || "",
|
firstName: row.firstName?.trim() || "",
|
||||||
lastName: row.lastName?.trim() || "",
|
lastName: row.lastName?.trim() || "",
|
||||||
})
|
})
|
||||||
@@ -229,7 +223,6 @@ export async function importItems(formData: ImportFormType) {
|
|||||||
category,
|
category,
|
||||||
deliveryNote,
|
deliveryNote,
|
||||||
assigned,
|
assigned,
|
||||||
username,
|
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
} = item
|
} = item
|
||||||
@@ -238,7 +231,7 @@ export async function importItems(formData: ImportFormType) {
|
|||||||
let newItem: Item | null = null
|
let newItem: Item | null = null
|
||||||
let newAsset: Asset | null = null
|
let newAsset: Asset | null = null
|
||||||
let newCategory: Category | null = null
|
let newCategory: Category | null = null
|
||||||
let newRecipient: Recipient | null = null
|
let newPerson: Person | null = null
|
||||||
let newAssignment: Assignment | null = null
|
let newAssignment: Assignment | null = null
|
||||||
|
|
||||||
const existingCategory = categoryId
|
const existingCategory = categoryId
|
||||||
@@ -290,14 +283,16 @@ export async function importItems(formData: ImportFormType) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (assigned && firstName && lastName) {
|
if (assigned && firstName && lastName) {
|
||||||
const finalUsername =
|
const existingPerson = firstName
|
||||||
username || `${firstName.toLowerCase()[0]}${lastName.toLowerCase()}`
|
? await PersonService.findAllPaginated({
|
||||||
const existingRecipient =
|
search: firstName,
|
||||||
await RecipientService.findByUsername(finalUsername)
|
page: 0,
|
||||||
|
pageSize: 1,
|
||||||
|
})
|
||||||
|
: null
|
||||||
|
|
||||||
if (!existingRecipient) {
|
if (!existingPerson || existingPerson.data.length === 0) {
|
||||||
newRecipient = await RecipientService.create({
|
newPerson = await PersonService.create({
|
||||||
username: finalUsername,
|
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
email: undefined,
|
email: undefined,
|
||||||
@@ -305,7 +300,7 @@ export async function importItems(formData: ImportFormType) {
|
|||||||
department: "OTHER",
|
department: "OTHER",
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
newRecipient = existingRecipient
|
newPerson = existingPerson.data[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
newAssignment = await AssignmentService.create({
|
newAssignment = await AssignmentService.create({
|
||||||
@@ -313,7 +308,7 @@ export async function importItems(formData: ImportFormType) {
|
|||||||
notes: deliveryNote || "",
|
notes: deliveryNote || "",
|
||||||
itemId: newItem?.id || "",
|
itemId: newItem?.id || "",
|
||||||
assetId: newAsset?.id || "",
|
assetId: newAsset?.id || "",
|
||||||
recipientId: newRecipient?.id || "",
|
recipientId: newPerson?.id || "",
|
||||||
assignmentDate: new Date(),
|
assignmentDate: new Date(),
|
||||||
createdBy: userId,
|
createdBy: userId,
|
||||||
})
|
})
|
||||||
@@ -324,15 +319,15 @@ export async function importItems(formData: ImportFormType) {
|
|||||||
quantity: stock || 1,
|
quantity: stock || 1,
|
||||||
type: assigned ? "ASSIGNMENT" : "IN",
|
type: assigned ? "ASSIGNMENT" : "IN",
|
||||||
itemId: newItem?.id || undefined,
|
itemId: newItem?.id || undefined,
|
||||||
recipientId: newRecipient?.id || undefined,
|
recipientId: newPerson?.id || undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newAssignment?.id) {
|
if (newAssignment?.id) {
|
||||||
movementData.assignmentId = newAssignment.id
|
movementData.assignmentId = newAssignment.id
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newRecipient?.id) {
|
if (newPerson?.id) {
|
||||||
movementData.recipientId = newRecipient.id
|
movementData.recipientId = newPerson.id
|
||||||
}
|
}
|
||||||
|
|
||||||
await MovementService.create({
|
await MovementService.create({
|
||||||
|
|||||||
@@ -4,22 +4,22 @@ import { revalidatePath } from "next/cache"
|
|||||||
import { flattenError } from "zod"
|
import { flattenError } from "zod"
|
||||||
import { getI18n } from "@/i18n/server"
|
import { getI18n } from "@/i18n/server"
|
||||||
import {
|
import {
|
||||||
buildCreateRecipientSchema,
|
buildCreatePersonSchema,
|
||||||
buildUpdateRecipientSchema,
|
buildUpdatePersonSchema,
|
||||||
type CreateRecipientFormType,
|
type CreatePersonFormType,
|
||||||
type UpdateRecipientFormType,
|
type UpdatePersonFormType,
|
||||||
} from "@/schemas/recipient.schema"
|
} from "@/schemas/person.schema"
|
||||||
import {
|
import {
|
||||||
createRecipientUseCase,
|
createPersonUseCase,
|
||||||
updateRecipientUseCase,
|
updatePersonUseCase,
|
||||||
} from "@/use-cases/recipient.use-cases"
|
} from "@/use-cases/person.use-cases"
|
||||||
|
|
||||||
import { localizeRecipientFieldErrors } from "./recipient.messages"
|
import { localizePersonFieldErrors } from "./person.messages"
|
||||||
|
|
||||||
export async function createNewRecipient(formData: CreateRecipientFormType) {
|
export async function createNewPerson(formData: CreatePersonFormType) {
|
||||||
const { dictionary } = await getI18n()
|
const { dictionary } = await getI18n()
|
||||||
const copy = dictionary.inventory.recipients
|
const copy = dictionary.inventory.people
|
||||||
const validatedFields = buildCreateRecipientSchema(copy.schema).safeParse(
|
const validatedFields = buildCreatePersonSchema(copy.schema).safeParse(
|
||||||
formData,
|
formData,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -31,17 +31,17 @@ export async function createNewRecipient(formData: CreateRecipientFormType) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await createRecipientUseCase(validatedFields.data)
|
const result = await createPersonUseCase(validatedFields.data)
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
return {
|
return {
|
||||||
...result,
|
...result,
|
||||||
errors: localizeRecipientFieldErrors(result.errors, copy.actions),
|
errors: localizePersonFieldErrors(result.errors, copy.actions),
|
||||||
message: copy.actions.createFailure,
|
message: copy.actions.createFailure,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
revalidatePath("/recipients")
|
revalidatePath("/people")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
@@ -56,10 +56,10 @@ export async function createNewRecipient(formData: CreateRecipientFormType) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateRecipient(formData: UpdateRecipientFormType) {
|
export async function updatePerson(formData: UpdatePersonFormType) {
|
||||||
const { dictionary } = await getI18n()
|
const { dictionary } = await getI18n()
|
||||||
const copy = dictionary.inventory.recipients
|
const copy = dictionary.inventory.people
|
||||||
const validatedFields = buildUpdateRecipientSchema(copy.schema).safeParse(
|
const validatedFields = buildUpdatePersonSchema(copy.schema).safeParse(
|
||||||
formData,
|
formData,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -71,17 +71,17 @@ export async function updateRecipient(formData: UpdateRecipientFormType) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await updateRecipientUseCase(validatedFields.data)
|
const result = await updatePersonUseCase(validatedFields.data)
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
return {
|
return {
|
||||||
...result,
|
...result,
|
||||||
errors: localizeRecipientFieldErrors(result.errors, copy.actions),
|
errors: localizePersonFieldErrors(result.errors, copy.actions),
|
||||||
message: copy.actions.updateFailure,
|
message: copy.actions.updateFailure,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
revalidatePath("/recipients")
|
revalidatePath("/people")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import type { Dictionary } from "@/i18n/dictionaries"
|
||||||
|
|
||||||
|
type PersonActionCopy = Dictionary["inventory"]["people"]["actions"]
|
||||||
|
|
||||||
|
type FieldErrors = Record<string, string[]>
|
||||||
|
|
||||||
|
const personErrorMessageKeys = {
|
||||||
|
"Email already exists": "duplicateEmail",
|
||||||
|
} as const satisfies Record<string, keyof PersonActionCopy>
|
||||||
|
|
||||||
|
function isPersonErrorMessage(
|
||||||
|
message: string,
|
||||||
|
): message is keyof typeof personErrorMessageKeys {
|
||||||
|
return message in personErrorMessageKeys
|
||||||
|
}
|
||||||
|
|
||||||
|
function localizePersonMessage(
|
||||||
|
message: string,
|
||||||
|
copy: PersonActionCopy,
|
||||||
|
): string {
|
||||||
|
if (!isPersonErrorMessage(message)) return message
|
||||||
|
|
||||||
|
return copy[personErrorMessageKeys[message]]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function localizePersonFieldErrors(
|
||||||
|
errors: FieldErrors | undefined,
|
||||||
|
copy: PersonActionCopy,
|
||||||
|
): FieldErrors | undefined {
|
||||||
|
if (!errors) return undefined
|
||||||
|
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(errors).map(([field, messages]) => [
|
||||||
|
field,
|
||||||
|
messages.map((message) => localizePersonMessage(message, copy)),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
import type { Dictionary } from "@/i18n/dictionaries"
|
|
||||||
|
|
||||||
type RecipientActionCopy = Dictionary["inventory"]["recipients"]["actions"]
|
|
||||||
|
|
||||||
type FieldErrors = Record<string, string[]>
|
|
||||||
|
|
||||||
const recipientErrorMessageKeys = {
|
|
||||||
"Username already exists": "duplicateUsername",
|
|
||||||
"Email already exists": "duplicateEmail",
|
|
||||||
} as const satisfies Record<string, keyof RecipientActionCopy>
|
|
||||||
|
|
||||||
function isRecipientErrorMessage(
|
|
||||||
message: string,
|
|
||||||
): message is keyof typeof recipientErrorMessageKeys {
|
|
||||||
return message in recipientErrorMessageKeys
|
|
||||||
}
|
|
||||||
|
|
||||||
function localizeRecipientMessage(
|
|
||||||
message: string,
|
|
||||||
copy: RecipientActionCopy,
|
|
||||||
): string {
|
|
||||||
if (!isRecipientErrorMessage(message)) return message
|
|
||||||
|
|
||||||
return copy[recipientErrorMessageKeys[message]]
|
|
||||||
}
|
|
||||||
|
|
||||||
export function localizeRecipientFieldErrors(
|
|
||||||
errors: FieldErrors | undefined,
|
|
||||||
copy: RecipientActionCopy,
|
|
||||||
): FieldErrors | undefined {
|
|
||||||
if (!errors) return undefined
|
|
||||||
|
|
||||||
return Object.fromEntries(
|
|
||||||
Object.entries(errors).map(([field, messages]) => [
|
|
||||||
field,
|
|
||||||
messages.map((message) => localizeRecipientMessage(message, copy)),
|
|
||||||
]),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -318,6 +318,80 @@ export const en = {
|
|||||||
idRequired: "Assignment ID is required",
|
idRequired: "Assignment ID is required",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
people: {
|
||||||
|
list: {
|
||||||
|
title: "People",
|
||||||
|
addLabel: "Add Person",
|
||||||
|
empty: "No people found.",
|
||||||
|
columns: {
|
||||||
|
name: "Name",
|
||||||
|
email: "Email",
|
||||||
|
phone: "Phone",
|
||||||
|
department: "Department",
|
||||||
|
actions: "Actions",
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
view: "View person",
|
||||||
|
edit: "Edit person",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
detail: {
|
||||||
|
notFound: "Person not found",
|
||||||
|
labels: {
|
||||||
|
email: "Email",
|
||||||
|
phone: "Phone",
|
||||||
|
department: "Department",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
new: {
|
||||||
|
title: "Add Person",
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
title: "Edit Person",
|
||||||
|
notFound: "Person not found",
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
firstNameLabel: "First Name",
|
||||||
|
firstNamePlaceholder: "First name",
|
||||||
|
lastNameLabel: "Last Name",
|
||||||
|
lastNamePlaceholder: "Last name",
|
||||||
|
departmentLabel: "Department",
|
||||||
|
departmentPlaceholder: "Select a department",
|
||||||
|
emailLabel: "Email",
|
||||||
|
emailPlaceholder: "Email",
|
||||||
|
phoneLabel: "Phone",
|
||||||
|
phonePlaceholder: "Phone",
|
||||||
|
createSubmit: "Create Person",
|
||||||
|
updateSubmit: "Update Person",
|
||||||
|
},
|
||||||
|
fallback: {
|
||||||
|
unknownDepartment: "Unknown department",
|
||||||
|
},
|
||||||
|
departments: {
|
||||||
|
IT: "IT",
|
||||||
|
ENGINEERING: "Engineering",
|
||||||
|
LOGISTICS: "Logistics",
|
||||||
|
TRAFFIC: "Traffic",
|
||||||
|
DRIVER: "Driver",
|
||||||
|
ADMINISTRATION: "Administration",
|
||||||
|
SALES: "Sales",
|
||||||
|
OTHER: "Other",
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
createSuccess: "Person created successfully",
|
||||||
|
createFailure: "Failed to create person",
|
||||||
|
updateSuccess: "Person updated successfully",
|
||||||
|
updateFailure: "Failed to update person",
|
||||||
|
duplicateEmail: "Email already exists",
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
firstNameRequired: "First name is required",
|
||||||
|
lastNameRequired: "Last name is required",
|
||||||
|
departmentRequired: "Department is required",
|
||||||
|
emailInvalid: "Email format is invalid",
|
||||||
|
idRequired: "ID is required",
|
||||||
|
},
|
||||||
|
},
|
||||||
recipients: {
|
recipients: {
|
||||||
list: {
|
list: {
|
||||||
title: "Recipients",
|
title: "Recipients",
|
||||||
|
|||||||
@@ -323,6 +323,80 @@ export const es = {
|
|||||||
idRequired: "El ID de asignación es obligatorio",
|
idRequired: "El ID de asignación es obligatorio",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
people: {
|
||||||
|
list: {
|
||||||
|
title: "Personas",
|
||||||
|
addLabel: "Agregar persona",
|
||||||
|
empty: "No se encontraron personas.",
|
||||||
|
columns: {
|
||||||
|
name: "Nombre",
|
||||||
|
email: "Correo electrónico",
|
||||||
|
phone: "Teléfono",
|
||||||
|
department: "Departamento",
|
||||||
|
actions: "Acciones",
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
view: "Ver persona",
|
||||||
|
edit: "Editar persona",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
detail: {
|
||||||
|
notFound: "Persona no encontrada",
|
||||||
|
labels: {
|
||||||
|
email: "Correo electrónico",
|
||||||
|
phone: "Teléfono",
|
||||||
|
department: "Departamento",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
new: {
|
||||||
|
title: "Agregar persona",
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
title: "Editar persona",
|
||||||
|
notFound: "Persona no encontrada",
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
firstNameLabel: "Nombre",
|
||||||
|
firstNamePlaceholder: "Nombre",
|
||||||
|
lastNameLabel: "Apellido",
|
||||||
|
lastNamePlaceholder: "Apellido",
|
||||||
|
departmentLabel: "Departamento",
|
||||||
|
departmentPlaceholder: "Selecciona un departamento",
|
||||||
|
emailLabel: "Correo electrónico",
|
||||||
|
emailPlaceholder: "Correo electrónico",
|
||||||
|
phoneLabel: "Teléfono",
|
||||||
|
phonePlaceholder: "Teléfono",
|
||||||
|
createSubmit: "Crear persona",
|
||||||
|
updateSubmit: "Actualizar persona",
|
||||||
|
},
|
||||||
|
fallback: {
|
||||||
|
unknownDepartment: "Departamento desconocido",
|
||||||
|
},
|
||||||
|
departments: {
|
||||||
|
IT: "IT",
|
||||||
|
ENGINEERING: "Ingeniería",
|
||||||
|
LOGISTICS: "Logística",
|
||||||
|
TRAFFIC: "Tráfico",
|
||||||
|
DRIVER: "Chofer",
|
||||||
|
ADMINISTRATION: "Administración",
|
||||||
|
SALES: "Ventas",
|
||||||
|
OTHER: "Otro",
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
createSuccess: "Persona creada correctamente",
|
||||||
|
createFailure: "Error al crear la persona",
|
||||||
|
updateSuccess: "Persona actualizada correctamente",
|
||||||
|
updateFailure: "Error al actualizar la persona",
|
||||||
|
duplicateEmail: "El correo electrónico ya existe",
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
firstNameRequired: "El nombre es obligatorio",
|
||||||
|
lastNameRequired: "El apellido es obligatorio",
|
||||||
|
departmentRequired: "El departamento es obligatorio",
|
||||||
|
emailInvalid: "El correo electrónico no es válido",
|
||||||
|
idRequired: "El ID es obligatorio",
|
||||||
|
},
|
||||||
|
},
|
||||||
recipients: {
|
recipients: {
|
||||||
list: {
|
list: {
|
||||||
title: "Destinatarios",
|
title: "Destinatarios",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export const SIGN_IN_URL = "/login"
|
|||||||
|
|
||||||
export const TOKEN_EXPIRATION_SECONDS = 60 * 60 * 2 // 2 hour
|
export const TOKEN_EXPIRATION_SECONDS = 60 * 60 * 2 // 2 hour
|
||||||
|
|
||||||
export const RECIPIENT_DEPARTMENTS = {
|
export const PERSON_DEPARTMENTS = {
|
||||||
IT: "IT",
|
IT: "IT",
|
||||||
ENGINEERING: "ENGINEERING",
|
ENGINEERING: "ENGINEERING",
|
||||||
LOGISTICS: "LOGISTICS",
|
LOGISTICS: "LOGISTICS",
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import type { Dictionary } from "@/i18n/dictionaries"
|
||||||
|
|
||||||
|
export type PersonSchemaCopy = Dictionary["inventory"]["people"]["schema"]
|
||||||
|
|
||||||
|
const defaultPersonSchemaCopy: PersonSchemaCopy = {
|
||||||
|
firstNameRequired: "First name is required",
|
||||||
|
lastNameRequired: "Last name is required",
|
||||||
|
departmentRequired: "Department is required",
|
||||||
|
emailInvalid: "Email format is invalid",
|
||||||
|
idRequired: "ID is required",
|
||||||
|
}
|
||||||
|
|
||||||
|
const personDepartments = [
|
||||||
|
"IT",
|
||||||
|
"ENGINEERING",
|
||||||
|
"TRAFFIC",
|
||||||
|
"DRIVER",
|
||||||
|
"LOGISTICS",
|
||||||
|
"ADMINISTRATION",
|
||||||
|
"SALES",
|
||||||
|
"OTHER",
|
||||||
|
] as const
|
||||||
|
|
||||||
|
function buildPersonBaseSchema(copy: PersonSchemaCopy) {
|
||||||
|
return z.object({
|
||||||
|
id: z.string().optional(),
|
||||||
|
firstName: z.string().min(1, {
|
||||||
|
error: copy.firstNameRequired,
|
||||||
|
}),
|
||||||
|
lastName: z.string().min(1, {
|
||||||
|
error: copy.lastNameRequired,
|
||||||
|
}),
|
||||||
|
department: z.enum(personDepartments, {
|
||||||
|
error: copy.departmentRequired,
|
||||||
|
}),
|
||||||
|
email: z.string().optional().nullable(),
|
||||||
|
phone: z.string().optional().nullable(),
|
||||||
|
userId: z.string().uuid().optional().nullable(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const personSchema = buildPersonBaseSchema(defaultPersonSchemaCopy)
|
||||||
|
|
||||||
|
export function buildCreatePersonSchema(copy: PersonSchemaCopy) {
|
||||||
|
return buildPersonBaseSchema(copy).superRefine((data, ctx) => {
|
||||||
|
if (data.email && !z.string().email().safeParse(data.email).success) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: "custom",
|
||||||
|
message: copy.emailInvalid,
|
||||||
|
path: ["email"],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createPersonSchema = buildCreatePersonSchema(
|
||||||
|
defaultPersonSchemaCopy,
|
||||||
|
)
|
||||||
|
|
||||||
|
export type CreatePersonFormType = z.infer<typeof createPersonSchema>
|
||||||
|
|
||||||
|
export function buildUpdatePersonSchema(copy: PersonSchemaCopy) {
|
||||||
|
return buildPersonBaseSchema(copy).extend({
|
||||||
|
id: z.string().nonempty(copy.idRequired),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updatePersonSchema = buildUpdatePersonSchema(
|
||||||
|
defaultPersonSchemaCopy,
|
||||||
|
)
|
||||||
|
|
||||||
|
export type UpdatePersonFormType = z.infer<typeof updatePersonSchema>
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
import { z } from "zod"
|
|
||||||
|
|
||||||
import type { Dictionary } from "@/i18n/dictionaries"
|
|
||||||
|
|
||||||
export type RecipientSchemaCopy =
|
|
||||||
Dictionary["inventory"]["recipients"]["schema"]
|
|
||||||
|
|
||||||
const defaultRecipientSchemaCopy: RecipientSchemaCopy = {
|
|
||||||
usernameRequired: "Username is required",
|
|
||||||
firstNameRequired: "First name is required",
|
|
||||||
lastNameRequired: "Last name is required",
|
|
||||||
departmentRequired: "Department is required",
|
|
||||||
emailInvalid: "Email format is invalid",
|
|
||||||
idRequired: "ID is required",
|
|
||||||
}
|
|
||||||
|
|
||||||
const recipientDepartments = [
|
|
||||||
"IT",
|
|
||||||
"ENGINEERING",
|
|
||||||
"TRAFFIC",
|
|
||||||
"DRIVER",
|
|
||||||
"LOGISTICS",
|
|
||||||
"ADMINISTRATION",
|
|
||||||
"SALES",
|
|
||||||
"OTHER",
|
|
||||||
] as const
|
|
||||||
|
|
||||||
function buildRecipientBaseSchema(copy: RecipientSchemaCopy) {
|
|
||||||
return z.object({
|
|
||||||
id: z.string().optional(),
|
|
||||||
username: z.string().min(1, {
|
|
||||||
error: copy.usernameRequired,
|
|
||||||
}),
|
|
||||||
firstName: z.string().min(1, {
|
|
||||||
error: copy.firstNameRequired,
|
|
||||||
}),
|
|
||||||
lastName: z.string().min(1, {
|
|
||||||
error: copy.lastNameRequired,
|
|
||||||
}),
|
|
||||||
department: z.enum(recipientDepartments, {
|
|
||||||
error: copy.departmentRequired,
|
|
||||||
}),
|
|
||||||
email: z.string().optional().nullable(),
|
|
||||||
phone: z.string().optional().nullable(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const recipientSchema = buildRecipientBaseSchema(
|
|
||||||
defaultRecipientSchemaCopy,
|
|
||||||
)
|
|
||||||
|
|
||||||
export function buildCreateRecipientSchema(copy: RecipientSchemaCopy) {
|
|
||||||
return buildRecipientBaseSchema(copy).superRefine((data, ctx) => {
|
|
||||||
if (data.email && !z.string().email().safeParse(data.email).success) {
|
|
||||||
ctx.addIssue({
|
|
||||||
code: "custom",
|
|
||||||
message: copy.emailInvalid,
|
|
||||||
path: ["email"],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createRecipientSchema = buildCreateRecipientSchema(
|
|
||||||
defaultRecipientSchemaCopy,
|
|
||||||
)
|
|
||||||
|
|
||||||
export type CreateRecipientFormType = z.infer<typeof createRecipientSchema>
|
|
||||||
|
|
||||||
export function buildUpdateRecipientSchema(copy: RecipientSchemaCopy) {
|
|
||||||
return buildRecipientBaseSchema(copy).extend({
|
|
||||||
id: z.string().nonempty(copy.idRequired),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const updateRecipientSchema = buildUpdateRecipientSchema(
|
|
||||||
defaultRecipientSchemaCopy,
|
|
||||||
)
|
|
||||||
|
|
||||||
export type UpdateRecipientFormType = z.infer<typeof updateRecipientSchema>
|
|
||||||
@@ -81,7 +81,7 @@ export const AssignmentService = {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
findAllByRecipient: async (
|
findAllByPerson: async (
|
||||||
recipientId: string,
|
recipientId: string,
|
||||||
): Promise<AssignmentWithRecipientItemAsset[]> => {
|
): Promise<AssignmentWithRecipientItemAsset[]> => {
|
||||||
return prisma.assignment.findMany({
|
return prisma.assignment.findMany({
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import type { Prisma, Person } from "@/generated/prisma/client"
|
||||||
|
import { paginate } from "@/lib/paginate"
|
||||||
|
import prisma from "@/lib/prisma"
|
||||||
|
|
||||||
|
export const PersonService = {
|
||||||
|
findAll: async (): Promise<Person[]> => {
|
||||||
|
return prisma.person.findMany({
|
||||||
|
orderBy: {
|
||||||
|
firstName: "asc",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
findAllPaginated: async ({
|
||||||
|
page = 0,
|
||||||
|
pageSize,
|
||||||
|
search,
|
||||||
|
}: {
|
||||||
|
page?: number
|
||||||
|
pageSize?: number
|
||||||
|
search?: string
|
||||||
|
}) => {
|
||||||
|
return paginate<Person>({
|
||||||
|
model: prisma.person,
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
where: {
|
||||||
|
...(search
|
||||||
|
? {
|
||||||
|
OR: [
|
||||||
|
{ email: { contains: search, mode: "insensitive" } },
|
||||||
|
{ firstName: { contains: search, mode: "insensitive" } },
|
||||||
|
{ lastName: { contains: search, mode: "insensitive" } },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
findAllPeopleCount: async (): Promise<number> => {
|
||||||
|
return prisma.person.count()
|
||||||
|
},
|
||||||
|
|
||||||
|
findById: async (
|
||||||
|
id: string,
|
||||||
|
db: Prisma.TransactionClient | typeof prisma = prisma,
|
||||||
|
): Promise<Person | null> => {
|
||||||
|
return db.person.findUnique({ where: { id } })
|
||||||
|
},
|
||||||
|
|
||||||
|
findByEmail: async (
|
||||||
|
email: string,
|
||||||
|
db: Prisma.TransactionClient | typeof prisma = prisma,
|
||||||
|
): Promise<Person | null> => {
|
||||||
|
return db.person.findUnique({ where: { email } })
|
||||||
|
},
|
||||||
|
|
||||||
|
create: async (
|
||||||
|
data: Prisma.PersonCreateInput,
|
||||||
|
db: Prisma.TransactionClient | typeof prisma = prisma,
|
||||||
|
): Promise<Person> => {
|
||||||
|
return db.person.create({ data })
|
||||||
|
},
|
||||||
|
|
||||||
|
update: async (
|
||||||
|
id: string,
|
||||||
|
data: Prisma.PersonUpdateInput,
|
||||||
|
db: Prisma.TransactionClient | typeof prisma = prisma,
|
||||||
|
): Promise<Person> => {
|
||||||
|
return db.person.update({ where: { id }, data })
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
import type { Prisma, Recipient } from "@/generated/prisma/client"
|
|
||||||
import { paginate } from "@/lib/paginate"
|
|
||||||
import prisma from "@/lib/prisma"
|
|
||||||
|
|
||||||
export const RecipientService = {
|
|
||||||
findAll: async (): Promise<Recipient[]> => {
|
|
||||||
return prisma.recipient.findMany({
|
|
||||||
orderBy: {
|
|
||||||
firstName: "asc",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
},
|
|
||||||
findAllPaginated: async ({
|
|
||||||
page = 0,
|
|
||||||
pageSize,
|
|
||||||
search,
|
|
||||||
}: {
|
|
||||||
page?: number
|
|
||||||
pageSize?: number
|
|
||||||
search?: string
|
|
||||||
}) => {
|
|
||||||
return paginate<Recipient>({
|
|
||||||
model: prisma.recipient,
|
|
||||||
page,
|
|
||||||
pageSize,
|
|
||||||
where: {
|
|
||||||
...(search
|
|
||||||
? {
|
|
||||||
OR: [
|
|
||||||
{ username: { contains: search, mode: "insensitive" } },
|
|
||||||
{ firstName: { contains: search, mode: "insensitive" } },
|
|
||||||
{ lastName: { contains: search, mode: "insensitive" } },
|
|
||||||
],
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
},
|
|
||||||
findAllRecipientsCount: async (): Promise<number> => {
|
|
||||||
return prisma.recipient.count()
|
|
||||||
},
|
|
||||||
|
|
||||||
findById: async (
|
|
||||||
id: string,
|
|
||||||
db: Prisma.TransactionClient | typeof prisma = prisma,
|
|
||||||
): Promise<Recipient | null> => {
|
|
||||||
return db.recipient.findUnique({ where: { id } })
|
|
||||||
},
|
|
||||||
|
|
||||||
findByUsername: async (
|
|
||||||
username: string,
|
|
||||||
db: Prisma.TransactionClient | typeof prisma = prisma,
|
|
||||||
): Promise<Recipient | null> => {
|
|
||||||
return db.recipient.findUnique({ where: { username } })
|
|
||||||
},
|
|
||||||
|
|
||||||
findByEmail: async (
|
|
||||||
email: string,
|
|
||||||
db: Prisma.TransactionClient | typeof prisma = prisma,
|
|
||||||
): Promise<Recipient | null> => {
|
|
||||||
return db.recipient.findUnique({ where: { email } })
|
|
||||||
},
|
|
||||||
|
|
||||||
create: async (
|
|
||||||
data: Prisma.RecipientCreateInput,
|
|
||||||
db: Prisma.TransactionClient | typeof prisma = prisma,
|
|
||||||
): Promise<Recipient> => {
|
|
||||||
return db.recipient.create({ data })
|
|
||||||
},
|
|
||||||
|
|
||||||
update: async (
|
|
||||||
id: string,
|
|
||||||
data: Prisma.RecipientUpdateInput,
|
|
||||||
db: Prisma.TransactionClient | typeof prisma = prisma,
|
|
||||||
): Promise<Recipient> => {
|
|
||||||
return db.recipient.update({ where: { id }, data })
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@ import type { Assignment as PrismaAssignment } from "@/generated/prisma/client"
|
|||||||
|
|
||||||
import type { Asset } from "./asset"
|
import type { Asset } from "./asset"
|
||||||
import type { Item } from "./item"
|
import type { Item } from "./item"
|
||||||
import type { Recipient } from "./recipient"
|
import type { Person } from "./person"
|
||||||
|
|
||||||
export type Assignment = PrismaAssignment
|
export type Assignment = PrismaAssignment
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ export type AssignmentSummary = Pick<Assignment, "id" | "quantity">
|
|||||||
|
|
||||||
export type AssignmentWithRecipientItemAsset = Assignment & {
|
export type AssignmentWithRecipientItemAsset = Assignment & {
|
||||||
returnDate: Date | null
|
returnDate: Date | null
|
||||||
recipient: Recipient | null
|
recipient: Person | null
|
||||||
item: Item | null
|
item: Item | null
|
||||||
asset: Asset | null
|
asset: Asset | null
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,6 @@ export interface ImportItem {
|
|||||||
category?: string
|
category?: string
|
||||||
deliveryNote?: string
|
deliveryNote?: string
|
||||||
assigned?: boolean
|
assigned?: boolean
|
||||||
username?: string
|
|
||||||
firstName?: string
|
firstName?: string
|
||||||
lastName?: string
|
lastName?: string
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -5,5 +5,5 @@ export * from "./import"
|
|||||||
export * from "./item"
|
export * from "./item"
|
||||||
export * from "./movement"
|
export * from "./movement"
|
||||||
export * from "./paginate"
|
export * from "./paginate"
|
||||||
export * from "./recipient"
|
export * from "./person"
|
||||||
export * from "./user"
|
export * from "./user"
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import type { Person as PrismaPerson } from "@/generated/prisma/client"
|
||||||
|
|
||||||
|
export type Person = PrismaPerson
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import type { Recipient as PrismaRecipient } from "@/generated/prisma/client"
|
|
||||||
|
|
||||||
export type Recipient = PrismaRecipient
|
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
import { Prisma } from "@/generated/prisma/client"
|
||||||
|
import prisma from "@/lib/prisma"
|
||||||
|
import type {
|
||||||
|
CreatePersonFormType,
|
||||||
|
UpdatePersonFormType,
|
||||||
|
} from "@/schemas/person.schema"
|
||||||
|
import { PersonService } from "@/services/person.service"
|
||||||
|
|
||||||
|
type FieldErrors = Record<string, string[]>
|
||||||
|
|
||||||
|
type PersonUseCaseResult =
|
||||||
|
| {
|
||||||
|
success: true
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
success: false
|
||||||
|
errors: FieldErrors
|
||||||
|
}
|
||||||
|
|
||||||
|
function personError(errors: FieldErrors): PersonUseCaseResult {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
errors,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function uniqueErrorFor(error: unknown): FieldErrors | null {
|
||||||
|
if (
|
||||||
|
!(error instanceof Prisma.PrismaClientKnownRequestError) ||
|
||||||
|
error.code !== "P2002"
|
||||||
|
) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const target = Array.isArray(error.meta?.target) ? error.meta.target : []
|
||||||
|
|
||||||
|
if (target.includes("email")) {
|
||||||
|
return { email: ["Email already exists"] }
|
||||||
|
}
|
||||||
|
|
||||||
|
return { email: ["Email already exists"] }
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createPersonUseCase(
|
||||||
|
input: CreatePersonFormType,
|
||||||
|
): Promise<PersonUseCaseResult> {
|
||||||
|
const { firstName, lastName, department, email, phone, userId } = input
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await prisma.$transaction(async (tx) => {
|
||||||
|
if (email) {
|
||||||
|
const existingPersonEmail = await PersonService.findByEmail(email, tx)
|
||||||
|
|
||||||
|
if (existingPersonEmail) {
|
||||||
|
return personError({ email: ["Email already exists"] })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await PersonService.create(
|
||||||
|
{
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
department,
|
||||||
|
email: email || null,
|
||||||
|
phone: phone || null,
|
||||||
|
...(userId ? { user: { connect: { id: userId } } } : {}),
|
||||||
|
},
|
||||||
|
tx,
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
const errors = uniqueErrorFor(error)
|
||||||
|
|
||||||
|
if (errors) {
|
||||||
|
return personError(errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updatePersonUseCase(
|
||||||
|
input: UpdatePersonFormType,
|
||||||
|
): Promise<PersonUseCaseResult> {
|
||||||
|
const { id, firstName, lastName, department, email, phone, userId } = input
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await prisma.$transaction(async (tx) => {
|
||||||
|
if (email) {
|
||||||
|
const existingPersonEmail = await PersonService.findByEmail(email, tx)
|
||||||
|
|
||||||
|
if (existingPersonEmail && existingPersonEmail.id !== id) {
|
||||||
|
return personError({ email: ["Email already exists"] })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await PersonService.update(
|
||||||
|
id,
|
||||||
|
{
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
department,
|
||||||
|
email: email || null,
|
||||||
|
phone: phone || null,
|
||||||
|
...(userId ? { user: { connect: { id: userId } } } : { userId: null }),
|
||||||
|
},
|
||||||
|
tx,
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
const errors = uniqueErrorFor(error)
|
||||||
|
|
||||||
|
if (errors) {
|
||||||
|
return personError(errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
import { Prisma } from "@/generated/prisma/client"
|
|
||||||
import prisma from "@/lib/prisma"
|
|
||||||
import type {
|
|
||||||
CreateRecipientFormType,
|
|
||||||
UpdateRecipientFormType,
|
|
||||||
} from "@/schemas/recipient.schema"
|
|
||||||
import { RecipientService } from "@/services/recipient.service"
|
|
||||||
|
|
||||||
type FieldErrors = Record<string, string[]>
|
|
||||||
|
|
||||||
type RecipientUseCaseResult =
|
|
||||||
| {
|
|
||||||
success: true
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
success: false
|
|
||||||
errors: FieldErrors
|
|
||||||
}
|
|
||||||
|
|
||||||
function recipientError(errors: FieldErrors): RecipientUseCaseResult {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
errors,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function uniqueErrorFor(error: unknown): FieldErrors | null {
|
|
||||||
if (
|
|
||||||
!(error instanceof Prisma.PrismaClientKnownRequestError) ||
|
|
||||||
error.code !== "P2002"
|
|
||||||
) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const target = Array.isArray(error.meta?.target) ? error.meta.target : []
|
|
||||||
|
|
||||||
if (target.includes("username")) {
|
|
||||||
return { username: ["Username already exists"] }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (target.includes("email")) {
|
|
||||||
return { email: ["Email already exists"] }
|
|
||||||
}
|
|
||||||
|
|
||||||
return { username: ["Username already exists"] }
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createRecipientUseCase(
|
|
||||||
input: CreateRecipientFormType,
|
|
||||||
): Promise<RecipientUseCaseResult> {
|
|
||||||
const { username, firstName, lastName, department, email, phone } = input
|
|
||||||
|
|
||||||
try {
|
|
||||||
return await prisma.$transaction(async (tx) => {
|
|
||||||
const existingRecipientUsername = await RecipientService.findByUsername(
|
|
||||||
username,
|
|
||||||
tx,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (existingRecipientUsername) {
|
|
||||||
return recipientError({ username: ["Username already exists"] })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (email) {
|
|
||||||
const existingRecipientEmail = await RecipientService.findByEmail(
|
|
||||||
email,
|
|
||||||
tx,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (existingRecipientEmail) {
|
|
||||||
return recipientError({ email: ["Email already exists"] })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await RecipientService.create(
|
|
||||||
{
|
|
||||||
username: username || (firstName[0] + lastName).toLowerCase(),
|
|
||||||
firstName,
|
|
||||||
lastName,
|
|
||||||
department,
|
|
||||||
email: email || null,
|
|
||||||
phone: phone || null,
|
|
||||||
},
|
|
||||||
tx,
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
const errors = uniqueErrorFor(error)
|
|
||||||
|
|
||||||
if (errors) {
|
|
||||||
return recipientError(errors)
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateRecipientUseCase(
|
|
||||||
input: UpdateRecipientFormType,
|
|
||||||
): Promise<RecipientUseCaseResult> {
|
|
||||||
const { id, username, firstName, lastName, department, email, phone } = input
|
|
||||||
|
|
||||||
try {
|
|
||||||
return await prisma.$transaction(async (tx) => {
|
|
||||||
const existingRecipient = await RecipientService.findByUsername(
|
|
||||||
username,
|
|
||||||
tx,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (existingRecipient && existingRecipient.id !== id) {
|
|
||||||
return recipientError({ username: ["Username already exists"] })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (email) {
|
|
||||||
const existingRecipientEmail = await RecipientService.findByEmail(
|
|
||||||
email,
|
|
||||||
tx,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (existingRecipientEmail && existingRecipientEmail.id !== id) {
|
|
||||||
return recipientError({ email: ["Email already exists"] })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await RecipientService.update(
|
|
||||||
id,
|
|
||||||
{
|
|
||||||
username,
|
|
||||||
firstName,
|
|
||||||
lastName,
|
|
||||||
department,
|
|
||||||
email: email || null,
|
|
||||||
phone: phone || null,
|
|
||||||
},
|
|
||||||
tx,
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
const errors = uniqueErrorFor(error)
|
|
||||||
|
|
||||||
if (errors) {
|
|
||||||
return recipientError(errors)
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import type {
|
import type {
|
||||||
PrismaClient,
|
PrismaClient,
|
||||||
RecipientDepartment,
|
PersonDepartment,
|
||||||
UserRole,
|
UserRole,
|
||||||
} from "@/generated/prisma/client"
|
} from "@/generated/prisma/client"
|
||||||
|
|
||||||
@@ -48,24 +48,22 @@ export async function createTestCategory(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createTestRecipient(
|
export async function createTestPerson(
|
||||||
prisma: PrismaClient,
|
prisma: PrismaClient,
|
||||||
overrides: Partial<{
|
overrides: Partial<{
|
||||||
username: string
|
|
||||||
firstName: string
|
firstName: string
|
||||||
lastName: string
|
lastName: string
|
||||||
department: RecipientDepartment
|
department: PersonDepartment
|
||||||
email: string | null
|
email: string | null
|
||||||
phone: string | null
|
phone: string | null
|
||||||
}> = {},
|
}> = {},
|
||||||
) {
|
) {
|
||||||
const suffix = nextSuffix()
|
const suffix = nextSuffix()
|
||||||
|
|
||||||
return prisma.recipient.create({
|
return prisma.person.create({
|
||||||
data: {
|
data: {
|
||||||
username: overrides.username ?? `test-recipient-${suffix}`,
|
|
||||||
firstName: overrides.firstName ?? "Test",
|
firstName: overrides.firstName ?? "Test",
|
||||||
lastName: overrides.lastName ?? "Recipient",
|
lastName: overrides.lastName ?? `Person-${suffix}`,
|
||||||
department: overrides.department ?? "OTHER",
|
department: overrides.department ?? "OTHER",
|
||||||
email: overrides.email ?? null,
|
email: overrides.email ?? null,
|
||||||
phone: overrides.phone ?? null,
|
phone: overrides.phone ?? null,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const TABLES_TO_TRUNCATE = [
|
|||||||
"Asset",
|
"Asset",
|
||||||
"Item",
|
"Item",
|
||||||
"Category",
|
"Category",
|
||||||
"Recipient",
|
"Person",
|
||||||
"User",
|
"User",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest"
|
|||||||
import type { PrismaClient } from "@/generated/prisma/client"
|
import type { PrismaClient } from "@/generated/prisma/client"
|
||||||
import {
|
import {
|
||||||
createTestItem,
|
createTestItem,
|
||||||
createTestRecipient,
|
createTestPerson,
|
||||||
createTestUser,
|
createTestUser,
|
||||||
} from "../helpers/factories"
|
} from "../helpers/factories"
|
||||||
import {
|
import {
|
||||||
@@ -80,7 +80,7 @@ describe("asset use-cases", () => {
|
|||||||
|
|
||||||
it("creates an assigned asset with assignment and ASSIGNMENT movement", async () => {
|
it("creates an assigned asset with assignment and ASSIGNMENT movement", async () => {
|
||||||
const actor = await createTestUser(prisma)
|
const actor = await createTestUser(prisma)
|
||||||
const recipient = await createTestRecipient(prisma)
|
const recipient = await createTestPerson(prisma)
|
||||||
const item = await createTestItem(prisma, { stock: 0 })
|
const item = await createTestItem(prisma, { stock: 0 })
|
||||||
|
|
||||||
const result = await createAssetUseCase({
|
const result = await createAssetUseCase({
|
||||||
@@ -133,7 +133,7 @@ describe("asset use-cases", () => {
|
|||||||
|
|
||||||
it("moves an available asset to assigned and back to available", async () => {
|
it("moves an available asset to assigned and back to available", async () => {
|
||||||
const actor = await createTestUser(prisma)
|
const actor = await createTestUser(prisma)
|
||||||
const recipient = await createTestRecipient(prisma)
|
const recipient = await createTestPerson(prisma)
|
||||||
const item = await createTestItem(prisma, { stock: 0 })
|
const item = await createTestItem(prisma, { stock: 0 })
|
||||||
|
|
||||||
const created = await createAssetUseCase({
|
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 () => {
|
it("returns an active assignment without restoring stock when an assigned asset moves to a terminal status", async () => {
|
||||||
const actor = await createTestUser(prisma)
|
const actor = await createTestUser(prisma)
|
||||||
const recipient = await createTestRecipient(prisma)
|
const recipient = await createTestPerson(prisma)
|
||||||
const item = await createTestItem(prisma, { stock: 0 })
|
const item = await createTestItem(prisma, { stock: 0 })
|
||||||
|
|
||||||
const created = await createAssetUseCase({
|
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 type { PrismaClient } from "@/generated/prisma/client"
|
||||||
import {
|
import {
|
||||||
createTestItem,
|
createTestItem,
|
||||||
createTestRecipient,
|
createTestPerson,
|
||||||
createTestUser,
|
createTestUser,
|
||||||
} from "../helpers/factories"
|
} from "../helpers/factories"
|
||||||
import {
|
import {
|
||||||
@@ -38,7 +38,7 @@ afterAll(async () => {
|
|||||||
describe("assignment use-cases", () => {
|
describe("assignment use-cases", () => {
|
||||||
it("creates an assignment, decrements stock, and records an ASSIGNMENT movement", async () => {
|
it("creates an assignment, decrements stock, and records an ASSIGNMENT movement", async () => {
|
||||||
const actor = await createTestUser(prisma)
|
const actor = await createTestUser(prisma)
|
||||||
const recipient = await createTestRecipient(prisma)
|
const recipient = await createTestPerson(prisma)
|
||||||
const item = await createTestItem(prisma, { stock: 5 })
|
const item = await createTestItem(prisma, { stock: 5 })
|
||||||
|
|
||||||
const assignmentDate = new Date("2026-01-01T00:00:00.000Z")
|
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 () => {
|
it("rejects assignment creation when item stock is insufficient", async () => {
|
||||||
const actor = await createTestUser(prisma)
|
const actor = await createTestUser(prisma)
|
||||||
const recipient = await createTestRecipient(prisma)
|
const recipient = await createTestPerson(prisma)
|
||||||
const item = await createTestItem(prisma, { stock: 1 })
|
const item = await createTestItem(prisma, { stock: 1 })
|
||||||
|
|
||||||
const result = await createAssignmentUseCase({
|
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 () => {
|
it("returns an assignment, restores stock, closes it, and records a RETURN movement", async () => {
|
||||||
const actor = await createTestUser(prisma)
|
const actor = await createTestUser(prisma)
|
||||||
const recipient = await createTestRecipient(prisma)
|
const recipient = await createTestPerson(prisma)
|
||||||
const item = await createTestItem(prisma, { stock: 4 })
|
const item = await createTestItem(prisma, { stock: 4 })
|
||||||
|
|
||||||
const created = await createAssignmentUseCase({
|
const created = await createAssignmentUseCase({
|
||||||
@@ -172,7 +172,7 @@ describe("assignment use-cases", () => {
|
|||||||
|
|
||||||
it("rejects returning the same assignment twice", async () => {
|
it("rejects returning the same assignment twice", async () => {
|
||||||
const actor = await createTestUser(prisma)
|
const actor = await createTestUser(prisma)
|
||||||
const recipient = await createTestRecipient(prisma)
|
const recipient = await createTestPerson(prisma)
|
||||||
const item = await createTestItem(prisma, { stock: 2 })
|
const item = await createTestItem(prisma, { stock: 2 })
|
||||||
|
|
||||||
const created = await createAssignmentUseCase({
|
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)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
Reference in New Issue
Block a user