Files
CapaKraken/packages/engine/src/__tests__/chargeability.test.ts
T

149 lines
5.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
});
});