mega-plant/lib/workforce-store.ts

46 lines
2.8 KiB
TypeScript
Raw Normal View History

import 'server-only';
import type { ClockEvent } from './clock';
import { clockPB } from './clock-store';
/**
* One row per person on the Plant roster. Operators carry what used to live only on
* /running/operators — the machine they run and their hourly rate — so the Employees
* pages can replace that page. Mechanics are not allocated to a machine (see AGENTS.md).
*/
export type WorkforceEmployee = {
id:string; recordId:string; kind:'operators'|'mechanics';
name:string; employeeNo:string; role:string;
/** Hourly rate from the operator sheet; null for mechanics, and for an operator with none recorded. */
rate:number|null;
machine:{id:string;code:string;name:string}|null;
/** True once the rate was changed on the dashboard; the seed then keeps it over the sheet. */
rateSetInApp:boolean;
source:string;
};
export type WorkforceMachine = { id:string; code:string; name:string; onSheet:boolean };
export type WorkforceData = { employees:WorkforceEmployee[]; machines:WorkforceMachine[]; events:ClockEvent[]; asOf:string };
export async function workforceData():Promise<WorkforceData> {
const pb=await clockPB();
const [operators,mechanics,assets,events]=await Promise.all([
pb.collection('operators').getFullList({sort:'name',expand:'asset'}),
pb.collection('mechanics').getFullList({sort:'name'}),
pb.collection('assets').getFullList({sort:'code',fields:'id,code,name,on_operator_sheet'}),
pb.collection('clock_events').getFullList<ClockEvent>({sort:'-scanned_at,-id'}),
]);
const employees:WorkforceEmployee[]=[
...operators.map(o=>{
const a=o.expand?.asset as {id:string;code:string;name:string}|undefined;
return {id:`operators:${o.id}`,recordId:o.id,kind:'operators' as const,name:String(o.name||''),employeeNo:String(o.employee_code||''),role:'Operator',
rate:typeof o.rate==='number' && o.rate>0?o.rate:null,machine:a?{id:a.id,code:String(a.code||''),name:String(a.name||'')}:null,rateSetInApp:!!o.rate_set_in_app,source:String(o.source||'')};
}),
...mechanics.map(m=>({id:`mechanics:${m.id}`,recordId:m.id,kind:'mechanics' as const,name:String(m.name||''),employeeNo:String(m.employee_code||''),
role:String(m.role||'Mechanic'),rate:null,machine:null,rateSetInApp:false,source:String(m.source||'')})),
].sort((a,b)=>a.name.localeCompare(b.name));
// Actor ids, GPS and request signatures are server audit details, not page data.
const safe=events.map(({id,employee,employee_name,employee_no,site,direction,scanned_at,session_minutes,has_location})=>
({id,employee,employee_name,employee_no,site,direction,scanned_at,session_minutes,has_location,actor:'',previous_event:''}));
const machines=assets.filter(a=>a.code).map(a=>({id:a.id,code:String(a.code),name:String(a.name||''),onSheet:!!a.on_operator_sheet}));
return {employees,machines,events:safe,asOf:new Date().toISOString()};
}