Merge branch 'main' of git.targo.ca:Targo/targo_backend into dev/setup/modules/MatthieuH

This commit is contained in:
Matthieu Haineault 2025-08-06 13:17:32 -04:00
commit ef5af80471
12 changed files with 90 additions and 74 deletions

View File

@ -545,6 +545,9 @@
] ]
} }
}, },
<<<<<<< HEAD
"/auth/v1/login": {
=======
"/Expenses": { "/Expenses": {
"post": { "post": {
"operationId": "ExpensesController_create", "operationId": "ExpensesController_create",
@ -1142,6 +1145,7 @@
} }
}, },
"/auth/login": { "/auth/login": {
>>>>>>> b0406b3a4c00223b9430ef29b60a4775beca4328
"get": { "get": {
"operationId": "AuthController_login", "operationId": "AuthController_login",
"parameters": [], "parameters": [],

View File

@ -19,6 +19,7 @@ import { BankCodesModule } from './modules/bank-codes/bank-codes.module';
import { OvertimeService } from './modules/business-logics/services/overtime.service'; import { OvertimeService } from './modules/business-logics/services/overtime.service';
import { BusinessLogicsModule } from './modules/business-logics/business-logics.module'; import { BusinessLogicsModule } from './modules/business-logics/business-logics.module';
import { OauthSessionsModule } from './modules/oauth-sessions/oauth-sessions.module'; import { OauthSessionsModule } from './modules/oauth-sessions/oauth-sessions.module';
import { CsvExportModule } from './modules/exports/csv-exports.module';
@Module({ @Module({
imports: [ imports: [
@ -27,6 +28,7 @@ import { OauthSessionsModule } from './modules/oauth-sessions/oauth-sessions.mod
AuthenticationModule, AuthenticationModule,
BankCodesModule, BankCodesModule,
BusinessLogicsModule, BusinessLogicsModule,
CsvExportModule,
CustomersModule, CustomersModule,
EmployeesModule, EmployeesModule,
ExpensesModule, ExpensesModule,

View File

@ -0,0 +1,50 @@
//lenght of a shift, rouded to nearest 'x' minute
export function computeHours(start: Date, end: Date, roundToMinutes?: number): number {
const diffMs = end.getTime() - start.getTime();
const totalMinutes = diffMs / 60000;
const minutes = roundToMinutes ?
Math.round(totalMinutes / roundToMinutes) * roundToMinutes :
totalMinutes;
return +(minutes / 60).toFixed(2);
}
//round the amount of hours to quarter
export function roundToQuarterHour(hours: number): number {
return Math.round(hours *4) / 4;
}
//calculate the number of the week (1 or 2)
export function computeWeekNumber(periodStart: Date, targetDate: Date): number {
const days = Math.floor( targetDate.getTime() - periodStart.getTime()) /
(1000 * 60 * 60 * 24);
return Math.floor(days / 7) +1;
}
//Date format YYY-MM-DD
export function formatDateISO(d:Date): string {
return d.toISOString().split('T')[0];
}
//fetch firts day of the week (Sunday)
export function getWeekStart(date:Date, firstDayOfWeek = 0): Date {
const d = new Date(date);
const day = d.getDay();
const diff = (day < firstDayOfWeek ? 7 : 0) + (day - firstDayOfWeek);
d.setDate(d.getDate() - diff);
d.setHours(0,0,0,0);
return d;
}
//fetch last day of the week (Saturday)
export function getWeekEnd(startOfWeek: Date): Date {
const d = new Date(startOfWeek);
d.setDate(d.getDate() + 6);
d.setHours(23,59,59,999);
return d;
}
//returns january 1st of the selected date's year
export function getYearStart(date:Date): Date {
return new Date(date.getFullYear(),0,1,0,0,0,0);
}

View File

@ -36,12 +36,18 @@ async function bootstrap() {
rolling: true, rolling: true,
cookie: { cookie: {
maxAge: 30 * 60 * 1000, maxAge: 30 * 60 * 1000,
httpOnly: false, httpOnly: true,
} }
})) }))
app.use(passport.initialize()); app.use(passport.initialize());
app.use(passport.session()); app.use(passport.session());
// Enable CORS
app.enableCors({
origin: 'http://localhost:9000',
credentials: true,
});
//swagger config //swagger config
const config = new DocumentBuilder() const config = new DocumentBuilder()
.setTitle('Targo_Backend') .setTitle('Targo_Backend')

View File

@ -6,12 +6,12 @@ import { Request, Response } from 'express';
export class AuthController { export class AuthController {
@UseGuards(OIDCLoginGuard) @UseGuards(OIDCLoginGuard)
@Get('/login') @Get('/v1/login')
login() {} login() {}
@Get('/callback') @Get('/callback')
@UseGuards(OIDCLoginGuard) @UseGuards(OIDCLoginGuard)
loginCallback(@Req() req: Request, @Res() res: Response) { loginCallback(@Req() req: Request, @Res() res: Response) {
res.redirect('/'); res.redirect('http://localhost:9000/#/login-success');
} }
} }

View File

@ -2,7 +2,7 @@ import { BadRequestException, Injectable, Logger } from "@nestjs/common";
import { PrismaService } from "../../../prisma/prisma.service"; import { PrismaService } from "../../../prisma/prisma.service";
//THIS SERVICE IS NOT USED RULES TO BE DETERMINED WITH MIKE/HR/ACCOUNTING //THIS SERVICE IS NOT USED, RULES TO BE DETERMINED WITH MIKE/HR/ACCOUNTING
@Injectable() @Injectable()
export class AfterHoursService { export class AfterHoursService {
private readonly logger = new Logger(AfterHoursService.name); private readonly logger = new Logger(AfterHoursService.name);

View File

@ -1,5 +1,6 @@
import { Injectable, Logger } from "@nestjs/common"; import { Injectable, Logger } from "@nestjs/common";
import { PrismaService } from "../../../prisma/prisma.service"; import { PrismaService } from "../../../prisma/prisma.service";
import { computeHours, getWeekStart } from "src/common/utils/date-utils";
@Injectable() @Injectable()
export class HolidayService { export class HolidayService {
@ -7,32 +8,15 @@ export class HolidayService {
constructor(private readonly prisma: PrismaService) {} constructor(private readonly prisma: PrismaService) {}
//return the sunday of the current week that includes the holiday
private getWeekStart(date: Date): Date {
const day = new Date(date);
const offset = day.getDay();
day.setDate(day.getDate() - offset);
day.setHours(0,0,0,0);
return day;
}
//rounds minutes to 5s
private computeHours(start: Date, end: Date): number {
const durationMS = end.getTime() - start.getTime();
const totalMinutes = durationMS / 60000;
const rounded = Math.round(totalMinutes / 5) * 5;
return rounded / 60;
}
private async computeHoursPrevious4Weeks(employeeId: number, holidayDate: Date): Promise<number> { private async computeHoursPrevious4Weeks(employeeId: number, holidayDate: Date): Promise<number> {
//sets the end of the window to 1ms before the week with the holiday //sets the end of the window to 1ms before the week with the holiday
const holidayWeekStart = this.getWeekStart(holidayDate); const holidayWeekStart = getWeekStart(holidayDate);
const windowEnd = new Date(holidayWeekStart.getTime() - 1); const windowEnd = new Date(holidayWeekStart.getTime() - 1);
//sets the start of the window to 28 days ( 4 completed weeks ) before the week with the holiday //sets the start of the window to 28 days ( 4 completed weeks ) before the week with the holiday
const windowStart = new Date(windowEnd.getTime() - 28 * 24 * 60 * 60000 + 1 ) const windowStart = new Date(windowEnd.getTime() - 28 * 24 * 60 * 60000 + 1 )
const validCodes = ['G1', 'G45', 'G56', 'G104', 'G105', 'G700']; const validCodes = ['G1', 'G45', 'G56', 'G104', 'G105', 'G700'];
//fetches all shift of the employee in said window ( 4 completed weeks ) //fetches all shift of the employee in said window ( 4 previous completed weeks )
const shifts = await this.prisma.shifts.findMany({ const shifts = await this.prisma.shifts.findMany({
where: { timesheet: { employee_id: employeeId } , where: { timesheet: { employee_id: employeeId } ,
date: { gte: windowStart, lte: windowEnd }, date: { gte: windowStart, lte: windowEnd },
@ -41,16 +25,16 @@ export class HolidayService {
select: { date: true, start_time: true, end_time: true }, select: { date: true, start_time: true, end_time: true },
}); });
const totalHours = shifts.map(s => this.computeHours(s.start_time, s.end_time)).reduce((sum, h)=> sum + h, 0); const totalHours = shifts.map(s => computeHours(s.start_time, s.end_time)).reduce((sum, h)=> sum + h, 0);
const dailyHours = totalHours / 20; const dailyHours = totalHours / 20;
return dailyHours; return dailyHours;
} }
async calculateHolidayPay( employeeId: number, holidayDate: Date, modifier: number): Promise<number> { async calculateHolidayPay( employeeId: number, holidayDate: Date, modifier: number): Promise<number> {
const hours = await this. computeHoursPrevious4Weeks(employeeId, holidayDate); const hours = await this.computeHoursPrevious4Weeks(employeeId, holidayDate);
const dailyRate = Math.min(hours, 8); const dailyRate = Math.min(hours, 8);
this.logger.debug(`Holiday pay calculation: hours=${hours.toFixed(2)}`); this.logger.debug(`Holiday pay calculation: hours= ${hours.toFixed(2)}`);
return dailyRate * modifier; return dailyRate * modifier;
} }
} }

View File

@ -1,5 +1,6 @@
import { Injectable, Logger } from '@nestjs/common'; import { Injectable, Logger } from '@nestjs/common';
import { PrismaService } from '../../../prisma/prisma.service'; import { PrismaService } from '../../../prisma/prisma.service';
import { getWeekStart, getWeekEnd, computeHours } from 'src/common/utils/date-utils';
@Injectable() @Injectable()
export class OvertimeService { export class OvertimeService {
@ -10,47 +11,18 @@ export class OvertimeService {
constructor(private prisma: PrismaService) {} constructor(private prisma: PrismaService) {}
// calculate decimal hours rounded to nearest 5 min
computedHours(start: Date, end: Date): number {
const durationMs = end.getTime() - start.getTime();
const totalMinutes = durationMs / 60000;
//rounded to 5 min
const rounded = Math.round(totalMinutes / 5) * 5;
const hours = rounded / 60;
this.logger.debug(`computedHours: raw=${totalMinutes.toFixed(1)}min rounded = ${rounded}min (${hours.toFixed(2)}h)`);
return hours;
}
//calculate Daily overtime //calculate Daily overtime
getDailyOvertimeHours(start: Date, end: Date): number { getDailyOvertimeHours(start: Date, end: Date): number {
const hours = this.computedHours(start, end); const hours = computeHours(start, end, 5);
const overtime = Math.max(0, hours - this.dailyMax); const overtime = Math.max(0, hours - this.dailyMax);
this.logger.debug(`getDailyOvertimeHours : ${overtime.toFixed(2)}h (threshold ${this.dailyMax})`); this.logger.debug(`getDailyOvertimeHours : ${overtime.toFixed(2)}h (threshold ${this.dailyMax})`);
return overtime; return overtime;
} }
//sets first day of the week to be sunday
private getWeekStart(date:Date): Date {
const d = new Date(date);
const day = d.getDay(); // return sunday = 0, monday = 1, etc
d.setDate(d.getDate() - day);
d.setHours(0,0,0,0,); // puts start of the week at sunday morning at 00:00
return d;
}
//sets last day of the week to be saturday
private getWeekEnd(startDate:Date): Date {
const d = new Date(startDate);
d.setDate(d.getDate() +6); //sets last day to be saturday
d.setHours(23,59,59,999); //puts end of the week at saturday night at 00:00 minus 1ms
return d;
}
//calculate Weekly overtime //calculate Weekly overtime
async getWeeklyOvertimeHours(employeeId: number, refDate: Date): Promise<number> { async getWeeklyOvertimeHours(employeeId: number, refDate: Date): Promise<number> {
const weekStart = this.getWeekStart(refDate); const weekStart = getWeekStart(refDate);
const weekEnd = this.getWeekEnd(weekStart); const weekEnd = getWeekEnd(weekStart);
//fetches all shifts containing hours //fetches all shifts containing hours
const shifts = await this.prisma.shifts.findMany({ const shifts = await this.prisma.shifts.findMany({
@ -63,7 +35,7 @@ export class OvertimeService {
}); });
//calculate total hours of those shifts minus weekly Max to find total overtime hours //calculate total hours of those shifts minus weekly Max to find total overtime hours
const total = shifts.map(shift => this.computedHours(shift.start_time, shift.end_time)) const total = shifts.map(shift => computeHours(shift.start_time, shift.end_time, 5))
.reduce((sum, hours)=> sum+hours, 0); .reduce((sum, hours)=> sum+hours, 0);
const overtime = Math.max(0, total - this.weeklyMax); const overtime = Math.max(0, total - this.weeklyMax);

View File

@ -1,5 +1,6 @@
import { Injectable, Logger } from "@nestjs/common"; import { Injectable, Logger } from "@nestjs/common";
import { PrismaService } from "../../../prisma/prisma.service"; import { PrismaService } from "../../../prisma/prisma.service";
import { getYearStart, roundToQuarterHour } from "src/common/utils/date-utils";
@Injectable() @Injectable()
export class SickLeaveService { export class SickLeaveService {
@ -7,10 +8,10 @@ export class SickLeaveService {
private readonly logger = new Logger(SickLeaveService.name); private readonly logger = new Logger(SickLeaveService.name);
async calculateSickLeavePay(employeeId: number, startDate: Date, daysRequested: number, modifier: number): Promise<number> { async calculateSickLeavePay(employeeId: number, referenceDate: Date, daysRequested: number, modifier: number): Promise<number> {
//sets the year to jan 1st to dec 31st //sets the year to jan 1st to dec 31st
const periodStart = new Date(startDate.getFullYear(), 0, 1); const periodStart = getYearStart(referenceDate);
const periodEnd = startDate; const periodEnd = referenceDate;
//fetches all shifts of a selected employee //fetches all shifts of a selected employee
const shifts = await this.prisma.shifts.findMany({ const shifts = await this.prisma.shifts.findMany({
@ -54,7 +55,7 @@ export class SickLeaveService {
const payableDays = Math.min(acquiredDays, daysRequested); const payableDays = Math.min(acquiredDays, daysRequested);
const rawHours = payableDays * 8 * modifier; const rawHours = payableDays * 8 * modifier;
const rounded = Math.round(rawHours * 4) / 4; const rounded = roundToQuarterHour(rawHours)
this.logger.debug(`Sick leave pay: days= ${payableDays}, modifier= ${modifier}, hours= ${rounded}`); this.logger.debug(`Sick leave pay: days= ${payableDays}, modifier= ${modifier}, hours= ${rounded}`);
return rounded; return rounded;
} }

View File

@ -2,6 +2,7 @@ import { Injectable, NotFoundException } from "@nestjs/common";
import { EmployeePeriodOverviewDto } from "../dtos/overview-employee-period.dto"; import { EmployeePeriodOverviewDto } from "../dtos/overview-employee-period.dto";
import { PayPeriodOverviewDto } from "../dtos/overview-pay-period.dto"; import { PayPeriodOverviewDto } from "../dtos/overview-pay-period.dto";
import { PrismaService } from "src/prisma/prisma.service"; import { PrismaService } from "src/prisma/prisma.service";
import { computeHours } from "src/common/utils/date-utils";
@Injectable() @Injectable()
export class PayPeriodsOverviewService { export class PayPeriodsOverviewService {
@ -40,7 +41,7 @@ export class PayPeriodsOverviewService {
const user = employee_record.user; const user = employee_record.user;
const employee_id = employee_record.user_id; const employee_id = employee_record.user_id;
const employee_name = `${user.first_name} ${user.last_name}`; const employee_name = `${user.first_name} ${user.last_name}`;
const hours = (shift.end_time.getTime() - shift.start_time.getTime() / 3600000); const hours = computeHours(shift.start_time, shift.end_time);
//check if employee had prior shifts and adds hours of found shift to the total hours //check if employee had prior shifts and adds hours of found shift to the total hours
if (map.has(employee_id)) { if (map.has(employee_id)) {

View File

@ -1,4 +1,5 @@
import { Injectable, NotFoundException } from "@nestjs/common"; import { Injectable, NotFoundException } from "@nestjs/common";
import { computeHours } from "src/common/utils/date-utils";
import { PrismaService } from "src/prisma/prisma.service"; import { PrismaService } from "src/prisma/prisma.service";
export interface OverviewRow { export interface OverviewRow {
@ -16,13 +17,7 @@ export interface OverviewRow {
export class ShiftsOverviewService { export class ShiftsOverviewService {
constructor(private readonly prisma: PrismaService) {} constructor(private readonly prisma: PrismaService) {}
private computeHours(start: Date, end: Date): number { async getSummary(periodId: number): Promise<ValidationRow[]> {
const diffMs = end.getTime() - start.getTime();
const hours = diffMs / 1000 / 3600;
return parseFloat(hours.toFixed(2));
}
async getSummary(periodId: number): Promise<OverviewRow[]> {
//fetch pay-period to display //fetch pay-period to display
const period = await this.prisma.payPeriods.findUnique({ const period = await this.prisma.payPeriods.findUnique({
where: { period_number: periodId }, where: { period_number: periodId },
@ -77,7 +72,7 @@ export class ShiftsOverviewService {
isValidated: false, isValidated: false,
}; };
} }
const hours = this.computeHours(s.start_time, s.end_time); const hours = computeHours(s.start_time, s.end_time);
switch(s.bank_code.type) { switch(s.bank_code.type) {
case 'regular' : row.totalRegularHrs += hours; case 'regular' : row.totalRegularHrs += hours;

View File

@ -4,6 +4,7 @@ import { CreateTimesheetDto } from '../dtos/create-timesheet.dto';
import { Timesheets, TimesheetsArchive } from '@prisma/client'; import { Timesheets, TimesheetsArchive } from '@prisma/client';
import { UpdateTimesheetDto } from '../dtos/update-timesheet.dto'; import { UpdateTimesheetDto } from '../dtos/update-timesheet.dto';
import { OvertimeService } from 'src/modules/business-logics/services/overtime.service'; import { OvertimeService } from 'src/modules/business-logics/services/overtime.service';
import { computeHours } from 'src/common/utils/date-utils';
@Injectable() @Injectable()
export class TimesheetsService { export class TimesheetsService {
@ -35,7 +36,7 @@ export class TimesheetsService {
return Promise.all( return Promise.all(
list.map(async timesheet => { list.map(async timesheet => {
const detailedShifts = timesheet.shift.map(s => { const detailedShifts = timesheet.shift.map(s => {
const hours = this.overtime.computedHours(s.start_time, s.end_time); const hours = computeHours(s.start_time, s.end_time,5);
const regularHours = Math.min(8, hours); const regularHours = Math.min(8, hours);
const dailyOvertime = this.overtime.getDailyOvertimeHours(s.start_time, s.end_time); const dailyOvertime = this.overtime.getDailyOvertimeHours(s.start_time, s.end_time);
const payRegular = regularHours * s.bank_code.modifier; const payRegular = regularHours * s.bank_code.modifier;
@ -65,7 +66,7 @@ export class TimesheetsService {
} }
const detailedShifts = timesheet.shift.map( s => { const detailedShifts = timesheet.shift.map( s => {
const hours = this.overtime.computedHours(s.start_time, s.end_time); const hours = computeHours(s.start_time, s.end_time);
const regularHours = Math.min(8, hours); const regularHours = Math.min(8, hours);
const dailyOvertime = this.overtime.getDailyOvertimeHours(s.start_time, s.end_time); const dailyOvertime = this.overtime.getDailyOvertimeHours(s.start_time, s.end_time);
const payRegular = regularHours * s.bank_code.modifier; const payRegular = regularHours * s.bank_code.modifier;