refactor(api): extract estimate read procedures

This commit is contained in:
2026-03-31 09:16:46 +02:00
parent 71b94d0ad1
commit aa47e4cb79
3 changed files with 409 additions and 217 deletions
@@ -245,6 +245,187 @@ describe("estimate router", () => {
});
});
describe("listVersions", () => {
it("returns estimate versions ordered from newest to oldest", async () => {
const findUnique = vi.fn().mockResolvedValue({
id: "est_1",
name: "Test Estimate",
status: EstimateStatus.DRAFT,
latestVersionNumber: 2,
versions: [
{
id: "ver_2",
versionNumber: 2,
label: "v2",
status: EstimateVersionStatus.SUBMITTED,
notes: null,
lockedAt: new Date("2026-03-14"),
createdAt: new Date("2026-03-14"),
updatedAt: new Date("2026-03-14"),
_count: {
assumptions: 1,
scopeItems: 2,
demandLines: 3,
resourceSnapshots: 4,
exports: 5,
},
},
],
});
const db = { estimate: { findUnique } };
const caller = createControllerCaller(db);
const result = await caller.listVersions({ estimateId: "est_1" });
expect(result.versions).toHaveLength(1);
expect(result.versions[0]?.id).toBe("ver_2");
expect(findUnique).toHaveBeenCalledWith(
expect.objectContaining({
where: { id: "est_1" },
select: expect.objectContaining({
versions: expect.objectContaining({
orderBy: { versionNumber: "desc" },
}),
}),
}),
);
});
it("throws NOT_FOUND when the estimate does not exist", async () => {
const findUnique = vi.fn().mockResolvedValue(null);
const db = { estimate: { findUnique } };
const caller = createControllerCaller(db);
await expect(caller.listVersions({ estimateId: "missing" })).rejects.toThrow(
expect.objectContaining({
code: "NOT_FOUND",
message: "Estimate not found",
}),
);
});
});
describe("getVersionSnapshot", () => {
it("returns aggregate counts and totals for the selected version", async () => {
const findUnique = vi.fn().mockResolvedValue({
id: "est_1",
name: "Test Estimate",
status: EstimateStatus.DRAFT,
baseCurrency: "EUR",
versions: [
{
id: "ver_2",
versionNumber: 2,
label: "Revision 2",
status: EstimateVersionStatus.SUBMITTED,
notes: "Ready",
lockedAt: new Date("2026-03-14"),
createdAt: new Date("2026-03-14"),
updatedAt: new Date("2026-03-15"),
assumptions: [
{ id: "a_1", category: "delivery", key: "onsite", label: "Onsite" },
{ id: "a_2", category: "delivery", key: "travel", label: "Travel" },
{ id: "a_3", category: "commercial", key: "buffer", label: "Buffer" },
],
scopeItems: [
{ id: "s_1", scopeType: "FEATURE", sequenceNo: 1, name: "Alpha" },
{ id: "s_2", scopeType: "FEATURE", sequenceNo: 2, name: "Beta" },
{ id: "s_3", scopeType: "SERVICE", sequenceNo: 3, name: "Gamma" },
],
demandLines: [
{
id: "d_1",
name: "Lead",
chapter: "Delivery",
hours: 10,
costTotalCents: 100_00,
priceTotalCents: 150_00,
currency: "EUR",
},
{
id: "d_2",
name: "QA",
chapter: null,
hours: 5,
costTotalCents: 50_00,
priceTotalCents: 90_00,
currency: "EUR",
},
],
resourceSnapshots: [
{
id: "r_1",
displayName: "Alice",
chapter: "Delivery",
currency: "EUR",
lcrCents: 10_000,
ucrCents: 15_000,
},
],
exports: [
{
id: "x_1",
format: EstimateExportFormat.XLSX,
fileName: "estimate.xlsx",
createdAt: new Date("2026-03-16"),
},
],
},
],
});
const db = { estimate: { findUnique } };
const caller = createControllerCaller(db);
const result = await caller.getVersionSnapshot({ estimateId: "est_1" });
expect(result.counts).toEqual({
assumptions: 3,
scopeItems: 3,
demandLines: 2,
resourceSnapshots: 1,
exports: 1,
});
expect(result.totals).toMatchObject({
hours: 15,
costTotalCents: 15000,
priceTotalCents: 24000,
});
expect(result.chapterBreakdown).toEqual([
expect.objectContaining({ chapter: "Delivery", lineCount: 1, hours: 10 }),
expect.objectContaining({ chapter: "Unassigned", lineCount: 1, hours: 5 }),
]);
expect(result.scopeTypeBreakdown).toEqual([
{ scopeType: "FEATURE", count: 2 },
{ scopeType: "SERVICE", count: 1 },
]);
expect(result.assumptionCategoryBreakdown).toEqual([
{ category: "delivery", count: 2 },
{ category: "commercial", count: 1 },
]);
});
it("throws NOT_FOUND when no matching version can be resolved", async () => {
const findUnique = vi.fn().mockResolvedValue({
id: "est_1",
name: "Test Estimate",
status: EstimateStatus.DRAFT,
baseCurrency: "EUR",
versions: [],
});
const db = { estimate: { findUnique } };
const caller = createControllerCaller(db);
await expect(
caller.getVersionSnapshot({ estimateId: "est_1", versionId: "missing_version" }),
).rejects.toThrow(
expect.objectContaining({
code: "NOT_FOUND",
message: "Estimate version not found",
}),
);
});
});
// ─── create ────────────────────────────────────────────────────────────────
describe("create", () => {