import { anonymizeResource, getAnonymizationDirectory } from "../lib/anonymization.js"; import { fmtDate, type TimelineEntriesFilters } from "./timeline-read-shared.js"; type TimelineAnonymizationDirectory = Awaited>; export function anonymizeResourceOnEntry( entry: T, directory: TimelineAnonymizationDirectory, ): T { if (!entry.resource) { return entry; } return { ...entry, resource: anonymizeResource(entry.resource, directory), }; } export function buildTimelineEntriesViewResponse< TReadModel extends { allocations: Array<{ resource?: { id: string } | null }>; assignments: Array<{ resource?: { id: string } | null }>; }, >( readModel: TReadModel, directory: TimelineAnonymizationDirectory, ): TReadModel { return { ...readModel, allocations: readModel.allocations.map((allocation) => anonymizeResourceOnEntry(allocation, directory), ), assignments: readModel.assignments.map((assignment) => anonymizeResourceOnEntry(assignment, directory), ), }; } export function summarizeTimelineEntries(readModel: { allocations: Array<{ projectId: string | null; resourceId: string | null }>; demands: Array<{ projectId: string | null }>; assignments: Array<{ projectId: string | null; resourceId: string | null }>; }) { const projectIds = new Set(); const resourceIds = new Set(); for (const entry of [...readModel.allocations, ...readModel.demands, ...readModel.assignments]) { if (entry.projectId) { projectIds.add(entry.projectId); } } for (const assignment of [...readModel.allocations, ...readModel.assignments]) { if (assignment.resourceId) { resourceIds.add(assignment.resourceId); } } return { allocationCount: readModel.allocations.length, demandCount: readModel.demands.length, assignmentCount: readModel.assignments.length, projectCount: projectIds.size, resourceCount: resourceIds.size, }; } export function buildTimelineEntriesDetailResponse< TReadModel extends { allocations: Array<{ projectId: string | null; resourceId: string | null; resource?: { id: string } | null; }>; demands: Array<{ projectId: string | null }>; assignments: Array<{ projectId: string | null; resourceId: string | null; resource?: { id: string } | null; }>; }, THolidayOverlay, >(input: { period: { startDate: Date; endDate: Date }; filters: Omit; readModel: TReadModel; directory: TimelineAnonymizationDirectory; holidayOverlays: THolidayOverlay[]; holidaySummary: { overlayCount: number; holidayResourceCount: number; byScope: Array<{ scope: string; count: number }>; }; }) { const view = buildTimelineEntriesViewResponse(input.readModel, input.directory); return { period: { startDate: fmtDate(input.period.startDate), endDate: fmtDate(input.period.endDate), }, filters: input.filters, summary: { ...summarizeTimelineEntries(input.readModel), ...input.holidaySummary, }, allocations: view.allocations, demands: input.readModel.demands, assignments: view.assignments, holidayOverlays: input.holidayOverlays, }; }