From 58baaade5de9c128ba7a71095570b6bc26d0857d Mon Sep 17 00:00:00 2001 From: Nicolas Drolet Date: Mon, 22 Dec 2025 14:09:35 -0500 Subject: [PATCH] refactor(pay-periods): refactored bulkApprove for crew to instead toggle approval of all shifts and expenses of a given employee --- src/common/utils/date-utils.ts | 2 +- .../controllers/auth.controller.ts | 4 +- .../services/abstract-user.service.ts | 1 - .../pay-period/pay-periods.controller.ts | 16 +++- .../services/pay-periods-command.service.ts | 96 +++++++++---------- .../services/pay-periods-query.service.ts | 31 +++--- 6 files changed, 75 insertions(+), 75 deletions(-) diff --git a/src/common/utils/date-utils.ts b/src/common/utils/date-utils.ts index e642e8f..cdedd53 100644 --- a/src/common/utils/date-utils.ts +++ b/src/common/utils/date-utils.ts @@ -9,7 +9,7 @@ export function computeHours(start: Date, end: Date, roundToMinutes?: number): n const minutes = roundToMinutes ? Math.round(totalMinutes / roundToMinutes) * roundToMinutes : totalMinutes; - return +(minutes / 60).toFixed(2); + return +(minutes / 60); } //round the amount of hours to quarter diff --git a/src/identity-and-account/authentication/controllers/auth.controller.ts b/src/identity-and-account/authentication/controllers/auth.controller.ts index dc526e9..d0a2523 100644 --- a/src/identity-and-account/authentication/controllers/auth.controller.ts +++ b/src/identity-and-account/authentication/controllers/auth.controller.ts @@ -17,8 +17,8 @@ export class AuthController { @Get('/callback') @UseGuards(OIDCLoginGuard) loginCallback(@Req() req: Request, @Res() res: Response) { - res.redirect("http://10.100.251.2:9013/#/v1/login-success"); - // res.redirect(process.env.REDIRECT_URL_DEV!); + // res.redirect("http://10.100.251.2:9013/#/v1/login-success"); + res.redirect(process.env.REDIRECT_URL_DEV!); } @Get('/me') diff --git a/src/identity-and-account/users-management/services/abstract-user.service.ts b/src/identity-and-account/users-management/services/abstract-user.service.ts index 872f7c2..51404f9 100644 --- a/src/identity-and-account/users-management/services/abstract-user.service.ts +++ b/src/identity-and-account/users-management/services/abstract-user.service.ts @@ -30,7 +30,6 @@ export abstract class AbstractUserService { let module_access: Modules[] = []; if (user.user_module_access !== null) module_access = toKeysFromBoolean(user.user_module_access); - console.log('module access: ', module_access); const clean_user = { first_name: user.first_name, diff --git a/src/time-and-attendance/pay-period/pay-periods.controller.ts b/src/time-and-attendance/pay-period/pay-periods.controller.ts index 52ba773..8104c69 100644 --- a/src/time-and-attendance/pay-period/pay-periods.controller.ts +++ b/src/time-and-attendance/pay-period/pay-periods.controller.ts @@ -43,16 +43,22 @@ export class PayPeriodsController { return this.queryService.findOneByYearPeriod(year, period_no); } - @Patch("crew/pay-period-approval") + @Patch("pay-period-approval") @ModuleAccessAllowed(ModulesEnum.timesheets_approval) - async bulkApproval(@Access('email') email:string, @Body() dto: BulkCrewApprovalDto) { - if (!email) throw new UnauthorizedException(`Session infos not found`); - return this.commandService.bulkApproveCrew(email, dto); + async bulkApproval( + @Body('email') email: string, + @Body('timesheet_ids') timesheet_ids: number[], + @Body('is_approved') is_approved: boolean, + ): Promise> { + if (!email) return {success: false, error: 'EMAIL_REQUIRED'}; + if (!timesheet_ids || timesheet_ids.length < 1) return {success: false, error: 'TIMESHEET_ID_REQUIRED'}; + if (!is_approved) return {success: false, error: 'APPROVAL_STATUS_REQUIRED'} + return this.commandService.bulkApproveEmployee(email, timesheet_ids, is_approved); } @Get('crew/:year/:periodNumber') @ModuleAccessAllowed(ModulesEnum.timesheets_approval) - async getCrewOverview(@Access('email') email:string, + async getCrewOverview(@Access('email') email: string, @Param('year', ParseIntPipe) year: number, @Param('periodNumber', ParseIntPipe) period_no: number, @Query('includeSubtree', new ParseBoolPipe({ optional: true })) include_subtree = false, diff --git a/src/time-and-attendance/pay-period/services/pay-periods-command.service.ts b/src/time-and-attendance/pay-period/services/pay-periods-command.service.ts index fb0d2d8..5799f3c 100644 --- a/src/time-and-attendance/pay-period/services/pay-periods-command.service.ts +++ b/src/time-and-attendance/pay-period/services/pay-periods-command.service.ts @@ -1,9 +1,10 @@ import { Injectable, NotFoundException } from "@nestjs/common"; import { PrismaService } from "src/prisma/prisma.service"; -import { BulkCrewApprovalDto } from "../dtos/bulk-crew-approval.dto"; import { PayPeriodsQueryService } from "./pay-periods-query.service"; import { TimesheetApprovalService } from "src/time-and-attendance/timesheets/services/timesheet-approval.service"; import { Result } from "src/common/errors/result-error.factory"; +import { EmailToIdResolver } from "src/common/mappers/email-id.mapper"; +import { Prisma } from "@prisma/client"; //change promise to return result pattern @@ -13,63 +14,56 @@ export class PayPeriodsCommandService { private readonly prisma: PrismaService, private readonly timesheetsApproval: TimesheetApprovalService, private readonly query: PayPeriodsQueryService, + private readonly emailResolver: EmailToIdResolver, ) { } //function to approve pay-periods according to selected crew members - async bulkApproveCrew(email: string, dto: BulkCrewApprovalDto): Promise> { - const { include_subtree, items } = dto; - if (!items?.length) return { success: false, error: 'EMPLOYEE_NOT_FOUND' }; + async bulkApproveEmployee(email: string, timesheet_ids: number[], is_approved: boolean): Promise> { + let shifts: Prisma.BatchPayload; + let expenses: Prisma.BatchPayload; - //fetch and validate supervisor status - const supervisor = await this.query.getSupervisor(email); - if (!supervisor) return { success: false, error: 'EMPLOYEE_NOT_FOUND' }; - if (!supervisor.is_supervisor) return { success: false, error: 'INVALID_EMPLOYEE' }; + //fetch employee id + const employee_id = await this.emailResolver.findIdByEmail(email); + if (!employee_id.success) return { success: false, error: employee_id.error } - //fetches emails of crew members linked to supervisor - const crew_emails = await this.query.resolveCrewEmails(supervisor.id, include_subtree); - if (!crew_emails.success) return { success: false, error: 'INVALID_EMAIL' }; + try { + shifts = await this.prisma.shifts.updateMany({ + where: { + timesheet: { + id: { in: timesheet_ids }, + employee_id: employee_id.data, + }, + }, + data: { + is_approved: is_approved, + } + }); - for (const item of items) { - if (!crew_emails.data.has(item.employee_email)) { - return { success: false, error: 'INVALID_EMPLOYEE' } - } + expenses = await this.prisma.expenses.updateMany({ + where: { + timesheet: { + id: { in: timesheet_ids }, + employee_id: employee_id.data, + }, + }, + data: { + is_approved: is_approved, + } + }); + + await this.prisma.timesheets.updateMany({ + where: { + id: { in: timesheet_ids}, + employee_id: employee_id.data, + }, + data: { + is_approved: is_approved, + } + }) + } catch (_error) { + return { success: false, error: 'UNKNOWN_ERROR_VALIDATING' } } - const period_cache = new Map(); - const getPeriod = async (year: number, period_no: number) => { - const key = `${year}-${period_no}`; - if (period_cache.has(key)) return period_cache.get(key)!; - - const period = await this.query.getPeriodWindow(year, period_no); - if (!period) throw new NotFoundException(`Pay period ${year}-${period_no} not found`); - period_cache.set(key, period); - return period; - }; - - let updated = 0; - - await this.prisma.$transaction(async (transaction) => { - for (const item of items) { - const { period_start, period_end } = await getPeriod(item.pay_year, item.period_no); - - const timesheets = await transaction.timesheets.findMany({ - where: { - employee: { user: { email: item.employee_email } }, - OR: [ - { shift: { some: { date: { gte: period_start, lte: period_end } } } }, - { expense: { some: { date: { gte: period_start, lte: period_end } } } }, - ], - }, - select: { id: true }, - }); - - for (const { id } of timesheets) { - await this.timesheetsApproval.cascadeApprovalWithtx(transaction, id, item.approve); - updated++; - } - - } - }); - return { success: true, data: { updated } }; + return { success: true, data: { shifts: shifts.count, expenses: expenses.count}} } } \ No newline at end of file diff --git a/src/time-and-attendance/pay-period/services/pay-periods-query.service.ts b/src/time-and-attendance/pay-period/services/pay-periods-query.service.ts index bbb326b..c2edc42 100644 --- a/src/time-and-attendance/pay-period/services/pay-periods-query.service.ts +++ b/src/time-and-attendance/pay-period/services/pay-periods-query.service.ts @@ -252,10 +252,11 @@ export class PayPeriodsQueryService { for (const employee of all_employees) { let is_active = true; + if (employee.last_work_day !== null) { is_active = this.checkForInactiveDate(employee.last_work_day) } - console.log('employee name: ', employee.user.first_name, employee.user.first_name, 'last work day: ', employee.last_work_day); + by_employee.set(employee.id, { email: employee.user.email, employee_name: employee.user.first_name + ' ' + employee.user.last_name, @@ -311,26 +312,26 @@ export class PayPeriodsQueryService { const hours = computeHours(shift.start_time, shift.end_time); const type = (shift.bank_code?.type ?? '').toUpperCase(); switch (type) { - case "EVENING": record.other_hours.evening_hours = Number((record.other_hours.evening_hours += hours).toFixed(2)); - record.total_hours = Number((record.total_hours += hours).toFixed(2)); + case "EVENING": record.other_hours.evening_hours += hours; + record.total_hours += hours; break; - case "EMERGENCY": record.other_hours.emergency_hours = Number((record.other_hours.emergency_hours += hours).toFixed(2)); - record.total_hours = Number((record.total_hours += hours).toFixed(2)); + case "EMERGENCY": record.other_hours.emergency_hours += hours; + record.total_hours += hours; break; - case "OVERTIME": record.other_hours.overtime_hours = Number((record.other_hours.overtime_hours += hours).toFixed(2)); - record.total_hours = Number((record.total_hours += hours).toFixed(2)); + case "OVERTIME": record.other_hours.overtime_hours += hours; + record.total_hours += hours; break; - case "SICK": record.other_hours.sick_hours = Number((record.other_hours.sick_hours += hours).toFixed(2)); - record.total_hours = Number((record.total_hours += hours).toFixed(2)); + case "SICK": record.other_hours.sick_hours += hours; + record.total_hours += hours; break; - case "HOLIDAY": record.other_hours.holiday_hours = Number((record.other_hours.holiday_hours += hours).toFixed(2)); - record.total_hours = Number((record.total_hours += hours).toFixed(2)); + case "HOLIDAY": record.other_hours.holiday_hours += hours; + record.total_hours += hours; break; - case "VACATION": record.other_hours.vacation_hours = Number(record.other_hours.vacation_hours += hours); - record.total_hours = Number((record.total_hours += hours).toFixed(2)); + case "VACATION": record.other_hours.vacation_hours += hours; + record.total_hours += hours; break; - case "REGULAR": record.regular_hours = Number((record.regular_hours += hours).toFixed(2)); - record.total_hours = Number((record.total_hours += hours).toFixed(2)); + case "REGULAR": record.regular_hours = record.regular_hours += hours; + record.total_hours += hours; break; }