MEGAINFRA-WEBAPP/scripts/migrations/2026-10-17-clock-scan-location.mts
DEls 0a7138f60c fix: harden workforce, plant and financials
Improve workforce scanning, site transfers and timesheet exports.
Audit plant imports, duplicate identities, rates and dashboard totals.
Repair delivery PO selection, periodic rechecks and receiving guards.
Align financial ledgers, supplier periods and mobile layouts.
Add the site map preview, shared theme controls and audit evidence.

Verify 3,455 application tests, six fuel export tests, TypeScript and
an optimized production build. Clean up a leaked test timer and keep
local worktrees and generated reports out of ESLint traversal.

Changed-file lint passes; repository-wide lint retains 33 existing
errors in unchanged files.
2026-10-19 13:18:31 +02:00

40 lines
2.6 KiB
TypeScript

/** Add scan GPS and transfer metadata, preserving all existing schema fields.
* DRY RUN: node --env-file=.env.local node_modules/tsx/dist/cli.mjs scripts/migrations/2026-10-17-clock-scan-location.mts
* APPLY: append --apply --backup /absolute/path/clock-events-schema.json
* Only schema changes; never changes worker, event or hour records.
*/
import PocketBase from "pocketbase";
import { writeFile, mkdir } from "node:fs/promises";
import { dirname } from "node:path";
const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL);
if (process.env.POCKETBASE_ADMIN_TOKEN) pb.authStore.save(process.env.POCKETBASE_ADMIN_TOKEN);
else await pb.collection("_superusers").authWithPassword(process.env.POCKETBASE_ADMIN_EMAIL!,process.env.POCKETBASE_ADMIN_PASSWORD!);
const collection = await pb.collections.getOne("clock_events");
const additions = [
{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},
{name:"is_transfer",type:"bool"},
{name:"previous_unclosed_in",type:"relation",collectionId:collection.id,maxSelect:1,cascadeDelete:false},
{name:"supervisor_note",type:"text",max:2000},
{name:"office_note",type:"text",max:2000},
{name:"office_resolved_at",type:"date"},
{name:"office_resolved_by",type:"text",max:128},
];
for (const field of additions) {
const existing = collection.fields.find((item:{name:string})=>item.name===field.name);
if (existing && existing.type !== field.type) throw new Error(`Unexpected type for ${field.name}`);
}
const missing = additions.filter(field=>!collection.fields.some((item:{name:string})=>item.name===field.name));
console.log(JSON.stringify({mode:process.argv.includes("--apply")?"apply":"dry-run",collection:collection.name,addFields:missing.map(field=>field.name)}));
if (process.argv.includes("--apply") && missing.length) {
const backup = process.argv[process.argv.indexOf("--backup")+1];
if (!process.argv.includes("--backup") || !backup) throw new Error("--apply requires --backup PATH");
await mkdir(dirname(backup),{recursive:true});
await writeFile(backup,JSON.stringify(collection,null,2),{flag:"wx"});
await pb.collections.update(collection.id,{fields:[...collection.fields,...missing]});
const verified = await pb.collections.getOne(collection.id);
for (const field of additions) if (!verified.fields.some((item:{name:string;type:string})=>item.name===field.name&&item.type===field.type)) throw new Error(`Schema verification failed: ${field.name}`);
console.log("Verified additive schema update. No attendance records were changed.");
}