cd78f72f33
Complete rename of all technical identifiers across the codebase: Package names (11 packages): - @planarchy/* → @capakraken/* in all package.json, tsconfig, imports Import statements: 277 files, 548 occurrences replaced Database & Docker: - PostgreSQL user/db: planarchy → capakraken - Docker volumes: planarchy_pgdata → capakraken_pgdata - Connection strings updated in docker-compose, .env, CI CI/CD: - GitHub Actions workflow: all filter commands updated - Test database credentials updated Infrastructure: - Redis channel: planarchy:sse → capakraken:sse - Logger service name: planarchy-api → capakraken-api - Anonymization seed updated - Start/stop/restart scripts updated Test data: - Seed emails: @planarchy.dev → @capakraken.dev - E2E test credentials: all 11 spec files updated - Email defaults: @planarchy.app → @capakraken.app - localStorage keys: planarchy_* → capakraken_* Documentation: 30+ .md files updated Verification: - pnpm install: workspace resolution works - TypeScript: only pre-existing TS2589 (no new errors) - Engine: 310/310 tests pass - Staffing: 37/37 tests pass Co-Authored-By: claude-flow <ruv@ruv.net>
167 lines
5.1 KiB
TypeScript
167 lines
5.1 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { calculateSAH, getDailyHours } from "../sah/calculator.js";
|
|
import type { SpainScheduleRule } from "@capakraken/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);
|
|
});
|
|
});
|