WFDASH-WEBAPP/scripts/migrations/2026-07-09-po-pdf-tokens.ts

92 lines
2.8 KiB
TypeScript

/**
* One-shot backfill — write `token` into every PurchaseOrderDocumentEntry
* in `purchase_orders.documents_history` that doesn't have one yet.
*
* Why this exists
* ────────────────
* The PO PDF route used to store the human-readable filename in
* `documents_history[i].token`, which then 404'd when PB's file proxy
* was asked to resolve it. The fix in the route handler stores the
* real on-disk token (the slug PB returns from the upload) instead —
* but legacy rows created before that change still have `file` and no
* `token`, so the new GET handler would 404 on them.
*
* Strategy:
* - For each PO row, read `documents_history` and `documents`.
* - For each entry missing `token`, look up `documents[idx]` (or
* `documents[0]` as a fallback). The two arrays are kept in sync
* by the POST handler, so index alignment is correct.
* - Patch the row with the new history array.
*
* Idempotent — re-running this script is a no-op once every entry has
* a token.
*
* Run with (from WFDASH_NEXTJS so .env.local is found):
* npx tsx scripts/migrations/2026-07-09-po-pdf-tokens.ts
*/
import { getAdminPocketBase } from "@/lib/server/pocketbase-admin";
type Entry = {
version: number;
file: string;
token?: string;
generatedAt: string;
generatedBy: string;
reason: string;
status: "current" | "superseded";
};
type PoRow = {
id: string;
documents?: string[] | string;
documents_history?: Entry[];
};
async function main() {
const pb = await getAdminPocketBase();
let patchedRows = 0;
let patchedEntries = 0;
let unresolvable = 0;
const list = await pb.collection("purchase_orders").getList<PoRow>(1, 500, {
fields: "id,documents,documents_history",
filter: "documents_history != null",
});
for (const po of list.items) {
const history = po.documents_history ?? [];
if (history.length === 0) continue;
const docsRaw = po.documents ?? [];
const docs = Array.isArray(docsRaw) ? docsRaw : docsRaw ? [docsRaw] : [];
let changed = false;
const newHistory = history.map((entry, idx) => {
if (entry.token) return entry;
const token = docs[idx] || docs[0] || "";
if (!token) {
unresolvable += 1;
return entry;
}
changed = true;
return { ...entry, token };
});
if (changed) {
await pb.collection("purchase_orders").update(po.id, {
documents_history: newHistory,
});
patchedRows += 1;
patchedEntries += newHistory.filter((e) => e.token).length - history.filter((e) => e.token).length;
}
}
console.log(
`[po-pdf-tokens] rows patched=${patchedRows} entries patched=${patchedEntries} unresolvable=${unresolvable}`,
);
}
main().catch((err) => {
console.error("[po-pdf-tokens] failed:", err);
process.exit(1);
});