targo-backend/src/modules/employees/services/employees.service.ts

402 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<Employees> {
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<Employees[]> {
return this.prisma.employees.findMany({
include: { user: true },
});
}
findListEmployees(): Promise<EmployeeListItemDto[]> {
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<Employees> {
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<EmployeeProfileItemDto> {
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<Employees> {
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<Employees> {
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<Employees | EmployeesArchive | null> {
// 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é nest 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<EmployeesArchive> {
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<Employees> {
// 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<EmployeesArchive[]> {
return this.prisma.employeesArchive.findMany();
}
//fetches an archived employee
async findOneArchived(id: number): Promise<EmployeesArchive> {
return this.prisma.employeesArchive.findUniqueOrThrow({ where: { id } });
}
}