/** * Migration — add `ready_for_collection` (bool) to `order_items`. * * Why: * The collection-pool flow needs a single toggle that says "the * supplier has confirmed this is ready to be collected" so the * dashboard can filter the pool into "ready to dispatch" vs * "still waiting on supplier" and the driver app can hide rows * the operator hasn't yet approved. This sits on top of the * existing `item_status` enum (which tracks the *who*, not the * *when-is-it-physically-ready*). * * Lifecycle: * - On the SMTP outcome callback that flips items into the * collection pool, `ready_for_collection` lands as `false` * (the supplier hasn't confirmed yet). * - Operator manually flips to `true` from the dashboard's * collection-pool page or the collection-map popover. * - Driver app's `getMyAssignedItems` and `getPoolItems` * filter on `ready_for_collection = true` so unready rows * never reach the driver. * * Backfill — every existing `collection_pool` row gets * `ready_for_collection = true` so today's rows keep flowing to * the driver app without operator intervention. New rows from the * route land `false` and require a manual flip. * * Idempotent — skips if `ready_for_collection` already exists. * * Run with: * npx tsx scripts/migrations/2026-07-07-order-items-ready-for-collection.ts */ /* eslint-disable */ import PocketBase from "pocketbase"; import { readFileSync } from "node:fs"; import { resolve } from "node:path"; 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 { /* fine — rely on shell env */ } } loadEnvFile(".env"); loadEnvFile(".env.local"); 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( "Set NEXT_PUBLIC_POCKETBASE_URL, PB_ADMIN_EMAIL, PB_ADMIN_PASSWORD (e.g. sourced from .env.local).", ); } const pb = new PocketBase(url); await pb.collection("_superusers").authWithPassword(email, password); console.log(`[order-items-ready-for-collection] Authenticated against ${url}`); const col = await pb.collections.getOne("order_items"); const fields = (col.fields as Array<{ name: string; type: string; required?: boolean; options?: Record; }>).slice(); if (fields.some((f) => f.name === "ready_for_collection")) { console.log( "[order-items-ready-for-collection] ready_for_collection already exists — skip field add", ); } else { fields.push({ name: "ready_for_collection", type: "bool", required: false, options: {}, }); await pb.collections.update(col.id, { fields }); console.log( "[order-items-ready-for-collection] added `ready_for_collection` (bool, optional) to order_items", ); } // Backfill — flip every existing collection_pool row to ready=true so // drivers keep seeing today's items without operator intervention. // New rows from recordPurchaseOrderSendOutcome land ready=false. const pool = await pb.collection("order_items").getFullList({ filter: 'item_status = "collection_pool"', fields: "id,item_status,ready_for_collection", }); let backfilled = 0; let skipped = 0; for (const row of pool) { if (row.ready_for_collection === true) { skipped++; continue; } await pb.collection("order_items").update(row.id, { ready_for_collection: true, }); backfilled++; } console.log( `[order-items-ready-for-collection] backfill — ${backfilled} rows set to ready=true, ${skipped} already true`, ); const fresh = await pb.collections.getOne("order_items"); const verify = (fresh.fields as Array<{ name: string; type: string }>) .filter((f) => f.name === "ready_for_collection") .map((f) => `${f.name}:${f.type}`) .join(", "); console.log( `[order-items-ready-for-collection] Post-update → ${verify || "FIELD MISSING"}`, ); console.log("[order-items-ready-for-collection] Migration complete."); } main().catch((err) => { console.error("[order-items-ready-for-collection] Migration failed:", err); process.exit(1); });