Files
CapaKraken/packages/api/src/router/staffing-capacity-summary.ts
T

132 lines
4.6 KiB
TypeScript

import { type WeekdayAvailability } from "@capakraken/shared";
import {
calculateEffectiveAvailableHours,
calculateEffectiveBookedHours,
countEffectiveWorkingDays,
type ResourceDailyAvailabilityContext,
} from "../lib/resource-capacity.js";
import { averagePerWorkingDay, getBaseDayAvailability, round1 } from "./staffing-shared.js";
type CapacityBooking = {
startDate: Date;
endDate: Date;
hoursPerDay: number;
};
export function summarizeHolidayAndAbsenceCapacity(input: {
availability: WeekdayAvailability;
context: ResourceDailyAvailabilityContext | undefined;
}) {
const holidayDates = [...(input.context?.holidayDates ?? new Set<string>())].sort();
const holidayWorkdayCount = holidayDates.reduce((count, isoDate) => (
count + (getBaseDayAvailability(input.availability, new Date(`${isoDate}T00:00:00.000Z`)) > 0 ? 1 : 0)
), 0);
const holidayHoursDeduction = holidayDates.reduce((sum, isoDate) => (
sum + getBaseDayAvailability(input.availability, new Date(`${isoDate}T00:00:00.000Z`))
), 0);
let excludedCapacityDays = 0;
for (const fraction of input.context?.absenceFractionsByDate.values() ?? []) {
excludedCapacityDays += fraction;
}
let absenceDayEquivalent = 0;
let absenceHoursDeduction = 0;
for (const [isoDate, fraction] of input.context?.vacationFractionsByDate ?? []) {
const dayHours = getBaseDayAvailability(input.availability, new Date(`${isoDate}T00:00:00.000Z`));
if (dayHours <= 0 || input.context?.holidayDates.has(isoDate)) {
continue;
}
absenceDayEquivalent += fraction;
absenceHoursDeduction += dayHours * fraction;
}
return {
excludedCapacityDays: round1(excludedCapacityDays),
holidayDates,
holidayWorkdayCount,
holidayHoursDeduction: round1(holidayHoursDeduction),
absenceDayEquivalent: round1(absenceDayEquivalent),
absenceHoursDeduction: round1(absenceHoursDeduction),
};
}
export function buildResourceCapacitySummary(input: {
availability: WeekdayAvailability;
periodStart: Date;
periodEnd: Date;
context: ResourceDailyAvailabilityContext | undefined;
bookings?: CapacityBooking[] | undefined;
}) {
const baseWorkingDays = countEffectiveWorkingDays({
availability: input.availability,
periodStart: input.periodStart,
periodEnd: input.periodEnd,
context: undefined,
});
const workingDays = countEffectiveWorkingDays({
availability: input.availability,
periodStart: input.periodStart,
periodEnd: input.periodEnd,
context: input.context,
});
const baseAvailableHours = calculateEffectiveAvailableHours({
availability: input.availability,
periodStart: input.periodStart,
periodEnd: input.periodEnd,
context: undefined,
});
const availableHours = calculateEffectiveAvailableHours({
availability: input.availability,
periodStart: input.periodStart,
periodEnd: input.periodEnd,
context: input.context,
});
const bookedHours = (input.bookings ?? []).reduce(
(sum, booking) =>
sum + calculateEffectiveBookedHours({
availability: input.availability,
startDate: booking.startDate,
endDate: booking.endDate,
hoursPerDay: booking.hoursPerDay,
periodStart: input.periodStart,
periodEnd: input.periodEnd,
context: input.context,
}),
0,
);
const remainingHours = Math.max(0, availableHours - bookedHours);
const holidayAndAbsence = summarizeHolidayAndAbsenceCapacity({
availability: input.availability,
context: input.context,
});
return {
baseWorkingDays: round1(baseWorkingDays),
workingDays: round1(workingDays),
baseAvailableHours: round1(baseAvailableHours),
availableHours: round1(availableHours),
bookedHours: round1(bookedHours),
remainingHours: round1(remainingHours),
remainingHoursPerDay: averagePerWorkingDay(remainingHours, workingDays),
holidaySummary: {
count: holidayAndAbsence.holidayDates.length,
workdayCount: holidayAndAbsence.holidayWorkdayCount,
hoursDeduction: holidayAndAbsence.holidayHoursDeduction,
holidayDates: holidayAndAbsence.holidayDates,
},
absenceSummary: {
dayEquivalent: holidayAndAbsence.absenceDayEquivalent,
hoursDeduction: holidayAndAbsence.absenceHoursDeduction,
},
excludedCapacityDays: holidayAndAbsence.excludedCapacityDays,
capacityBreakdown: {
formula: "baseAvailableHours - holidayHoursDeduction - absenceHoursDeduction = availableHours",
baseAvailableHours: round1(baseAvailableHours),
holidayHoursDeduction: holidayAndAbsence.holidayHoursDeduction,
absenceHoursDeduction: holidayAndAbsence.absenceHoursDeduction,
availableHours: round1(availableHours),
},
};
}