clean(): file cleaning

This commit is contained in:
Matthieu Haineault 2025-11-14 09:35:05 -05:00
parent 7968359bfe
commit 1589df979f
10 changed files with 138 additions and 273 deletions

View File

@ -637,25 +637,16 @@
]
}
},
"/preferences": {
"/preferences/update_preferences": {
"patch": {
"operationId": "PreferencesController_updatePreferences",
"parameters": [
{
"name": "PreferencesDto",
"required": true,
"in": "body",
"schema": {
"$ref": "#/components/schemas/PreferencesDto"
}
}
],
"parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "number"
"$ref": "#/components/schemas/PreferencesDto"
}
}
}

View File

@ -1,6 +1,7 @@
import { Controller, Get, Patch, Param, Body, NotFoundException, Req, Post } from "@nestjs/common";
import { Employees } from "@prisma/client";
import { RolesAllowed } from "src/common/decorators/roles.decorators";
import { Result } from "src/common/errors/result-error.factory";
import { GLOBAL_CONTROLLER_ROLES, MANAGER_ROLES } from "src/common/shared/role-groupes";
import { CreateEmployeeDto } from "src/identity-and-account/employees/dtos/create-employee.dto";
import { EmployeeListItemDto } from "src/identity-and-account/employees/dtos/list-employee.dto";
@ -18,14 +19,14 @@ export class EmployeesController {
) { }
@Get('profile')
findOneProfile(@Req() req): Promise<EmployeeProfileItemDto> {
findOneProfile(@Req() req): Promise<Result<EmployeeProfileItemDto,string>> {
const email = req.user?.email;
return this.employeesService.findOneProfile(email);
}
@Get('employee-list')
@RolesAllowed(...MANAGER_ROLES)
findListEmployees(): Promise<EmployeeListItemDto[]> {
findListEmployees(): Promise<Result<EmployeeListItemDto[], string>> {
return this.employeesService.findListEmployees();
}

View File

@ -1,5 +1,6 @@
import { Injectable, NotFoundException } from "@nestjs/common";
import { Employees, Users } from "@prisma/client";
import { Result } from "src/common/errors/result-error.factory";
import { CreateEmployeeDto } from "src/identity-and-account/employees/dtos/create-employee.dto";
import { EmployeeListItemDto } from "src/identity-and-account/employees/dtos/list-employee.dto";
import { EmployeeProfileItemDto } from "src/identity-and-account/employees/dtos/profil-employee.dto";
@ -9,8 +10,8 @@ import { PrismaService } from "src/prisma/prisma.service";
export class EmployeesService {
constructor(private readonly prisma: PrismaService) { }
findListEmployees(): Promise<EmployeeListItemDto[]> {
return this.prisma.employees.findMany({
async findListEmployees(): Promise<Result<EmployeeListItemDto[], string>> {
return {success: true, data:await this.prisma.employees.findMany({
select: {
user: {
select: {
@ -41,11 +42,11 @@ export class EmployeesService {
employee_full_name: `${r.user.first_name} ${r.user.last_name}`,
supervisor_full_name: r.supervisor ? `${r.supervisor.user.first_name} ${r.supervisor.user.last_name}` : null,
})),
);
)}
}
async findOneProfile(email: string): Promise<EmployeeProfileItemDto> {
const emp = await this.prisma.employees.findFirst({
async findOneProfile(email: string): Promise<Result<EmployeeProfileItemDto, string>> {
const employee = await this.prisma.employees.findFirst({
where: { user: { email } },
select: {
user: {
@ -73,20 +74,23 @@ export class EmployeesService {
last_work_day: true,
}
});
if (!emp) throw new NotFoundException(`Employee with email ${email} not found`);
if (!employee)return {success: false, error: `Employee with email ${email} not found`};
return {
first_name: emp.user.first_name,
last_name: emp.user.last_name,
email: emp.user.email,
residence: emp.user.residence,
phone_number: emp.user.phone_number,
company_name: emp.company_code,
job_title: emp.job_title,
employee_full_name: `${emp.user.first_name} ${emp.user.last_name}`,
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,
supervisor_full_name: emp.supervisor ? `${emp.supervisor.user.first_name}, ${emp.supervisor.user.last_name}` : null,
success: true,
data: {
first_name: employee.user.first_name,
last_name: employee.user.last_name,
email: employee.user.email,
residence: employee.user.residence,
phone_number: employee.user.phone_number,
company_name: employee.company_code,
job_title: employee.job_title,
employee_full_name: `${employee.user.first_name} ${employee.user.last_name}`,
first_work_day: employee.first_work_day.toISOString().slice(0, 10),
last_work_day: employee.last_work_day ? employee.last_work_day.toISOString().slice(0, 10) : null,
supervisor_full_name: employee.supervisor ? `${employee.supervisor.user.first_name}, ${employee.supervisor.user.last_name}` : null,
}
};
}
@ -128,107 +132,4 @@ export class EmployeesService {
});
});
}
//_____________________________________________________________________________________________
// Deprecated or unused methods
//_____________________________________________________________________________________________
// findAll(): Promise<Employees[]> {
// return this.prisma.employees.findMany({
// include: { user: true },
// });
// }
// 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 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;
// });
// }
}

View File

@ -1,17 +1,19 @@
import { Body, Controller, Patch } from "@nestjs/common";
import { Body, Controller, Patch, Req } from "@nestjs/common";
import { PreferencesService } from "../services/preferences.service";
import { PreferencesDto } from "../dtos/preferences.dto";
import { Result } from "src/common/errors/result-error.factory";
@Controller('preferences')
export class PreferencesController {
constructor(private readonly service: PreferencesService){}
@Patch()
@Patch('update_preferences')
async updatePreferences(
@Body() user_id: number,
@Req()req,
@Body() payload: PreferencesDto
) {
return this.service.updatePreferences(user_id, payload);
): Promise<Result<PreferencesDto, string>> {
const email = req.user?.email;
return this.service.updatePreferences(email, payload);
}
}

View File

@ -1,25 +1,11 @@
import { IsInt } from "class-validator";
export class PreferencesDto {
@IsInt()
notifications: number;
@IsInt()
dark_mode: number;
@IsInt()
lang_switch: number;
@IsInt()
lefty_mode: number;
@IsInt()
employee_list_display: number;
@IsInt()
validation_display: number;
@IsInt()
timesheet_display: number;
}

View File

@ -2,14 +2,24 @@ import { PreferencesDto } from "../dtos/preferences.dto";
import { PrismaService } from "src/prisma/prisma.service";
import { Preferences } from "@prisma/client";
import { Injectable } from "@nestjs/common";
import { Result } from "src/common/errors/result-error.factory";
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
@Injectable()
export class PreferencesService {
constructor( private readonly prisma: PrismaService ){}
constructor(
private readonly prisma: PrismaService,
private readonly emailResolver: EmailToIdResolver,
async updatePreferences(user_id: number, dto: PreferencesDto ): Promise<Preferences> {
return this.prisma.preferences.update({
where: { id: user_id },
) { }
async updatePreferences(email: string, dto: PreferencesDto): Promise<Result<Preferences, string>> {
const user_id = await this.emailResolver.resolveUserIdWithEmail(email);
if (!user_id.success) return { success: false, error: user_id.error }
return {
success: true,
data: await this.prisma.preferences.update({
where: { user_id: user_id.data },
data: {
notifications: dto.notifications,
dark_mode: dto.dark_mode,
@ -20,6 +30,7 @@ export class PreferencesService {
timesheet_display: dto.timesheet_display,
},
include: { user: true },
});
})
}
}
}

View File

@ -1,35 +1,25 @@
import { BadRequestException, Injectable, Logger } from "@nestjs/common";
import { Injectable } from "@nestjs/common";
import { Result } from "src/common/errors/result-error.factory";
import { PrismaService } from "src/prisma/prisma.service";
@Injectable()
export class MileageService {
private readonly logger = new Logger(MileageService.name);
constructor(private readonly prisma: PrismaService) { }
public async calculateReimbursement(amount: number, bank_code_id: number): Promise<number> {
if(amount < 0) {
throw new BadRequestException(`The amount most be higher than 0`);
}
public async calculateReimbursement(amount: number, bank_code_id: number): Promise<Result<number, string>> {
if (amount < 0) return { success: false, error: 'The amount must be higher than 0' };
//fetch modifier
const bank_code = await this.prisma.bankCodes.findUnique({
where: { id: bank_code_id },
select: { modifier: true, type: true },
});
if(!bank_code) {
throw new BadRequestException(`bank_code ${bank_code_id} not found`);
}
if(bank_code.type !== 'mileage') {
this.logger.warn(`bank_code ${bank_code_id} of type ${bank_code.type} is used for mileage`)
}
if (!bank_code) return { success: false, error: `bank_code ${bank_code_id} not found` }
//calculate total amount to reimburs
const reimboursement = amount * bank_code.modifier;
const result = parseFloat(reimboursement.toFixed(2));
this.logger.debug(`calculateReimbursement -> amount= ${amount}, modifier= ${bank_code.modifier}, total= ${result}`);
return result;
return { success: true, data: result };
}
}

View File

@ -3,6 +3,7 @@ import { Prisma, PrismaClient } from '@prisma/client';
import { getWeekStart, getWeekEnd, computeHours } from 'src/common/utils/date-utils';
import { PrismaService } from 'src/prisma/prisma.service';
import { DAILY_LIMIT_HOURS, WEEKLY_LIMIT_HOURS } from 'src/common/utils/constants.utils';
import { Result } from 'src/common/errors/result-error.factory';
type Tx = Prisma.TransactionClient | PrismaClient;
@ -26,13 +27,11 @@ type WeekOvertimeSummary = {
@Injectable()
export class OvertimeService {
private logger = new Logger(OvertimeService.name);
private INCLUDED_TYPES = ['EMERGENCY', 'EVENING', 'OVERTIME', 'REGULAR'] as const; // included types for weekly overtime calculation
constructor(private prisma: PrismaService) { }
async getWeekOvertimeSummary( timesheet_id: number, date: Date, tx?: Tx ): Promise<WeekOvertimeSummary>{
async getWeekOvertimeSummary(timesheet_id: number, date: Date, tx?: Tx): Promise<Result<WeekOvertimeSummary, string>> {
const db = tx ?? this.prisma;
const week_start = getWeekStart(date);
@ -88,14 +87,9 @@ export class OvertimeService {
}
const total_overtime = weekly_overtime + daily_kept_sum;
this.logger.debug(
`[OVERTIME][SUMMARY][ts=${timesheet_id}] week=${week_start.toISOString().slice(0,10)}..${week_end
.toISOString()
.slice(0,10)} week_total=${week_total_hours.toFixed(2)}h weekly=${weekly_overtime.toFixed(
2,
)}h daily_kept=${daily_kept_sum.toFixed(2)}h total=${total_overtime.toFixed(2)}h`,
);
return {
success: true,
data: {
week_start: week_start.toISOString().slice(0, 10),
week_end: week_end.toISOString().slice(0, 10),
week_total_hours,
@ -103,6 +97,7 @@ export class OvertimeService {
daily_overtime_kept: daily_kept_sum,
total_overtime,
breakdown,
}
};
}
}

View File

@ -1,13 +1,12 @@
import { getYearStart, roundToQuarterHour } from "src/common/utils/date-utils";
import { Injectable, Logger } from "@nestjs/common";
import { PrismaService } from "src/prisma/prisma.service";
import { Result } from "src/common/errors/result-error.factory";
@Injectable()
export class SickLeaveService {
constructor(private readonly prisma: PrismaService) { }
private readonly logger = new Logger(SickLeaveService.name);
//switch employeeId for email
async calculateSickLeavePay(
employee_id: number,
@ -15,9 +14,9 @@ export class SickLeaveService {
days_requested: number,
hours_per_day: number,
modifier: number,
): Promise<number> {
): Promise<Result<number, string>> {
if (days_requested <= 0 || hours_per_day <= 0 || modifier <= 0) {
return 0;
return { success: true, data: 0 };
}
//sets the year to jan 1st to dec 31st
@ -38,13 +37,10 @@ export class SickLeaveService {
shifts.map((shift) => shift.date.toISOString().slice(0, 10)),
);
const days_worked = worked_dates.size;
this.logger.debug(
`Sick leave: days worked= ${days_worked} in ${period_start.toDateString()} -> ${period_end.toDateString()}`,
);
//less than 30 worked days returns 0
if (days_worked < 30) {
return 0;
return { success: true, data: 0 };
}
//default 3 days allowed after 30 worked days
@ -70,16 +66,9 @@ export class SickLeaveService {
//cap of 10 days
if (acquired_days > 10) acquired_days = 10;
this.logger.debug(
`Sick leave: threshold Date = ${threshold_date.toDateString()}, bonusMonths = ${months}, acquired Days = ${acquired_days}`,
);
const payable_days = Math.min(acquired_days, days_requested);
const raw_hours = payable_days * hours_per_day * modifier;
const rounded = roundToQuarterHour(raw_hours);
this.logger.debug(
`Sick leave pay: days= ${payable_days}, hoursPerDay= ${hours_per_day}, modifier= ${modifier}, hours= ${rounded}`,
);
return rounded;
return {success:true, data: rounded};
}
}

View File

@ -1,22 +1,27 @@
import { Injectable, Logger, NotFoundException } from "@nestjs/common";
import { Result } from "src/common/errors/result-error.factory";
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
import { PrismaService } from "src/prisma/prisma.service";
@Injectable()
export class VacationService {
constructor(private readonly prisma: PrismaService) {}
private readonly logger = new Logger(VacationService.name);
constructor(
private readonly prisma: PrismaService,
private readonly emailResolver: EmailToIdResolver,
) {}
//switch employeeId for email
async calculateVacationPay(employee_id: number, start_date: Date, days_requested: number, modifier: number): Promise<number> {
async calculateVacationPay(email: string, start_date: Date, days_requested: number, modifier: number): Promise<Result<number, string>> {
const employee_id = await this.emailResolver.findIdByEmail(email);
if(!employee_id.success) return { success: false, error: employee_id.error}
//fetch hiring date
const employee = await this.prisma.employees.findUnique({
where: { id: employee_id },
where: { id: employee_id.data },
select: { first_work_day: true },
});
if(!employee) {
throw new NotFoundException(`Employee #${employee_id} not found`);
}
if(!employee) return { success:false, error:`Employee #${employee_id} not found`}
const hire_date = employee.first_work_day;
//sets "year" to may 1st to april 30th
@ -26,8 +31,6 @@ export class VacationService {
const period_start = new Date(year_of_request, 4, 1); //may = 4
const period_end = new Date(year_of_request + 1, 4, 0); //day 0 of may == april 30th
this.logger.debug(`Vacation period for request: ${period_start.toDateString()} -> ${period_end.toDateString()}`);
//steps to reach to get more vacation weeks in years
const checkpoint = [5, 10, 15];
const anniversaries = checkpoint.map(years => {
@ -36,8 +39,6 @@ export class VacationService {
return anniversary_date;
}).filter(d => d>= period_start && d <= period_end).sort((a,b) => a.getTime() - b.getTime());
this.logger.debug(`anniversatries steps during the period: ${anniversaries.map(date => date.toDateString()).join(',') || 'aucun'}`);
const boundaries = [period_start, ...anniversaries,period_end];
//calculate prorata per segment
let total_vacation_days = 0;
@ -67,10 +68,8 @@ export class VacationService {
const raw_hours = payable_days * 8 * modifier;
const rounded_hours = Math.round(raw_hours * 4) / 4;
this.logger.debug(`Vacation pay: entitledDays=${total_vacation_days.toFixed(2)}, requestedDays=${days_requested},
payableDays=${payable_days.toFixed(2)}, hours=${rounded_hours}`);
return rounded_hours;
return {success:true, data:rounded_hours};
}