targo-backend/src/modules/timesheets/~misc_deprecated-files/timesheet.utils.ts

171 lines
6.6 KiB
TypeScript

import {
DayKey, DAY_KEYS, EXPENSE_TYPES, ExpenseRow,
SHIFT_TYPES, ShiftRow, make_hours, ShiftsHours, ExpensesAmount
} from "./timesheet.types";
import {
isBetweenUTC, dayKeyFromDate, toTimeString, round2,
toUTCDateOnly, endOfDayUTC, addDays
} from "./timesheet.helpers";
import { WeekDto, ShiftDto, TimesheetPeriodDto, DayExpensesDto, ExpenseDto } from "../dtos/timesheet-period.dto";
import { getWeekStart, getWeekEnd, formatDateISO } from "src/common/utils/date-utils";
import { makeAmounts, makeEmptyWeek } from "./timesheet.mappers";
import { toDateString } from "src/modules/pay-periods/utils/pay-year.util";
import { MS_PER_HOUR } from "src/modules/shared/constants/date-time.constant";
export function computeWeekRange(week_offset = 0){
//sets current week Sunday -> Saturday
const base = new Date();
const offset = new Date(base);
offset.setDate(offset.getDate() + (week_offset * 7));
const start = getWeekStart(offset, 0);
const end = getWeekEnd(start);
const start_day = formatDateISO(start);
const end_day = formatDateISO(end);
const label = `${(start_day)}.${(end_day)}`;
return { start, end, start_day, end_day, label }
};
export function buildWeek(
week_start: Date,
week_end: Date,
shifts: ShiftRow[],
expenses: ExpenseRow[],
): WeekDto {
const week = makeEmptyWeek(week_start);
let all_approved = true;
const day_times: Record<DayKey, Array<{ start: Date; end: Date }>> = DAY_KEYS.reduce((acc, key) => {
acc[key] = []; return acc;
}, {} as Record<DayKey, Array<{ start: Date; end: Date}>>);
const day_hours: Record<DayKey, ShiftsHours> = DAY_KEYS.reduce((acc, key) => {
acc[key] = make_hours(); return acc;
}, {} as Record<DayKey, ShiftsHours>);
const day_amounts: Record<DayKey, ExpensesAmount> = DAY_KEYS.reduce((acc, key) => {
acc[key] = makeAmounts(); return acc;
}, {} as Record<DayKey, ExpensesAmount>);
const day_expense_rows: Record<DayKey, DayExpensesDto> = DAY_KEYS.reduce((acc, key) => {
acc[key] = {
expenses: [{
type: '',
amount: -1,
mileage: -1,
comment: '',
is_approved: false,
supervisor_comment: '',
}],
total_expense: -1,
total_mileage: -1,
};
return acc;
}, {} as Record<DayKey, DayExpensesDto>);
//regroup hours per type of shifts
const week_shifts = shifts.filter(shift => isBetweenUTC(shift.date, week_start, week_end));
for (const shift of week_shifts) {
const key = dayKeyFromDate(shift.date, true);
week.shifts[key].shifts.push({
date: toDateString(shift.date),
type: shift.type,
start_time: toTimeString(shift.start_time),
end_time: toTimeString(shift.end_time),
comment: shift.comment,
is_approved: shift.is_approved ?? true,
is_remote: shift.is_remote,
} as ShiftDto);
day_times[key].push({ start: shift.start_time, end: shift.end_time});
const duration = Math.max(0, (shift.end_time.getTime() - shift.start_time.getTime())/ MS_PER_HOUR);
const type = (shift.type || '').toUpperCase();
if ( type === SHIFT_TYPES.REGULAR) day_hours[key].regular += duration;
else if( type === SHIFT_TYPES.EVENING) day_hours[key].evening += duration;
else if( type === SHIFT_TYPES.EMERGENCY) day_hours[key].emergency += duration;
else if( type === SHIFT_TYPES.OVERTIME) day_hours[key].overtime += duration;
else if( type === SHIFT_TYPES.SICK) day_hours[key].sick += duration;
else if( type === SHIFT_TYPES.VACATION) day_hours[key].vacation += duration;
else if( type === SHIFT_TYPES.HOLIDAY) day_hours[key].holiday += duration;
all_approved = all_approved && (shift.is_approved ?? true );
}
//regroupe amounts to type of expenses
const week_expenses = expenses.filter(expense => isBetweenUTC(expense.date, week_start, week_end));
for (const expense of week_expenses) {
const key = dayKeyFromDate(expense.date, true);
const type = (expense.type || '').toUpperCase();
const row: ExpenseDto = {
type,
amount: round2(expense.amount ?? 0),
mileage: round2(expense.mileage ?? 0),
comment: expense.comment ?? '',
is_approved: expense.is_approved ?? true,
supervisor_comment: expense.supervisor_comment ?? '',
};
day_expense_rows[key].expenses.push(row);
if(type === EXPENSE_TYPES.MILEAGE) {
day_amounts[key].mileage += row.mileage ?? 0;
} else {
day_amounts[key].expense += row.amount;
}
all_approved = all_approved && row.is_approved;
}
for (const key of DAY_KEYS) {
//return exposed dto data
week.shifts[key].regular_hours = round2(day_hours[key].regular);
week.shifts[key].evening_hours = round2(day_hours[key].evening);
week.shifts[key].overtime_hours = round2(day_hours[key].overtime);
week.shifts[key].emergency_hours = round2(day_hours[key].emergency);
//calculate gaps between shifts
const times = day_times[key].sort((a,b) => a.start.getTime() - b.start.getTime());
let gaps = 0;
for (let i = 1; i < times.length; i++) {
const gap = (times[i].start.getTime() - times[i - 1].end.getTime()) / MS_PER_HOUR;
if(gap > 0) gaps += gap;
}
week.shifts[key].break_durations = round2(gaps);
//daily totals
const totals = day_amounts[key];
day_expense_rows[key].total_mileage = round2(totals.mileage);
day_expense_rows[key].total_expense = round2(totals.expense);
}
week.is_approved = all_approved;
return week;
}
export function buildPeriod(
period_start: Date,
period_end: Date,
shifts: ShiftRow[],
expenses: ExpenseRow[],
employeeFullName = ''
): TimesheetPeriodDto {
const week1_start = toUTCDateOnly(period_start);
const week1_end = endOfDayUTC(addDays(week1_start, 6));
const week2_start = toUTCDateOnly(addDays(week1_start, 7));
const week2_end = endOfDayUTC(period_end);
const weeks: WeekDto[] = [
buildWeek(week1_start, week1_end, shifts, expenses),
buildWeek(week2_start, week2_end, shifts, expenses),
];
return {
weeks,
employee_full_name: employeeFullName,
};
}