import { Prisma } from "prisma/postgres/generated/prisma/client/postgres/client"; import { toDateFromString, sevenDaysFrom, toStringFromDate, toHHmmFromDate } from "src/common/utils/date-utils"; import { BankCodeDto } from "src/time-and-attendance/bank-codes/bank-codes.dto"; import { Timesheet } from "src/time-and-attendance/timesheets/timesheet.dto"; export const mapOneTimesheet = ( timesheet: Prisma.TimesheetsGetPayload<{ include: { employee: { include: { user } }, shift: { include: { bank_code }, orderBy: { start_time: 'asc' } }, expense: { include: { bank_code } }, } }> ): Timesheet => { //converts string to UTC date format const start = toDateFromString(timesheet.start_date); const day_dates = sevenDaysFrom(start); //map of shifts by days const shifts_by_date = new Map[]>(); for (const shift of timesheet.shift) { const date_string = toStringFromDate(shift.date); const arr = shifts_by_date.get(date_string) ?? []; arr.push(shift); shifts_by_date.set(date_string, arr); } //map of expenses by days const expenses_by_date = new Map[]>(); for (const expense of timesheet.expense) { const date_string = toStringFromDate(expense.date); const arr = expenses_by_date.get(date_string) ?? []; arr.push(expense); expenses_by_date.set(date_string, arr); } //weekly totals const weekly_hours: TotalHours = emptyHours(); const weekly_expenses: TotalExpenses = emptyExpenses(); //map of days const days = day_dates.map((date) => { const date_iso = toStringFromDate(date); const shifts_source = shifts_by_date.get(date_iso) ?? []; const expenses_source = expenses_by_date.get(date_iso) ?? []; //inner map of shifts const shifts = shifts_source.map((shift) => ({ timesheet_id: shift.timesheet_id, date: toStringFromDate(shift.date), start_time: toHHmmFromDate(shift.start_time), end_time: toHHmmFromDate(shift.end_time), type: shift.bank_code?.type ?? '', is_remote: shift.is_remote ?? false, is_approved: shift.is_approved ?? false, id: shift.id ?? null, comment: shift.comment ?? null, })); //inner map of expenses const expenses = expenses_source.map((expense) => ({ date: toStringFromDate(expense.date), amount: expense.amount != null ? Number(expense.amount) : undefined, mileage: expense.mileage != null ? Number(expense.mileage) : undefined, id: expense.id ?? null, timesheet_id: expense.timesheet_id, is_approved: expense.is_approved ?? false, comment: expense.comment ?? '', supervisor_comment: expense.supervisor_comment, type: expense.bank_code.type, })); //daily totals const daily_hours = emptyHours(); const daily_expenses = emptyExpenses(); //daily totals by shift types for (const shift of shifts_source) { const hours = diffOfHours(shift.start_time, shift.end_time); const subgroup = hoursSubGroupFromBankCode(shift.bank_code); daily_hours[subgroup] += hours; weekly_hours[subgroup] += hours; } // TODO: ADD DAILY OVERTIME CHECK HERE // const dailyOvertimeOwed = Math.max(daily_hours.regular - timesheet.employee.daily_expected_hours, 0) // daily_hours.overtime = dailyOvertimeOwed; // daily_hours.regular -= dailyOvertimeOwed; //totals by expense types for (const expense of expenses_source) { const subgroup = expenseSubgroupFromBankCode(expense.bank_code); if (subgroup === 'mileage') { daily_expenses.mileage += Number(expense.mileage); weekly_expenses.mileage += Number(expense.mileage); } else { daily_expenses[subgroup] += Number(expense.amount); weekly_expenses[subgroup] += Number(expense.amount); } } return { date: date_iso, shifts, expenses, daily_hours, daily_expenses, }; }); const totalWeekHoursWorked = weekly_hours.regular + weekly_hours.evening + weekly_hours.emergency + weekly_hours.holiday + weekly_hours.vacation weekly_hours.overtime = Math.max(totalWeekHoursWorked - 40, 0); return { timesheet_id: timesheet.id, is_approved: timesheet.is_approved ?? false, days, weekly_hours, weekly_expenses, }; } //filled array with default values const emptyHours = (): TotalHours => { return { regular: 0, evening: 0, emergency: 0, overtime: 0, vacation: 0, holiday: 0, sick: 0, banking: 0, withdraw_banked: 0 } }; const emptyExpenses = (): TotalExpenses => { return { expenses: 0, per_diem: 0, on_call: 0, mileage: 0 } }; //calculate the differences of hours const diffOfHours = (a: Date, b: Date): number => { const ms = new Date(b).getTime() - new Date(a).getTime(); return Math.max(0, Math.round((ms / 36e5) * 1000) / 1000); } // shift's subgroup types const hoursSubGroupFromBankCode = (bank_code: BankCodeDto): keyof TotalHours => { const type = bank_code.type; if (type.includes('EVENING')) return 'evening'; if (type.includes('EMERGENCY')) return 'emergency'; if (type.includes('OVERTIME')) return 'overtime'; if (type.includes('VACATION')) return 'vacation'; if (type.includes('HOLIDAY')) return 'holiday'; if (type.includes('SICK')) return 'sick'; if (type.includes('BANKING')) return 'banking'; if (type.includes('WITHDRAW_BANKED')) return 'withdraw_banked' return 'regular' } // expense's subgroup types const expenseSubgroupFromBankCode = (bank_code: BankCodeDto): keyof TotalExpenses => { const type = bank_code.type; if (type.includes('MILEAGE')) return 'mileage'; if (type.includes('PER_DIEM')) return 'per_diem'; if (type.includes('ON_CALL')) return 'on_call'; return 'expenses'; } export type TotalHours = { regular: number; evening: number; emergency: number; overtime: number; vacation: number; holiday: number; sick: number; banking: number; withdraw_banked: number; }; export type TotalExpenses = { expenses: number; per_diem: number; on_call: number; mileage: number; };