feat(planning): ship holiday-aware planning and assistant upgrades

This commit is contained in:
2026-03-28 22:49:28 +01:00
parent 2a005794e7
commit 4f48afe7b4
151 changed files with 17738 additions and 1940 deletions
@@ -0,0 +1,102 @@
import { VacationStatus, VacationType } from "@capakraken/db";
import { getResolvedCalendarHolidayStrings, toIsoDate } from "./holiday-availability.js";
type ResourceHolidayContextDb = {
resource: {
findUnique: (args: any) => any;
};
country?: {
findUnique: (args: any) => any;
};
metroCity?: {
findUnique: (args: any) => any;
};
holidayCalendar?: {
findMany: (args: any) => any;
};
vacation: {
findMany: (args: any) => any;
};
};
export type ResourceHolidayContext = {
countryId?: string | null;
countryCode?: string | null;
countryName?: string | null;
federalState?: string | null;
metroCityId?: string | null;
metroCityName?: string | null;
calendarHolidayStrings: string[];
publicHolidayStrings: string[];
};
function clampToDay(value: Date): Date {
const date = new Date(value);
date.setUTCHours(0, 0, 0, 0);
return date;
}
export async function loadResourceHolidayContext(
db: ResourceHolidayContextDb,
resourceId: string,
periodStart: Date,
periodEnd: Date,
): Promise<ResourceHolidayContext> {
const resource = typeof db.resource?.findUnique === "function"
? await db.resource.findUnique({
where: { id: resourceId },
select: {
federalState: true,
countryId: true,
metroCityId: true,
country: { select: { code: true, name: true } },
metroCity: { select: { name: true } },
},
})
: null;
const holidayVacations = typeof db.vacation?.findMany === "function"
? await db.vacation.findMany({
where: {
resourceId,
type: VacationType.PUBLIC_HOLIDAY,
status: VacationStatus.APPROVED,
startDate: { lte: periodEnd },
endDate: { gte: periodStart },
},
select: { startDate: true, endDate: true },
})
: [];
const calendarHolidayStrings = await getResolvedCalendarHolidayStrings(db, {
periodStart,
periodEnd,
countryId: resource?.countryId ?? null,
countryCode: resource?.country?.code ?? null,
federalState: resource?.federalState ?? null,
metroCityId: resource?.metroCityId ?? null,
metroCityName: resource?.metroCity?.name ?? null,
});
const publicHolidayStrings = new Set<string>();
for (const holiday of holidayVacations) {
const cursor = clampToDay(new Date(Math.max(holiday.startDate.getTime(), periodStart.getTime())));
const end = clampToDay(new Date(Math.min(holiday.endDate.getTime(), periodEnd.getTime())));
while (cursor <= end) {
publicHolidayStrings.add(toIsoDate(cursor));
cursor.setUTCDate(cursor.getUTCDate() + 1);
}
}
return {
countryId: resource?.countryId ?? null,
countryCode: resource?.country?.code ?? null,
countryName: resource?.country?.name ?? null,
federalState: resource?.federalState ?? null,
metroCityId: resource?.metroCityId ?? null,
metroCityName: resource?.metroCity?.name ?? null,
calendarHolidayStrings,
publicHolidayStrings: [...publicHolidayStrings].sort(),
};
}