WFDASH-WEBAPP/scripts/migrations/2026-07-07-order-items-ready-for-collection.ts
SJCoetz 7f924ccc45 refactor: switch hardcoded UI colors to semantic theme tokens
Overhaul the app's color system by replacing all hardcoded Tailwind color classes with semantic theme variables to ensure consistent theming across all components, pages, and tests.

Additional changes:
- Add assigned date support for order item status updates: add `assignedDate` field to the order item status PATCH API, implement server-side normalization/validation, and add corresponding tests
- Refactor purchase order PDF workflows to use the consolidated `purchase_orders.documents` field instead of the legacy separate `purchase_order_pdfs` collection
- Fix quote number parsing and formatting to support both dashed (Q-YYYY-MM-NNN) and dashless (QYYYYMMNNN) formats
- Rename `driver_pool` item status to `collection_pool` across the codebase
- Add null handling for order item quantities and pricing to display "—" for missing values
- Improve filter tab styling: make mobile tabs scrollable, prevent text wrapping, hide scrollbar
- Add new utility scripts, API routes, migrations, and polish small UI/UX fixes including tsconfig updates and theme hook improvements
2026-07-07 17:22:03 +02:00

133 lines
4.6 KiB
TypeScript

/**
* 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);
});