Files
CapaKraken/packages/api/src/__tests__/dashboard-router.test.ts
T

1106 lines
35 KiB
TypeScript

import { SystemRole } from "@capakraken/shared";
import { beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("@capakraken/application", async (importOriginal) => {
const actual = await importOriginal<typeof import("@capakraken/application")>();
return {
...actual,
getDashboardOverview: vi.fn(),
getDashboardPeakTimes: vi.fn(),
getDashboardDemand: vi.fn(),
getDashboardTopValueResources: vi.fn(),
getDashboardChargeabilityOverview: vi.fn(),
getDashboardBudgetForecast: vi.fn(),
getDashboardSkillGapSummary: vi.fn(),
getDashboardProjectHealth: vi.fn(),
};
});
vi.mock("../lib/cache.js", () => ({
cacheGet: vi.fn().mockResolvedValue(null),
cacheSet: vi.fn().mockResolvedValue(undefined),
}));
vi.mock("../lib/anonymization.js", () => ({
anonymizeResources: vi.fn((resources: unknown[]) => resources),
getAnonymizationDirectory: vi.fn().mockResolvedValue(null),
}));
import {
getDashboardOverview,
getDashboardPeakTimes,
getDashboardDemand,
getDashboardTopValueResources,
getDashboardChargeabilityOverview,
getDashboardBudgetForecast,
getDashboardProjectHealth,
getDashboardSkillGapSummary,
} from "@capakraken/application";
import { dashboardRouter } from "../router/dashboard.js";
import { createCallerFactory } from "../trpc.js";
const createCaller = createCallerFactory(dashboardRouter);
function createProtectedCaller(db: Record<string, unknown>) {
return createCaller({
session: {
user: { email: "user@example.com", name: "User", image: null },
expires: "2099-01-01T00:00:00.000Z",
},
db: db as never,
dbUser: {
id: "user_1",
systemRole: SystemRole.USER,
permissionOverrides: null,
},
});
}
function createControllerCaller(db: Record<string, unknown>) {
return createCaller({
session: {
user: { email: "controller@example.com", name: "Controller", image: null },
expires: "2099-01-01T00:00:00.000Z",
},
db: db as never,
dbUser: {
id: "user_2",
systemRole: SystemRole.CONTROLLER,
permissionOverrides: null,
},
});
}
function createUnauthenticatedCaller(db: Record<string, unknown>) {
return createCaller({
session: null,
db: db as never,
dbUser: null,
});
}
describe("dashboard router", () => {
beforeEach(() => {
vi.clearAllMocks();
});
// ─── getOverview ──────────────────────────────────────────────────────────
describe("getOverview", () => {
it("returns expected shape with resource and project counts", async () => {
const overview = {
totalResources: 42,
activeResources: 38,
totalProjects: 15,
activeProjects: 10,
draftProjects: 3,
completedProjects: 2,
totalBudgetCents: 5_000_000_00,
avgWinProbability: 78,
};
vi.mocked(getDashboardOverview).mockResolvedValue(overview);
const caller = createControllerCaller({});
const result = await caller.getOverview();
expect(result).toMatchObject({
totalResources: 42,
activeResources: 38,
totalProjects: 15,
activeProjects: 10,
});
expect(getDashboardOverview).toHaveBeenCalledTimes(1);
});
it("rejects unauthenticated users", async () => {
const caller = createUnauthenticatedCaller({});
await expect(caller.getOverview()).rejects.toThrow("Authentication required");
});
});
describe("getStatisticsDetail", () => {
it("returns assistant-friendly statistics derived from the canonical dashboard overview", async () => {
vi.mocked(getDashboardOverview).mockResolvedValue({
totalResources: 12,
activeResources: 10,
inactiveResources: 2,
totalProjects: 7,
activeProjects: 4,
inactiveProjects: 3,
totalAllocations: 21,
activeAllocations: 18,
cancelledAllocations: 3,
approvedVacations: 6,
totalEstimates: 9,
budgetSummary: {
totalBudgetCents: 1_234_56,
totalCostCents: 654_32,
avgUtilizationPercent: 53,
},
budgetBasis: {
remainingBudgetCents: 58_024,
budgetedProjects: 5,
unbudgetedProjects: 2,
trackedAssignmentCount: 18,
windowStart: null,
windowEnd: null,
},
projectsByStatus: [
{ status: "ACTIVE", count: 4 },
{ status: "DRAFT", count: 2 },
{ status: "DONE", count: 1 },
],
chapterUtilization: [
{ chapter: "CGI", resourceCount: 5, avgChargeabilityTarget: 78 },
{ chapter: "Compositing", resourceCount: 3, avgChargeabilityTarget: 74 },
{ chapter: "Unassigned", resourceCount: 2, avgChargeabilityTarget: 0 },
],
recentActivity: [],
});
const caller = createControllerCaller({});
const result = await caller.getStatisticsDetail();
expect(getDashboardOverview).toHaveBeenCalledTimes(1);
expect(result).toEqual({
activeResources: 10,
totalProjects: 7,
activeProjects: 4,
totalAllocations: 21,
approvedVacations: 6,
totalEstimates: 9,
totalBudget: "1.234,56 EUR",
projectsByStatus: {
ACTIVE: 4,
DRAFT: 2,
DONE: 1,
},
topChapters: [
{ chapter: "CGI", count: 5 },
{ chapter: "Compositing", count: 3 },
{ chapter: "Unassigned", count: 2 },
],
});
});
});
// ─── getPeakTimes ─────────────────────────────────────────────────────────
describe("getPeakTimes", () => {
it("returns array of time periods", async () => {
const peakData = [
{ period: "2026-03", totalHours: 1200, entries: 15 },
{ period: "2026-04", totalHours: 1400, entries: 18 },
];
vi.mocked(getDashboardPeakTimes).mockResolvedValue(peakData);
const caller = createControllerCaller({});
const result = await caller.getPeakTimes({
startDate: "2026-03-01T00:00:00.000Z",
endDate: "2026-06-30T00:00:00.000Z",
granularity: "month",
groupBy: "project",
});
expect(result).toHaveLength(2);
expect(result[0]).toHaveProperty("period", "2026-03");
expect(getDashboardPeakTimes).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
granularity: "month",
groupBy: "project",
}),
);
});
it("passes week granularity to application layer", async () => {
vi.mocked(getDashboardPeakTimes).mockResolvedValue([]);
const caller = createControllerCaller({});
await caller.getPeakTimes({
startDate: "2026-03-01T00:00:00.000Z",
endDate: "2026-03-31T00:00:00.000Z",
granularity: "week",
groupBy: "chapter",
});
expect(getDashboardPeakTimes).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
granularity: "week",
groupBy: "chapter",
}),
);
});
});
// ─── getDemand ────────────────────────────────────────────────────────────
describe("getDemand", () => {
it("returns demand entries grouped by project", async () => {
const demandData = [
{ groupKey: "Project Alpha", totalHours: 500, headcount: 3 },
{ groupKey: "Project Beta", totalHours: 300, headcount: 2 },
];
vi.mocked(getDashboardDemand).mockResolvedValue(demandData);
const caller = createControllerCaller({});
const result = await caller.getDemand({
startDate: "2026-01-01T00:00:00.000Z",
endDate: "2026-12-31T00:00:00.000Z",
groupBy: "project",
});
expect(result).toHaveLength(2);
expect(getDashboardDemand).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({ groupBy: "project" }),
);
});
it("supports grouping by chapter", async () => {
vi.mocked(getDashboardDemand).mockResolvedValue([]);
const caller = createControllerCaller({});
await caller.getDemand({
startDate: "2026-06-01T00:00:00.000Z",
endDate: "2026-06-30T00:00:00.000Z",
groupBy: "chapter",
});
expect(getDashboardDemand).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({ groupBy: "chapter" }),
);
});
});
describe("getProjectHealthDetail", () => {
it("returns assistant-friendly health detail derived from the canonical dashboard read model", async () => {
vi.mocked(getDashboardProjectHealth).mockResolvedValue([
{
id: "project_critical",
projectName: "Critical Project",
shortCode: "CRIT",
status: "ACTIVE",
clientId: "client_1",
clientName: "Acme",
budgetHealth: 25,
staffingHealth: 40,
timelineHealth: 30,
compositeScore: 35,
budgetCents: 100_000,
spentCents: 82_000,
remainingBudgetCents: 18_000,
budgetUtilizationPercent: 82,
demandHeadcountTotal: 5,
demandHeadcountFilled: 2,
demandHeadcountOpen: 3,
demandRequirementCount: 2,
plannedEndDate: new Date("2026-06-30T00:00:00.000Z"),
daysUntilEndDate: 12,
timelineStatus: "DUE_SOON",
calendarLocations: [
{
countryCode: "DE",
countryName: "Germany",
federalState: "BY",
metroCityName: "Munich",
assignmentCount: 2,
spentCents: 52_000,
},
],
derivation: {
periodStart: "2026-06-01",
periodEnd: "2026-06-30",
calendarContextCount: 1,
holidayAwareAssignmentCount: 2,
fallbackAssignmentCount: 0,
baseSpentCents: 90_000,
adjustedSpentCents: 82_000,
publicHolidayDayEquivalent: 1,
publicHolidayCostDeductionCents: 5_000,
absenceDayEquivalent: 0.6,
absenceCostDeductionCents: 3_000,
},
},
{
id: "project_healthy",
projectName: "Healthy Project",
shortCode: "HLTH",
status: "ACTIVE",
clientId: "client_1",
clientName: "Acme",
budgetHealth: 90,
staffingHealth: 92,
timelineHealth: 86,
compositeScore: 89,
budgetCents: 200_000,
spentCents: 20_000,
remainingBudgetCents: 180_000,
budgetUtilizationPercent: 10,
demandHeadcountTotal: 4,
demandHeadcountFilled: 4,
demandHeadcountOpen: 0,
demandRequirementCount: 1,
plannedEndDate: new Date("2026-09-15T00:00:00.000Z"),
daysUntilEndDate: 89,
timelineStatus: "ON_TRACK",
calendarLocations: [],
},
]);
const caller = createControllerCaller({});
const result = await caller.getProjectHealthDetail();
expect(getDashboardProjectHealth).toHaveBeenCalledTimes(1);
expect(result).toEqual({
projects: [
{
projectId: "project_critical",
projectName: "Critical Project",
shortCode: "CRIT",
status: "ACTIVE",
overall: 35,
budget: 25,
staffing: 40,
timeline: 30,
rating: "critical",
budgetBasis: {
budgetCents: 100_000,
spentCents: 82_000,
remainingBudgetCents: 18_000,
budgetUtilizationPercent: 82,
calendarLocations: [
{
countryCode: "DE",
countryName: "Germany",
federalState: "BY",
metroCityName: "Munich",
assignmentCount: 2,
spentCents: 52_000,
},
],
derivation: {
periodStart: "2026-06-01",
periodEnd: "2026-06-30",
calendarContextCount: 1,
holidayAwareAssignmentCount: 2,
fallbackAssignmentCount: 0,
baseSpentCents: 90_000,
adjustedSpentCents: 82_000,
publicHolidayDayEquivalent: 1,
publicHolidayCostDeductionCents: 5_000,
absenceDayEquivalent: 0.6,
absenceCostDeductionCents: 3_000,
},
},
staffingBasis: {
demandHeadcountTotal: 5,
demandHeadcountFilled: 2,
demandHeadcountOpen: 3,
demandRequirementCount: 2,
},
timelineBasis: {
plannedEndDate: "2026-06-30T00:00:00.000Z",
daysUntilEndDate: 12,
timelineStatus: "DUE_SOON",
},
context: {
clientId: "client_1",
clientName: "Acme",
},
},
{
projectId: "project_healthy",
projectName: "Healthy Project",
shortCode: "HLTH",
status: "ACTIVE",
overall: 89,
budget: 90,
staffing: 92,
timeline: 86,
rating: "healthy",
budgetBasis: {
budgetCents: 200_000,
spentCents: 20_000,
remainingBudgetCents: 180_000,
budgetUtilizationPercent: 10,
calendarLocations: [],
derivation: null,
},
staffingBasis: {
demandHeadcountTotal: 4,
demandHeadcountFilled: 4,
demandHeadcountOpen: 0,
demandRequirementCount: 1,
},
timelineBasis: {
plannedEndDate: "2026-09-15T00:00:00.000Z",
daysUntilEndDate: 89,
timelineStatus: "ON_TRACK",
},
context: {
clientId: "client_1",
clientName: "Acme",
},
},
],
summary: {
healthy: 1,
atRisk: 0,
critical: 1,
},
});
});
});
// ─── getTopValueResources ─────────────────────────────────────────────────
describe("getTopValueResources", () => {
it("returns sorted resources with default limit", async () => {
const resources = [
{
id: "res_1",
eid: "alice",
displayName: "Alice",
chapter: "Delivery",
valueScore: 95,
valueScoreBreakdown: {
skillDepth: 88,
skillBreadth: 79,
costEfficiency: 84,
chargeability: 76,
experience: 90,
total: 95,
},
valueScoreUpdatedAt: new Date("2026-03-01T00:00:00.000Z"),
lcrCents: 12_300,
countryCode: "DE",
countryName: "Germany",
federalState: "BY",
metroCityName: "Munich",
},
{
id: "res_2",
eid: "bob",
displayName: "Bob",
chapter: "Data",
valueScore: 88,
valueScoreBreakdown: {
skillDepth: 80,
skillBreadth: 77,
costEfficiency: 91,
chargeability: 72,
experience: 81,
total: 88,
},
valueScoreUpdatedAt: new Date("2026-03-02T00:00:00.000Z"),
lcrCents: 10_800,
countryCode: "US",
countryName: "United States",
federalState: "CA",
metroCityName: "San Francisco",
},
];
vi.mocked(getDashboardTopValueResources).mockResolvedValue(resources);
const caller = createControllerCaller({});
const result = await caller.getTopValueResources({ limit: 10 });
expect(result).toEqual(resources);
expect(getDashboardTopValueResources).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({ limit: 10 }),
);
});
it("respects custom limit", async () => {
vi.mocked(getDashboardTopValueResources).mockResolvedValue([]);
const caller = createControllerCaller({});
await caller.getTopValueResources({ limit: 5 });
expect(getDashboardTopValueResources).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({ limit: 5 }),
);
});
});
// ─── getChargeabilityOverview ─────────────────────────────────────────────
describe("getChargeabilityOverview", () => {
it("returns chargeability data with top and watchlist arrays", async () => {
const overview = {
avgChargeability: 72,
top: [{ id: "res_1", displayName: "Alice", chargeability: 95 }],
watchlist: [{ id: "res_3", displayName: "Carol", chargeability: 30 }],
};
vi.mocked(getDashboardChargeabilityOverview).mockResolvedValue(overview);
const caller = createControllerCaller({});
const result = await caller.getChargeabilityOverview({
includeProposed: false,
topN: 10,
watchlistThreshold: 15,
});
expect(result).toHaveProperty("top");
expect(result).toHaveProperty("watchlist");
expect(result.top).toHaveLength(1);
expect(result.watchlist).toHaveLength(1);
});
it("passes includeProposed flag to application layer", async () => {
vi.mocked(getDashboardChargeabilityOverview).mockResolvedValue({
avgChargeability: 60,
top: [],
watchlist: [],
});
const caller = createControllerCaller({});
await caller.getChargeabilityOverview({
includeProposed: true,
topN: 5,
watchlistThreshold: 20,
});
expect(getDashboardChargeabilityOverview).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
includeProposed: true,
topN: 5,
watchlistThreshold: 20,
}),
);
});
it("requires controller role — blocks USER", async () => {
const caller = createProtectedCaller({});
await expect(
caller.getChargeabilityOverview({
includeProposed: false,
topN: 10,
watchlistThreshold: 15,
}),
).rejects.toThrow(
expect.objectContaining({ code: "FORBIDDEN" }),
);
});
});
describe("getBudgetForecast", () => {
it("returns budget forecast rows with calendar location context", async () => {
vi.mocked(getDashboardBudgetForecast).mockResolvedValue([
{
projectId: "project_1",
projectName: "Alpha",
shortCode: "ALPHA",
clientId: "client_1",
clientName: "Client One",
budgetCents: 100_000,
spentCents: 40_000,
remainingCents: 60_000,
burnRate: 10_000,
estimatedExhaustionDate: "2026-06-30",
pctUsed: 40,
activeAssignmentCount: 2,
derivation: {
periodStart: "2026-03-01",
periodEnd: "2026-03-31",
calendarContextCount: 1,
holidayAwareAssignmentCount: 2,
fallbackAssignmentCount: 0,
baseBurnRateCents: 12_000,
adjustedBurnRateCents: 10_000,
publicHolidayDayEquivalent: 1,
publicHolidayCostDeductionCents: 1_000,
absenceDayEquivalent: 0.5,
absenceCostDeductionCents: 1_000,
},
calendarLocations: [
{
countryCode: "DE",
countryName: "Germany",
federalState: "BY",
metroCityName: "Munich",
activeAssignmentCount: 2,
burnRateCents: 10_000,
},
],
},
]);
const caller = createControllerCaller({});
const result = await caller.getBudgetForecast();
expect(result).toHaveLength(1);
expect(result[0]).toMatchObject({
projectName: "Alpha",
activeAssignmentCount: 2,
derivation: {
holidayAwareAssignmentCount: 2,
baseBurnRateCents: 12_000,
adjustedBurnRateCents: 10_000,
},
calendarLocations: [
expect.objectContaining({
countryCode: "DE",
federalState: "BY",
metroCityName: "Munich",
}),
],
});
expect(getDashboardBudgetForecast).toHaveBeenCalledTimes(1);
});
it("returns assistant-friendly budget forecast detail derived from the canonical dashboard read model", async () => {
vi.mocked(getDashboardBudgetForecast).mockResolvedValue([
{
projectId: "project_1",
projectName: "Alpha",
shortCode: "ALPHA",
clientId: "client_1",
clientName: "Client One",
budgetCents: 100_000,
spentCents: 40_000,
remainingCents: 60_000,
burnRate: 10_000,
estimatedExhaustionDate: "2026-06-30",
pctUsed: 40,
activeAssignmentCount: 2,
derivation: {
periodStart: "2026-03-01",
periodEnd: "2026-03-31",
calendarContextCount: 1,
holidayAwareAssignmentCount: 2,
fallbackAssignmentCount: 0,
baseBurnRateCents: 12_000,
adjustedBurnRateCents: 10_000,
publicHolidayDayEquivalent: 1,
publicHolidayCostDeductionCents: 1_000,
absenceDayEquivalent: 0.5,
absenceCostDeductionCents: 1_000,
},
calendarLocations: [
{
countryCode: "DE",
countryName: "Germany",
federalState: "BY",
metroCityName: "Munich",
activeAssignmentCount: 2,
burnRateCents: 10_000,
},
],
},
]);
const caller = createControllerCaller({});
const result = await caller.getBudgetForecastDetail();
expect(getDashboardBudgetForecast).toHaveBeenCalledTimes(1);
expect(result).toEqual({
forecasts: [
expect.objectContaining({
projectId: "project_1",
projectName: "Alpha",
shortCode: "ALPHA",
budgetCents: 100_000,
spentCents: 40_000,
remainingCents: 60_000,
projectedCents: 100_000,
burnRateCents: 10_000,
utilization: "40%",
derivation: expect.objectContaining({
holidayAwareAssignmentCount: 2,
baseBurnRateCents: 12_000,
adjustedBurnRateCents: 10_000,
publicHolidayCostDeductionCents: 1_000,
absenceCostDeductionCents: 1_000,
}),
burnStatus: "on_track",
calendarLocations: [
expect.objectContaining({
countryCode: "DE",
federalState: "BY",
metroCityName: "Munich",
}),
],
}),
],
});
});
});
describe("getDetail", () => {
it("returns the canonical assistant dashboard detail payload", async () => {
vi.mocked(getDashboardOverview).mockResolvedValue({
budgetBasis: {
windowStart: "2026-01-01T00:00:00.000Z",
windowEnd: "2026-06-30T00:00:00.000Z",
},
chapterUtilization: [
{
chapter: "Delivery",
resourceCount: 4,
avgChargeabilityTarget: 78,
},
],
});
vi.mocked(getDashboardPeakTimes).mockResolvedValue([
{
period: "2026-03",
totalHours: 320.4,
capacityHours: 400.2,
utilizationPct: 80,
derivation: {
periodStart: "2026-03-01",
periodEnd: "2026-03-31",
calendarContextCount: 1,
resourceCount: 4,
groupCount: 1,
baseAvailableHours: 416.2,
effectiveAvailableHours: 400.2,
publicHolidayHoursDeduction: 16,
absenceDayEquivalent: 1.5,
absenceHoursDeduction: 12.5,
calendarLocations: [
{
countryCode: "DE",
countryName: "Germany",
federalState: "BY",
metroCityName: "Augsburg",
resourceCount: 4,
effectiveAvailableHours: 400.2,
},
],
bookedHours: 320.4,
capacityHours: 400.2,
remainingCapacityHours: 79.8,
overbookedHours: 0,
utilizationPct: 80,
},
},
]);
vi.mocked(getDashboardSkillGapSummary).mockResolvedValue({
roleGaps: [
{ role: "Pipeline TD", needed: 4, filled: 1, gap: 3, fillRate: 25 },
{ role: "Lighting TD", needed: 2, filled: 1, gap: 1, fillRate: 50 },
],
totalOpenPositions: 4,
skillSupplyTop10: [
{ skill: "houdini", resourceCount: 5 },
{ skill: "nuke", resourceCount: 4 },
],
resourcesByRole: [
{ role: "Compositor", count: 6 },
{ role: "Pipeline TD", count: 2 },
],
});
vi.mocked(getDashboardTopValueResources).mockResolvedValue([
{
id: "res_1",
eid: "pparker",
displayName: "Peter Parker",
chapter: "Delivery",
valueScore: 91,
valueScoreBreakdown: {
skillDepth: 85,
skillBreadth: 74,
costEfficiency: 93,
chargeability: 78,
experience: 88,
total: 91,
},
valueScoreUpdatedAt: new Date("2026-03-03T00:00:00.000Z"),
lcrCents: 9_500,
countryCode: "DE",
countryName: "Germany",
federalState: "BY",
metroCityName: "Augsburg",
},
]);
vi.mocked(getDashboardDemand).mockResolvedValue([
{
id: "project_1",
name: "Gelddruckmaschine",
shortCode: "GDM",
allocatedHours: 120,
requiredFTEs: 4,
resourceCount: 2,
derivation: {
periodStart: "2026-01-01",
periodEnd: "2026-06-30",
periodWorkingHoursBase: 1040,
requiredHours: 2080,
requiredFTEs: 4,
fillPct: 50,
demandSource: "DEMAND_REQUIREMENTS",
calendarLocations: [
{
countryCode: "DE",
countryName: "Germany",
federalState: "BY",
metroCityName: "Augsburg",
resourceCount: 2,
allocatedHours: 120,
},
],
},
},
]);
vi.mocked(getDashboardChargeabilityOverview).mockResolvedValue({
rows: [
{
id: "res_1",
eid: "pparker",
displayName: "Peter Parker",
chapter: "Delivery",
chargeabilityTarget: 78,
actualChargeability: 70,
expectedChargeability: 76,
derivation: {
weeklyAvailabilityHours: 40,
baseWorkingDays: 22,
effectiveWorkingDayEquivalent: 21,
baseAvailableHours: 176,
effectiveAvailableHours: 168,
publicHolidayCount: 1,
publicHolidayWorkdayCount: 1,
publicHolidayHoursDeduction: 8,
absenceDayEquivalent: 0,
absenceHoursDeduction: 0,
actualBookedHours: 117.6,
expectedBookedHours: 127.7,
targetBookedHours: 131,
unassignedHours: 40.3,
},
},
],
top: [],
watchlist: [],
month: "2026-03",
});
vi.mocked(getDashboardProjectHealth).mockResolvedValue([
{
id: "project_1",
projectName: "Gelddruckmaschine",
shortCode: "GDM",
status: "ACTIVE",
clientId: "client_1",
clientName: "Acme",
budgetHealth: 58,
staffingHealth: 46,
timelineHealth: 61,
compositeScore: 55,
budgetUtilizationPercent: 73,
remainingBudgetCents: 88_000,
demandHeadcountTotal: 4,
demandHeadcountFilled: 2,
demandHeadcountOpen: 2,
demandRequirementCount: 2,
plannedEndDate: new Date("2026-09-30T00:00:00.000Z"),
daysUntilEndDate: 183,
timelineStatus: "DUE_SOON",
derivation: {
periodStart: "2026-01-01",
periodEnd: "2026-06-30",
calendarContextCount: 1,
holidayAwareAssignmentCount: 2,
fallbackAssignmentCount: 0,
baseSpentCents: 140_000,
adjustedSpentCents: 132_000,
publicHolidayDayEquivalent: 1,
publicHolidayCostDeductionCents: 5_000,
absenceDayEquivalent: 0.5,
absenceCostDeductionCents: 3_000,
},
},
]);
const caller = createControllerCaller({});
const result = await caller.getDetail({ section: "all" });
expect(result).toEqual({
peakTimes: [
{
month: "2026-03",
totalHours: 320.4,
totalHoursPerDay: 320.4,
capacityHours: 400.2,
utilizationPct: 80,
calendarContextCount: 1,
calendarLocations: [
{
countryCode: "DE",
countryName: "Germany",
federalState: "BY",
metroCityName: "Augsburg",
resourceCount: 4,
effectiveAvailableHours: 400.2,
},
],
explainability: {
periodStart: "2026-03-01",
periodEnd: "2026-03-31",
resourceCount: 4,
groupCount: 1,
baseAvailableHours: 416.2,
effectiveAvailableHours: 400.2,
publicHolidayHoursDeduction: 16,
absenceDayEquivalent: 1.5,
absenceHoursDeduction: 12.5,
remainingCapacityHours: 79.8,
overbookedHours: 0,
},
},
],
topResources: [
{
name: "Peter Parker",
eid: "pparker",
chapter: "Delivery",
lcr: "95,00 EUR",
valueScore: 91,
valueScoreBreakdown: {
skillDepth: 85,
skillBreadth: 74,
costEfficiency: 93,
chargeability: 78,
experience: 88,
total: 91,
},
valueScoreUpdatedAt: "2026-03-03T00:00:00.000Z",
countryCode: "DE",
countryName: "Germany",
federalState: "BY",
metroCityName: "Augsburg",
},
],
demandPipeline: [
{
project: "Gelddruckmaschine (GDM)",
needed: 2,
requiredFTEs: 4,
allocatedResources: 2,
allocatedHours: 120,
calendarLocations: [
{
countryCode: "DE",
countryName: "Germany",
federalState: "BY",
metroCityName: "Augsburg",
resourceCount: 2,
allocatedHours: 120,
},
],
explainability: {
periodStart: "2026-01-01",
periodEnd: "2026-06-30",
periodWorkingHoursBase: 1040,
requiredHours: 2080,
fillPct: 50,
demandSource: "DEMAND_REQUIREMENTS",
calendarContextCount: 1,
},
},
],
chargeabilityByChapter: [
{
chapter: "Delivery",
headcount: 1,
avgTargetPct: 78,
avgActualPct: 70,
avgExpectedPct: 76,
gapToTargetPct: 8,
avgTarget: "78%",
avgActual: "70%",
avgExpected: "76%",
explainability: {
month: "2026-03",
resourceCount: 1,
derivedHeadcount: 1,
baseAvailableHours: 176,
effectiveAvailableHours: 168,
actualBookedHours: 117.6,
expectedBookedHours: 127.7,
targetBookedHours: 131,
publicHolidayHoursDeduction: 8,
absenceDayEquivalent: 0,
absenceHoursDeduction: 0,
unassignedHours: 40.3,
},
},
],
projectHealth: [
{
project: "Gelddruckmaschine (GDM)",
status: "ACTIVE",
overall: 55,
rating: "at_risk",
budget: 58,
staffing: 46,
timeline: 61,
timelineStatus: "DUE_SOON",
daysUntilEndDate: 183,
demandHeadcountOpen: 2,
explainability: {
demandHeadcountTotal: 4,
demandHeadcountFilled: 2,
demandHeadcountOpen: 2,
demandRequirementCount: 2,
plannedEndDate: "2026-09-30T00:00:00.000Z",
budgetUtilizationPercent: 73,
remainingBudgetCents: 88_000,
calendarContextCount: 1,
holidayAwareAssignmentCount: 2,
publicHolidayCostDeductionCents: 5_000,
absenceCostDeductionCents: 3_000,
},
},
],
skillGaps: {
totalOpenPositions: 4,
roleGaps: [
{ role: "Pipeline TD", gap: 3, needed: 4, filled: 1, fillRate: 25 },
{ role: "Lighting TD", gap: 1, needed: 2, filled: 1, fillRate: 50 },
],
topSkillsInSupply: [
{ skill: "houdini", resourceCount: 5 },
{ skill: "nuke", resourceCount: 4 },
],
resourcesByRole: [
{ role: "Compositor", count: 6 },
{ role: "Pipeline TD", count: 2 },
],
},
});
expect(getDashboardPeakTimes).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
startDate: new Date("2026-01-01T00:00:00.000Z"),
endDate: new Date("2026-06-30T00:00:00.000Z"),
granularity: "month",
groupBy: "project",
}),
);
expect(getDashboardChargeabilityOverview).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
includeProposed: false,
topN: 10,
watchlistThreshold: 15,
}),
);
expect(getDashboardSkillGapSummary).toHaveBeenCalledWith(expect.anything());
expect(getDashboardProjectHealth).toHaveBeenCalledTimes(1);
});
});
});