WFDASH-WEBAPP/scripts/migrations/2026-07-07-backfill-assigned-rfc.ts

126 lines
4.5 KiB
TypeScript
Raw Normal View History

/**
* Backfill — flip `ready_for_collection` to `true` on every row that is
* already `item_status = "assigned"` but never had the flag set.
*
* Why this exists as a sibling to the original migration:
*
* `2026-07-07-order-items-ready-for-collection.ts` introduced the
* `ready_for_collection` bool AND backfilled every existing
* `collection_pool` row to `true`. It deliberately did NOT touch
* rows that were already `assigned` — at the time the migration
* ran, the theory was "assigned items have already been hand-vetted
* by the operator, so the flag should already be on them, so we
* don't need to backfill".
*
* That theory was wrong. In practice, several `assigned` rows had
* been created via paths that did not set the flag (direct PB admin
* edits, an older assignment workflow that pre-dated the column).
* The driver app's `getMyAssignedItems` filter requires
* `ready_for_collection = true`, so the driver sees an empty
* Assigned tab even though they have real stops in the DB.
*
* This script mirrors the original pool-backfill shape so the gap
* closes without forcing the operator to re-toggle every row in the
* dashboard.
*
* Idempotent — only flips rows where `ready_for_collection != true`.
*
* Run with (from WFDASH_NEXTJS so .env.local is found):
* npx tsx scripts/migrations/2026-07-07-backfill-assigned-rfc.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(`[backfill-assigned-rfc] Authenticated against ${url}`);
// Sanity check — make sure the column exists before we query on it.
const col = await pb.collections.getOne("order_items");
const hasField = (col.fields as Array<{ name: string }>).some(
(f) => f.name === "ready_for_collection",
);
if (!hasField) {
throw new Error(
"ready_for_collection field missing on order_items — run 2026-07-07-order-items-ready-for-collection.ts first.",
);
}
// Find every assigned row that isn't already ready.
// `!= true` matches false AND null — null is the default for rows
// created before the column existed, plus any rows where the
// boolean was never written.
const rows = await pb.collection("order_items").getFullList({
filter: 'item_status = "assigned" && ready_for_collection != true',
fields: "id,item_status,ready_for_collection,drv_id",
});
console.log(
`[backfill-assigned-rfc] candidates (assigned, rfc!=true): ${rows.length}`,
);
let flipped = 0;
let failed = 0;
for (const row of rows) {
try {
await pb.collection("order_items").update(row.id, {
ready_for_collection: true,
});
flipped++;
} catch (err) {
failed++;
console.error(
`[backfill-assigned-rfc] update failed for ${row.id}:`,
err instanceof Error ? err.message : err,
);
}
}
// Post-state snapshot — confirm the filter now returns 0.
const remaining = await pb.collection("order_items").getFullList({
filter: 'item_status = "assigned" && ready_for_collection != true',
fields: "id",
});
console.log(`[backfill-assigned-rfc] flipped: ${flipped}`);
console.log(`[backfill-assigned-rfc] failed: ${failed}`);
console.log(
`[backfill-assigned-rfc] post-state: ${remaining.length} assigned rows still have rfc!=true`,
);
console.log("[backfill-assigned-rfc] Done.");
}
main().catch((err) => {
console.error("[backfill-assigned-rfc] failed:", err);
process.exit(1);
});