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())].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), }, }; }