5a4836d292
- 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>
122 lines
4.3 KiB
TypeScript
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();
|
|
});
|
|
}
|