46 lines
3 KiB
JavaScript
46 lines
3 KiB
JavaScript
// Additive only. Never seeds, copies, deletes or updates employee/financial rows.
|
|
// node --env-file=.env.local scripts/setup-clock.mjs [--apply]
|
|
import PocketBase from 'pocketbase';
|
|
const apply=process.argv.includes('--apply');
|
|
try {
|
|
const pb=new PocketBase(process.env.NEXT_PUBLIC_PB_URL);
|
|
pb.autoCancellation(false);
|
|
await pb.collection('_superusers').authWithPassword(process.env.PB_ADMIN,process.env.PB_PASS);
|
|
const sites=await pb.collections.getOne('sites');
|
|
const users=await pb.collections.getOne('users');
|
|
const schemas=[
|
|
{name:'clock_events',fields:[
|
|
{name:'employee',type:'text',required:true},
|
|
{name:'employee_no',type:'text',required:true},{name:'employee_name',type:'text',required:true},
|
|
{name:'site',type:'relation',collectionId:sites.id,required:true,maxSelect:1,cascadeDelete:false},
|
|
{name:'direction',type:'select',required:true,maxSelect:1,values:['in','out']},
|
|
{name:'scanned_at',type:'date',required:true},
|
|
{name:'actor',type:'relation',collectionId:users.id,required:true,maxSelect:1,cascadeDelete:false},
|
|
{name:'previous_event',type:'text'},{name:'request_signature',type:'text',required:true},
|
|
{name:'session_minutes',type:'number',min:0,onlyInt:true},
|
|
{name:'has_location',type:'bool'}, {name:'latitude',type:'number',min:-90,max:90},
|
|
{name:'longitude',type:'number',min:-180,max:180},{name:'location_accuracy_m',type:'number',min:0},
|
|
], indexes:[
|
|
'CREATE INDEX idx_plant_clock_employee_time ON clock_events (employee, scanned_at DESC)',
|
|
'CREATE UNIQUE INDEX idx_plant_clock_previous ON clock_events (employee, previous_event)',
|
|
]},
|
|
{name:'clock_scan_locks',fields:[{name:'employee',type:'text',required:true},{name:'created',type:'autodate',onCreate:true,onUpdate:false}],
|
|
indexes:['CREATE UNIQUE INDEX idx_plant_clock_lock ON clock_scan_locks (employee)']},
|
|
];
|
|
for(const schema of schemas) {
|
|
let existing;
|
|
try {existing=await pb.collections.getOne(schema.name);}catch(e){if(e.status!==404)throw e;}
|
|
if(existing) {
|
|
const missing=schema.fields.filter(f=>!existing.fields.some(h=>h.name===f.name && h.type===f.type));
|
|
if(missing.length || schema.indexes.some(i=>!existing.indexes.includes(i)) || ['listRule','viewRule','createRule','updateRule','deleteRule'].some(r=>existing[r]!==null)) {
|
|
throw new Error(`${schema.name} schema/rules differ from the clock contract; review before changing existing data.`);
|
|
}
|
|
console.log(`${schema.name}: already ready`);continue;
|
|
}
|
|
if(!apply){console.log(`${schema.name}: would create private collection`);continue;}
|
|
await pb.send('/api/collections',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({
|
|
type:'base',...schema,listRule:null,viewRule:null,createRule:null,updateRule:null,deleteRule:null,
|
|
})});
|
|
console.log(`${schema.name}: created (server access only)`);
|
|
}
|
|
} catch(e) {console.error('Clock setup failed:',e.status??'',e.message);process.exitCode=1;}
|