import { VacationStatus } from "@capakraken/db"; import type { WeekdayAvailability } from "@capakraken/shared"; import { asHolidayResolverDb, collectHolidayAvailability, getResolvedCalendarHolidays, } from "../lib/holiday-availability.js"; import { calculateEffectiveAvailableHours, countEffectiveWorkingDays, loadResourceDailyAvailabilityContexts, } from "../lib/resource-capacity.js"; type ResourceLocation = { countryId: string | null; federalState: string | null; metroCityId: string | null; country?: { code?: string | null; name?: string | null } | null; metroCity?: { name?: string | null } | null; }; type ResourceAvailabilityInput = { db: any; resource: ResourceLocation & { id: string; }; weeklyAvailability: WeekdayAvailability; monthStart: Date; monthEnd: Date; }; function getAvailabilityHoursForDate( availability: WeekdayAvailability, date: Date, ): number { const dayKey = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"][date.getUTCDay()] as keyof WeekdayAvailability; return availability[dayKey] ?? 0; } function sumAvailabilityHoursForDates( availability: WeekdayAvailability, dates: Date[], ): number { return dates.reduce((sum, date) => sum + getAvailabilityHoursForDate(availability, date), 0); } export async function loadResourceGraphAvailability(input: ResourceAvailabilityInput) { const { db, resource, weeklyAvailability, monthStart, monthEnd } = input; const vacations = await db.vacation.findMany({ where: { resourceId: resource.id, status: VacationStatus.APPROVED, startDate: { lte: monthEnd }, endDate: { gte: monthStart }, }, select: { startDate: true, endDate: true, type: true, isHalfDay: true }, }); const resolvedHolidays = await getResolvedCalendarHolidays(asHolidayResolverDb(db), { periodStart: monthStart, periodEnd: monthEnd, countryId: resource.countryId, countryCode: resource.country?.code, federalState: resource.federalState, metroCityId: resource.metroCityId, metroCityName: resource.metroCity?.name, }); const holidayAvailability = collectHolidayAvailability({ vacations, periodStart: monthStart, periodEnd: monthEnd, countryCode: resource.country?.code, federalState: resource.federalState, metroCityName: resource.metroCity?.name, resolvedHolidayStrings: resolvedHolidays.map((holiday) => holiday.date), }); const publicHolidayStrings = holidayAvailability.publicHolidayStrings; const absenceDateStrings = holidayAvailability.absenceDateStrings; const absenceDays = holidayAvailability.absenceDays; const halfDayCount = absenceDays.filter((absence) => absence.isHalfDay).length; const vacationDayCount = absenceDays.filter((absence) => absence.type === "VACATION").length; const sickDayCount = absenceDays.filter((absence) => absence.type === "SICK").length; const publicHolidayCount = resolvedHolidays.length; const absenceDayEquivalent = absenceDays.reduce((sum, absence) => { if (absence.type === "PUBLIC_HOLIDAY") { return sum; } return sum + (absence.isHalfDay ? 0.5 : 1); }, 0); const contexts = await loadResourceDailyAvailabilityContexts( db, [{ id: resource.id, availability: weeklyAvailability, countryId: resource.countryId, countryCode: resource.country?.code, federalState: resource.federalState, metroCityId: resource.metroCityId, metroCityName: resource.metroCity?.name, }], monthStart, monthEnd, ); const availabilityContext = contexts.get(resource.id); const baseWorkingDays = countEffectiveWorkingDays({ availability: weeklyAvailability, periodStart: monthStart, periodEnd: monthEnd, context: undefined, }); const effectiveWorkingDays = countEffectiveWorkingDays({ availability: weeklyAvailability, periodStart: monthStart, periodEnd: monthEnd, context: availabilityContext, }); const baseAvailableHours = calculateEffectiveAvailableHours({ availability: weeklyAvailability, periodStart: monthStart, periodEnd: monthEnd, context: undefined, }); const effectiveAvailableHours = calculateEffectiveAvailableHours({ availability: weeklyAvailability, periodStart: monthStart, periodEnd: monthEnd, context: availabilityContext, }); const publicHolidayDates = resolvedHolidays.map((holiday) => new Date(`${holiday.date}T00:00:00.000Z`)); const publicHolidayWorkdayCount = publicHolidayDates.reduce((count, date) => ( count + (getAvailabilityHoursForDate(weeklyAvailability, date) > 0 ? 1 : 0) ), 0); const publicHolidayHoursDeduction = sumAvailabilityHoursForDates( weeklyAvailability, publicHolidayDates, ); const absenceHoursDeduction = absenceDays.reduce((sum, absence) => { if (absence.type === "PUBLIC_HOLIDAY") { return sum; } const baseHours = getAvailabilityHoursForDate(weeklyAvailability, absence.date); return sum + baseHours * (absence.isHalfDay ? 0.5 : 1); }, 0); const effectiveHoursPerWorkingDay = effectiveWorkingDays > 0 ? effectiveAvailableHours / effectiveWorkingDays : 0; const holidayScopeSummary = [ resource.country?.code ?? "—", resource.federalState ?? "—", resource.metroCity?.name ?? "—", ].join(" / "); const holidayExamples = resolvedHolidays.length > 0 ? resolvedHolidays.slice(0, 4).map((holiday) => `${holiday.date} ${holiday.name}`).join(", ") : "none"; const holidayScopeBreakdown = resolvedHolidays.reduce>((counts, holiday) => { counts[holiday.scope] = (counts[holiday.scope] ?? 0) + 1; return counts; }, {}); return { absenceDateStrings, absenceDayEquivalent, absenceDays, absenceHoursDeduction, availabilityContext, baseAvailableHours, baseWorkingDays, effectiveAvailableHours, effectiveHoursPerWorkingDay, effectiveWorkingDays, halfDayCount, holidayExamples, holidayScopeBreakdown, holidayScopeSummary, publicHolidayCount, publicHolidayHoursDeduction, publicHolidayStrings, publicHolidayWorkdayCount, resolvedHolidays, sickDayCount, vacationDayCount, }; }