diff --git a/docs/swagger/swagger-spec.json b/docs/swagger/swagger-spec.json index 28fa9d9..e12e07a 100644 --- a/docs/swagger/swagger-spec.json +++ b/docs/swagger/swagger-spec.json @@ -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" } } } diff --git a/src/identity-and-account/employees/controllers/employees.controller.ts b/src/identity-and-account/employees/controllers/employees.controller.ts index a3dd411..fd3bbfc 100644 --- a/src/identity-and-account/employees/controllers/employees.controller.ts +++ b/src/identity-and-account/employees/controllers/employees.controller.ts @@ -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 { + findOneProfile(@Req() req): Promise> { const email = req.user?.email; return this.employeesService.findOneProfile(email); } @Get('employee-list') @RolesAllowed(...MANAGER_ROLES) - findListEmployees(): Promise { + findListEmployees(): Promise> { return this.employeesService.findListEmployees(); } diff --git a/src/identity-and-account/employees/services/employees.service.ts b/src/identity-and-account/employees/services/employees.service.ts index 22cee41..bd4d8ce 100644 --- a/src/identity-and-account/employees/services/employees.service.ts +++ b/src/identity-and-account/employees/services/employees.service.ts @@ -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,14 +10,14 @@ import { PrismaService } from "src/prisma/prisma.service"; export class EmployeesService { constructor(private readonly prisma: PrismaService) { } - findListEmployees(): Promise { - return this.prisma.employees.findMany({ + async findListEmployees(): Promise> { + return {success: true, data:await this.prisma.employees.findMany({ select: { user: { select: { first_name: true, - last_name: true, - email: true, + last_name: true, + email: true, }, }, supervisor: { @@ -24,37 +25,37 @@ export class EmployeesService { user: { select: { first_name: true, - last_name: true, + last_name: true, }, }, }, }, - job_title: true, + job_title: true, company_code: true, } }).then(rows => rows.map(r => ({ - first_name: r.user.first_name, - last_name: r.user.last_name, - email: r.user.email, - company_name: r.company_code, - job_title: r.job_title, - employee_full_name: `${r.user.first_name} ${r.user.last_name}`, + first_name: r.user.first_name, + last_name: r.user.last_name, + email: r.user.email, + company_name: r.company_code, + job_title: r.job_title, + 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 { - const emp = await this.prisma.employees.findFirst({ + async findOneProfile(email: string): Promise> { + const employee = await this.prisma.employees.findFirst({ where: { user: { email } }, select: { user: { select: { - first_name: true, - last_name: true, - email: true, + first_name: true, + last_name: true, + email: true, phone_number: true, - residence: true, + residence: true, }, }, supervisor: { @@ -62,35 +63,38 @@ export class EmployeesService { user: { select: { first_name: true, - last_name: true, + last_name: true, }, }, }, }, - job_title: true, - company_code: true, + job_title: true, + company_code: true, first_work_day: true, - last_work_day: true, + 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, + } }; } - async create(dto: CreateEmployeeDto): Promise { + async create(dto: CreateEmployeeDto): Promise { const { first_name, last_name, @@ -128,107 +132,4 @@ export class EmployeesService { }); }); } - - //_____________________________________________________________________________________________ - // Deprecated or unused methods - //_____________________________________________________________________________________________ - - - - // findAll(): Promise { - // return this.prisma.employees.findMany({ - // include: { user: true }, - // }); - // } - - // 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 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; - // }); - // } - - - } \ No newline at end of file diff --git a/src/identity-and-account/preferences/controllers/preferences.controller.ts b/src/identity-and-account/preferences/controllers/preferences.controller.ts index a185837..64ecce9 100644 --- a/src/identity-and-account/preferences/controllers/preferences.controller.ts +++ b/src/identity-and-account/preferences/controllers/preferences.controller.ts @@ -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> { + const email = req.user?.email; + return this.service.updatePreferences(email, payload); } } \ No newline at end of file diff --git a/src/identity-and-account/preferences/dtos/preferences.dto.ts b/src/identity-and-account/preferences/dtos/preferences.dto.ts index 6a41b33..560b8a8 100644 --- a/src/identity-and-account/preferences/dtos/preferences.dto.ts +++ b/src/identity-and-account/preferences/dtos/preferences.dto.ts @@ -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; } \ No newline at end of file diff --git a/src/identity-and-account/preferences/services/preferences.service.ts b/src/identity-and-account/preferences/services/preferences.service.ts index 4e3c108..ae5aea5 100644 --- a/src/identity-and-account/preferences/services/preferences.service.ts +++ b/src/identity-and-account/preferences/services/preferences.service.ts @@ -1,25 +1,36 @@ import { PreferencesDto } from "../dtos/preferences.dto"; -import { PrismaService } from "src/prisma/prisma.service"; -import { Preferences } from "@prisma/client"; -import { Injectable } from "@nestjs/common"; +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 { - return this.prisma.preferences.update({ - where: { id: user_id }, - data: { - notifications: dto.notifications, - dark_mode: dto.dark_mode, - lang_switch: dto.lang_switch, - lefty_mode: dto.lefty_mode, - employee_list_display: dto.employee_list_display, - validation_display: dto.validation_display, - timesheet_display: dto.timesheet_display, - }, - include: { user: true }, - }); + ) { } + + async updatePreferences(email: string, dto: PreferencesDto): Promise> { + 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, + lang_switch: dto.lang_switch, + lefty_mode: dto.lefty_mode, + employee_list_display: dto.employee_list_display, + validation_display: dto.validation_display, + timesheet_display: dto.timesheet_display, + }, + include: { user: true }, + }) + } } } \ No newline at end of file diff --git a/src/time-and-attendance/domains/services/mileage.service.ts b/src/time-and-attendance/domains/services/mileage.service.ts index 2b589a9..799af07 100644 --- a/src/time-and-attendance/domains/services/mileage.service.ts +++ b/src/time-and-attendance/domains/services/mileage.service.ts @@ -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) {} + constructor(private readonly prisma: PrismaService) { } - public async calculateReimbursement(amount: number, bank_code_id: number): Promise { - if(amount < 0) { - throw new BadRequestException(`The amount most be higher than 0`); - } + public async calculateReimbursement(amount: number, bank_code_id: number): Promise> { + 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 }; } - } \ No newline at end of file diff --git a/src/time-and-attendance/domains/services/overtime.service.ts b/src/time-and-attendance/domains/services/overtime.service.ts index 9b7082c..211f4a5 100644 --- a/src/time-and-attendance/domains/services/overtime.service.ts +++ b/src/time-and-attendance/domains/services/overtime.service.ts @@ -3,40 +3,39 @@ 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; type WeekOvertimeSummary = { - week_start:string; - week_end: string; + week_start: string; + week_end: string; week_total_hours: number; - weekly_overtime: number; + weekly_overtime: number; daily_overtime_kept: number; total_overtime: number; breakdown: Array<{ - date:string; + date: string; day_hours: number; - day_overtime: number; - daily_kept: number; + day_overtime: number; + daily_kept: number; running_total_before: number; }>; }; @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 + private INCLUDED_TYPES = ['EMERGENCY', 'EVENING', 'OVERTIME', 'REGULAR'] as const; // included types for weekly overtime calculation - constructor(private prisma: PrismaService) {} + constructor(private prisma: PrismaService) { } - async getWeekOvertimeSummary( timesheet_id: number, date: Date, tx?: Tx ): Promise{ + async getWeekOvertimeSummary(timesheet_id: number, date: Date, tx?: Tx): Promise> { const db = tx ?? this.prisma; const week_start = getWeekStart(date); - const week_end = getWeekEnd(week_start); + const week_end = getWeekEnd(week_start); const shifts = await db.shifts.findMany({ where: { @@ -45,24 +44,24 @@ export class OvertimeService { bank_code: { type: { in: this.INCLUDED_TYPES as unknown as string[] } }, }, select: { date: true, start_time: true, end_time: true }, - orderBy: [{date: 'asc'}, {start_time: 'asc'}], + orderBy: [{ date: 'asc' }, { start_time: 'asc' }], }); const day_totals = new Map(); - for (const shift of shifts){ - const key = shift.date.toISOString().slice(0,10); + for (const shift of shifts) { + const key = shift.date.toISOString().slice(0, 10); const hours = computeHours(shift.start_time, shift.end_time, 5); day_totals.set(key, (day_totals.get(key) ?? 0) + hours); } const days: string[] = []; - for(let i = 0; i < 7; i++){ + for (let i = 0; i < 7; i++) { const day = new Date(week_start.getTime() + i * 24 * 60 * 60 * 1000); - days.push(day.toISOString().slice(0,10)); + days.push(day.toISOString().slice(0, 10)); } - const week_total_hours = [ ...day_totals.values()].reduce((a,b) => a + b, 0); - const weekly_overtime = Math.max(0, week_total_hours - WEEKLY_LIMIT_HOURS); + const week_total_hours = [...day_totals.values()].reduce((a, b) => a + b, 0); + const weekly_overtime = Math.max(0, week_total_hours - WEEKLY_LIMIT_HOURS); let running = 0; let daily_kept_sum = 0; @@ -73,7 +72,7 @@ export class OvertimeService { const day_overtime = Math.max(0, day_hours - DAILY_LIMIT_HOURS); const cap_before_40 = Math.max(0, WEEKLY_LIMIT_HOURS - running); - const daily_kept = Math.min(day_overtime, cap_before_40); + const daily_kept = Math.min(day_overtime, cap_before_40); breakdown.push({ date: key, @@ -88,21 +87,17 @@ 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 { - week_start: week_start.toISOString().slice(0, 10), - week_end: week_end.toISOString().slice(0, 10), - week_total_hours, - weekly_overtime, - daily_overtime_kept: daily_kept_sum, - total_overtime, - breakdown, + success: true, + data: { + week_start: week_start.toISOString().slice(0, 10), + week_end: week_end.toISOString().slice(0, 10), + week_total_hours, + weekly_overtime, + daily_overtime_kept: daily_kept_sum, + total_overtime, + breakdown, + } }; } } diff --git a/src/time-and-attendance/domains/services/sick-leave.service.ts b/src/time-and-attendance/domains/services/sick-leave.service.ts index 60a847e..4122262 100644 --- a/src/time-and-attendance/domains/services/sick-leave.service.ts +++ b/src/time-and-attendance/domains/services/sick-leave.service.ts @@ -1,12 +1,11 @@ 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); + constructor(private readonly prisma: PrismaService) { } //switch employeeId for email async calculateSickLeavePay( @@ -15,9 +14,9 @@ export class SickLeaveService { days_requested: number, hours_per_day: number, modifier: number, - ): Promise { + ): Promise> { 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}; } } diff --git a/src/time-and-attendance/domains/services/vacation.service.ts b/src/time-and-attendance/domains/services/vacation.service.ts index 2e3a214..922948a 100644 --- a/src/time-and-attendance/domains/services/vacation.service.ts +++ b/src/time-and-attendance/domains/services/vacation.service.ts @@ -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 { + async calculateVacationPay(email: string, start_date: Date, days_requested: number, modifier: number): Promise> { + 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}; }