WFDASH-WEBAPP/scripts/migrations/2026-07-07-order-items-ready-for-collection.ts

134 lines
4.6 KiB
TypeScript
Raw Normal View History

/**
* 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<string, unknown>;
}>).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);
});