diff --git a/packages/api/src/router/computation-graph-project-snapshot.ts b/packages/api/src/router/computation-graph-project-snapshot.ts new file mode 100644 index 0000000..a16322e --- /dev/null +++ b/packages/api/src/router/computation-graph-project-snapshot.ts @@ -0,0 +1,100 @@ +import type { TRPCContext } from "../trpc.js"; + +export type ProjectGraphInput = { + projectId: string; +}; + +export async function loadProjectGraphSnapshot( + ctx: { db: TRPCContext["db"] }, + input: ProjectGraphInput, +) { + const project = await ctx.db.project.findUniqueOrThrow({ + where: { id: input.projectId }, + select: { + id: true, + name: true, + shortCode: true, + budgetCents: true, + winProbability: true, + startDate: true, + endDate: true, + }, + }); + + const estimate = await ctx.db.estimate.findFirst({ + where: { projectId: input.projectId }, + select: { + id: true, + versions: { + orderBy: { versionNumber: "desc" }, + take: 1, + select: { + id: true, + commercialTerms: true, + demandLines: { + select: { + id: true, + hours: true, + costRateCents: true, + billRateCents: true, + costTotalCents: true, + priceTotalCents: true, + chapter: true, + monthlySpread: true, + scopeItemId: true, + resourceId: true, + }, + }, + scopeItems: { + select: { + id: true, + name: true, + scopeType: true, + frameCount: true, + itemCount: true, + unitMode: true, + }, + }, + resourceSnapshots: { + select: { + id: true, + resourceId: true, + displayName: true, + chapter: true, + lcrCents: true, + ucrCents: true, + location: true, + level: true, + }, + }, + }, + }, + }, + orderBy: { updatedAt: "desc" }, + }); + const latestVersion = estimate?.versions[0]; + + let effortRuleCount = 0; + let experienceRuleCount = 0; + try { + effortRuleCount = await ctx.db.effortRule.count(); + experienceRuleCount = await ctx.db.experienceMultiplierRule.count(); + } catch { + // tables may not exist yet + } + + const projectAllocations = await ctx.db.assignment.findMany({ + where: { projectId: input.projectId }, + select: { status: true, dailyCostCents: true, startDate: true, endDate: true, hoursPerDay: true }, + }); + + return { + project, + latestVersion, + effortRuleCount, + experienceRuleCount, + projectAllocations, + }; +} + +export type ProjectGraphSnapshot = Awaited>; diff --git a/packages/api/src/router/computation-graph-project.ts b/packages/api/src/router/computation-graph-project.ts index 76282fc..1e6c575 100644 --- a/packages/api/src/router/computation-graph-project.ts +++ b/packages/api/src/router/computation-graph-project.ts @@ -4,92 +4,21 @@ import { distributeHoursToWeeks, summarizeEstimateDemandLines, } from "@capakraken/engine"; -import type { TRPCContext } from "../trpc.js"; import { fmtEur } from "../lib/format-utils.js"; +import { loadProjectGraphSnapshot, type ProjectGraphInput } from "./computation-graph-project-snapshot.js"; import { type GraphLink, type GraphNode, fmtNum, l, n } from "./computation-graph-shared.js"; -type ProjectGraphInput = { - projectId: string; -}; - export async function readProjectGraphSnapshot( - ctx: { db: TRPCContext["db"] }, + ctx: Parameters[0], input: ProjectGraphInput, ) { - const project = await ctx.db.project.findUniqueOrThrow({ - where: { id: input.projectId }, - select: { - id: true, - name: true, - shortCode: true, - budgetCents: true, - winProbability: true, - startDate: true, - endDate: true, - }, - }); - - const estimate = await ctx.db.estimate.findFirst({ - where: { projectId: input.projectId }, - select: { - id: true, - versions: { - orderBy: { versionNumber: "desc" }, - take: 1, - select: { - id: true, - commercialTerms: true, - demandLines: { - select: { - id: true, - hours: true, - costRateCents: true, - billRateCents: true, - costTotalCents: true, - priceTotalCents: true, - chapter: true, - monthlySpread: true, - scopeItemId: true, - resourceId: true, - }, - }, - scopeItems: { - select: { - id: true, - name: true, - scopeType: true, - frameCount: true, - itemCount: true, - unitMode: true, - }, - }, - resourceSnapshots: { - select: { - id: true, - resourceId: true, - displayName: true, - chapter: true, - lcrCents: true, - ucrCents: true, - location: true, - level: true, - }, - }, - }, - }, - }, - orderBy: { updatedAt: "desc" }, - }); - const latestVersion = estimate?.versions[0]; - - let effortRuleCount = 0; - let experienceRuleCount = 0; - try { - effortRuleCount = await ctx.db.effortRule.count(); - experienceRuleCount = await ctx.db.experienceMultiplierRule.count(); - } catch { - // tables may not exist yet - } + const { + project, + latestVersion, + effortRuleCount, + experienceRuleCount, + projectAllocations, + } = await loadProjectGraphSnapshot(ctx, input); const nodes: GraphNode[] = []; const links: GraphLink[] = []; @@ -328,11 +257,6 @@ export async function readProjectGraphSnapshot( } } - const projectAllocations = await ctx.db.assignment.findMany({ - where: { projectId: input.projectId }, - select: { status: true, dailyCostCents: true, startDate: true, endDate: true, hoursPerDay: true }, - }); - if (projectAllocations.length > 0) { const budgetStatus = computeBudgetStatus( project.budgetCents,