refactor(api): strengthen report template persistence

This commit is contained in:
2026-03-31 22:35:15 +02:00
parent f2bcf4b7f0
commit cb8669c489
2 changed files with 324 additions and 29 deletions
@@ -116,6 +116,70 @@ describe("report router", () => {
expect(result.csv).toContain("Alice,DE,1,8,4,156,124.8,156");
});
it("keeps holiday and absence deductions separate in resource_month rows", async () => {
const db = {
resource: {
findMany: vi.fn().mockResolvedValue([
{
id: "res_1",
eid: "alice",
displayName: "Alice",
email: "alice@example.com",
chapter: "VFX",
resourceType: "EMPLOYEE",
isActive: true,
chgResponsibility: false,
rolledOff: false,
departed: false,
lcrCents: 7500,
ucrCents: 10000,
currency: "EUR",
fte: 1,
availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8 },
chargeabilityTarget: 80,
federalState: "BY",
countryId: "country_de",
metroCityId: null,
country: { code: "DE", name: "Germany" },
metroCity: null,
orgUnit: { name: "Delivery" },
managementLevelGroup: null,
managementLevel: { name: "Senior" },
},
]),
},
};
const caller = createControllerCaller(db);
const result = await caller.getReportData({
entity: "resource_month",
columns: [
"displayName",
"monthlyPublicHolidayCount",
"monthlyPublicHolidayHoursDeduction",
"monthlyAbsenceDayEquivalent",
"monthlyAbsenceHoursDeduction",
"monthlySahHours",
],
filters: [],
periodMonth: "2026-04",
limit: 100,
offset: 0,
});
expect(result.rows).toEqual([
{
id: "res_1:2026-04",
displayName: "Alice",
monthlyPublicHolidayCount: 1,
monthlyPublicHolidayHoursDeduction: 8,
monthlyAbsenceDayEquivalent: 0.5,
monthlyAbsenceHoursDeduction: 4,
monthlySahHours: 156,
},
]);
});
it("rejects invalid resource_month period months instead of silently normalizing them", async () => {
const caller = createControllerCaller({});
@@ -191,4 +255,75 @@ describe("report router", () => {
message: expect.stringContaining("lcrCents"),
});
});
it("returns page-local grouping metadata and grouped CSV sections", async () => {
const db = {
resource: {
findMany: vi.fn().mockResolvedValue([
{
id: "res_2",
displayName: "Bob",
chapter: "Delivery",
},
{
id: "res_1",
displayName: "Alice",
chapter: "Design",
},
{
id: "res_3",
displayName: "Cara",
chapter: "Design",
},
]),
count: vi.fn().mockResolvedValue(3),
},
};
const caller = createControllerCaller(db);
const data = await caller.getReportData({
entity: "resource",
columns: ["displayName", "chapter"],
filters: [],
groupBy: "chapter",
sortBy: "displayName",
sortDir: "asc",
limit: 10,
offset: 0,
});
expect(db.resource.findMany).toHaveBeenCalledWith({
select: {
id: true,
displayName: true,
chapter: true,
},
where: {},
orderBy: [{ chapter: "asc" }, { displayName: "asc" }],
take: 10,
skip: 0,
});
expect(data.rows).toEqual([
{ id: "res_2", displayName: "Bob", chapter: "Delivery" },
{ id: "res_1", displayName: "Alice", chapter: "Design" },
{ id: "res_3", displayName: "Cara", chapter: "Design" },
]);
expect(data.groups).toEqual([
{ key: "chapter:Delivery", label: "Delivery", rowCount: 1, startIndex: 0 },
{ key: "chapter:Design", label: "Design", rowCount: 2, startIndex: 1 },
]);
const csv = await caller.exportReport({
entity: "resource",
columns: ["displayName", "chapter"],
filters: [],
groupBy: "chapter",
sortBy: "displayName",
sortDir: "asc",
limit: 10,
});
expect(csv.csv).toContain("Chapter: Design (2),");
expect(csv.csv).toContain("Chapter: Delivery (1),");
});
});