refactor(api): extract chargeability report procedures

This commit is contained in:
2026-03-31 20:42:33 +02:00
parent 00d5fe7923
commit 958d2368c1
4 changed files with 665 additions and 312 deletions
@@ -0,0 +1,168 @@
import { PermissionKey } from "@capakraken/shared";
import { TRPCError } from "@trpc/server";
import { describe, expect, it } from "vitest";
import {
buildChargeabilityReportDetail,
getChargeabilityReportDetail,
} from "../router/chargeability-report-procedure-support.js";
describe("chargeability report procedure support", () => {
it("builds a filtered detailed report with rounded percentages", () => {
const result = buildChargeabilityReportDetail(
{
monthKeys: ["2026-03"],
groupTotals: [
{
monthKey: "2026-03",
totalFte: 1.234,
chg: 0.456,
target: 0.8,
gap: -0.344,
chargeabilityRatio: 0.456,
targetRatio: 0.8,
gapRatio: -0.344,
},
],
resources: [
{
id: "resource_1",
eid: "E-001",
displayName: "Alice",
fte: 1.234,
country: "ES",
federalState: null,
city: "Barcelona",
orgUnit: "CGI",
mgmtGroup: "Senior",
mgmtLevel: "L7",
targetPct: 0.8,
months: [
{
monthKey: "2026-03",
sah: 120.44,
sahHours: 120.44,
chg: 0.456,
bd: 0.1,
mdi: 0.05,
mo: 0.07,
pdr: 0.08,
absence: 0.09,
unassigned: 0.154,
chargeabilityRatio: 0.456,
businessDevelopmentRatio: 0.1,
marketDevelopmentInnovationRatio: 0.05,
managementOverheadRatio: 0.07,
peopleDevelopmentRecruitingRatio: 0.08,
plannedAbsenceRatio: 0.09,
unassignedRatio: 0.154,
chargeabilityHours: 54.9,
businessDevelopmentHours: 12,
marketDevelopmentInnovationHours: 6,
managementOverheadHours: 8.4,
peopleDevelopmentRecruitingHours: 9.6,
plannedAbsenceHours: 10.8,
unassignedHours: 18.5,
targetRatio: 0.8,
targetHours: 96.4,
gapRatio: -0.344,
gapHours: -41.4,
derivation: {
baseAvailableHours: 128,
publicHolidayCount: 1,
publicHolidayWorkdayCount: 1,
publicHolidayHoursDeduction: 8,
absenceDayEquivalent: 0.5,
absenceHoursDeduction: 4,
effectiveAvailableHours: 120,
},
},
],
},
{
id: "resource_2",
eid: "E-002",
displayName: "Bob",
fte: 1,
country: "DE",
federalState: "BY",
city: "Munich",
orgUnit: "CGI",
mgmtGroup: "Senior",
mgmtLevel: "L7",
targetPct: 0.75,
months: [],
},
],
},
{
startMonth: "2026-03",
endMonth: "2026-03",
resourceQuery: "ali",
resourceLimit: 1,
includeProposed: false,
},
);
expect(result.filters).toEqual({
startMonth: "2026-03",
endMonth: "2026-03",
orgUnitId: null,
managementLevelGroupId: null,
countryId: null,
includeProposed: false,
resourceQuery: "ali",
});
expect(result.groupTotals).toEqual([
{
monthKey: "2026-03",
totalFte: 1.2,
chargeabilityPct: 45.6,
targetPct: 80,
gapPct: -34.4,
},
]);
expect(result.resourceCount).toBe(1);
expect(result.returnedResourceCount).toBe(1);
expect(result.truncated).toBe(false);
expect(result.resources[0]).toEqual(
expect.objectContaining({
displayName: "Alice",
targetPct: 80,
managementLevelGroup: "Senior",
managementLevel: "L7",
months: [
expect.objectContaining({
monthKey: "2026-03",
sah: 120.4,
chargeabilityPct: 45.6,
targetPct: 80,
gapPct: -34.4,
}),
],
}),
);
});
it("rejects detailed reports without the cost-view permission", async () => {
await expect(
getChargeabilityReportDetail(
{
db: {
resource: { findMany: async () => [] },
} as never,
permissions: new Set(),
},
{
startMonth: "2026-03",
endMonth: "2026-03",
includeProposed: false,
},
),
).rejects.toEqual(
expect.objectContaining<Partial<TRPCError>>({
code: "FORBIDDEN",
message: `Permission required: ${PermissionKey.VIEW_COSTS}`,
}),
);
});
});