import { describe, expect, it } from "vitest"; import { computeAvailableHours, computeBookedHours, computeChargeability, } from "../allocation/chargeability.js"; // Standard Mon–Fri 8h availability const stdAvail = { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8 }; // Week of 2025-01-06 (Mon) to 2025-01-10 (Fri) const weekStart = new Date("2025-01-06"); const weekEnd = new Date("2025-01-10"); describe("computeAvailableHours", () => { it("Mon–Fri 8h resource: 1 calendar week = 40 available hours", () => { expect(computeAvailableHours(stdAvail, weekStart, weekEnd)).toBe(40); }); it("excludes Saturday and Sunday when not in availability", () => { // 2025-01-04 (Sat) to 2025-01-05 (Sun) expect(computeAvailableHours(stdAvail, new Date("2025-01-04"), new Date("2025-01-05"))).toBe(0); }); it("counts Saturday when enabled in availability", () => { const avail = { ...stdAvail, saturday: 4 }; // Mon 06 to Sat 11 Jan: 5 weekdays × 8 + 1 Saturday × 4 = 44 expect(computeAvailableHours(avail, new Date("2025-01-06"), new Date("2025-01-11"))).toBe(44); }); it("single day period (Monday): returns daily hours", () => { expect(computeAvailableHours(stdAvail, weekStart, weekStart)).toBe(8); }); }); describe("computeBookedHours", () => { it("no allocations → bookedHours = 0", () => { expect(computeBookedHours(stdAvail, [], weekStart, weekEnd)).toBe(0); }); it("full 8h allocation covering entire period counts all working days", () => { const alloc = { startDate: weekStart, endDate: weekEnd, hoursPerDay: 8 }; expect(computeBookedHours(stdAvail, [alloc], weekStart, weekEnd)).toBe(40); }); it("allocation partially overlapping: only overlap days counted", () => { // Allocation spans Wed-Sun; period is Mon-Fri → overlap is Wed-Fri (3 days) const alloc = { startDate: new Date("2025-01-08"), // Wed endDate: new Date("2025-01-12"), // Sun hoursPerDay: 8, }; expect(computeBookedHours(stdAvail, [alloc], weekStart, weekEnd)).toBe(24); // 3 days × 8 }); it("allocation spanning weekends: weekends excluded from booked hours", () => { // Mon 06 to Mon 13 (spans a weekend), but weekend has 0 avail const alloc = { startDate: new Date("2025-01-06"), endDate: new Date("2025-01-13"), hoursPerDay: 8, }; // Period is Mon-Fri week only expect(computeBookedHours(stdAvail, [alloc], weekStart, weekEnd)).toBe(40); }); it("Saturday with availability counted when allocation covers it", () => { const avail = { ...stdAvail, saturday: 4 }; const alloc = { startDate: new Date("2025-01-06"), // Mon endDate: new Date("2025-01-11"), // Sat hoursPerDay: 6, }; // Mon–Fri: 5 days × 6 = 30, Sat: 1 day × 6 = 6 → total 36 expect(computeBookedHours(avail, [alloc], new Date("2025-01-06"), new Date("2025-01-11"))).toBe(36); }); it("allocation entirely before period: no hours counted", () => { const alloc = { startDate: new Date("2024-12-30"), endDate: new Date("2025-01-03"), hoursPerDay: 8, }; expect(computeBookedHours(stdAvail, [alloc], weekStart, weekEnd)).toBe(0); }); it("allocation entirely after period: no hours counted", () => { const alloc = { startDate: new Date("2025-01-13"), endDate: new Date("2025-01-17"), hoursPerDay: 8, }; expect(computeBookedHours(stdAvail, [alloc], weekStart, weekEnd)).toBe(0); }); }); describe("computeChargeability", () => { it("no allocations → chargeability = 0, bookedHours = 0", () => { const result = computeChargeability(stdAvail, [], weekStart, weekEnd); expect(result.chargeability).toBe(0); expect(result.bookedHours).toBe(0); expect(result.availableHours).toBe(40); }); it("full 8h allocation covering entire period → chargeability = 100", () => { const alloc = { startDate: weekStart, endDate: weekEnd, hoursPerDay: 8 }; const result = computeChargeability(stdAvail, [alloc], weekStart, weekEnd); expect(result.chargeability).toBe(100); expect(result.bookedHours).toBe(40); }); it("50% allocation → chargeability = 50", () => { const alloc = { startDate: weekStart, endDate: weekEnd, hoursPerDay: 4 }; const result = computeChargeability(stdAvail, [alloc], weekStart, weekEnd); expect(result.chargeability).toBe(50); expect(result.bookedHours).toBe(20); }); it("over-allocated (booked > available): chargeability capped at 100", () => { // Two allocations totalling 12h/day on a resource with 8h availability const allocs = [ { startDate: weekStart, endDate: weekEnd, hoursPerDay: 8 }, { startDate: weekStart, endDate: weekEnd, hoursPerDay: 4 }, ]; const result = computeChargeability(stdAvail, allocs, weekStart, weekEnd); expect(result.chargeability).toBe(100); expect(result.bookedHours).toBe(60); // not capped in raw value }); it("single day period (start === end): correct result", () => { const alloc = { startDate: weekStart, endDate: weekStart, hoursPerDay: 4 }; const result = computeChargeability(stdAvail, [alloc], weekStart, weekStart); expect(result.availableHours).toBe(8); expect(result.bookedHours).toBe(4); expect(result.chargeability).toBe(50); }); it("zero available hours (all weekend period): chargeability = 0", () => { const alloc = { startDate: new Date("2025-01-04"), endDate: new Date("2025-01-05"), hoursPerDay: 8, }; const result = computeChargeability(stdAvail, [alloc], new Date("2025-01-04"), new Date("2025-01-05")); expect(result.availableHours).toBe(0); expect(result.chargeability).toBe(0); }); });