import { describe, it, expect, beforeAll, afterAll } from "vitest"; import prisma from "../config/database"; import { getWorkfund } from "../services/attendance.service"; /** * Print-recap parity for the Bilance page's monthly fund overview * (getWorkfund), per the binding formulas (computeMzdaSummary): * overtime (Přesčas) = max(0, worked + dovolená − fond) * missing (Chybějící hodiny) = max(0, fond − (worked + dovolená + nemoc * + neplacené volno)) * The two coverage bases differ deliberately: only dovolená earns toward * overtime, every approved absence covers the fond. */ const USERNAME = "wf_parity_test_user"; let userId: number; const june = (day: string) => new Date(`2026-06-${day}T00:00:00Z`); const dt = (day: string, hm: string) => new Date(`2026-06-${day}T${hm}:00`); beforeAll(async () => { await prisma.attendance.deleteMany({ where: { users: { username: USERNAME } }, }); await prisma.users.deleteMany({ where: { username: USERNAME } }); // getAttendanceUsers includes only users whose role carries // attendance.record — the seeded admin role has every permission. const adminRole = await prisma.roles.findFirst({ where: { name: "admin" } }); if (!adminRole) throw new Error("Admin role not found — seed the DB first"); const user = await prisma.users.create({ data: { username: USERNAME, email: `${USERNAME}@test.local`, password_hash: "$2a$12$LJ3m4ys3Lg4oLBFnYP2amuPBzJnJBbGzCl5Y6X9Y8r0q5.s3L6OyO", first_name: "Workfund", last_name: "Parity", is_active: true, role_id: adminRole.id, }, }); userId = user.id; // June 2026 (fond 176 = 22 non-holiday weekdays × 8): // 10 work days of 8h net (08:00–16:30, 30min break) → worked 80 const workDays = ["01", "02", "03", "04", "05", "08", "09", "10", "11", "12"]; for (const d of workDays) { await prisma.attendance.create({ data: { user_id: userId, shift_date: june(d), arrival_time: dt(d, "08:00"), departure_time: dt(d, "16:30"), break_start: dt(d, "12:00"), break_end: dt(d, "12:30"), leave_type: "work", }, }); } // dovolená 24, nemoc 16, neplacené volno 8 const leaves: Array<[string, string, number]> = [ ["15", "vacation", 8], ["16", "vacation", 8], ["17", "vacation", 8], ["18", "sick", 8], ["19", "sick", 8], ["22", "unpaid", 8], ]; for (const [d, type, hours] of leaves) { await prisma.attendance.create({ data: { user_id: userId, shift_date: june(d), leave_type: type as never, leave_hours: hours, }, }); } }); afterAll(async () => { await prisma.attendance.deleteMany({ where: { user_id: userId } }); await prisma.users.deleteMany({ where: { id: userId } }); }); describe("getWorkfund — print-recap parity", () => { it("computes overtime from worked+dovolená and missing from all approved absences", async () => { const data = await getWorkfund(2026); // getWorkfund's future-year early return types months as a bare {} — // narrow to the populated shape (2026 is never a future year here). const months = data.months as Record< string, { fund: number; users: Record< string, { worked: number; covered: number; covered_prescas: number; overtime: number; missing: number; } >; } >; const juneData = months["6"]; expect(juneData).toBeTruthy(); expect(juneData.fund).toBe(176); const us = juneData.users[String(userId)]; expect(us).toBeTruthy(); expect(us.worked).toBe(80); // covered (manko base): 80 + 24 + 16 + 8 expect(us.covered).toBe(128); // covered_prescas (Přesčas base): 80 + 24 — sick/unpaid excluded expect(us.covered_prescas).toBe(104); // Chybějící hodiny: 176 − 128 expect(us.missing).toBe(48); // Přesčas: max(0, 104 − 176) expect(us.overtime).toBe(0); }); });