feat(timesheet): added comment and supervisor comment to payload of findAll

This commit is contained in:
Matthieu Haineault 2025-09-10 16:20:37 -04:00
parent a343ace0b7
commit 125f443ec0
3 changed files with 76 additions and 21 deletions

View File

@ -3,12 +3,15 @@ export class ShiftDto {
type: string; type: string;
start_time: string; start_time: string;
end_time : string; end_time : string;
comment: string;
is_approved: boolean; is_approved: boolean;
is_remote: boolean; is_remote: boolean;
} }
export class ExpenseDto { export class ExpenseDto {
amount: number; amount: number;
comment: string;
supervisor_comment: string;
total_mileage: number; total_mileage: number;
total_expense: number; total_expense: number;
is_approved: boolean; is_approved: boolean;
@ -22,6 +25,7 @@ export class DetailedShifts {
evening_hours: number; evening_hours: number;
overtime_hours: number; overtime_hours: number;
emergency_hours: number; emergency_hours: number;
comment: string;
short_date: string; short_date: string;
break_durations?: number; break_durations?: number;
} }

View File

@ -1,14 +1,12 @@
import { Injectable, NotFoundException } from '@nestjs/common'; import { Injectable, NotFoundException } from '@nestjs/common';
import { PrismaService } from 'src/prisma/prisma.service'; import { PrismaService } from 'src/prisma/prisma.service';
import { Timesheets, TimesheetsArchive } from '@prisma/client'; import { Timesheets, TimesheetsArchive } from '@prisma/client';
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, formatDateISO, getCurrentWeek, getWeekEnd, getWeekStart } from 'src/common/utils/date-utils'; import { computeHours, formatDateISO, getWeekEnd, getWeekStart } from 'src/common/utils/date-utils';
import { TimesheetPeriodDto } from '../dtos/timesheet-period.dto'; import { TimesheetPeriodDto } from '../dtos/timesheet-period.dto';
import { buildPeriod, endOfDayUTC, toUTCDateOnly } from '../utils/timesheet.helpers'; import { buildPeriod, endOfDayUTC, toUTCDateOnly } from '../utils/timesheet.helpers';
import type { ShiftRow, ExpenseRow } from '../utils/timesheet.helpers'; import type { ShiftRow, ExpenseRow } from '../utils/timesheet.helpers';
import { TimesheetDto } from '../dtos/overview-timesheet.dto'; import { TimesheetDto } from '../dtos/overview-timesheet.dto';
import { CreateWeekShiftsDto } from '../dtos/create-timesheet.dto';
@Injectable() @Injectable()
@ -21,14 +19,14 @@ export class TimesheetsQueryService {
async findAll(year: number, period_no: number, email: string): Promise<TimesheetPeriodDto> { async findAll(year: number, period_no: number, email: string): Promise<TimesheetPeriodDto> {
//finds the employee //finds the employee
const employee = await this.prisma.employees.findFirst({ const employee = await this.prisma.employees.findFirst({
where: { user: { is: { email } } }, where: { user: { is: { email } } },
select: { id: true }, select: { id: true },
}); });
if(!employee) throw new NotFoundException(`no employee with email ${email} found`); if(!employee) throw new NotFoundException(`no employee with email ${email} found`);
//finds the period //finds the period
const period = await this.prisma.payPeriods.findFirst({ const period = await this.prisma.payPeriods.findFirst({
where: { pay_year: year, pay_period_no: period_no }, where: { pay_year: year, pay_period_no: period_no },
select: { period_start: true, period_end: true }, select: { period_start: true, period_end: true },
}); });
if(!period) throw new NotFoundException(`Period ${year}-${period_no} not found`); if(!period) throw new NotFoundException(`Period ${year}-${period_no} not found`);
@ -45,6 +43,7 @@ export class TimesheetsQueryService {
date: true, date: true,
start_time: true, start_time: true,
end_time: true, end_time: true,
comment: true,
is_approved: true, is_approved: true,
is_remote: true, is_remote: true,
bank_code: { select: { type: true } }, bank_code: { select: { type: true } },
@ -60,31 +59,37 @@ export class TimesheetsQueryService {
select: { select: {
date: true, date: true,
amount: true, amount: true,
comment: true,
supervisor_comment: true,
is_approved: true, is_approved: true,
bank_code: { select: { type: true } }, bank_code: { select: { type: true } },
}, },
orderBy: { date: 'asc' }, orderBy: { date: 'asc' },
}); });
const to_num = (value: any) => value && typeof (value as any).toNumber === 'function' const to_num = (value: any) =>
? (value as any).toNumber() value && typeof value.toNumber === 'function' ? value.toNumber() :
: Number(value); typeof value === 'number' ? value :
value ? Number(value) : 0;
// data mapping // data mapping
const shifts: ShiftRow[] = raw_shifts.map(shift => ({ const shifts: ShiftRow[] = raw_shifts.map(shift => ({
date: shift.date, date: shift.date,
start_time: shift.start_time, start_time: shift.start_time,
end_time: shift.end_time, end_time: shift.end_time,
type: String(shift.bank_code?.type ?? '').toUpperCase(), comment: shift.comment ?? '',
is_approved: shift.is_approved ?? true, is_approved: shift.is_approved ?? true,
is_remote: shift.is_remote ?? true, is_remote: shift.is_remote ?? true,
type: String(shift.bank_code?.type ?? '').toUpperCase(),
})); }));
const expenses: ExpenseRow[] = raw_expenses.map(expense => ({ const expenses: ExpenseRow[] = raw_expenses.map(expense => ({
date: expense.date, date: expense.date,
amount: to_num(expense.amount), amount: to_num(expense.amount),
type: String(expense.bank_code?.type ?? '').toUpperCase(), comment: expense.comment ?? '',
supervisor_comment: expense.supervisor_comment ?? '',
is_approved: expense.is_approved ?? true, is_approved: expense.is_approved ?? true,
type: String(expense.bank_code?.type ?? '').toUpperCase(),
})); }));
return buildPeriod(period.period_start, period.period_end, shifts , expenses); return buildPeriod(period.period_start, period.period_end, shifts , expenses);
@ -231,7 +236,7 @@ export class TimesheetsQueryService {
await this.prisma.$transaction(async transaction => { await this.prisma.$transaction(async transaction => {
//fetches all timesheets to cutoff //fetches all timesheets to cutoff
const oldSheets = await transaction.timesheets.findMany({ const oldSheets = await transaction.timesheets.findMany({
where: { shift: { every: { date: { lt: cutoff } } }, where: { shift: { some: { date: { lt: cutoff } } },
}, },
select: { select: {
id: true, id: true,

View File

@ -34,8 +34,23 @@ const EXPENSE_TYPES = {
} as const; } as const;
//DB line types //DB line types
export type ShiftRow = { date: Date; start_time: Date; end_time: Date; is_approved?: boolean; is_remote: boolean; type: string }; export type ShiftRow = {
export type ExpenseRow = { date: Date; amount: number; type: string; is_approved?: boolean; }; date: Date;
start_time: Date;
end_time: Date;
comment: string;
is_approved?: boolean;
is_remote: boolean;
type: string
};
export type ExpenseRow = {
date: Date;
amount: number;
comment: string;
supervisor_comment: string;
is_approved?: boolean;
type: string;
};
//helper functions //helper functions
export function toUTCDateOnly(date: Date | string): Date { export function toUTCDateOnly(date: Date | string): Date {
@ -84,6 +99,7 @@ export function makeEmptyWeek(week_start: Date): WeekDto {
evening_hours: 0, evening_hours: 0,
emergency_hours: 0, emergency_hours: 0,
overtime_hours: 0, overtime_hours: 0,
comment: '',
short_date: shortDate(addDays(week_start, offset)), short_date: shortDate(addDays(week_start, offset)),
break_durations: 0, break_durations: 0,
}); });
@ -129,19 +145,44 @@ export function buildWeek(
}, {} as Record<DayKey, Array<{ start: Date; end: Date}>>); }, {} as Record<DayKey, Array<{ start: Date; end: Date}>>);
//shifts's hour by type //shifts's hour by type
type ShiftsHours = type ShiftsHours = {
{regular: number; evening: number; overtime: number; emergency: number; sick: number; vacation: number; holiday: number;}; regular: number;
const make_hours = (): ShiftsHours => evening: number;
({ regular: 0, evening: 0, overtime: 0, emergency: 0, sick: 0, vacation: 0, holiday: 0 }); overtime: number;
emergency: number;
sick: number;
vacation: number;
holiday: number;
};
const make_hours = (): ShiftsHours => ({
regular: 0,
evening: 0,
overtime: 0,
emergency: 0,
sick: 0,
vacation: 0,
holiday: 0
});
const day_hours: Record<DayKey, ShiftsHours> = DAY_KEYS.reduce((acc, key) => { const day_hours: Record<DayKey, ShiftsHours> = DAY_KEYS.reduce((acc, key) => {
acc[key] = make_hours(); return acc; acc[key] = make_hours(); return acc;
}, {} as Record<DayKey, ShiftsHours>); }, {} as Record<DayKey, ShiftsHours>);
//expenses's amount by type //expenses's amount by type
type ExpensesAmount = type ExpensesAmount = {
{mileage: number; expense: number; per_diem: number; commission: number; prime_dispo: number }; mileage: number;
const make_amounts = (): ExpensesAmount => expense: number;
({ mileage: 0, expense: 0, per_diem: 0, commission: 0, prime_dispo: 0 }); per_diem: number;
commission: number;
prime_dispo: number
};
const make_amounts = (): ExpensesAmount => ({
mileage: 0,
expense: 0,
per_diem: 0,
commission: 0,
prime_dispo: 0
});
const day_amounts: Record<DayKey, ExpensesAmount> = DAY_KEYS.reduce((acc, key) => { const day_amounts: Record<DayKey, ExpensesAmount> = DAY_KEYS.reduce((acc, key) => {
acc[key] = make_amounts(); return acc; acc[key] = make_amounts(); return acc;
}, {} as Record<DayKey, ExpensesAmount>); }, {} as Record<DayKey, ExpensesAmount>);
@ -159,6 +200,7 @@ export function buildWeek(
type: shift.type, type: shift.type,
start_time: toTimeString(shift.start_time), start_time: toTimeString(shift.start_time),
end_time: toTimeString(shift.end_time), end_time: toTimeString(shift.end_time),
comment: shift.comment,
is_approved: shift.is_approved ?? true, is_approved: shift.is_approved ?? true,
is_remote: shift.is_remote, is_remote: shift.is_remote,
} as ShiftDto); } as ShiftDto);
@ -230,6 +272,8 @@ export function buildWeek(
for(const row of dayExpenseRows[key].km) { for(const row of dayExpenseRows[key].km) {
week.expenses[key].km.push({ week.expenses[key].km.push({
amount: round2(row.amount), amount: round2(row.amount),
comment: row.comment,
supervisor_comment: row.supervisor_comment,
total_mileage: round2(total_mileage), total_mileage: round2(total_mileage),
total_expense: round2(total_expense), total_expense: round2(total_expense),
is_approved: row.is_approved ?? true, is_approved: row.is_approved ?? true,
@ -240,6 +284,8 @@ export function buildWeek(
for(const row of dayExpenseRows[key].cash) { for(const row of dayExpenseRows[key].cash) {
week.expenses[key].cash.push({ week.expenses[key].cash.push({
amount: round2(row.amount), amount: round2(row.amount),
comment: row.comment,
supervisor_comment: row.supervisor_comment,
total_mileage: round2(total_mileage), total_mileage: round2(total_mileage),
total_expense: round2(total_expense), total_expense: round2(total_expense),
is_approved: row.is_approved ?? true, is_approved: row.is_approved ?? true,