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> { 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(); 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, } }; } }