refactor(api): extract timeline project context support

This commit is contained in:
2026-03-31 15:00:01 +02:00
parent b669de54e1
commit e1de9a3a98
3 changed files with 379 additions and 92 deletions
@@ -0,0 +1,181 @@
import { TRPCError } from "@trpc/server";
import { describe, expect, it } from "vitest";
import {
buildTimelineProjectAssignmentConflicts,
buildTimelineProjectContextSummary,
resolveTimelineProjectContextPeriod,
} from "../router/timeline-project-context-support.js";
describe("timeline project context support", () => {
it("derives the detail period from explicit input and validates order", () => {
expect(resolveTimelineProjectContextPeriod({
requestedStartDate: "2026-04-03",
requestedEndDate: "2026-04-12",
projectStartDate: new Date("2026-04-01T00:00:00.000Z"),
projectEndDate: new Date("2026-04-10T00:00:00.000Z"),
})).toEqual({
startDate: new Date("2026-04-03T00:00:00.000Z"),
endDate: new Date("2026-04-12T00:00:00.000Z"),
});
expect(() =>
resolveTimelineProjectContextPeriod({
requestedStartDate: "2026-04-12",
requestedEndDate: "2026-04-03",
}),
).toThrowError(new TRPCError({
code: "BAD_REQUEST",
message: "endDate must be on or after startDate.",
}));
});
it("builds assignment conflict summaries with cross-project overlap counts", () => {
expect(buildTimelineProjectAssignmentConflicts({
projectId: "project_1",
assignments: [
{
id: "assignment_1",
resourceId: "resource_1",
resource: { displayName: "Alice" },
startDate: new Date("2026-04-01T00:00:00.000Z"),
endDate: new Date("2026-04-05T00:00:00.000Z"),
hoursPerDay: 8,
},
{
id: "placeholder_1",
resourceId: null,
resource: null,
startDate: new Date("2026-04-01T00:00:00.000Z"),
endDate: new Date("2026-04-05T00:00:00.000Z"),
hoursPerDay: 4,
},
],
allResourceAllocations: [
{
id: "assignment_1",
resourceId: "resource_1",
projectId: "project_1",
project: { name: "Current", shortCode: "CUR" },
startDate: new Date("2026-04-01T00:00:00.000Z"),
endDate: new Date("2026-04-05T00:00:00.000Z"),
hoursPerDay: 8,
status: "ACTIVE",
},
{
id: "booking_2",
resourceId: "resource_1",
projectId: "project_2",
project: { name: "Other", shortCode: "OTH" },
startDate: new Date("2026-04-04T00:00:00.000Z"),
endDate: new Date("2026-04-07T00:00:00.000Z"),
hoursPerDay: 6,
status: "ACTIVE",
},
{
id: "booking_3",
resourceId: "resource_1",
projectId: "project_1",
project: { name: "Current", shortCode: "CUR" },
startDate: new Date("2026-04-02T00:00:00.000Z"),
endDate: new Date("2026-04-03T00:00:00.000Z"),
hoursPerDay: 2,
status: "PROPOSED",
},
],
})).toEqual([
{
assignmentId: "assignment_1",
resourceId: "resource_1",
resourceName: "Alice",
startDate: "2026-04-01",
endDate: "2026-04-05",
hoursPerDay: 8,
overlapCount: 2,
crossProjectOverlapCount: 1,
overlaps: [
{
id: "booking_2",
projectId: "project_2",
projectName: "Other",
projectShortCode: "OTH",
startDate: "2026-04-04",
endDate: "2026-04-07",
hoursPerDay: 6,
status: "ACTIVE",
sameProject: false,
},
{
id: "booking_3",
projectId: "project_1",
projectName: "Current",
projectShortCode: "CUR",
startDate: "2026-04-02",
endDate: "2026-04-03",
hoursPerDay: 2,
status: "PROPOSED",
sameProject: true,
},
],
},
]);
});
it("combines timeline counts with holiday overlay summary data", () => {
expect(buildTimelineProjectContextSummary({
allocations: [{ projectId: "project_1", resourceId: "resource_1" }],
demands: [{ projectId: "project_1" }],
assignments: [{ projectId: "project_1", resourceId: "resource_1" }],
resourceIds: ["resource_1", "resource_2"],
allResourceAllocations: [{ id: "booking_1" }, { id: "booking_2" }],
assignmentConflicts: [
{ crossProjectOverlapCount: 0 },
{ crossProjectOverlapCount: 2 },
],
holidayOverlays: [
{
id: "overlay_1",
resourceId: "resource_1",
startDate: "2026-04-03",
endDate: "2026-04-03",
note: "Holiday",
scope: "COUNTRY",
calendarName: "DE",
sourceType: "CALENDAR",
countryCode: "DE",
countryName: "Germany",
federalState: null,
metroCityName: null,
},
{
id: "overlay_2",
resourceId: "resource_2",
startDate: "2026-04-04",
endDate: "2026-04-04",
note: "Holiday",
scope: "CITY",
calendarName: "Berlin",
sourceType: "CALENDAR",
countryCode: "DE",
countryName: "Germany",
federalState: "BE",
metroCityName: "Berlin",
},
],
})).toEqual({
allocationCount: 1,
demandCount: 1,
assignmentCount: 1,
projectCount: 1,
resourceCount: 1,
resourceIds: 2,
allResourceAllocationCount: 2,
conflictedAssignmentCount: 1,
overlayCount: 2,
holidayResourceCount: 2,
byScope: [
{ scope: "CITY", count: 1 },
{ scope: "COUNTRY", count: 1 },
],
});
});
});