46 lines
2.8 KiB
TypeScript
46 lines
2.8 KiB
TypeScript
|
|
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()};
|
||
|
|
}
|