// scripts/migrations/2026-07-16-add-stock-purchase-documents-history.ts // // Adds a `documents_history` JSON field to `stock_purchases` so the // stock-PO PDF route can append per-regeneration audit entries (file // name, version, generatedAt, generatedBy, reason) — same pattern as // the customer-PO `purchase_orders.documents_history` field added by // 2026-07-06. // // We need this so the stock-PO PDF route can keep parallel structure // with the customer-PO PDF route (the file proxy + download path // reads from `documents_history` to find the versioned PDF). Stock // POs are simpler in v1 — typically generated once + sent — but // adding the field now means the route code stays parallel and we // don't have to migrate again when stock-PO cost edits / re-sends // become a thing. // // Idempotent: re-running is a no-op when the field is already // present. /* eslint-disable */ import { readFileSync } from "node:fs"; import { resolve } from "node:path"; import PocketBase from "pocketbase"; function loadEnvFile(name: string) { try { const raw = readFileSync(resolve(process.cwd(), name), "utf8"); for (const line of raw.split("\n")) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith("#")) continue; const m = trimmed.match(/^([A-Z0-9_]+)\s*=\s*(.+)$/i); if (m && !process.env[m[1]]) { process.env[m[1]] = m[2].replace(/^["']|["']$/g, "").trim(); } } } catch { // .env.local doesn't exist — fall through; PB env vars may be // set in the shell. } } loadEnvFile(".env.local"); const COLLECTION_NAME = "stock_purchases"; const FIELD_NAME = "documents_history"; async function main() { const url = process.env.NEXT_PUBLIC_POCKETBASE_URL; const email = process.env.PB_ADMIN_EMAIL; const password = process.env.PB_ADMIN_PASSWORD; if (!url || !email || !password) { throw new Error( "NEXT_PUBLIC_POCKETBASE_URL, PB_ADMIN_EMAIL, PB_ADMIN_PASSWORD must be set.", ); } const pb = new PocketBase(url); await pb.collection("_superusers").authWithPassword(email, password); const collection = await pb.collections.getOne(COLLECTION_NAME); const existing = (collection.fields ?? []) as Array<{ name: string }>; if (existing.some((f) => f.name === FIELD_NAME)) { console.log( `[stock-purchase-documents-history] field already present on ${COLLECTION_NAME}`, ); return; } // Same shape as the customer-PO `documents_history` field: // { version: number, file: string, token: string, // generatedAt: string, generatedBy: string, reason: string, // status: "current" | "superseded" } // The route writes a fresh entry on each regen + flips older // "current" entries to "superseded". const newField = { name: FIELD_NAME, type: "json", required: false, presentable: false, maxSize: 0, // no PB-side cap }; await pb.collections.update(collection.id, { fields: [...existing, newField], }); console.log( `[stock-purchase-documents-history] created documents_history (json) on ${COLLECTION_NAME}`, ); } main().catch((err) => { console.error("[stock-purchase-documents-history] failed:", err); process.exit(1); });