feat(po-pdf): add token backfill migration script
This commit is contained in:
parent
f146e42ed0
commit
5d222ff281
2 changed files with 95 additions and 1 deletions
|
|
@ -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",
|
||||
|
|
|
|||
92
scripts/migrations/2026-07-09-po-pdf-tokens.ts
Normal file
92
scripts/migrations/2026-07-09-po-pdf-tokens.ts
Normal 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);
|
||||
});
|
||||
Loading…
Reference in a new issue