171 lines
6.6 KiB
TypeScript
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,
|
|
};
|
|
} |