refactor(api): extract chargeability report procedures
This commit is contained in:
@@ -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}`,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user