- Workforce: Employees register, per-employee dashboards, Attendance table - Plant Clock: scan-only clock, bottom tab bar (Employees / Oil & Diesel / Job cards) - Oil & Diesel stock: diesel tank (650 L, opening 589 L), level overwrite dialog, QR on each card, book-out screen with searchable vehicle picker, filterable "Booked out" table (today/week/month/all/range) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
56 lines
3 KiB
TypeScript
56 lines
3 KiB
TypeScript
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');
|
|
});
|