import { TRPCError } from "@trpc/server"; import { describe, expect, it } from "vitest"; import { buildTimelineProjectAssignmentConflicts, buildTimelineProjectContextDetailResponse, buildTimelineProjectContextResponse, buildTimelineProjectContextSummary, buildTimelineShiftPreviewDetailResponse, 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 }, ], }); }); it("builds anonymized project context responses and detail summaries", () => { const directory = { config: { enabled: true, mode: "alias", domain: "example.test" }, byResourceId: new Map([ [ "resource_1", { displayName: "Anon Alice", eid: "ANON-001", email: "anon-alice@example.test", }, ], ]), byAliasEid: new Map([["anon-001", "resource_1"]]), }; expect(buildTimelineProjectContextResponse({ project: { id: "project_1" }, allocations: [ { id: "allocation_1", projectId: "project_1", resourceId: "resource_1", resource: { id: "resource_1", displayName: "Alice", eid: "E-001" }, }, ], demands: [{ id: "demand_1", projectId: "project_1" }], assignments: [ { id: "assignment_1", projectId: "project_1", resourceId: "resource_1", resource: { id: "resource_1", displayName: "Alice", eid: "E-001" }, }, ], allResourceAllocations: [ { id: "booking_1", resourceId: "resource_1", resource: { id: "resource_1", displayName: "Alice", eid: "E-001" }, }, ], resourceIds: ["resource_1"], directory: directory as never, })).toEqual({ project: { id: "project_1" }, allocations: [ { id: "allocation_1", projectId: "project_1", resourceId: "resource_1", resource: { id: "resource_1", displayName: "Anon Alice", eid: "ANON-001" }, }, ], demands: [{ id: "demand_1", projectId: "project_1" }], assignments: [ { id: "assignment_1", projectId: "project_1", resourceId: "resource_1", resource: { id: "resource_1", displayName: "Anon Alice", eid: "ANON-001" }, }, ], allResourceAllocations: [ { id: "booking_1", resourceId: "resource_1", resource: { id: "resource_1", displayName: "Anon Alice", eid: "ANON-001" }, }, ], resourceIds: ["resource_1"], }); expect(buildTimelineProjectContextDetailResponse({ project: { id: "project_1" }, period: { startDate: new Date("2026-04-01T00:00:00.000Z"), endDate: new Date("2026-04-05T00:00:00.000Z"), }, allocations: [ { id: "allocation_1", projectId: "project_1", resourceId: "resource_1", resource: { id: "resource_1", displayName: "Alice", eid: "E-001" }, }, ], demands: [{ id: "demand_1", projectId: "project_1" }], assignments: [ { id: "assignment_1", projectId: "project_1", resourceId: "resource_1", resource: { id: "resource_1", displayName: "Alice", eid: "E-001" }, }, ], allResourceAllocations: [ { id: "booking_1", resourceId: "resource_1", resource: { id: "resource_1", displayName: "Alice", eid: "E-001" }, }, ], resourceIds: ["resource_1"], assignmentConflicts: [{ crossProjectOverlapCount: 1, assignmentId: "assignment_1" }], holidayOverlays: [], directory: directory as never, })).toEqual({ project: { id: "project_1" }, period: { startDate: "2026-04-01", endDate: "2026-04-05", }, summary: { allocationCount: 1, demandCount: 1, assignmentCount: 1, projectCount: 1, resourceCount: 1, resourceIds: 1, allResourceAllocationCount: 1, conflictedAssignmentCount: 1, overlayCount: 0, holidayResourceCount: 0, byScope: [], }, allocations: [ { id: "allocation_1", projectId: "project_1", resourceId: "resource_1", resource: { id: "resource_1", displayName: "Anon Alice", eid: "ANON-001" }, }, ], demands: [{ id: "demand_1", projectId: "project_1" }], assignments: [ { id: "assignment_1", projectId: "project_1", resourceId: "resource_1", resource: { id: "resource_1", displayName: "Anon Alice", eid: "ANON-001" }, }, ], allResourceAllocations: [ { id: "booking_1", resourceId: "resource_1", resource: { id: "resource_1", displayName: "Anon Alice", eid: "ANON-001" }, }, ], assignmentConflicts: [{ crossProjectOverlapCount: 1, assignmentId: "assignment_1" }], holidayOverlays: [], resourceIds: ["resource_1"], }); }); it("builds shift preview detail payloads with formatted dates", () => { expect(buildTimelineShiftPreviewDetailResponse({ project: { id: "project_1", name: "Project One", shortCode: "PRJ", status: "ACTIVE", responsiblePerson: "Alice", startDate: new Date("2026-04-01T00:00:00.000Z"), endDate: new Date("2026-04-10T00:00:00.000Z"), }, requestedShift: { newStartDate: new Date("2026-04-03T00:00:00.000Z"), newEndDate: new Date("2026-04-12T00:00:00.000Z"), }, preview: { valid: true }, })).toEqual({ project: { id: "project_1", name: "Project One", shortCode: "PRJ", status: "ACTIVE", responsiblePerson: "Alice", startDate: "2026-04-01", endDate: "2026-04-10", }, requestedShift: { newStartDate: "2026-04-03", newEndDate: "2026-04-12", }, preview: { valid: true }, }); }); });