feat(po-pdf): add token backfill migration script

This commit is contained in:
SJCoetz 2026-07-09 22:28:50 +02:00
parent f146e42ed0
commit 5d222ff281
2 changed files with 95 additions and 1 deletions

View file

@ -29,7 +29,8 @@
"pb:migrate:price-list-sku-normalised": "npx tsx scripts/migrations/2026-06-30-add-price-list-sku-normalised.ts",
"pb:migrate:quote-items-sku": "npx tsx scripts/migrations/2026-06-30-add-quote-items-sku-and-pricing.ts",
"pb:migrate:rfq-outbound": "npx tsx scripts/migrations/2026-06-30-create-rfq-outbound.ts",
"pb:migrate:sprint-categories": "npx tsx scripts/migrations/2026-07-02-add-sprint-categories-and-claims.ts",
"pb:migrate:sprint-categories": "npx tsx scripts/migrations/2026-07-02-add-sprint-categories-and-claims.ts",
"pb:migrate:po-pdf-tokens": "npx tsx scripts/migrations/2026-07-09-po-pdf-tokens.ts",
"smoke:counter": "npx tsx scripts/smoke/counter.ts",
"smoke:counter-concurrent": "npx tsx scripts/smoke/counter-concurrent.ts",
"smoke:create-from-email": "npx tsx scripts/smoke/create-from-email.ts",
@ -56,6 +57,7 @@
"lucide-react": "^1.16.0",
"next": "16.2.6",
"next-themes": "^0.4.6",
"pdf-lib": "^1.17.1",
"pocketbase": "^0.26.9",
"react": "19.2.4",
"react-dom": "19.2.4",

View file

@ -0,0 +1,92 @@
/**
* 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);
});