fix(api): reuse cached dashboard detail reads

This commit is contained in:
2026-03-31 23:11:49 +02:00
parent 7908ab6d05
commit 79e0fd82f5
2 changed files with 220 additions and 2 deletions
@@ -28,16 +28,21 @@ vi.mock("../lib/anonymization.js", () => ({
}));
import {
getDashboardBudgetForecast,
getDashboardChargeabilityOverview,
getDashboardDemand,
getDashboardOverview,
getDashboardPeakTimes,
getDashboardProjectHealth,
getDashboardTopValueResources,
} from "@capakraken/application";
import { cacheGet } from "../lib/cache.js";
import { anonymizeResources } from "../lib/anonymization.js";
import {
getDashboardBudgetForecastDetail,
getDashboardChargeabilityOverviewRead,
getDashboardDetail,
getDashboardProjectHealthDetail,
getDashboardStatisticsDetail,
} from "../router/dashboard-procedure-support.js";
@@ -281,4 +286,217 @@ describe("dashboard procedure support", () => {
watchlist: [{ id: "res_2", anonymized: true }],
});
});
it("reuses cached budget forecast rows for detail payloads", async () => {
vi.mocked(cacheGet).mockResolvedValueOnce([
{
projectId: "project_1",
projectName: "Apollo",
shortCode: "APO",
clientId: "client_1",
clientName: "Acme",
budgetCents: 500_000,
spentCents: 320_000,
remainingCents: 180_000,
burnRate: 120_000,
estimatedExhaustionDate: "2026-05-31",
pctUsed: 64,
activeAssignmentCount: 3,
calendarLocations: [
{
countryCode: "DE",
countryName: "Germany",
federalState: "BY",
metroCityName: "Munich",
activeAssignmentCount: 2,
burnRateCents: 80_000,
},
],
derivation: {
periodStart: "2026-03-01",
periodEnd: "2026-03-31",
calendarContextCount: 1,
holidayAwareAssignmentCount: 2,
fallbackAssignmentCount: 1,
baseBurnRateCents: 130_000,
adjustedBurnRateCents: 120_000,
publicHolidayDayEquivalent: 1,
publicHolidayCostDeductionCents: 5_000,
absenceDayEquivalent: 0.5,
absenceCostDeductionCents: 5_000,
},
},
]);
const result = await getDashboardBudgetForecastDetail(createContext());
expect(getDashboardBudgetForecast).not.toHaveBeenCalled();
expect(result).toEqual({
forecasts: [
{
projectId: "project_1",
projectName: "Apollo",
shortCode: "APO",
clientId: "client_1",
clientName: "Acme",
budget: "5.000,00 EUR",
budgetCents: 500_000,
spent: "3.200,00 EUR",
spentCents: 320_000,
remaining: "1.800,00 EUR",
remainingCents: 180_000,
projected: "5.000,00 EUR",
projectedCents: 500_000,
burnRate: "1.200,00 EUR",
burnRateCents: 120_000,
utilization: "64%",
estimatedExhaustionDate: "2026-05-31",
activeAssignmentCount: 3,
calendarLocations: [
{
countryCode: "DE",
countryName: "Germany",
federalState: "BY",
metroCityName: "Munich",
activeAssignmentCount: 2,
burnRateCents: 80_000,
},
],
derivation: {
periodStart: "2026-03-01",
periodEnd: "2026-03-31",
calendarContextCount: 1,
holidayAwareAssignmentCount: 2,
fallbackAssignmentCount: 1,
baseBurnRateCents: 130_000,
adjustedBurnRateCents: 120_000,
publicHolidayDayEquivalent: 1,
publicHolidayCostDeductionCents: 5_000,
absenceDayEquivalent: 0.5,
absenceCostDeductionCents: 5_000,
},
burnStatus: "on_track",
},
],
});
});
it("reuses cached project health rows for detail payloads", async () => {
vi.mocked(cacheGet).mockResolvedValueOnce([
{
id: "project_1",
projectName: "Apollo",
shortCode: "APO",
status: "ACTIVE",
clientId: "client_1",
clientName: "Acme",
budgetHealth: 78,
staffingHealth: 66,
timelineHealth: 55,
compositeScore: 66,
budgetCents: 500_000,
spentCents: 320_000,
remainingBudgetCents: 180_000,
budgetUtilizationPercent: 64,
demandHeadcountTotal: 5,
demandHeadcountFilled: 3,
demandHeadcountOpen: 2,
demandRequirementCount: 2,
plannedEndDate: new Date("2026-06-30T00:00:00.000Z"),
daysUntilEndDate: 107,
timelineStatus: "ON_TRACK" as const,
calendarLocations: [
{
countryCode: "DE",
countryName: "Germany",
federalState: "BY",
metroCityName: "Munich",
assignmentCount: 3,
spentCents: 320_000,
},
],
derivation: {
periodStart: "2026-03-01",
periodEnd: "2026-03-31",
calendarContextCount: 1,
holidayAwareAssignmentCount: 2,
fallbackAssignmentCount: 1,
baseSpentCents: 330_000,
adjustedSpentCents: 320_000,
publicHolidayDayEquivalent: 1,
publicHolidayCostDeductionCents: 5_000,
absenceDayEquivalent: 0.5,
absenceCostDeductionCents: 5_000,
},
},
]);
const result = await getDashboardProjectHealthDetail(createContext());
expect(getDashboardProjectHealth).not.toHaveBeenCalled();
expect(result).toEqual({
projects: [
{
projectId: "project_1",
projectName: "Apollo",
shortCode: "APO",
status: "ACTIVE",
overall: 66,
budget: 78,
staffing: 66,
timeline: 55,
rating: "at_risk",
budgetBasis: {
budgetCents: 500_000,
spentCents: 320_000,
remainingBudgetCents: 180_000,
budgetUtilizationPercent: 64,
calendarLocations: [
{
countryCode: "DE",
countryName: "Germany",
federalState: "BY",
metroCityName: "Munich",
assignmentCount: 3,
spentCents: 320_000,
},
],
derivation: {
periodStart: "2026-03-01",
periodEnd: "2026-03-31",
calendarContextCount: 1,
holidayAwareAssignmentCount: 2,
fallbackAssignmentCount: 1,
baseSpentCents: 330_000,
adjustedSpentCents: 320_000,
publicHolidayDayEquivalent: 1,
publicHolidayCostDeductionCents: 5_000,
absenceDayEquivalent: 0.5,
absenceCostDeductionCents: 5_000,
},
},
staffingBasis: {
demandHeadcountTotal: 5,
demandHeadcountFilled: 3,
demandHeadcountOpen: 2,
demandRequirementCount: 2,
},
timelineBasis: {
plannedEndDate: "2026-06-30T00:00:00.000Z",
daysUntilEndDate: 107,
timelineStatus: "ON_TRACK",
},
context: {
clientId: "client_1",
clientName: "Acme",
},
},
],
summary: {
healthy: 0,
atRisk: 1,
critical: 0,
},
});
});
});