import { Injectable, NotFoundException } from '@nestjs/common'; import { PrismaService } from 'src/prisma/prisma.service'; import { CreateEmployeeDto } from '../dtos/create-employee.dto'; import { UpdateEmployeeDto } from '../dtos/update-employee.dto'; import { Employees, EmployeesArchive, Users } from '@prisma/client'; import { EmployeeListItemDto } from '../dtos/list-employee.dto'; import { EmployeeProfileItemDto } from '../dtos/profil-employee.dto'; function toDateOrNull(v?: string | null): Date | null { if (!v) return null; const day = new Date(v); return isNaN(day.getTime()) ? null : day; } function toDateOrUndefined(v?: string | null): Date | undefined { const day = toDateOrNull(v ?? undefined); return day === null ? undefined : day; } @Injectable() export class EmployeesService { constructor(private readonly prisma: PrismaService) {} async create(dto: CreateEmployeeDto): Promise { const { first_name, last_name, email, phone_number, residence, external_payroll_id, company_code, job_title, first_work_day, last_work_day, is_supervisor, } = dto; return this.prisma.$transaction(async (transaction) => { const user: Users = await transaction.users.create({ data: { first_name, last_name, email, phone_number, residence, }, }); return transaction.employees.create({ data: { user_id: user.id, external_payroll_id, company_code, job_title, first_work_day, last_work_day, is_supervisor, }, }); }); } findAll(): Promise { return this.prisma.employees.findMany({ include: { user: true }, }); } findListEmployees(): Promise { return this.prisma.employees.findMany({ select: { user: { select: { first_name: true, last_name: true, email: true, }, }, supervisor: { select: { user: { select: { first_name: true, last_name: true, }, }, }, }, job_title: true, company_code: true, } }).then(rows => rows.map(r => ({ first_name: r.user.first_name, last_name: r.user.last_name, employee_full_name: `${r.user.first_name} ${r.user.last_name}`, email: r.user.email, supervisor_full_name: r.supervisor ? `${r.supervisor.user.first_name} ${r.supervisor.user.last_name}` : null, company_name: r.company_code, job_title: r.job_title, })), ); } async findOne(email: string): Promise { const emp = await this.prisma.employees.findFirst({ where: { user: { email } }, include: { user: true }, }); //add search for archived employees if (!emp) { throw new NotFoundException(`Employee with email: ${email} not found`); } return emp; } async findOneProfile(email:string): Promise { const emp = await this.prisma.employees.findFirst({ where: { user: { email } }, select: { user: { select: { first_name: true, last_name: true, email: true, phone_number: true, residence: true, }, }, supervisor: { select: { user: { select: { first_name: true, last_name: true, }, }, }, }, job_title: true, company_code: true, first_work_day: true, last_work_day: true, } }); if (!emp) throw new NotFoundException(`Employee with email ${email} not found`); return { first_name: emp.user.first_name, last_name: emp.user.last_name, employee_full_name: `${emp.user.first_name} ${emp.user.last_name}`, email: emp.user.email, residence: emp.user.residence, phone_number: emp.user.phone_number, supervisor_full_name: emp.supervisor ? `${emp.supervisor.user.first_name}, ${emp.supervisor.user.last_name}` : null, company_name: emp.company_code, job_title: emp.job_title, first_work_day: emp.first_work_day.toISOString().slice(0,10), last_work_day: emp.last_work_day ? emp.last_work_day.toISOString().slice(0,10) : null, }; } async update( email: string, dto: UpdateEmployeeDto, ): Promise { const emp = await this.findOne(email); const { first_name, last_name, phone_number, residence, external_payroll_id, company_code, job_title, first_work_day, last_work_day, is_supervisor, email: new_email, } = dto; return this.prisma.$transaction(async (transaction) => { if( first_name !== undefined || last_name !== undefined || new_email !== undefined || phone_number !== undefined || residence !== undefined ){ await transaction.users.update({ where: { id: emp.user_id }, data: { ...(first_name !== undefined && { first_name }), ...(last_name !== undefined && { last_name }), ...(email !== undefined && { email }), ...(phone_number !== undefined && { phone_number }), ...(residence !== undefined && { residence }), }, }); } const updated = await transaction.employees.update({ where: { id: emp.id }, data: { ...(external_payroll_id !== undefined && { external_payroll_id }), ...(company_code !== undefined && { company_code }), ...(first_work_day !== undefined && { first_work_day }), ...(last_work_day !== undefined && { last_work_day }), ...(job_title !== undefined && { job_title }), ...(is_supervisor !== undefined && { is_supervisor }), }, }); return updated; }); } async remove(email: string): Promise { const emp = await this.findOne(email); return this.prisma.$transaction(async (transaction) => { await transaction.employees.updateMany({ where: { supervisor_id: emp.id }, data: { supervisor_id: null }, }); const deleted_employee = await transaction.employees.delete({ where: {id: emp.id }, }); await transaction.users.delete({ where: { id: emp.user_id }, }); return deleted_employee; }); } //archivation functions ****************************************************** async patchEmployee(email: string, dto: UpdateEmployeeDto): Promise { // 1) Tenter sur employés actifs const active = await this.prisma.employees.findFirst({ where: { user: { email } }, include: { user: true }, }); if (active) { // Archivage : si on reçoit un last_work_day défini et que l'employé n’est pas déjà terminé if (dto.last_work_day !== undefined && active.last_work_day == null && dto.last_work_day !== null) { return this.archiveOnTermination(active, dto); } // Sinon, update standard (split Users/Employees) const { first_name, last_name, email: new_email, phone_number, residence, external_payroll_id, company_code, job_title, first_work_day, last_work_day, supervisor_id, is_supervisor, } = dto as any; const first_work_d = toDateOrUndefined(first_work_day); const last_work_d = Object.prototype.hasOwnProperty('last_work_day') ? toDateOrNull(last_work_day ?? null) : undefined; await this.prisma.$transaction(async (transaction) => { if( first_name !== undefined || last_name !== undefined || new_email !== undefined || phone_number !== undefined || residence !== undefined ) { await transaction.users.update({ where: { id: active.user_id }, data: { ...(first_name !== undefined ? { first_name } : {}), ...(last_name !== undefined ? { last_name } : {}), ...(email !== undefined ? { email: new_email }: {}), ...(phone_number !== undefined ? { phone_number } : {}), ...(residence !== undefined ? { residence } : {}), }, }); } const updated = await transaction.employees.update({ where: { id: active.id }, data: { ...(external_payroll_id !== undefined ? { external_payroll_id } : {}), ...(company_code !== undefined ? { company_code } : {}), ...(job_title !== undefined ? { job_title } : {}), ...(first_work_d !== undefined ? { first_work_day: first_work_d } : {}), ...(last_work_d !== undefined ? { last_work_day: last_work_d } : {}), ...(is_supervisor !== undefined ? { is_supervisor } : {}), ...(supervisor_id !== undefined ? { supervisor_id } : {}), }, include: { user: true }, }); return updated; }); return this.prisma.employees.findFirst({ where: { user: {email} } }); } const user = await this.prisma.users.findUnique({where: {email}}); if(!user) return null; // 2) Pas trouvé en actifs → regarder en archive (pour restauration) const archived = await this.prisma.employeesArchive.findFirst({ where: { user_id: user.id }, include: { user: true }, }); if (archived) { // Condition de restauration : last_work_day === null ou first_work_day fourni const restore = dto.last_work_day === null || dto.first_work_day != null; if (restore) { return this.restoreEmployee(archived, dto); } } // 3) Ni actif, ni archivé → 404 dans le controller return null; } //transfers the employee to archive and then delete from employees table private async archiveOnTermination(active: Employees & {user: Users }, dto: UpdateEmployeeDto): Promise { const last_work_d = toDateOrNull(dto.last_work_day!); if(!last_work_d) throw new Error('invalide last_work_day for archive'); return this.prisma.$transaction(async transaction => { //detach crew from supervisor if employee is a supervisor await transaction.employees.updateMany({ where: { supervisor_id: active.id }, data: { supervisor_id: null }, }) const archived = await transaction.employeesArchive.create({ data: { employee_id: active.id, user_id: active.user_id, first_name: active.user.first_name, last_name: active.user.last_name, external_payroll_id: active.external_payroll_id, company_code: active.company_code, job_title: active.job_title, first_work_day: active.first_work_day, last_work_day: last_work_d, supervisor_id: active.supervisor_id ?? null, is_supervisor: active.is_supervisor, }, include: { user: true} }); //delete from employees table await transaction.employees.delete({ where: { id: active.id } }); //return archived employee return archived }); } //transfers the employee from archive to the employees table private async restoreEmployee(archived: EmployeesArchive & { user:Users }, dto: UpdateEmployeeDto): Promise { // const first_work_d = toDateOrUndefined(dto.first_work_day); return this.prisma.$transaction(async transaction => { //restores the archived employee into the employees table const restored = await transaction.employees.create({ data: { user_id: archived.user_id, external_payroll_id: archived.external_payroll_id, company_code: archived.company_code, job_title: archived.job_title, first_work_day: archived.first_work_day, last_work_day: null, is_supervisor: archived.is_supervisor ?? false, }, }); //deleting archived entry by id await transaction.employeesArchive.delete({ where: { id: archived.id } }); //return restored employee return restored; }); } //fetches all archived employees async findAllArchived(): Promise { return this.prisma.employeesArchive.findMany(); } //fetches an archived employee async findOneArchived(id: number): Promise { return this.prisma.employeesArchive.findUniqueOrThrow({ where: { id } }); } }