import { Prisma, UserStatus } from "@/generated/prisma/client" import { normalizeEmail } from "@/lib/email" import prisma from "@/lib/prisma" import { getPasswordHash } from "@/lib/security" import type { CreatePersonFormType, UpdatePersonFormType, } from "@/schemas/person.schema" import type { UnifiedCreateFormType, UnifiedUpdateFormType, } from "@/schemas/user.schema" import { PersonService } from "@/services/person.service" import { getUserByEmail } from "@/services/user.service" type FieldErrors = Record 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 { 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 { 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 } } export async function createPersonUserUseCase( input: UnifiedCreateFormType, ): Promise { const { firstName, lastName, department, email, phone, role, password, isActive, } = input try { return await prisma.$transaction(async (tx) => { // Cross-table email uniqueness: check both Person and User tables const existingPersonEmail = await PersonService.findByEmail(email, tx) if (existingPersonEmail) { return personError({ email: ["Email already exists"] }) } const existingUserEmail = await getUserByEmail(email, tx) if (existingUserEmail) { return personError({ email: ["Email already exists"] }) } if (role === "NO_USER") { // Person-only creation — no User record await PersonService.create( { firstName, lastName, department, email, phone: phone ?? null, }, tx, ) return { success: true } } // Person + User creation if (!password) { return personError({ password: ["Password is required"] }) } const person = await PersonService.create( { firstName, lastName, department, email, phone: phone ?? null, }, tx, ) const userName = `${firstName} ${lastName}` const hashedPassword = await getPasswordHash(password) const status = isActive ? UserStatus.ACTIVE : UserStatus.DISABLED const now = new Date() const user = await tx.user.create({ data: { name: userName, email, emailNormalized: normalizeEmail(email), passwordHash: hashedPassword, role, status, ...(status === UserStatus.ACTIVE ? { activatedAt: now } : {}), passwordChangedAt: now, }, }) await PersonService.update( person.id, { user: { connect: { id: user.id } } }, tx, ) return { success: true } }) } catch (error) { const errors = uniqueErrorFor(error) if (errors) { return personError(errors) } throw error } } export async function updatePersonUserUseCase( input: UnifiedUpdateFormType, ): Promise { const { id, firstName, lastName, department, email, phone, role, isActive, password, } = input try { return await prisma.$transaction(async (tx) => { const existing = await PersonService.findById(id, tx) if (!existing) { return personError({ id: ["Person not found"] }) } 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, }, tx, ) // If the person has a linked user, update User fields. if (existing.userId) { const userData: Prisma.UserUpdateInput = {} if (role !== undefined) { userData.role = role } if (isActive !== undefined) { userData.status = isActive ? UserStatus.ACTIVE : UserStatus.DISABLED if (isActive) { userData.activatedAt = new Date() } } if (password && password.length >= 8) { userData.passwordHash = await getPasswordHash(password) userData.passwordChangedAt = new Date() } if (Object.keys(userData).length > 0) { await tx.user.update({ where: { id: existing.userId }, data: userData, }) } } return { success: true } }) } catch (error) { const errors = uniqueErrorFor(error) if (errors) { return personError(errors) } throw error } }