refactor(api): extract timeline project read builders

This commit is contained in:
2026-03-31 15:09:19 +02:00
parent b1ada431e1
commit 4e9e452b94
3 changed files with 332 additions and 49 deletions
@@ -1,6 +1,7 @@
import { TRPCError } from "@trpc/server";
import { summarizeHolidayOverlays, type formatHolidayOverlays } from "./timeline-holiday-read.js";
import {
anonymizeResourceOnEntry,
createTimelineDateRange,
fmtDate,
rangesOverlap,
@@ -8,6 +9,8 @@ import {
toDate,
} from "./timeline-read-shared.js";
type TimelineAnonymizationDirectory = Parameters<typeof anonymizeResourceOnEntry>[1];
export interface ResolveTimelineProjectContextPeriodInput {
requestedStartDate?: string | undefined;
requestedEndDate?: string | undefined;
@@ -158,3 +161,117 @@ export function buildTimelineProjectContextSummary(input: {
...summarizeHolidayOverlays(input.holidayOverlays),
};
}
export function buildTimelineProjectContextResponse<
TProject,
TAllocation extends { resource?: { id: string } | null },
TDemand,
TAssignment extends { resource?: { id: string } | null },
TBooking extends { resource?: { id: string } | null },
>(input: {
project: TProject;
allocations: TAllocation[];
demands: TDemand[];
assignments: TAssignment[];
allResourceAllocations: TBooking[];
resourceIds: string[];
directory: TimelineAnonymizationDirectory;
}) {
return {
project: input.project,
allocations: input.allocations.map((allocation) =>
anonymizeResourceOnEntry(allocation, input.directory),
),
demands: input.demands,
assignments: input.assignments.map((assignment) =>
anonymizeResourceOnEntry(assignment, input.directory),
),
allResourceAllocations: input.allResourceAllocations.map((allocation) =>
anonymizeResourceOnEntry(allocation, input.directory),
),
resourceIds: input.resourceIds,
};
}
export function buildTimelineProjectContextDetailResponse<
TProject,
TAllocation extends { projectId: string | null; resourceId: string | null; resource?: { id: string } | null },
TDemand extends { projectId: string | null },
TAssignment extends { projectId: string | null; resourceId: string | null; resource?: { id: string } | null },
TBooking extends { resource?: { id: string } | null },
TConflict extends { crossProjectOverlapCount: number },
>(input: {
project: TProject;
period: { startDate: Date; endDate: Date };
allocations: TAllocation[];
demands: TDemand[];
assignments: TAssignment[];
allResourceAllocations: TBooking[];
resourceIds: string[];
assignmentConflicts: TConflict[];
holidayOverlays: ReturnType<typeof formatHolidayOverlays>;
directory: TimelineAnonymizationDirectory;
}) {
const base = buildTimelineProjectContextResponse({
project: input.project,
allocations: input.allocations,
demands: input.demands,
assignments: input.assignments,
allResourceAllocations: input.allResourceAllocations,
resourceIds: input.resourceIds,
directory: input.directory,
});
return {
...base,
period: {
startDate: fmtDate(input.period.startDate),
endDate: fmtDate(input.period.endDate),
},
summary: buildTimelineProjectContextSummary({
allocations: input.allocations,
demands: input.demands,
assignments: input.assignments,
resourceIds: input.resourceIds,
allResourceAllocations: input.allResourceAllocations,
assignmentConflicts: input.assignmentConflicts,
holidayOverlays: input.holidayOverlays,
}),
assignmentConflicts: input.assignmentConflicts,
holidayOverlays: input.holidayOverlays,
};
}
export function buildTimelineShiftPreviewDetailResponse<TPreview>(input: {
project: {
id: string;
name: string;
shortCode: string | null;
status: string;
responsiblePerson: string | null;
startDate: Date;
endDate: Date;
};
requestedShift: {
newStartDate: Date;
newEndDate: Date;
};
preview: TPreview;
}) {
return {
project: {
id: input.project.id,
name: input.project.name,
shortCode: input.project.shortCode,
status: input.project.status,
responsiblePerson: input.project.responsiblePerson,
startDate: fmtDate(input.project.startDate),
endDate: fmtDate(input.project.endDate),
},
requestedShift: {
newStartDate: fmtDate(input.requestedShift.newStartDate),
newEndDate: fmtDate(input.requestedShift.newEndDate),
},
preview: input.preview,
};
}