refactor(people): replace department with teamId in schemas, services, use-cases and types
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -4,6 +4,12 @@ import prisma from "@/lib/prisma"
|
||||
|
||||
const personWithUserSelect = {
|
||||
include: {
|
||||
team: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user