feat(timesheets): added an option to generate a second timesheet in case of a pay-period as no data in either the 1st or 2nd week

This commit is contained in:
Matthieu Haineault 2025-10-22 13:50:17 -04:00
parent 7fe2b6265a
commit 60aac39daa
2 changed files with 104 additions and 41 deletions

View File

@ -1,3 +1,11 @@
export function weekStartSunday(date_local: Date): Date {
const start = new Date(Date.UTC(date_local.getFullYear(), date_local.getMonth(), date_local.getDate()));
const dow = start.getDay();
start.setDate(start.getDate() - dow);
start.setHours(0, 0, 0, 0);
return start;
}
export const toDateFromString = ( date: Date | string):Date => { export const toDateFromString = ( date: Date | string):Date => {
const d = new Date(date); const d = new Date(date);
return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate())); return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
@ -10,6 +18,7 @@ export const sevenDaysFrom = (date: Date | string): Date[] => {
return d; return d;
}); });
} }
export const toStringFromDate = (date: Date | string): string => { export const toStringFromDate = (date: Date | string): string => {
const d = toDateFromString(date); const d = toDateFromString(date);
const year = d.getUTCFullYear(); const year = d.getUTCFullYear();
@ -24,3 +33,4 @@ export const toHHmmFromDate = (input: Date | string): string => {
const mm = String(date.getUTCMinutes()).padStart(2, '0'); const mm = String(date.getUTCMinutes()).padStart(2, '0');
return `${hh}:${mm}`; return `${hh}:${mm}`;
} }

View File

@ -19,22 +19,42 @@ type TotalExpenses = {
mileage: number; mileage: number;
}; };
const NUMBER_OF_TIMESHEETS_TO_RETURN = 2;
@Injectable() @Injectable()
export class GetTimesheetsOverviewService { export class GetTimesheetsOverviewService {
constructor(private readonly prisma: PrismaService) { } constructor(private readonly prisma: PrismaService) { }
//-----------------------------------------------------------------------------------
// GET TIMESHEETS FOR A SELECTED EMPLOYEE
//-----------------------------------------------------------------------------------
async getTimesheetsForEmployeeByPeriod(employee_id: number, pay_year: number, pay_period_no: number) { async getTimesheetsForEmployeeByPeriod(employee_id: number, pay_year: number, pay_period_no: number) {
//find period using year and period_no //find period using year and period_no
const period = await this.prisma.payPeriods.findFirst({ const period = await this.prisma.payPeriods.findFirst({ where: { pay_year, pay_period_no } });
where: { pay_year, pay_period_no },
});
if (!period) throw new NotFoundException(`Pay period ${pay_year}-${pay_period_no} not found`); if (!period) throw new NotFoundException(`Pay period ${pay_year}-${pay_period_no} not found`);
//loads the timesheets related to the fetched pay-period //loads the timesheets related to the fetched pay-period
const rows = await this.loadTimesheets({ const timesheet_range = { employee_id, start_date: { gte: period.period_start, lte: period.period_end } };
employee_id, let rows = await this.loadTimesheets(timesheet_range);
start_date: { gte: period.period_start, lte: period.period_end },
}); //Normalized dates from pay-period
const normalized_start = toDateFromString(period.period_start);
const normalized_end = toDateFromString(period.period_end);
//creates empty timesheet to make sure to return desired amount of timesheet
for (let i = 0; i < NUMBER_OF_TIMESHEETS_TO_RETURN; i++) {
const week_start = new Date(normalized_start);
week_start.setUTCDate(week_start.getUTCDate() + i * 7);
if (week_start.getTime() > normalized_end.getTime()) break;
const exists = rows.some(
(row) => toDateFromString(row.start_date).getTime() === week_start.getTime()
);
if (!exists) await this.ensureTimesheet(employee_id, week_start);
}
rows = await this.loadTimesheets(timesheet_range);
//find user infos using the employee_id //find user infos using the employee_id
const employee = await this.prisma.employees.findUnique({ const employee = await this.prisma.employees.findUnique({
@ -56,6 +76,7 @@ export class GetTimesheetsOverviewService {
//----------------------------------------------------------------------------------- //-----------------------------------------------------------------------------------
// MAPPERS & HELPERS // MAPPERS & HELPERS
//----------------------------------------------------------------------------------- //-----------------------------------------------------------------------------------
//fetch timesheet's infos //fetch timesheet's infos
private async loadTimesheets(where: any) { private async loadTimesheets(where: any) {
return this.prisma.timesheets.findMany({ return this.prisma.timesheets.findMany({
@ -172,6 +193,38 @@ export class GetTimesheetsOverviewService {
weekly_expenses, weekly_expenses,
}; };
} }
private ensureTimesheet = async (employee_id: number, start_date: Date | string) => {
const start = toDateFromString(start_date);
let row = await this.prisma.timesheets.findFirst({
where: { employee_id, start_date: start },
include: {
employee: { include: { user: true } },
shift: { include: { bank_code: true } },
expense: { include: { bank_code: true, attachment_record: true } },
},
});
if (row) return row;
await this.prisma.timesheets.create({
data: {
employee_id,
start_date: start,
is_approved: false
},
});
row = await this.prisma.timesheets.findFirst({
where: { employee_id, start_date: start },
include: {
employee: { include: { user: true } },
shift: { include: { bank_code: true } },
expense: { include: { bank_code: true, attachment_record: true } },
},
});
return row!;
}
} }
//filled array with default values //filled array with default values