From 7912695b8f36a126c2121d0bbfeeb34b95ee577a Mon Sep 17 00:00:00 2001 From: Nicolas Drolet Date: Mon, 24 Nov 2025 09:02:31 -0500 Subject: [PATCH] fix(payperiods): refactor general overview method to return all employee overviews. --- docs/swagger/swagger-spec.json | 8 +++ prisma/schema.prisma | 2 +- src/common/utils/date-utils.ts | 1 + .../services/expense-upsert.service.ts | 2 +- .../controllers/pay-periods.controller.ts | 2 +- .../services/pay-periods-query.service.ts | 52 +++++++++++++++---- .../controllers/timesheet.controller.ts | 7 +-- .../timesheets/dtos/timesheet.dto.ts | 8 +-- .../timesheet-get-overview.service.ts | 36 +++++++------ 9 files changed, 82 insertions(+), 36 deletions(-) diff --git a/docs/swagger/swagger-spec.json b/docs/swagger/swagger-spec.json index e12e07a..b5bb7c6 100644 --- a/docs/swagger/swagger-spec.json +++ b/docs/swagger/swagger-spec.json @@ -104,6 +104,14 @@ "schema": { "type": "number" } + }, + { + "name": "employee_email", + "required": true, + "in": "query", + "schema": { + "type": "string" + } } ], "responses": { diff --git a/prisma/schema.prisma b/prisma/schema.prisma index b2171ad..1f73700 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -11,7 +11,7 @@ generator client { datasource db { provider = "postgresql" - url = env("DATABASE_URL_DEV") + url = env("DATABASE_URL_STAGING") } model Users { diff --git a/src/common/utils/date-utils.ts b/src/common/utils/date-utils.ts index da63283..0d6ddcb 100644 --- a/src/common/utils/date-utils.ts +++ b/src/common/utils/date-utils.ts @@ -57,6 +57,7 @@ import { ANCHOR_ISO, MS_PER_DAY, PERIODS_PER_YEAR, PERIOD_DAYS } from "src/commo //ensures the week starts from sunday export function weekStartSunday(date_local: Date): Date { const start_date = new Date(); + start_date.setUTCFullYear(date_local.getUTCFullYear(), date_local.getUTCMonth()); start_date.setDate(date_local.getUTCDate() - date_local.getUTCDay()); return start_date; } diff --git a/src/time-and-attendance/expenses/services/expense-upsert.service.ts b/src/time-and-attendance/expenses/services/expense-upsert.service.ts index 422e418..0945443 100644 --- a/src/time-and-attendance/expenses/services/expense-upsert.service.ts +++ b/src/time-and-attendance/expenses/services/expense-upsert.service.ts @@ -100,7 +100,7 @@ export class ExpenseUpsertService { //push updates and get updated datas const expense = await this.prisma.expenses.update({ - where: { id: dto.id, timesheet_id: dto.timesheet_id }, + where: { id: dto.id, timesheet_id: timesheet.id }, data, select: expense_select, }); diff --git a/src/time-and-attendance/pay-period/controllers/pay-periods.controller.ts b/src/time-and-attendance/pay-period/controllers/pay-periods.controller.ts index 1d43791..421acb8 100644 --- a/src/time-and-attendance/pay-period/controllers/pay-periods.controller.ts +++ b/src/time-and-attendance/pay-period/controllers/pay-periods.controller.ts @@ -47,7 +47,7 @@ export class PayPeriodsController { } @Get('crew/:year/:periodNumber') - @RolesAllowed(RoleEnum.SUPERVISOR) + @RolesAllowed(RoleEnum.SUPERVISOR, RoleEnum.ADMIN) async getCrewOverview(@Req() req, @Param('year', ParseIntPipe) year: number, @Param('periodNumber', ParseIntPipe) period_no: number, 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 b786a13..2048b3e 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 @@ -5,6 +5,7 @@ import { PayPeriodOverviewDto } from "../dtos/overview-pay-period.dto"; import { EmployeePeriodOverviewDto } from "../dtos/overview-employee-period.dto"; import { PayPeriodDto } from "../dtos/pay-period.dto"; import { mapPayPeriodToDto } from "../mappers/pay-periods.mapper"; +import { Prisma } from "@prisma/client"; @Injectable() export class PayPeriodsQueryService { constructor(private readonly prisma: PrismaService) { } @@ -142,14 +143,17 @@ export class PayPeriodsQueryService { : new Date(`${period.payday}T00:00:00.000Z`); //restrictEmployeeIds = filter for shifts and expenses by employees - const where_employee = options?.filtered_employee_ids?.length ? { employee_id: { in: options.filtered_employee_ids } } : {}; + const where_employee = options?.filtered_employee_ids?.length ? + { + date: { gte: start, lte: end }, + timesheet: { employee_id: { in: options.filtered_employee_ids } }, + } + : + { date: { gte: start, lte: end } }; // SHIFTS (filtered by crew) const shifts = await this.prisma.shifts.findMany({ - where: { - date: { gte: start, lte: end }, - timesheet: where_employee, - }, + where: where_employee, select: { start_time: true, end_time: true, @@ -177,10 +181,7 @@ export class PayPeriodsQueryService { // EXPENSES (filtered by crew) const expenses = await this.prisma.expenses.findMany({ - where: { - date: { gte: start, lte: end }, - timesheet: where_employee, - }, + where: where_employee, select: { amount: true, timesheet: { @@ -228,6 +229,39 @@ export class PayPeriodsQueryService { is_remote: true, }); } + } else { + const all_employees = await this.prisma.employees.findMany({ + include: { + user: { + select: { + first_name: true, + last_name: true, + email: true + } + } + }, + }); + + for (const employee of all_employees) { + by_employee.set(employee.id, { + email: employee.user.email, + employee_name: employee.user.first_name + ' ' + employee.user.last_name, + regular_hours: 0, + other_hours: { + evening_hours: 0, + emergency_hours: 0, + overtime_hours: 0, + sick_hours: 0, + holiday_hours: 0, + vacation_hours: 0, + }, + total_hours: 0, + expenses: 0, + mileage: 0, + is_approved: true, + is_remote: true, + }); + } } const ensure = (id: number, name: string, email: string) => { diff --git a/src/time-and-attendance/time-tracker/timesheets/controllers/timesheet.controller.ts b/src/time-and-attendance/time-tracker/timesheets/controllers/timesheet.controller.ts index ea9b181..6549695 100644 --- a/src/time-and-attendance/time-tracker/timesheets/controllers/timesheet.controller.ts +++ b/src/time-and-attendance/time-tracker/timesheets/controllers/timesheet.controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Get, Param, ParseBoolPipe, ParseIntPipe, Patch, Req, UnauthorizedException } from "@nestjs/common"; +import { Body, Controller, Get, Param, ParseBoolPipe, ParseIntPipe, Patch, Query, Req, UnauthorizedException } from "@nestjs/common"; import { RolesAllowed } from "src/common/decorators/roles.decorators"; import { GetTimesheetsOverviewService } from "src/time-and-attendance/time-tracker/timesheets/services/timesheet-get-overview.service"; import { TimesheetApprovalService } from "src/time-and-attendance/time-tracker/timesheets/services/timesheet-approval.service"; @@ -17,11 +17,12 @@ export class TimesheetController { getTimesheetByPayPeriod( @Req() req, @Param('year', ParseIntPipe) year: number, - @Param('period_number', ParseIntPipe) period_number: number + @Param('period_number', ParseIntPipe) period_number: number, + @Query('employee_email') employee_email?: string, ) { const email = req.user?.email; if (!email) throw new UnauthorizedException('Unauthorized User'); - return this.timesheetOverview.getTimesheetsForEmployeeByPeriod(email, year, period_number); + return this.timesheetOverview.getTimesheetsForEmployeeByPeriod(email, year, period_number, employee_email); } @Patch('timesheet-approval') diff --git a/src/time-and-attendance/time-tracker/timesheets/dtos/timesheet.dto.ts b/src/time-and-attendance/time-tracker/timesheets/dtos/timesheet.dto.ts index 0626bae..24d26bc 100644 --- a/src/time-and-attendance/time-tracker/timesheets/dtos/timesheet.dto.ts +++ b/src/time-and-attendance/time-tracker/timesheets/dtos/timesheet.dto.ts @@ -14,16 +14,16 @@ export class Timesheet { timesheet_id: number; is_approved: boolean; days: TimesheetDay[]; - weekly_hours: TotalHours[]; - weekly_expenses: TotalExpenses[]; + weekly_hours: TotalHours; + weekly_expenses: TotalExpenses; } export class TimesheetDay { date: string; shifts: Shift[]; expenses: Expense[]; - daily_hours: TotalHours[]; - daily_expenses: TotalExpenses[]; + daily_hours: TotalHours; + daily_expenses: TotalExpenses; } export class TotalHours { diff --git a/src/time-and-attendance/time-tracker/timesheets/services/timesheet-get-overview.service.ts b/src/time-and-attendance/time-tracker/timesheets/services/timesheet-get-overview.service.ts index 9f5481a..56853fe 100644 --- a/src/time-and-attendance/time-tracker/timesheets/services/timesheet-get-overview.service.ts +++ b/src/time-and-attendance/time-tracker/timesheets/services/timesheet-get-overview.service.ts @@ -35,15 +35,17 @@ export class GetTimesheetsOverviewService { //----------------------------------------------------------------------------------- // GET TIMESHEETS FOR A SELECTED EMPLOYEE //----------------------------------------------------------------------------------- - async getTimesheetsForEmployeeByPeriod(email: string, pay_year: number, pay_period_no: number): Promise> { + async getTimesheetsForEmployeeByPeriod(email: string, pay_year: number, pay_period_no: number, employee_email?: string): Promise> { try { + const account_email = employee_email ?? email; + //find period using year and period_no const period = await this.prisma.payPeriods.findFirst({ where: { pay_year, pay_period_no } }); if (!period) return { success: false, error: `Pay period ${pay_year}-${pay_period_no} not found` }; //fetch the employee_id using the email - const employee_id = await this.emailResolver.findIdByEmail(email); - if (!employee_id.success) return { success: false, error: `employee with email: ${email} not found` + employee_id.error } + const employee_id = await this.emailResolver.findIdByEmail(account_email); + if (!employee_id.success) return { success: false, error: `employee with email: ${account_email} not found` + employee_id.error } //loads the timesheets related to the fetched pay-period let rows = await this.loadTimesheets(employee_id.data, period.period_start, period.period_end); @@ -132,8 +134,8 @@ export class GetTimesheetsOverviewService { expenses_by_date.set(date_string, arr); } //weekly totals - const weekly_hours: TotalHours[] = [emptyHours()]; - const weekly_expenses: TotalExpenses[] = [emptyExpenses()]; + const weekly_hours: TotalHours = emptyHours(); + const weekly_expenses: TotalExpenses = emptyExpenses(); //map of days const days = day_dates.map((date) => { @@ -169,15 +171,15 @@ export class GetTimesheetsOverviewService { })); //daily totals - const daily_hours = [emptyHours()]; - const daily_expenses = [emptyExpenses()]; + const daily_hours = emptyHours(); + const daily_expenses = emptyExpenses(); //totals by shift types for (const shift of shifts_source) { const hours = diffOfHours(shift.start_time, shift.end_time); const subgroup = hoursSubGroupFromBankCode(shift.bank_code); - daily_hours[0][subgroup] += hours; - weekly_hours[0][subgroup] += hours; + daily_hours[subgroup] += hours; + weekly_hours[subgroup] += hours; } //totals by expense types @@ -185,20 +187,20 @@ export class GetTimesheetsOverviewService { const subgroup = expenseSubgroupFromBankCode(expense.bank_code); if (subgroup === 'mileage') { const mileage = num(expense.mileage); - daily_expenses[0].mileage += mileage; - weekly_expenses[0].mileage += mileage; + daily_expenses.mileage += mileage; + weekly_expenses.mileage += mileage; } else if (subgroup === 'per_diem') { const amount = num(expense.amount); - daily_expenses[0].per_diem += amount; - weekly_expenses[0].per_diem += amount; + daily_expenses.per_diem += amount; + weekly_expenses.per_diem += amount; } else if (subgroup === 'on_call') { const amount = num(expense.amount); - daily_expenses[0].on_call += amount; - weekly_expenses[0].on_call += amount; + daily_expenses.on_call += amount; + weekly_expenses.on_call += amount; } else { const amount = num(expense.amount); - daily_expenses[0].expenses += amount; - weekly_expenses[0].expenses += amount; + daily_expenses.expenses += amount; + weekly_expenses.expenses += amount; } } return {