refactor(api): extract timeline project procedure support
This commit is contained in:
@@ -0,0 +1,262 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("../lib/anonymization.js", () => ({
|
||||
getAnonymizationDirectory: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../router/timeline-project-load-support.js", () => ({
|
||||
loadTimelineProjectContext: vi.fn(),
|
||||
previewTimelineProjectShift: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../router/timeline-project-context-support.js", () => ({
|
||||
loadTimelineProjectContextDetailArtifacts: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../router/timeline-project-context-response-support.js", () => ({
|
||||
buildTimelineProjectContextResponse: vi.fn(),
|
||||
buildTimelineProjectContextDetailResponse: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@capakraken/application", () => ({
|
||||
listAssignmentBookings: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@capakraken/engine", () => ({
|
||||
computeBudgetStatus: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../router/timeline-project-query-support.js", () => ({
|
||||
findTimelineProjectOrThrow: vi.fn(),
|
||||
timelineBudgetStatusProjectSelect: { id: true },
|
||||
timelineShiftPreviewProjectSelect: { id: true },
|
||||
}));
|
||||
|
||||
vi.mock("../router/timeline-project-read-support.js", () => ({
|
||||
buildTimelineBudgetStatusAllocations: vi.fn(),
|
||||
buildTimelineBudgetStatusResponse: vi.fn(),
|
||||
buildTimelineShiftPreviewDetailResponse: vi.fn(),
|
||||
}));
|
||||
|
||||
import { listAssignmentBookings } from "@capakraken/application";
|
||||
import { computeBudgetStatus } from "@capakraken/engine";
|
||||
import { getAnonymizationDirectory } from "../lib/anonymization.js";
|
||||
import {
|
||||
loadTimelineProjectContextDetailArtifacts,
|
||||
} from "../router/timeline-project-context-support.js";
|
||||
import {
|
||||
buildTimelineProjectContextDetailResponse,
|
||||
buildTimelineProjectContextResponse,
|
||||
} from "../router/timeline-project-context-response-support.js";
|
||||
import {
|
||||
loadTimelineProjectContext,
|
||||
previewTimelineProjectShift,
|
||||
} from "../router/timeline-project-load-support.js";
|
||||
import {
|
||||
buildTimelineBudgetStatusAllocations,
|
||||
buildTimelineBudgetStatusResponse,
|
||||
buildTimelineShiftPreviewDetailResponse,
|
||||
} from "../router/timeline-project-read-support.js";
|
||||
import {
|
||||
findTimelineProjectOrThrow,
|
||||
timelineBudgetStatusProjectSelect,
|
||||
timelineShiftPreviewProjectSelect,
|
||||
} from "../router/timeline-project-query-support.js";
|
||||
import {
|
||||
readTimelineProjectBudgetStatusResponse,
|
||||
readTimelineProjectContextDetailResponse,
|
||||
readTimelineProjectContextResponse,
|
||||
readTimelineProjectShiftPreviewDetail,
|
||||
} from "../router/timeline-project-procedure-support.js";
|
||||
|
||||
const getAnonymizationDirectoryMock = vi.mocked(getAnonymizationDirectory);
|
||||
const loadTimelineProjectContextMock = vi.mocked(loadTimelineProjectContext);
|
||||
const previewTimelineProjectShiftMock = vi.mocked(previewTimelineProjectShift);
|
||||
const loadTimelineProjectContextDetailArtifactsMock = vi.mocked(loadTimelineProjectContextDetailArtifacts);
|
||||
const buildTimelineProjectContextResponseMock = vi.mocked(buildTimelineProjectContextResponse);
|
||||
const buildTimelineProjectContextDetailResponseMock = vi.mocked(buildTimelineProjectContextDetailResponse);
|
||||
const listAssignmentBookingsMock = vi.mocked(listAssignmentBookings);
|
||||
const computeBudgetStatusMock = vi.mocked(computeBudgetStatus);
|
||||
const findTimelineProjectOrThrowMock = vi.mocked(findTimelineProjectOrThrow);
|
||||
const buildTimelineBudgetStatusAllocationsMock = vi.mocked(buildTimelineBudgetStatusAllocations);
|
||||
const buildTimelineBudgetStatusResponseMock = vi.mocked(buildTimelineBudgetStatusResponse);
|
||||
const buildTimelineShiftPreviewDetailResponseMock = vi.mocked(buildTimelineShiftPreviewDetailResponse);
|
||||
|
||||
describe("timeline project procedure support", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("builds project context responses from loaded context and anonymization directory", async () => {
|
||||
loadTimelineProjectContextMock.mockResolvedValueOnce({
|
||||
project: { id: "project_1" },
|
||||
allocations: [{ id: "allocation_1" }],
|
||||
demands: [{ id: "demand_1" }],
|
||||
assignments: [{ id: "assignment_1" }],
|
||||
allResourceAllocations: [{ id: "booking_1" }],
|
||||
resourceIds: ["resource_1"],
|
||||
} as never);
|
||||
getAnonymizationDirectoryMock.mockResolvedValueOnce({ enabled: true } as never);
|
||||
buildTimelineProjectContextResponseMock.mockReturnValueOnce({ ok: true } as never);
|
||||
|
||||
await expect(readTimelineProjectContextResponse({} as never, "project_1")).resolves.toEqual({
|
||||
ok: true,
|
||||
});
|
||||
|
||||
expect(loadTimelineProjectContextMock).toHaveBeenCalledWith({}, "project_1");
|
||||
expect(getAnonymizationDirectoryMock).toHaveBeenCalledWith({});
|
||||
expect(buildTimelineProjectContextResponseMock).toHaveBeenCalledWith({
|
||||
project: { id: "project_1" },
|
||||
allocations: [{ id: "allocation_1" }],
|
||||
demands: [{ id: "demand_1" }],
|
||||
assignments: [{ id: "assignment_1" }],
|
||||
allResourceAllocations: [{ id: "booking_1" }],
|
||||
resourceIds: ["resource_1"],
|
||||
directory: { enabled: true },
|
||||
});
|
||||
});
|
||||
|
||||
it("builds project context detail responses from loaded artifacts", async () => {
|
||||
loadTimelineProjectContextMock.mockResolvedValueOnce({
|
||||
project: {
|
||||
id: "project_1",
|
||||
startDate: new Date("2026-04-01T00:00:00.000Z"),
|
||||
endDate: new Date("2026-04-10T00:00:00.000Z"),
|
||||
},
|
||||
allocations: [{ id: "allocation_1" }],
|
||||
demands: [{ id: "demand_1", startDate: new Date("2026-04-01T00:00:00.000Z") }],
|
||||
assignments: [{ id: "assignment_1", startDate: new Date("2026-04-02T00:00:00.000Z") }],
|
||||
allResourceAllocations: [{ id: "booking_1" }],
|
||||
resourceIds: ["resource_1"],
|
||||
} as never);
|
||||
getAnonymizationDirectoryMock.mockResolvedValueOnce({ enabled: true } as never);
|
||||
loadTimelineProjectContextDetailArtifactsMock.mockResolvedValueOnce({
|
||||
period: {
|
||||
startDate: new Date("2026-04-01T00:00:00.000Z"),
|
||||
endDate: new Date("2026-04-10T00:00:00.000Z"),
|
||||
},
|
||||
holidayOverlays: [{ id: "overlay_1" }],
|
||||
assignmentConflicts: [{ assignmentId: "assignment_1" }],
|
||||
} as never);
|
||||
buildTimelineProjectContextDetailResponseMock.mockReturnValueOnce({ detail: true } as never);
|
||||
|
||||
await expect(
|
||||
readTimelineProjectContextDetailResponse({} as never, {
|
||||
projectId: "project_1",
|
||||
startDate: "2026-04-03",
|
||||
endDate: "2026-04-12",
|
||||
durationDays: 10,
|
||||
}),
|
||||
).resolves.toEqual({ detail: true });
|
||||
|
||||
expect(loadTimelineProjectContextDetailArtifactsMock).toHaveBeenCalledWith({}, {
|
||||
projectId: "project_1",
|
||||
requestedStartDate: "2026-04-03",
|
||||
requestedEndDate: "2026-04-12",
|
||||
durationDays: 10,
|
||||
projectStartDate: new Date("2026-04-01T00:00:00.000Z"),
|
||||
projectEndDate: new Date("2026-04-10T00:00:00.000Z"),
|
||||
firstAssignmentStartDate: new Date("2026-04-02T00:00:00.000Z"),
|
||||
firstDemandStartDate: new Date("2026-04-01T00:00:00.000Z"),
|
||||
assignments: [{ id: "assignment_1", startDate: new Date("2026-04-02T00:00:00.000Z") }],
|
||||
allResourceAllocations: [{ id: "booking_1" }],
|
||||
resourceIds: ["resource_1"],
|
||||
});
|
||||
expect(buildTimelineProjectContextDetailResponseMock).toHaveBeenCalledWith({
|
||||
project: {
|
||||
id: "project_1",
|
||||
startDate: new Date("2026-04-01T00:00:00.000Z"),
|
||||
endDate: new Date("2026-04-10T00:00:00.000Z"),
|
||||
},
|
||||
period: {
|
||||
startDate: new Date("2026-04-01T00:00:00.000Z"),
|
||||
endDate: new Date("2026-04-10T00:00:00.000Z"),
|
||||
},
|
||||
allocations: [{ id: "allocation_1" }],
|
||||
demands: [{ id: "demand_1", startDate: new Date("2026-04-01T00:00:00.000Z") }],
|
||||
assignments: [{ id: "assignment_1", startDate: new Date("2026-04-02T00:00:00.000Z") }],
|
||||
allResourceAllocations: [{ id: "booking_1" }],
|
||||
resourceIds: ["resource_1"],
|
||||
assignmentConflicts: [{ assignmentId: "assignment_1" }],
|
||||
holidayOverlays: [{ id: "overlay_1" }],
|
||||
directory: { enabled: true },
|
||||
});
|
||||
});
|
||||
|
||||
it("builds shift preview detail responses", async () => {
|
||||
findTimelineProjectOrThrowMock.mockResolvedValueOnce({ id: "project_1" } as never);
|
||||
previewTimelineProjectShiftMock.mockResolvedValueOnce({ valid: true } as never);
|
||||
buildTimelineShiftPreviewDetailResponseMock.mockReturnValueOnce({ preview: true } as never);
|
||||
|
||||
await expect(
|
||||
readTimelineProjectShiftPreviewDetail({} as never, {
|
||||
projectId: "project_1",
|
||||
newStartDate: new Date("2026-04-03T00:00:00.000Z"),
|
||||
newEndDate: new Date("2026-04-12T00:00:00.000Z"),
|
||||
}),
|
||||
).resolves.toEqual({ preview: true });
|
||||
|
||||
expect(findTimelineProjectOrThrowMock).toHaveBeenCalledWith({}, {
|
||||
projectId: "project_1",
|
||||
select: timelineShiftPreviewProjectSelect,
|
||||
});
|
||||
expect(previewTimelineProjectShiftMock).toHaveBeenCalledWith({}, {
|
||||
projectId: "project_1",
|
||||
newStartDate: new Date("2026-04-03T00:00:00.000Z"),
|
||||
newEndDate: new Date("2026-04-12T00:00:00.000Z"),
|
||||
});
|
||||
expect(buildTimelineShiftPreviewDetailResponseMock).toHaveBeenCalledWith({
|
||||
project: { id: "project_1" },
|
||||
requestedShift: {
|
||||
newStartDate: new Date("2026-04-03T00:00:00.000Z"),
|
||||
newEndDate: new Date("2026-04-12T00:00:00.000Z"),
|
||||
},
|
||||
preview: { valid: true },
|
||||
});
|
||||
});
|
||||
|
||||
it("builds budget status responses from project bookings", async () => {
|
||||
findTimelineProjectOrThrowMock.mockResolvedValueOnce({
|
||||
id: "project_1",
|
||||
budgetCents: 100_000,
|
||||
winProbability: 80,
|
||||
startDate: new Date("2026-04-01T00:00:00.000Z"),
|
||||
endDate: new Date("2026-04-10T00:00:00.000Z"),
|
||||
} as never);
|
||||
listAssignmentBookingsMock.mockResolvedValueOnce([{ id: "booking_1" }] as never);
|
||||
buildTimelineBudgetStatusAllocationsMock.mockReturnValueOnce([{ id: "allocation_1" }] as never);
|
||||
computeBudgetStatusMock.mockReturnValueOnce({ withinBudget: true } as never);
|
||||
buildTimelineBudgetStatusResponseMock.mockReturnValueOnce({ budget: true } as never);
|
||||
|
||||
await expect(readTimelineProjectBudgetStatusResponse({} as never, "project_1")).resolves.toEqual({
|
||||
budget: true,
|
||||
});
|
||||
|
||||
expect(findTimelineProjectOrThrowMock).toHaveBeenCalledWith({}, {
|
||||
projectId: "project_1",
|
||||
select: timelineBudgetStatusProjectSelect,
|
||||
});
|
||||
expect(listAssignmentBookingsMock).toHaveBeenCalledWith({}, {
|
||||
projectIds: ["project_1"],
|
||||
});
|
||||
expect(buildTimelineBudgetStatusAllocationsMock).toHaveBeenCalledWith([{ id: "booking_1" }]);
|
||||
expect(computeBudgetStatusMock).toHaveBeenCalledWith(
|
||||
100_000,
|
||||
80,
|
||||
[{ id: "allocation_1" }],
|
||||
new Date("2026-04-01T00:00:00.000Z"),
|
||||
new Date("2026-04-10T00:00:00.000Z"),
|
||||
);
|
||||
expect(buildTimelineBudgetStatusResponseMock).toHaveBeenCalledWith({
|
||||
project: {
|
||||
id: "project_1",
|
||||
budgetCents: 100_000,
|
||||
winProbability: 80,
|
||||
startDate: new Date("2026-04-01T00:00:00.000Z"),
|
||||
endDate: new Date("2026-04-10T00:00:00.000Z"),
|
||||
},
|
||||
budgetStatus: { withinBudget: true },
|
||||
totalAllocations: 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,150 @@
|
||||
import { listAssignmentBookings } from "@capakraken/application";
|
||||
import { computeBudgetStatus } from "@capakraken/engine";
|
||||
import { getAnonymizationDirectory } from "../lib/anonymization.js";
|
||||
import {
|
||||
loadTimelineProjectContextDetailArtifacts,
|
||||
} from "./timeline-project-context-support.js";
|
||||
import {
|
||||
buildTimelineProjectContextDetailResponse,
|
||||
buildTimelineProjectContextResponse,
|
||||
} from "./timeline-project-context-response-support.js";
|
||||
import {
|
||||
loadTimelineProjectContext,
|
||||
previewTimelineProjectShift,
|
||||
} from "./timeline-project-load-support.js";
|
||||
import {
|
||||
buildTimelineBudgetStatusAllocations,
|
||||
buildTimelineBudgetStatusResponse,
|
||||
buildTimelineShiftPreviewDetailResponse,
|
||||
} from "./timeline-project-read-support.js";
|
||||
import {
|
||||
findTimelineProjectOrThrow,
|
||||
timelineBudgetStatusProjectSelect,
|
||||
timelineShiftPreviewProjectSelect,
|
||||
} from "./timeline-project-query-support.js";
|
||||
|
||||
type TimelineProjectProcedureDb =
|
||||
& Parameters<typeof getAnonymizationDirectory>[0]
|
||||
& Parameters<typeof loadTimelineProjectContext>[0]
|
||||
& Parameters<typeof loadTimelineProjectContextDetailArtifacts>[0]
|
||||
& Parameters<typeof findTimelineProjectOrThrow>[0]
|
||||
& Parameters<typeof listAssignmentBookings>[0];
|
||||
|
||||
export async function readTimelineProjectContextResponse(
|
||||
db: TimelineProjectProcedureDb,
|
||||
projectId: string,
|
||||
) {
|
||||
const {
|
||||
project,
|
||||
allocations,
|
||||
demands,
|
||||
assignments,
|
||||
allResourceAllocations,
|
||||
resourceIds,
|
||||
} = await loadTimelineProjectContext(db, projectId);
|
||||
const directory = await getAnonymizationDirectory(db);
|
||||
|
||||
return buildTimelineProjectContextResponse({
|
||||
project,
|
||||
allocations,
|
||||
demands,
|
||||
assignments,
|
||||
allResourceAllocations,
|
||||
resourceIds,
|
||||
directory,
|
||||
});
|
||||
}
|
||||
|
||||
export async function readTimelineProjectContextDetailResponse(
|
||||
db: TimelineProjectProcedureDb,
|
||||
input: {
|
||||
projectId: string;
|
||||
startDate?: string | undefined;
|
||||
endDate?: string | undefined;
|
||||
durationDays?: number | undefined;
|
||||
},
|
||||
) {
|
||||
const projectContext = await loadTimelineProjectContext(db, input.projectId);
|
||||
const directory = await getAnonymizationDirectory(db);
|
||||
const { period, holidayOverlays, assignmentConflicts } =
|
||||
await loadTimelineProjectContextDetailArtifacts(db, {
|
||||
projectId: input.projectId,
|
||||
requestedStartDate: input.startDate,
|
||||
requestedEndDate: input.endDate,
|
||||
durationDays: input.durationDays,
|
||||
projectStartDate: projectContext.project.startDate,
|
||||
projectEndDate: projectContext.project.endDate,
|
||||
firstAssignmentStartDate: projectContext.assignments[0]?.startDate,
|
||||
firstDemandStartDate: projectContext.demands[0]?.startDate,
|
||||
assignments: projectContext.assignments,
|
||||
allResourceAllocations: projectContext.allResourceAllocations,
|
||||
resourceIds: projectContext.resourceIds,
|
||||
});
|
||||
|
||||
return buildTimelineProjectContextDetailResponse({
|
||||
project: projectContext.project,
|
||||
period,
|
||||
allocations: projectContext.allocations,
|
||||
demands: projectContext.demands,
|
||||
assignments: projectContext.assignments,
|
||||
allResourceAllocations: projectContext.allResourceAllocations,
|
||||
resourceIds: projectContext.resourceIds,
|
||||
assignmentConflicts,
|
||||
holidayOverlays,
|
||||
directory,
|
||||
});
|
||||
}
|
||||
|
||||
export async function readTimelineProjectShiftPreviewDetail(
|
||||
db: TimelineProjectProcedureDb,
|
||||
input: {
|
||||
projectId: string;
|
||||
newStartDate: Date;
|
||||
newEndDate: Date;
|
||||
},
|
||||
) {
|
||||
const [project, preview] = await Promise.all([
|
||||
findTimelineProjectOrThrow(db, {
|
||||
projectId: input.projectId,
|
||||
select: timelineShiftPreviewProjectSelect,
|
||||
}),
|
||||
previewTimelineProjectShift(db, input),
|
||||
]);
|
||||
|
||||
return buildTimelineShiftPreviewDetailResponse({
|
||||
project,
|
||||
requestedShift: {
|
||||
newStartDate: input.newStartDate,
|
||||
newEndDate: input.newEndDate,
|
||||
},
|
||||
preview,
|
||||
});
|
||||
}
|
||||
|
||||
export async function readTimelineProjectBudgetStatusResponse(
|
||||
db: TimelineProjectProcedureDb,
|
||||
projectId: string,
|
||||
) {
|
||||
const project = await findTimelineProjectOrThrow(db, {
|
||||
projectId,
|
||||
select: timelineBudgetStatusProjectSelect,
|
||||
});
|
||||
|
||||
const bookings = await listAssignmentBookings(db, {
|
||||
projectIds: [project.id],
|
||||
});
|
||||
|
||||
const budgetStatus = computeBudgetStatus(
|
||||
project.budgetCents,
|
||||
project.winProbability,
|
||||
buildTimelineBudgetStatusAllocations(bookings),
|
||||
project.startDate,
|
||||
project.endDate,
|
||||
);
|
||||
|
||||
return buildTimelineBudgetStatusResponse({
|
||||
project,
|
||||
budgetStatus,
|
||||
totalAllocations: bookings.length,
|
||||
});
|
||||
}
|
||||
@@ -1,55 +1,18 @@
|
||||
import { listAssignmentBookings } from "@capakraken/application";
|
||||
import { computeBudgetStatus } from "@capakraken/engine";
|
||||
import { ShiftProjectSchema } from "@capakraken/shared";
|
||||
import { z } from "zod";
|
||||
import { getAnonymizationDirectory } from "../lib/anonymization.js";
|
||||
import { controllerProcedure } from "../trpc.js";
|
||||
import { previewTimelineProjectShift } from "./timeline-project-load-support.js";
|
||||
import {
|
||||
loadTimelineProjectContextDetailArtifacts,
|
||||
} from "./timeline-project-context-support.js";
|
||||
import {
|
||||
loadTimelineProjectContext,
|
||||
previewTimelineProjectShift,
|
||||
} from "./timeline-project-load-support.js";
|
||||
import {
|
||||
buildTimelineProjectContextDetailResponse,
|
||||
buildTimelineProjectContextResponse,
|
||||
} from "./timeline-project-context-response-support.js";
|
||||
import {
|
||||
buildTimelineBudgetStatusAllocations,
|
||||
buildTimelineBudgetStatusResponse,
|
||||
buildTimelineShiftPreviewDetailResponse,
|
||||
} from "./timeline-project-read-support.js";
|
||||
import {
|
||||
findTimelineProjectOrThrow,
|
||||
timelineBudgetStatusProjectSelect,
|
||||
timelineShiftPreviewProjectSelect,
|
||||
} from "./timeline-project-query-support.js";
|
||||
readTimelineProjectBudgetStatusResponse,
|
||||
readTimelineProjectContextDetailResponse,
|
||||
readTimelineProjectContextResponse,
|
||||
readTimelineProjectShiftPreviewDetail,
|
||||
} from "./timeline-project-procedure-support.js";
|
||||
|
||||
export const timelineProjectReadProcedures = {
|
||||
getProjectContext: controllerProcedure
|
||||
.input(z.object({ projectId: z.string() }))
|
||||
.query(async ({ ctx, input }) => {
|
||||
const {
|
||||
project,
|
||||
allocations,
|
||||
demands,
|
||||
assignments,
|
||||
allResourceAllocations,
|
||||
resourceIds,
|
||||
} = await loadTimelineProjectContext(ctx.db, input.projectId);
|
||||
const directory = await getAnonymizationDirectory(ctx.db);
|
||||
|
||||
return buildTimelineProjectContextResponse({
|
||||
project,
|
||||
allocations,
|
||||
demands,
|
||||
assignments,
|
||||
allResourceAllocations,
|
||||
resourceIds,
|
||||
directory,
|
||||
});
|
||||
}),
|
||||
.query(async ({ ctx, input }) => readTimelineProjectContextResponse(ctx.db, input.projectId)),
|
||||
|
||||
getProjectContextDetail: controllerProcedure
|
||||
.input(
|
||||
@@ -60,37 +23,7 @@ export const timelineProjectReadProcedures = {
|
||||
durationDays: z.number().int().min(1).max(366).optional(),
|
||||
}),
|
||||
)
|
||||
.query(async ({ ctx, input }) => {
|
||||
const projectContext = await loadTimelineProjectContext(ctx.db, input.projectId);
|
||||
const directory = await getAnonymizationDirectory(ctx.db);
|
||||
const { period, holidayOverlays, assignmentConflicts } =
|
||||
await loadTimelineProjectContextDetailArtifacts(ctx.db, {
|
||||
projectId: input.projectId,
|
||||
requestedStartDate: input.startDate,
|
||||
requestedEndDate: input.endDate,
|
||||
durationDays: input.durationDays,
|
||||
projectStartDate: projectContext.project.startDate,
|
||||
projectEndDate: projectContext.project.endDate,
|
||||
firstAssignmentStartDate: projectContext.assignments[0]?.startDate,
|
||||
firstDemandStartDate: projectContext.demands[0]?.startDate,
|
||||
assignments: projectContext.assignments,
|
||||
allResourceAllocations: projectContext.allResourceAllocations,
|
||||
resourceIds: projectContext.resourceIds,
|
||||
});
|
||||
|
||||
return buildTimelineProjectContextDetailResponse({
|
||||
project: projectContext.project,
|
||||
period,
|
||||
allocations: projectContext.allocations,
|
||||
demands: projectContext.demands,
|
||||
assignments: projectContext.assignments,
|
||||
allResourceAllocations: projectContext.allResourceAllocations,
|
||||
resourceIds: projectContext.resourceIds,
|
||||
assignmentConflicts,
|
||||
holidayOverlays,
|
||||
directory,
|
||||
});
|
||||
}),
|
||||
.query(async ({ ctx, input }) => readTimelineProjectContextDetailResponse(ctx.db, input)),
|
||||
|
||||
previewShift: controllerProcedure
|
||||
.input(ShiftProjectSchema)
|
||||
@@ -98,49 +31,9 @@ export const timelineProjectReadProcedures = {
|
||||
|
||||
getShiftPreviewDetail: controllerProcedure
|
||||
.input(ShiftProjectSchema)
|
||||
.query(async ({ ctx, input }) => {
|
||||
const [project, preview] = await Promise.all([
|
||||
findTimelineProjectOrThrow(ctx.db, {
|
||||
projectId: input.projectId,
|
||||
select: timelineShiftPreviewProjectSelect,
|
||||
}),
|
||||
previewTimelineProjectShift(ctx.db, input),
|
||||
]);
|
||||
|
||||
return buildTimelineShiftPreviewDetailResponse({
|
||||
project,
|
||||
requestedShift: {
|
||||
newStartDate: input.newStartDate,
|
||||
newEndDate: input.newEndDate,
|
||||
},
|
||||
preview,
|
||||
});
|
||||
}),
|
||||
.query(async ({ ctx, input }) => readTimelineProjectShiftPreviewDetail(ctx.db, input)),
|
||||
|
||||
getBudgetStatus: controllerProcedure
|
||||
.input(z.object({ projectId: z.string() }))
|
||||
.query(async ({ ctx, input }) => {
|
||||
const project = await findTimelineProjectOrThrow(ctx.db, {
|
||||
projectId: input.projectId,
|
||||
select: timelineBudgetStatusProjectSelect,
|
||||
});
|
||||
|
||||
const bookings = await listAssignmentBookings(ctx.db, {
|
||||
projectIds: [project.id],
|
||||
});
|
||||
|
||||
const budgetStatus = computeBudgetStatus(
|
||||
project.budgetCents,
|
||||
project.winProbability,
|
||||
buildTimelineBudgetStatusAllocations(bookings),
|
||||
project.startDate,
|
||||
project.endDate,
|
||||
);
|
||||
|
||||
return buildTimelineBudgetStatusResponse({
|
||||
project,
|
||||
budgetStatus,
|
||||
totalAllocations: bookings.length,
|
||||
});
|
||||
}),
|
||||
.query(async ({ ctx, input }) => readTimelineProjectBudgetStatusResponse(ctx.db, input.projectId)),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user