113 lines
3.3 KiB
TypeScript
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,
|
|
};
|
|
}
|