import { describe, it, expect } from "vitest"; import { calculateSAH, getDailyHours } from "../sah/calculator.js"; import type { SpainScheduleRule } from "@planarchy/shared"; const spainRules: SpainScheduleRule = { type: "spain", fridayHours: 6.5, summerPeriod: { from: "07-01", to: "09-15" }, summerHours: 6.5, regularHours: 9, }; describe("getDailyHours", () => { it("returns base hours when no schedule rules", () => { expect(getDailyHours(new Date("2026-03-10"), 8)).toBe(8); // Monday expect(getDailyHours(new Date("2026-03-13"), 9)).toBe(9); // Thursday }); it("returns Spain friday hours", () => { // 2026-03-13 is a Friday expect(getDailyHours(new Date("2026-03-13"), 8, spainRules)).toBe(6.5); }); it("returns Spain summer hours Mon-Thu", () => { // 2026-07-06 is a Monday in summer expect(getDailyHours(new Date("2026-07-06"), 8, spainRules)).toBe(6.5); }); it("returns Spain regular hours Mon-Thu outside summer", () => { // 2026-03-09 is a Monday, not summer expect(getDailyHours(new Date("2026-03-09"), 8, spainRules)).toBe(9); }); }); describe("calculateSAH", () => { it("calculates basic SAH for a full-time German employee in one week", () => { // 2026-03-09 (Mon) to 2026-03-13 (Fri) — 5 working days, no holidays const result = calculateSAH({ dailyWorkingHours: 8, fte: 1.0, periodStart: new Date("2026-03-09"), periodEnd: new Date("2026-03-13"), publicHolidays: [], absenceDays: [], }); expect(result.calendarDays).toBe(5); expect(result.weekendDays).toBe(0); expect(result.grossWorkingDays).toBe(5); expect(result.netWorkingDays).toBe(5); expect(result.standardAvailableHours).toBe(40); }); it("reduces hours by FTE factor", () => { const result = calculateSAH({ dailyWorkingHours: 8, fte: 0.5, periodStart: new Date("2026-03-09"), periodEnd: new Date("2026-03-13"), publicHolidays: [], absenceDays: [], }); expect(result.standardAvailableHours).toBe(20); expect(result.effectiveHoursPerDay).toBe(4); }); it("deducts public holidays", () => { const result = calculateSAH({ dailyWorkingHours: 8, fte: 1.0, periodStart: new Date("2026-03-09"), periodEnd: new Date("2026-03-13"), publicHolidays: ["2026-03-10"], // Tuesday is holiday absenceDays: [], }); expect(result.publicHolidayDays).toBe(1); expect(result.netWorkingDays).toBe(4); expect(result.standardAvailableHours).toBe(32); }); it("deducts absence days", () => { const result = calculateSAH({ dailyWorkingHours: 8, fte: 1.0, periodStart: new Date("2026-03-09"), periodEnd: new Date("2026-03-13"), publicHolidays: [], absenceDays: ["2026-03-11", "2026-03-12"], // Wed + Thu }); expect(result.absenceDays).toBe(2); expect(result.netWorkingDays).toBe(3); expect(result.standardAvailableHours).toBe(24); }); it("handles weekends correctly", () => { // 2026-03-07 (Sat) to 2026-03-13 (Fri) — includes a full weekend const result = calculateSAH({ dailyWorkingHours: 8, fte: 1.0, periodStart: new Date("2026-03-07"), periodEnd: new Date("2026-03-13"), publicHolidays: [], absenceDays: [], }); expect(result.calendarDays).toBe(7); expect(result.weekendDays).toBe(2); expect(result.grossWorkingDays).toBe(5); expect(result.netWorkingDays).toBe(5); expect(result.standardAvailableHours).toBe(40); }); it("uses 9h for India", () => { const result = calculateSAH({ dailyWorkingHours: 9, fte: 1.0, periodStart: new Date("2026-03-09"), periodEnd: new Date("2026-03-13"), publicHolidays: [], absenceDays: [], }); expect(result.standardAvailableHours).toBe(45); }); it("handles Spain variable schedule in summer", () => { // 2026-07-06 (Mon) to 2026-07-10 (Fri) — all summer, includes Friday const result = calculateSAH({ dailyWorkingHours: 8, scheduleRules: spainRules, fte: 1.0, periodStart: new Date("2026-07-06"), periodEnd: new Date("2026-07-10"), publicHolidays: [], absenceDays: [], }); // Mon-Fri all summer: 6.5 * 5 = 32.5 expect(result.standardAvailableHours).toBe(32.5); }); it("handles Spain variable schedule outside summer", () => { // 2026-03-09 (Mon) to 2026-03-13 (Fri) — non-summer const result = calculateSAH({ dailyWorkingHours: 8, scheduleRules: spainRules, fte: 1.0, periodStart: new Date("2026-03-09"), periodEnd: new Date("2026-03-13"), publicHolidays: [], absenceDays: [], }); // Mon-Thu: 9h each = 36, Fri: 6.5h = total 42.5 expect(result.standardAvailableHours).toBe(42.5); }); it("combines FTE with Spain schedule", () => { const result = calculateSAH({ dailyWorkingHours: 8, scheduleRules: spainRules, fte: 0.5, periodStart: new Date("2026-03-09"), periodEnd: new Date("2026-03-13"), publicHolidays: [], absenceDays: [], }); // (9*4 + 6.5*1) * 0.5 = 42.5 * 0.5 = 21.25 expect(result.standardAvailableHours).toBe(21.25); }); });