import { toIsoDate, type WeekdayAvailability } from "@capakraken/shared"; export { toIsoDate, round1, averagePerWorkingDay } from "@capakraken/shared"; import { getAvailabilityHoursForDate, calculateEffectiveDayAvailability as _calcEffective, type ResourceDailyAvailabilityContext } from "../lib/resource-capacity.js"; export const ACTIVE_STATUSES = new Set(["PROPOSED", "CONFIRMED", "ACTIVE"]); function createUtcDate(year: number, monthIndex: number, day: number): Date { return new Date(Date.UTC(year, monthIndex, day)); } function normalizeUtcDate(value: Date): Date { return createUtcDate(value.getUTCFullYear(), value.getUTCMonth(), value.getUTCDate()); } export function createDateRange(input: { startDate?: Date | undefined; endDate?: Date | undefined; durationDays?: number | undefined; }): { startDate: Date; endDate: Date } { const startDate = input.startDate ? normalizeUtcDate(input.startDate) : createUtcDate(new Date().getUTCFullYear(), new Date().getUTCMonth(), new Date().getUTCDate()); const endDate = input.endDate ? normalizeUtcDate(input.endDate) : createUtcDate( startDate.getUTCFullYear(), startDate.getUTCMonth(), startDate.getUTCDate() + Math.max((input.durationDays ?? 21) - 1, 0), ); if (endDate < startDate) { throw new Error("endDate must be on or after startDate."); } return { startDate, endDate }; } export function getBaseDayAvailability( availability: WeekdayAvailability, date: Date, ): number { return getAvailabilityHoursForDate(availability, date); } export function getEffectiveDayAvailability( availability: WeekdayAvailability, date: Date, context: ResourceDailyAvailabilityContext | undefined, ): number { return _calcEffective({ availability, date, context }); } function overlapsDateRange(startDate: Date, endDate: Date, date: Date): boolean { return date >= startDate && date <= endDate; } export function createLocationLabel(input: { countryCode?: string | null; federalState?: string | null; metroCityName?: string | null; }): string { return [ input.countryCode ?? null, input.federalState ?? null, input.metroCityName ?? null, ].filter((value): value is string => Boolean(value && value.trim().length > 0)).join(" / "); } export function calculateAllocatedHoursForDay(input: { bookings: Array<{ startDate: Date; endDate: Date; hoursPerDay: number; status: string; isChargeable?: boolean }>; date: Date; context: ResourceDailyAvailabilityContext | undefined; }): { allocatedHours: number; chargeableHours: number } { const isoDate = toIsoDate(input.date); const dayFraction = Math.max(0, 1 - (input.context?.absenceFractionsByDate.get(isoDate) ?? 0)); return input.bookings.reduce( (acc, booking) => { if (!ACTIVE_STATUSES.has(booking.status) || !overlapsDateRange(booking.startDate, booking.endDate, input.date)) { return acc; } const effectiveHours = booking.hoursPerDay * dayFraction; acc.allocatedHours += effectiveHours; if (booking.isChargeable) { acc.chargeableHours += effectiveHours; } return acc; }, { allocatedHours: 0, chargeableHours: 0 }, ); }