mega-plant/lib/workforce.test.ts

57 lines
3 KiB
TypeScript
Raw Normal View History

import { test } from 'node:test';
import assert from 'node:assert/strict';
import type { ClockEvent } from './clock';
import { attendanceSummary, employeeIdFromSlug, employeeSlug, periodDays, sessionsOf, shiftPeriod, weekStart } from './workforce';
let n=0;
const ev=(direction:'in'|'out',scanned_at:string):ClockEvent=>({id:`event${String(++n).padStart(10,'0')}`,employee:'mechanics:abcdefghijklmno',
employee_no:'MCW001',employee_name:'Worker',site:'',direction,scanned_at,actor:'',previous_event:'',session_minutes:0,has_location:false});
test('slug round-trips and rejects anything else',()=>{
assert.equal(employeeIdFromSlug(employeeSlug('operators:abcdefghijklmno')),'operators:abcdefghijklmno');
assert.equal(employeeIdFromSlug('assets-abcdefghijklmno'),null);
assert.equal(employeeIdFromSlug('operators-short'),null);
});
test('weeks start on Monday',()=>{
assert.equal(weekStart('2026-10-22'),'2026-10-19'); // Thursday
assert.equal(weekStart('2026-10-19'),'2026-10-19');
assert.equal(weekStart('2026-10-25'),'2026-10-19'); // Sunday
});
test('an open IN today counts to now; an open IN on an earlier day counts nothing',()=>{
const now='2026-10-22T10:00:00Z';
const s=sessionsOf([ev('in','2026-10-21T05:00:00Z'),ev('in','2026-10-22T06:00:00Z')],now);
assert.deepEqual(s.map(x=>x.minutes),[0,240]);
});
test('summary adds lunch-split days and reports a missed clock-out',()=>{
const now='2026-10-22T14:00:00Z';
const events=[
ev('in','2026-10-20T05:00:00Z'), // Tue, never clocked out
ev('in','2026-10-21T05:00:00Z'),ev('out','2026-10-21T10:00:00Z'),ev('in','2026-10-21T10:30:00Z'),ev('out','2026-10-21T14:30:00Z'),
ev('in','2026-10-22T05:00:00Z'),ev('out','2026-10-22T13:00:00Z'),
ev('in','2026-09-30T05:00:00Z'),ev('out','2026-09-30T06:00:00Z'), // last month
];
const s=attendanceSummary(events,now);
assert.equal(s.status,'out');
assert.equal(s.todayMinutes,480);
assert.equal(s.weekMinutes,480+540);
assert.equal(s.monthMinutes,480+540);
assert.equal(s.daysThisMonth,3);
assert.deepEqual(s.missed.map(e=>e.scanned_at),['2026-10-20T05:00:00Z']);
assert.equal(s.days[0].day,'2026-10-22');
});
test('nobody scanned today is away',()=>{
assert.equal(attendanceSummary([],'2026-10-22T10:00:00Z').status,'away');
assert.equal(attendanceSummary([ev('in','2026-10-21T05:00:00Z')],'2026-10-22T10:00:00Z').status,'away');
});
test('period days: a day, a Monday-first week, a whole month',()=>{
assert.deepEqual(periodDays('day','2026-10-22'),['2026-10-22']);
const w=periodDays('week','2026-10-22');assert.equal(w[0],'2026-10-19');assert.equal(w[6],'2026-10-25');
assert.equal(periodDays('month','2026-02-10').length,28);assert.equal(periodDays('month','2026-10-22').at(-1),'2026-10-31');
});
test('shifting a period crosses month and year boundaries',()=>{
assert.equal(shiftPeriod('day','2026-10-31',1),'2026-11-01');
assert.equal(shiftPeriod('week','2026-10-22',-1),'2026-10-12');
assert.equal(shiftPeriod('month','2026-12-15',1),'2027-01-01');
assert.equal(shiftPeriod('month','2026-01-31',-1),'2025-12-01');
});