Files
CapaKraken/packages/api/src/router/timeline-entry-response-support.ts
T

113 lines
3.3 KiB
TypeScript

import { anonymizeResource, getAnonymizationDirectory } from "../lib/anonymization.js";
import { fmtDate, type TimelineEntriesFilters } from "./timeline-read-shared.js";
type TimelineAnonymizationDirectory = Awaited<ReturnType<typeof getAnonymizationDirectory>>;
export function anonymizeResourceOnEntry<T extends { resource?: { id: string } | null }>(
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<string>();
const resourceIds = new Set<string>();
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<TimelineEntriesFilters, "startDate" | "endDate">;
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,
};
}