fix(payperiods): refactor general overview method to return all employee overviews.

This commit is contained in:
Nicolas Drolet 2025-11-24 09:02:31 -05:00
parent 2958403f08
commit 7912695b8f
9 changed files with 82 additions and 36 deletions

View File

@ -104,6 +104,14 @@
"schema": { "schema": {
"type": "number" "type": "number"
} }
},
{
"name": "employee_email",
"required": true,
"in": "query",
"schema": {
"type": "string"
}
} }
], ],
"responses": { "responses": {

View File

@ -11,7 +11,7 @@ generator client {
datasource db { datasource db {
provider = "postgresql" provider = "postgresql"
url = env("DATABASE_URL_DEV") url = env("DATABASE_URL_STAGING")
} }
model Users { model Users {

View File

@ -57,6 +57,7 @@ import { ANCHOR_ISO, MS_PER_DAY, PERIODS_PER_YEAR, PERIOD_DAYS } from "src/commo
//ensures the week starts from sunday //ensures the week starts from sunday
export function weekStartSunday(date_local: Date): Date { export function weekStartSunday(date_local: Date): Date {
const start_date = new Date(); const start_date = new Date();
start_date.setUTCFullYear(date_local.getUTCFullYear(), date_local.getUTCMonth());
start_date.setDate(date_local.getUTCDate() - date_local.getUTCDay()); start_date.setDate(date_local.getUTCDate() - date_local.getUTCDay());
return start_date; return start_date;
} }

View File

@ -100,7 +100,7 @@ export class ExpenseUpsertService {
//push updates and get updated datas //push updates and get updated datas
const expense = await this.prisma.expenses.update({ 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, data,
select: expense_select, select: expense_select,
}); });

View File

@ -47,7 +47,7 @@ export class PayPeriodsController {
} }
@Get('crew/:year/:periodNumber') @Get('crew/:year/:periodNumber')
@RolesAllowed(RoleEnum.SUPERVISOR) @RolesAllowed(RoleEnum.SUPERVISOR, RoleEnum.ADMIN)
async getCrewOverview(@Req() req, async getCrewOverview(@Req() req,
@Param('year', ParseIntPipe) year: number, @Param('year', ParseIntPipe) year: number,
@Param('periodNumber', ParseIntPipe) period_no: number, @Param('periodNumber', ParseIntPipe) period_no: number,

View File

@ -5,6 +5,7 @@ import { PayPeriodOverviewDto } from "../dtos/overview-pay-period.dto";
import { EmployeePeriodOverviewDto } from "../dtos/overview-employee-period.dto"; import { EmployeePeriodOverviewDto } from "../dtos/overview-employee-period.dto";
import { PayPeriodDto } from "../dtos/pay-period.dto"; import { PayPeriodDto } from "../dtos/pay-period.dto";
import { mapPayPeriodToDto } from "../mappers/pay-periods.mapper"; import { mapPayPeriodToDto } from "../mappers/pay-periods.mapper";
import { Prisma } from "@prisma/client";
@Injectable() @Injectable()
export class PayPeriodsQueryService { export class PayPeriodsQueryService {
constructor(private readonly prisma: PrismaService) { } constructor(private readonly prisma: PrismaService) { }
@ -142,14 +143,17 @@ export class PayPeriodsQueryService {
: new Date(`${period.payday}T00:00:00.000Z`); : new Date(`${period.payday}T00:00:00.000Z`);
//restrictEmployeeIds = filter for shifts and expenses by employees //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) // SHIFTS (filtered by crew)
const shifts = await this.prisma.shifts.findMany({ const shifts = await this.prisma.shifts.findMany({
where: { where: where_employee,
date: { gte: start, lte: end },
timesheet: where_employee,
},
select: { select: {
start_time: true, start_time: true,
end_time: true, end_time: true,
@ -177,10 +181,7 @@ export class PayPeriodsQueryService {
// EXPENSES (filtered by crew) // EXPENSES (filtered by crew)
const expenses = await this.prisma.expenses.findMany({ const expenses = await this.prisma.expenses.findMany({
where: { where: where_employee,
date: { gte: start, lte: end },
timesheet: where_employee,
},
select: { select: {
amount: true, amount: true,
timesheet: { timesheet: {
@ -228,6 +229,39 @@ export class PayPeriodsQueryService {
is_remote: true, 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) => { const ensure = (id: number, name: string, email: string) => {

View File

@ -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 { RolesAllowed } from "src/common/decorators/roles.decorators";
import { GetTimesheetsOverviewService } from "src/time-and-attendance/time-tracker/timesheets/services/timesheet-get-overview.service"; 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"; import { TimesheetApprovalService } from "src/time-and-attendance/time-tracker/timesheets/services/timesheet-approval.service";
@ -17,11 +17,12 @@ export class TimesheetController {
getTimesheetByPayPeriod( getTimesheetByPayPeriod(
@Req() req, @Req() req,
@Param('year', ParseIntPipe) year: number, @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; const email = req.user?.email;
if (!email) throw new UnauthorizedException('Unauthorized User'); 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') @Patch('timesheet-approval')

View File

@ -14,16 +14,16 @@ export class Timesheet {
timesheet_id: number; timesheet_id: number;
is_approved: boolean; is_approved: boolean;
days: TimesheetDay[]; days: TimesheetDay[];
weekly_hours: TotalHours[]; weekly_hours: TotalHours;
weekly_expenses: TotalExpenses[]; weekly_expenses: TotalExpenses;
} }
export class TimesheetDay { export class TimesheetDay {
date: string; date: string;
shifts: Shift[]; shifts: Shift[];
expenses: Expense[]; expenses: Expense[];
daily_hours: TotalHours[]; daily_hours: TotalHours;
daily_expenses: TotalExpenses[]; daily_expenses: TotalExpenses;
} }
export class TotalHours { export class TotalHours {

View File

@ -35,15 +35,17 @@ export class GetTimesheetsOverviewService {
//----------------------------------------------------------------------------------- //-----------------------------------------------------------------------------------
// GET TIMESHEETS FOR A SELECTED EMPLOYEE // GET TIMESHEETS FOR A SELECTED EMPLOYEE
//----------------------------------------------------------------------------------- //-----------------------------------------------------------------------------------
async getTimesheetsForEmployeeByPeriod(email: string, pay_year: number, pay_period_no: number): Promise<Result<Timesheets, string>> { async getTimesheetsForEmployeeByPeriod(email: string, pay_year: number, pay_period_no: number, employee_email?: string): Promise<Result<Timesheets, string>> {
try { try {
const account_email = employee_email ?? email;
//find period using year and period_no //find period using year and period_no
const period = await this.prisma.payPeriods.findFirst({ where: { pay_year, pay_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` }; if (!period) return { success: false, error: `Pay period ${pay_year}-${pay_period_no} not found` };
//fetch the employee_id using the email //fetch the employee_id using the email
const employee_id = await this.emailResolver.findIdByEmail(email); const employee_id = await this.emailResolver.findIdByEmail(account_email);
if (!employee_id.success) return { success: false, error: `employee with email: ${email} not found` + employee_id.error } 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 //loads the timesheets related to the fetched pay-period
let rows = await this.loadTimesheets(employee_id.data, period.period_start, period.period_end); 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); expenses_by_date.set(date_string, arr);
} }
//weekly totals //weekly totals
const weekly_hours: TotalHours[] = [emptyHours()]; const weekly_hours: TotalHours = emptyHours();
const weekly_expenses: TotalExpenses[] = [emptyExpenses()]; const weekly_expenses: TotalExpenses = emptyExpenses();
//map of days //map of days
const days = day_dates.map((date) => { const days = day_dates.map((date) => {
@ -169,15 +171,15 @@ export class GetTimesheetsOverviewService {
})); }));
//daily totals //daily totals
const daily_hours = [emptyHours()]; const daily_hours = emptyHours();
const daily_expenses = [emptyExpenses()]; const daily_expenses = emptyExpenses();
//totals by shift types //totals by shift types
for (const shift of shifts_source) { for (const shift of shifts_source) {
const hours = diffOfHours(shift.start_time, shift.end_time); const hours = diffOfHours(shift.start_time, shift.end_time);
const subgroup = hoursSubGroupFromBankCode(shift.bank_code); const subgroup = hoursSubGroupFromBankCode(shift.bank_code);
daily_hours[0][subgroup] += hours; daily_hours[subgroup] += hours;
weekly_hours[0][subgroup] += hours; weekly_hours[subgroup] += hours;
} }
//totals by expense types //totals by expense types
@ -185,20 +187,20 @@ export class GetTimesheetsOverviewService {
const subgroup = expenseSubgroupFromBankCode(expense.bank_code); const subgroup = expenseSubgroupFromBankCode(expense.bank_code);
if (subgroup === 'mileage') { if (subgroup === 'mileage') {
const mileage = num(expense.mileage); const mileage = num(expense.mileage);
daily_expenses[0].mileage += mileage; daily_expenses.mileage += mileage;
weekly_expenses[0].mileage += mileage; weekly_expenses.mileage += mileage;
} else if (subgroup === 'per_diem') { } else if (subgroup === 'per_diem') {
const amount = num(expense.amount); const amount = num(expense.amount);
daily_expenses[0].per_diem += amount; daily_expenses.per_diem += amount;
weekly_expenses[0].per_diem += amount; weekly_expenses.per_diem += amount;
} else if (subgroup === 'on_call') { } else if (subgroup === 'on_call') {
const amount = num(expense.amount); const amount = num(expense.amount);
daily_expenses[0].on_call += amount; daily_expenses.on_call += amount;
weekly_expenses[0].on_call += amount; weekly_expenses.on_call += amount;
} else { } else {
const amount = num(expense.amount); const amount = num(expense.amount);
daily_expenses[0].expenses += amount; daily_expenses.expenses += amount;
weekly_expenses[0].expenses += amount; weekly_expenses.expenses += amount;
} }
} }
return { return {