104 lines
3.6 KiB
TypeScript
104 lines
3.6 KiB
TypeScript
import { Injectable } from '@nestjs/common';
|
|
import { Prisma, PrismaClient } from '@prisma/client';
|
|
import { getWeekStart, getWeekEnd, computeHours } from 'src/common/utils/date-utils';
|
|
import { PrismaService } from 'src/prisma/prisma.service';
|
|
import { DAILY_LIMIT_HOURS, WEEKLY_LIMIT_HOURS } from 'src/common/utils/constants.utils';
|
|
import { Result } from 'src/common/errors/result-error.factory';
|
|
|
|
|
|
type Tx = Prisma.TransactionClient | PrismaClient;
|
|
|
|
type WeekOvertimeSummary = {
|
|
week_start: string;
|
|
week_end: string;
|
|
week_total_hours: number;
|
|
weekly_overtime: number;
|
|
daily_overtime_kept: number;
|
|
total_overtime: number;
|
|
breakdown: Array<{
|
|
date: string;
|
|
day_hours: number;
|
|
day_overtime: number;
|
|
daily_kept: number;
|
|
running_total_before: number;
|
|
}>;
|
|
};
|
|
|
|
@Injectable()
|
|
export class OvertimeService {
|
|
|
|
private INCLUDED_TYPES = ['EMERGENCY', 'EVENING', 'OVERTIME', 'REGULAR'] as const; // included types for weekly overtime calculation
|
|
|
|
constructor(private prisma: PrismaService) { }
|
|
|
|
async getWeekOvertimeSummary(timesheet_id: number, date: Date, tx?: Tx): Promise<Result<WeekOvertimeSummary, string>> {
|
|
const db = tx ?? this.prisma;
|
|
|
|
const week_start = getWeekStart(date);
|
|
const week_end = getWeekEnd(week_start);
|
|
|
|
const shifts = await db.shifts.findMany({
|
|
where: {
|
|
timesheet_id,
|
|
date: { gte: week_start, lte: week_end },
|
|
bank_code: { type: { in: this.INCLUDED_TYPES as unknown as string[] } },
|
|
},
|
|
select: { date: true, start_time: true, end_time: true },
|
|
orderBy: [{ date: 'asc' }, { start_time: 'asc' }],
|
|
});
|
|
|
|
const day_totals = new Map<string, number>();
|
|
for (const shift of shifts) {
|
|
const key = shift.date.toISOString().slice(0, 10);
|
|
const hours = computeHours(shift.start_time, shift.end_time, 5);
|
|
day_totals.set(key, (day_totals.get(key) ?? 0) + hours);
|
|
}
|
|
|
|
const days: string[] = [];
|
|
for (let i = 0; i < 7; i++) {
|
|
const day = new Date(week_start.getTime() + i * 24 * 60 * 60 * 1000);
|
|
days.push(day.toISOString().slice(0, 10));
|
|
}
|
|
|
|
const week_total_hours = [...day_totals.values()].reduce((a, b) => a + b, 0);
|
|
const weekly_overtime = Math.max(0, week_total_hours - WEEKLY_LIMIT_HOURS);
|
|
|
|
let running = 0;
|
|
let daily_kept_sum = 0;
|
|
const breakdown: WeekOvertimeSummary['breakdown'] = [];
|
|
|
|
for (const key of days) {
|
|
const day_hours = day_totals.get(key) ?? 0;
|
|
const day_overtime = Math.max(0, day_hours - DAILY_LIMIT_HOURS);
|
|
|
|
const cap_before_40 = Math.max(0, WEEKLY_LIMIT_HOURS - running);
|
|
const daily_kept = Math.min(day_overtime, cap_before_40);
|
|
|
|
breakdown.push({
|
|
date: key,
|
|
day_hours,
|
|
day_overtime,
|
|
daily_kept,
|
|
running_total_before: running,
|
|
});
|
|
|
|
daily_kept_sum += daily_kept;
|
|
running += day_hours;
|
|
}
|
|
const total_overtime = weekly_overtime + daily_kept_sum;
|
|
|
|
return {
|
|
success: true,
|
|
data: {
|
|
week_start: week_start.toISOString().slice(0, 10),
|
|
week_end: week_end.toISOString().slice(0, 10),
|
|
week_total_hours,
|
|
weekly_overtime,
|
|
daily_overtime_kept: daily_kept_sum,
|
|
total_overtime,
|
|
breakdown,
|
|
}
|
|
};
|
|
}
|
|
}
|