refactor(people): replace department with teamId in schemas, services, use-cases and types

This commit is contained in:
2026-06-26 01:28:37 +02:00
parent 0d7326c680
commit 2919479f45
7 changed files with 112 additions and 49 deletions
+1
View File
@@ -6,6 +6,7 @@ type FieldErrors = Record<string, string[]>
const personErrorMessageKeys = {
"Email already exists": "duplicateEmail",
"Team not found": "teamNotFound",
} as const satisfies Record<string, keyof PersonActionCopy>
function isPersonErrorMessage(
+1 -1
View File
@@ -76,7 +76,7 @@ export function localizeUnifiedCreateFieldErrors(
return message
if (field === "lastName" && message === schemaCopy.lastNameRequired)
return message
if (field === "department" && message === schemaCopy.departmentRequired)
if (field === "teamId" && message === schemaCopy.teamIdInvalid)
return message
if (field === "email" && message === schemaCopy.emailInvalid)
return message
-11
View File
@@ -8,17 +8,6 @@ export const SIGN_IN_URL = "/login"
export const TOKEN_EXPIRATION_SECONDS = 60 * 60 * 2 // 2 hour
export const PERSON_DEPARTMENTS = {
IT: "IT",
ENGINEERING: "ENGINEERING",
LOGISTICS: "LOGISTICS",
TRAFFIC: "TRAFFIC",
DRIVER: "DRIVER",
ADMINISTRATION: "ADMINISTRATION",
SALES: "SALES",
OTHER: "OTHER",
} as const
export const ITEM_STATUS = {
AVAILABLE: "AVAILABLE",
ASSIGNED: "ASSIGNED",
+6 -15
View File
@@ -7,23 +7,12 @@ 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",
userIdInvalid: "User ID must be a valid UUID",
teamIdInvalid: "Team must be a valid id",
}
export const personDepartments = [
"IT",
"ENGINEERING",
"TRAFFIC",
"DRIVER",
"LOGISTICS",
"ADMINISTRATION",
"SALES",
"OTHER",
] as const
function buildPersonBaseSchema(copy: PersonSchemaCopy) {
return z.object({
id: z.string().optional(),
@@ -33,9 +22,11 @@ function buildPersonBaseSchema(copy: PersonSchemaCopy) {
lastName: z.string().min(1, {
error: copy.lastNameRequired,
}),
department: z.enum(personDepartments, {
error: copy.departmentRequired,
}),
teamId: z
.string()
.uuid({ error: copy.teamIdInvalid })
.optional()
.nullable(),
email: z.string().optional().nullable(),
phone: z.string().optional().nullable(),
userId: z
+10 -7
View File
@@ -1,7 +1,6 @@
import { z } from "zod"
import type { Dictionary } from "@/i18n/dictionaries"
import { personDepartments } from "@/schemas/person.schema"
export type UserSchemaCopy = Dictionary["admin"]["users"]["schema"]
@@ -93,9 +92,11 @@ export function buildUnifiedUpdateSchema(copy: UnifiedSchemaCopy) {
id: z.string().nonempty(copy.idRequired),
firstName: z.string().trim().min(1, { error: copy.firstNameRequired }),
lastName: z.string().trim().min(1, { error: copy.lastNameRequired }),
department: z.enum(personDepartments, {
error: copy.departmentRequired,
}),
teamId: z
.string()
.uuid({ error: copy.teamIdInvalid })
.optional()
.nullable(),
email: z
.union([z.email({ error: copy.emailInvalid }), z.literal(""), z.null()])
.optional(),
@@ -129,9 +130,11 @@ export function buildUnifiedCreateSchema(copy: UnifiedSchemaCopy) {
.object({
firstName: z.string().trim().min(1, { error: copy.firstNameRequired }),
lastName: z.string().trim().min(1, { error: copy.lastNameRequired }),
department: z.enum(personDepartments, {
error: copy.departmentRequired,
}),
teamId: z
.string()
.uuid({ error: copy.teamIdInvalid })
.optional()
.nullable(),
email: z.email({ error: copy.emailInvalid }),
phone: z.string().optional().nullable(),
role: unifiedFormRoleSchema,
+6
View File
@@ -4,6 +4,12 @@ import prisma from "@/lib/prisma"
const personWithUserSelect = {
include: {
team: {
select: {
id: true,
name: true,
},
},
user: {
select: {
id: true,
+88 -15
View File
@@ -11,6 +11,7 @@ import type {
UnifiedUpdateFormType,
} from "@/schemas/user.schema"
import { PersonService } from "@/services/person.service"
import { TeamService } from "@/services/team.service"
import { getUserByEmail } from "@/services/user.service"
type FieldErrors = Record<string, string[]>
@@ -48,10 +49,70 @@ function uniqueErrorFor(error: unknown): FieldErrors | null {
return { email: ["Email already exists"] }
}
function foreignKeyErrorFor(error: unknown): FieldErrors | null {
if (
!(error instanceof Prisma.PrismaClientKnownRequestError) ||
error.code !== "P2003"
) {
return null
}
const fieldName = error.meta?.field_name
if (fieldName === "Person_teamId_fkey" || fieldName === "teamId") {
return { teamId: ["Team not found"] }
}
return null
}
function errorFor(error: unknown): FieldErrors | null {
return uniqueErrorFor(error) ?? foreignKeyErrorFor(error)
}
function teamRelationInputForCreate(teamId: string | null | undefined) {
if (teamId) {
return { team: { connect: { id: teamId } } }
}
return {}
}
function teamRelationInputForUpdate(teamId: string | null | undefined) {
if (teamId) {
return { team: { connect: { id: teamId } } }
}
return { team: { disconnect: true } }
}
function userRelationInputForCreate(userId: string | null | undefined) {
if (userId) {
return { user: { connect: { id: userId } } }
}
return {}
}
async function validateTeamId(
teamId: string | null | undefined,
tx: Prisma.TransactionClient,
): Promise<PersonUseCaseResult | null> {
if (!teamId) return null
const team = await TeamService.findById(teamId, tx)
if (!team) {
return personError({ teamId: ["Team not found"] })
}
return null
}
export async function createPersonUseCase(
input: CreatePersonFormType,
): Promise<PersonUseCaseResult> {
const { firstName, lastName, department, email, phone, userId } = input
const { firstName, lastName, teamId, email, phone, userId } = input
try {
return await prisma.$transaction(async (tx) => {
@@ -63,14 +124,17 @@ export async function createPersonUseCase(
}
}
const teamError = await validateTeamId(teamId, tx)
if (teamError) return teamError
await PersonService.create(
{
firstName,
lastName,
department,
...teamRelationInputForCreate(teamId),
email: email || null,
phone: phone || null,
...(userId ? { user: { connect: { id: userId } } } : {}),
...userRelationInputForCreate(userId),
},
tx,
)
@@ -80,7 +144,7 @@ export async function createPersonUseCase(
}
})
} catch (error) {
const errors = uniqueErrorFor(error)
const errors = errorFor(error)
if (errors) {
return personError(errors)
@@ -93,7 +157,7 @@ export async function createPersonUseCase(
export async function updatePersonUseCase(
input: UpdatePersonFormType,
): Promise<PersonUseCaseResult> {
const { id, firstName, lastName, department, email, phone, userId } = input
const { id, firstName, lastName, teamId, email, phone, userId } = input
try {
return await prisma.$transaction(async (tx) => {
@@ -105,17 +169,20 @@ export async function updatePersonUseCase(
}
}
const teamError = await validateTeamId(teamId, tx)
if (teamError) return teamError
await PersonService.update(
id,
{
firstName,
lastName,
department,
...teamRelationInputForUpdate(teamId),
email: email || null,
phone: phone || null,
...(userId
? { user: { connect: { id: userId } } }
: { userId: null }),
: { user: { disconnect: true } }),
},
tx,
)
@@ -125,7 +192,7 @@ export async function updatePersonUseCase(
}
})
} catch (error) {
const errors = uniqueErrorFor(error)
const errors = errorFor(error)
if (errors) {
return personError(errors)
@@ -141,7 +208,7 @@ export async function createPersonUserUseCase(
const {
firstName,
lastName,
department,
teamId,
email,
phone,
role,
@@ -162,13 +229,16 @@ export async function createPersonUserUseCase(
return personError({ email: ["Email already exists"] })
}
const teamError = await validateTeamId(teamId, tx)
if (teamError) return teamError
if (role === "NO_USER") {
// Person-only creation — no User record
await PersonService.create(
{
firstName,
lastName,
department,
...teamRelationInputForCreate(teamId),
email,
phone: phone ?? null,
},
@@ -187,7 +257,7 @@ export async function createPersonUserUseCase(
{
firstName,
lastName,
department,
...teamRelationInputForCreate(teamId),
email,
phone: phone ?? null,
},
@@ -221,7 +291,7 @@ export async function createPersonUserUseCase(
return { success: true }
})
} catch (error) {
const errors = uniqueErrorFor(error)
const errors = errorFor(error)
if (errors) {
return personError(errors)
@@ -238,7 +308,7 @@ export async function updatePersonUserUseCase(
id,
firstName,
lastName,
department,
teamId,
email,
phone,
role,
@@ -261,12 +331,15 @@ export async function updatePersonUserUseCase(
}
}
const teamError = await validateTeamId(teamId, tx)
if (teamError) return teamError
await PersonService.update(
id,
{
firstName,
lastName,
department,
...teamRelationInputForUpdate(teamId),
email: email || null,
phone: phone || null,
},
@@ -302,7 +375,7 @@ export async function updatePersonUserUseCase(
return { success: true }
})
} catch (error) {
const errors = uniqueErrorFor(error)
const errors = errorFor(error)
if (errors) {
return personError(errors)