refactor(api): extract timeline entry response support
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import { buildSplitAllocationReadModel } from "@capakraken/application";
|
||||
import type { PrismaClient } from "@capakraken/db";
|
||||
import { z } from "zod";
|
||||
import { anonymizeResource, getAnonymizationDirectory } from "../lib/anonymization.js";
|
||||
import { loadTimelineEntryRecords } from "./timeline-entry-query-support.js";
|
||||
export {
|
||||
buildSelfServiceTimelineInput,
|
||||
@@ -43,7 +42,6 @@ export const TimelineWindowFiltersSchema = z.object({
|
||||
});
|
||||
|
||||
type TimelineWindowFiltersInput = z.infer<typeof TimelineWindowFiltersSchema>;
|
||||
type TimelineAnonymizationDirectory = Awaited<ReturnType<typeof getAnonymizationDirectory>>;
|
||||
|
||||
export function getAssignmentResourceIds(
|
||||
readModel: ReturnType<typeof buildSplitAllocationReadModel>,
|
||||
@@ -71,89 +69,6 @@ export function createEmptyTimelineEntriesView() {
|
||||
});
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
export function rangesOverlap(
|
||||
leftStart: Date,
|
||||
leftEnd: Date,
|
||||
@@ -167,19 +82,6 @@ export function toDate(value: Date | string): Date {
|
||||
return value instanceof Date ? value : new Date(value);
|
||||
}
|
||||
|
||||
export function anonymizeResourceOnEntry<T extends { resource?: { id: string } | null }>(
|
||||
entry: T,
|
||||
directory: Awaited<ReturnType<typeof getAnonymizationDirectory>>,
|
||||
): T {
|
||||
if (!entry.resource) {
|
||||
return entry;
|
||||
}
|
||||
return {
|
||||
...entry,
|
||||
resource: anonymizeResource(entry.resource, directory),
|
||||
};
|
||||
}
|
||||
|
||||
export async function loadTimelineEntriesReadModel(
|
||||
db: TimelineEntriesDbClient,
|
||||
input: TimelineEntriesFilters,
|
||||
|
||||
Reference in New Issue
Block a user