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 { 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({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()}; }