refactor(api): extract timeline project query and read helpers
This commit is contained in:
@@ -14,13 +14,9 @@ vi.mock("../router/timeline-holiday-read.js", async () => {
|
||||
});
|
||||
|
||||
import {
|
||||
buildTimelineBudgetStatusAllocations,
|
||||
buildTimelineBudgetStatusResponse,
|
||||
buildTimelineProjectContextDetailResponse,
|
||||
buildTimelineProjectContextResponse,
|
||||
buildTimelineProjectContextSummary,
|
||||
buildTimelineShiftValidationBookings,
|
||||
buildTimelineShiftPreviewDetailResponse,
|
||||
loadTimelineProjectContextDetailArtifacts,
|
||||
resolveTimelineProjectContextPeriod,
|
||||
} from "../router/timeline-project-context-support.js";
|
||||
@@ -366,116 +362,6 @@ describe("timeline project context support", () => {
|
||||
});
|
||||
});
|
||||
|
||||
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 },
|
||||
});
|
||||
});
|
||||
|
||||
it("maps shift validation bookings and budget status payloads", () => {
|
||||
expect(
|
||||
buildTimelineShiftValidationBookings([
|
||||
{
|
||||
id: "booking_1",
|
||||
resourceId: "resource_1",
|
||||
projectId: "project_1",
|
||||
startDate: new Date("2026-04-01T00:00:00.000Z"),
|
||||
endDate: new Date("2026-04-05T00:00:00.000Z"),
|
||||
hoursPerDay: 8,
|
||||
status: "CONFIRMED",
|
||||
},
|
||||
{
|
||||
id: "booking_2",
|
||||
resourceId: null,
|
||||
projectId: "project_1",
|
||||
startDate: new Date("2026-04-01T00:00:00.000Z"),
|
||||
endDate: new Date("2026-04-05T00:00:00.000Z"),
|
||||
hoursPerDay: 4,
|
||||
status: "PROPOSED",
|
||||
},
|
||||
]),
|
||||
).toEqual([
|
||||
{
|
||||
id: "booking_1",
|
||||
resourceId: "resource_1",
|
||||
projectId: "project_1",
|
||||
startDate: new Date("2026-04-01T00:00:00.000Z"),
|
||||
endDate: new Date("2026-04-05T00:00:00.000Z"),
|
||||
hoursPerDay: 8,
|
||||
status: "CONFIRMED",
|
||||
},
|
||||
]);
|
||||
|
||||
expect(
|
||||
buildTimelineBudgetStatusAllocations([
|
||||
{
|
||||
status: "CONFIRMED",
|
||||
dailyCostCents: 40000,
|
||||
startDate: new Date("2026-04-01T00:00:00.000Z"),
|
||||
endDate: new Date("2026-04-05T00:00:00.000Z"),
|
||||
hoursPerDay: 8,
|
||||
},
|
||||
]),
|
||||
).toEqual([
|
||||
{
|
||||
status: "CONFIRMED",
|
||||
dailyCostCents: 40000,
|
||||
startDate: new Date("2026-04-01T00:00:00.000Z"),
|
||||
endDate: new Date("2026-04-05T00:00:00.000Z"),
|
||||
hoursPerDay: 8,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(
|
||||
buildTimelineBudgetStatusResponse({
|
||||
project: {
|
||||
name: "Project One",
|
||||
shortCode: "PRJ",
|
||||
budgetCents: 120000,
|
||||
},
|
||||
budgetStatus: {
|
||||
withinBudget: true,
|
||||
varianceCents: 2000,
|
||||
},
|
||||
totalAllocations: 3,
|
||||
}),
|
||||
).toEqual({
|
||||
withinBudget: true,
|
||||
varianceCents: 2000,
|
||||
projectName: "Project One",
|
||||
projectCode: "PRJ",
|
||||
totalAllocations: 3,
|
||||
budgetCents: 120000,
|
||||
});
|
||||
});
|
||||
|
||||
it("loads detail artifacts with formatted holiday overlays only when resources exist", async () => {
|
||||
loadTimelineHolidayOverlaysMock.mockResolvedValueOnce([
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
findTimelineProjectOrThrow,
|
||||
timelineBudgetStatusProjectSelect,
|
||||
} from "../router/timeline-project-query-support.js";
|
||||
|
||||
describe("timeline project query support", () => {
|
||||
it("loads a project with the requested select", async () => {
|
||||
const project = { id: "project_1", name: "Project One", budgetCents: 120000 };
|
||||
const findUnique = vi.fn().mockResolvedValue(project);
|
||||
|
||||
await expect(findTimelineProjectOrThrow({
|
||||
project: { findUnique },
|
||||
} as never, {
|
||||
projectId: "project_1",
|
||||
select: timelineBudgetStatusProjectSelect,
|
||||
})).resolves.toEqual(project);
|
||||
|
||||
expect(findUnique).toHaveBeenCalledWith({
|
||||
where: { id: "project_1" },
|
||||
select: timelineBudgetStatusProjectSelect,
|
||||
});
|
||||
});
|
||||
|
||||
it("throws a not found error when the project does not exist", async () => {
|
||||
const findUnique = vi.fn().mockResolvedValue(null);
|
||||
|
||||
await expect(findTimelineProjectOrThrow({
|
||||
project: { findUnique },
|
||||
} as never, {
|
||||
projectId: "missing_project",
|
||||
select: timelineBudgetStatusProjectSelect,
|
||||
})).rejects.toThrowError(new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Project not found",
|
||||
}));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,120 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
buildTimelineBudgetStatusAllocations,
|
||||
buildTimelineBudgetStatusResponse,
|
||||
buildTimelineShiftPreviewDetailResponse,
|
||||
buildTimelineShiftValidationBookings,
|
||||
} from "../router/timeline-project-read-support.js";
|
||||
|
||||
describe("timeline project read support", () => {
|
||||
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 },
|
||||
});
|
||||
});
|
||||
|
||||
it("maps shift validation bookings and budget status payloads", () => {
|
||||
expect(
|
||||
buildTimelineShiftValidationBookings([
|
||||
{
|
||||
id: "booking_1",
|
||||
resourceId: "resource_1",
|
||||
projectId: "project_1",
|
||||
startDate: new Date("2026-04-01T00:00:00.000Z"),
|
||||
endDate: new Date("2026-04-05T00:00:00.000Z"),
|
||||
hoursPerDay: 8,
|
||||
status: "CONFIRMED",
|
||||
},
|
||||
{
|
||||
id: "booking_2",
|
||||
resourceId: null,
|
||||
projectId: "project_1",
|
||||
startDate: new Date("2026-04-01T00:00:00.000Z"),
|
||||
endDate: new Date("2026-04-05T00:00:00.000Z"),
|
||||
hoursPerDay: 4,
|
||||
status: "PROPOSED",
|
||||
},
|
||||
]),
|
||||
).toEqual([
|
||||
{
|
||||
id: "booking_1",
|
||||
resourceId: "resource_1",
|
||||
projectId: "project_1",
|
||||
startDate: new Date("2026-04-01T00:00:00.000Z"),
|
||||
endDate: new Date("2026-04-05T00:00:00.000Z"),
|
||||
hoursPerDay: 8,
|
||||
status: "CONFIRMED",
|
||||
},
|
||||
]);
|
||||
|
||||
expect(
|
||||
buildTimelineBudgetStatusAllocations([
|
||||
{
|
||||
status: "CONFIRMED",
|
||||
dailyCostCents: 40000,
|
||||
startDate: new Date("2026-04-01T00:00:00.000Z"),
|
||||
endDate: new Date("2026-04-05T00:00:00.000Z"),
|
||||
hoursPerDay: 8,
|
||||
},
|
||||
]),
|
||||
).toEqual([
|
||||
{
|
||||
status: "CONFIRMED",
|
||||
dailyCostCents: 40000,
|
||||
startDate: new Date("2026-04-01T00:00:00.000Z"),
|
||||
endDate: new Date("2026-04-05T00:00:00.000Z"),
|
||||
hoursPerDay: 8,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(
|
||||
buildTimelineBudgetStatusResponse({
|
||||
project: {
|
||||
name: "Project One",
|
||||
shortCode: "PRJ",
|
||||
budgetCents: 120000,
|
||||
},
|
||||
budgetStatus: {
|
||||
withinBudget: true,
|
||||
varianceCents: 2000,
|
||||
},
|
||||
totalAllocations: 3,
|
||||
}),
|
||||
).toEqual({
|
||||
withinBudget: true,
|
||||
varianceCents: 2000,
|
||||
projectName: "Project One",
|
||||
projectCode: "PRJ",
|
||||
totalAllocations: 3,
|
||||
budgetCents: 120000,
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user