- Added an audit trail feature that logs every create, update, and delete action on delivery notes and orders, capturing detailed changes per field. - Introduced the `AuditTimeline` component to display the history of changes for delivery notes and orders, enhancing traceability. - Updated the `page.tsx` files for delivery notes and orders to fetch and display audit logs, ensuring users can view the history of modifications. - Enhanced the `package.json` with a new migration script for setting up the audit log functionality. This feature improves accountability and transparency in the management of delivery notes and orders.
113 lines
4 KiB
TypeScript
113 lines
4 KiB
TypeScript
/**
|
|
* One-shot migration: create the `place_overrides` collection.
|
|
*
|
|
* Run with:
|
|
*
|
|
* npx tsx scripts/migrations/2026-09-28-create-place-overrides.ts
|
|
*
|
|
* The operator's decisions about the places a vehicle tracker reports:
|
|
* which company a location belongs to, what to call it, and — because
|
|
* assigning the same company to two nearby locations merges their hours
|
|
* automatically — how to merge them.
|
|
*
|
|
* Rows are keyed by COORDINATES, not by the report's internal cluster id.
|
|
* That matters: clusters are recomputed on every read and their centroid
|
|
* drifts, so a stored cluster key would go stale. Matching an override to a
|
|
* cluster by proximity instead means a decision keeps working even after
|
|
* the underlying GPS jitters.
|
|
*
|
|
* Read side: `src/lib/server/rep-time-log-service.ts` applies overrides
|
|
* before labelling. Written by
|
|
* `POST /api/v1/reports/rep-performance/[repId]/time-log/places`.
|
|
*
|
|
* Idempotent — safe to re-run; exits early if the collection exists.
|
|
*/
|
|
import "./_env-loader";
|
|
import PocketBase from "pocketbase";
|
|
|
|
const PB_URL = process.env.NEXT_PUBLIC_POCKETBASE_URL || "http://127.0.0.1:8090";
|
|
const ADMIN_EMAIL = process.env.PB_ADMIN_EMAIL;
|
|
const ADMIN_PASSWORD = process.env.PB_ADMIN_PASSWORD;
|
|
|
|
const text = (name: string, max: number, required = false) => ({
|
|
name,
|
|
type: "text",
|
|
required,
|
|
max,
|
|
});
|
|
|
|
async function main() {
|
|
if (!ADMIN_EMAIL || !ADMIN_PASSWORD) {
|
|
throw new Error(
|
|
"Set PB_ADMIN_EMAIL and PB_ADMIN_PASSWORD env vars (e.g. sourced from .env.local).",
|
|
);
|
|
}
|
|
|
|
const pb = new PocketBase(PB_URL);
|
|
await pb.collection("_superusers").authWithPassword(ADMIN_EMAIL, ADMIN_PASSWORD);
|
|
console.log(`[place-overrides] Authenticated against ${PB_URL}`);
|
|
|
|
const existing = await pb.collections
|
|
.getFullList()
|
|
.then((all) => all.find((c) => c.name === "place_overrides"));
|
|
|
|
if (existing) {
|
|
console.log("[place-overrides] place_overrides already exists — skipping.");
|
|
return;
|
|
}
|
|
|
|
await pb.collections.create({
|
|
name: "place_overrides",
|
|
type: "base",
|
|
// The operator is authenticated; reads happen through the admin client
|
|
// in the service, but the rule keeps the collection usable from the app.
|
|
listRule: "@request.auth.id != ''",
|
|
viewRule: "@request.auth.id != ''",
|
|
createRule: null,
|
|
updateRule: null,
|
|
deleteRule: null,
|
|
fields: [
|
|
// The place's anchor point. An override is matched to a tracker cluster
|
|
// within ~300 m, so small centroid drift is absorbed.
|
|
{ name: "lat", type: "number", required: true },
|
|
{ name: "lon", type: "number", required: true },
|
|
// Optional custom name, e.g. "Blyvoor main gate". Takes effect when no
|
|
// company is set, or as a display suffix when one is.
|
|
text("label", 200),
|
|
// The company this location belongs to. Setting the same company on two
|
|
// locations is how a MERGE is expressed: the report then rolls both up
|
|
// under one company key and their hours add together.
|
|
{
|
|
name: "company",
|
|
type: "relation",
|
|
required: false,
|
|
collectionId: "pbc_3866053794",
|
|
cascadeDelete: false,
|
|
minSelect: 0,
|
|
maxSelect: 1,
|
|
},
|
|
// The tracker's own address for this spot, kept purely as a hint in the
|
|
// admin UI so an operator can tell overrides apart.
|
|
text("source_address", 300),
|
|
// Who made the change, for the audit trail.
|
|
text("actor_id", 32),
|
|
text("actor_name", 200),
|
|
{ name: "created", type: "autodate", onCreate: true, onUpdate: false },
|
|
{ name: "updated", type: "autodate", onCreate: true, onUpdate: true },
|
|
],
|
|
indexes: [
|
|
// Proximity lookups happen in memory after a full read, so an index is
|
|
// only needed to keep a future location filter cheap.
|
|
"CREATE INDEX idx_place_overrides_company ON place_overrides (company)",
|
|
],
|
|
});
|
|
|
|
console.log(
|
|
"[place-overrides] Created place_overrides collection. Migration complete.",
|
|
);
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error("[place-overrides] Migration failed:", err);
|
|
process.exit(1);
|
|
});
|