refactor(api): extract timeline holiday load support

This commit is contained in:
2026-03-31 17:40:44 +02:00
parent 7a64fe5ce5
commit a3fb95ae07
3 changed files with 291 additions and 97 deletions
@@ -1,27 +1,26 @@
import { buildSplitAllocationReadModel } from "@capakraken/application";
import { Prisma, VacationType } from "@capakraken/db";
import { z } from "zod";
import { asHolidayResolverDb, getResolvedCalendarHolidays } from "../lib/holiday-availability.js";
import { controllerProcedure, protectedProcedure } from "../trpc.js";
import {
buildSelfServiceTimelineInput,
createTimelineDateRange,
createTimelineFilters,
fmtDate,
loadTimelineEntriesReadModel,
TimelineEntriesDbClient,
TimelineEntriesFilters,
TimelineWindowFiltersSchema,
} from "./timeline-read-shared.js";
import {
loadTimelineHolidayOverlays,
} from "./timeline-holiday-load-support.js";
import {
buildTimelineHolidayOverlayDetailResponse,
buildTimelineHolidayResourceIds,
buildTimelineHolidayResourceWhere,
formatTimelineHolidayOverlays,
summarizeTimelineHolidayOverlays,
type TimelineHolidayOverlayRecord,
} from "./timeline-holiday-support.js";
export {
loadTimelineHolidayOverlays,
loadTimelineHolidayOverlaysForReadModel,
} from "./timeline-holiday-load-support.js";
export function formatHolidayOverlays(
overlays: TimelineHolidayOverlayRecord[],
) {
@@ -34,94 +33,6 @@ export function summarizeHolidayOverlays(
return summarizeTimelineHolidayOverlays(overlays);
}
export async function loadTimelineHolidayOverlays(
db: TimelineEntriesDbClient,
input: TimelineEntriesFilters,
) {
const readModel = await loadTimelineEntriesReadModel(db, input);
return loadTimelineHolidayOverlaysForReadModel(db, input, readModel);
}
export async function loadTimelineHolidayOverlaysForReadModel(
db: TimelineEntriesDbClient,
input: TimelineEntriesFilters,
readModel: ReturnType<typeof buildSplitAllocationReadModel>,
) {
const resourceWhere = buildTimelineHolidayResourceWhere({
chapters: input.chapters,
eids: input.eids,
countryCodes: input.countryCodes,
});
const matchingResources = resourceWhere
? await db.resource.findMany({
where: resourceWhere,
select: { id: true },
})
: [];
const resourceIds = buildTimelineHolidayResourceIds({
assignmentResourceIds: readModel.assignments.map((assignment) => assignment.resourceId),
requestedResourceIds: input.resourceIds,
matchingFilterResourceIds: matchingResources.map((resource) => resource.id),
});
if (resourceIds.length === 0) {
return [];
}
const resources = await db.resource.findMany({
where: { id: { in: resourceIds } },
select: {
id: true,
countryId: true,
federalState: true,
metroCityId: true,
country: { select: { code: true, name: true } },
metroCity: { select: { name: true } },
},
});
const overlays = await Promise.all(
resources.map(async (resource) => {
const holidays = await getResolvedCalendarHolidays(asHolidayResolverDb(db), {
periodStart: input.startDate,
periodEnd: input.endDate,
countryId: resource.countryId,
countryCode: resource.country?.code ?? null,
federalState: resource.federalState,
metroCityId: resource.metroCityId,
metroCityName: resource.metroCity?.name ?? null,
});
return holidays.map((holiday) => {
const holidayDate = new Date(`${holiday.date}T00:00:00.000Z`);
return {
id: `calendar-holiday:${resource.id}:${holiday.date}`,
resourceId: resource.id,
type: VacationType.PUBLIC_HOLIDAY,
status: "APPROVED" as const,
startDate: holidayDate,
endDate: holidayDate,
note: holiday.name,
scope: holiday.scope,
calendarName: holiday.calendarName,
sourceType: holiday.sourceType,
countryCode: resource.country?.code ?? null,
countryName: resource.country?.name ?? null,
federalState: resource.federalState ?? null,
metroCityName: resource.metroCity?.name ?? null,
};
});
}),
);
return overlays.flat().sort((left, right) => {
if (left.resourceId !== right.resourceId) {
return left.resourceId.localeCompare(right.resourceId);
}
return left.startDate.getTime() - right.startDate.getTime();
});
}
export const timelineHolidayReadProcedures = {
getHolidayOverlays: controllerProcedure
.input(TimelineWindowFiltersSchema)