Files
CapaKraken/packages/api/src/router/timeline-holiday-load-support.ts
T
Hartmut 5a4836d292 perf(api): eliminate 3 N+1 query patterns
- timeline-holiday-load-support: deduplicate getResolvedCalendarHolidays
  by location key so resources sharing the same country/state/city resolve
  holidays once instead of per-resource
- rate-card-lookup: add lookupRatesBatch that loads rate card lines once
  and scores locally per demand line, replacing per-line DB round-trips
  in estimate-demand-lines autoFillDemandLineRates
- config-readmodels: include _count in utilization-category list query
  instead of calling getById per category for project counts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 22:59:45 +02:00

122 lines
4.3 KiB
TypeScript

import { buildSplitAllocationReadModel } from "@capakraken/application";
import { VacationType } from "@capakraken/db";
import { asHolidayResolverDb, getResolvedCalendarHolidays } from "../lib/holiday-availability.js";
import {
buildTimelineHolidayResourceIds,
buildTimelineHolidayResourceWhere,
} from "./timeline-holiday-support.js";
import {
loadTimelineEntriesReadModel,
TimelineEntriesDbClient,
TimelineEntriesFilters,
} from "./timeline-read-shared.js";
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 } },
},
});
// Group resources by location key to deduplicate holiday resolution.
// Resources sharing the same (countryId, federalState, metroCityId) get
// identical holidays, so we resolve once per location instead of once per resource.
const locationGroups = new Map<
string,
{ locationResource: (typeof resources)[0]; resourceIds: string[] }
>();
for (const resource of resources) {
const key = `${resource.countryId ?? ""}:${resource.federalState ?? ""}:${resource.metroCityId ?? ""}`;
const existing = locationGroups.get(key);
if (existing) {
existing.resourceIds.push(resource.id);
} else {
locationGroups.set(key, { locationResource: resource, resourceIds: [resource.id] });
}
}
const resolverDb = asHolidayResolverDb(db);
const overlays = await Promise.all(
[...locationGroups.values()].map(async ({ locationResource, resourceIds }) => {
const holidays = await getResolvedCalendarHolidays(resolverDb, {
periodStart: input.startDate,
periodEnd: input.endDate,
countryId: locationResource.countryId,
countryCode: locationResource.country?.code ?? null,
federalState: locationResource.federalState,
metroCityId: locationResource.metroCityId,
metroCityName: locationResource.metroCity?.name ?? null,
});
return resourceIds.flatMap((resourceId) => {
const resource = resources.find((r) => r.id === resourceId)!;
return holidays.map((holiday) => {
const holidayDate = new Date(`${holiday.date}T00:00:00.000Z`);
return {
id: `calendar-holiday:${resourceId}:${holiday.date}`,
resourceId,
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();
});
}