179 lines
6.7 KiB
TypeScript
179 lines
6.7 KiB
TypeScript
|
|
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<string, Prisma.ShiftsGetPayload<{ include: { bank_code }, orderBy: { start_time: 'asc' } }>[]>();
|
|
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<string, Prisma.ExpensesGetPayload<{ include: { bank_code: Prisma.BankCodesDefaultArgs } }>[]>();
|
|
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;
|
|
}; |