chore(repo): initialize planarchy workspace

This commit is contained in:
2026-03-14 14:31:09 +01:00
commit dd55d0e78b
769 changed files with 166461 additions and 0 deletions
@@ -0,0 +1,148 @@
import { describe, expect, it } from "vitest";
import {
computeAvailableHours,
computeBookedHours,
computeChargeability,
} from "../allocation/chargeability.js";
// Standard MonFri 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("MonFri 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,
};
// MonFri: 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);
});
});