WFDASH-WEBAPP/scripts/migrations/2026-07-06-order-items-collection-pool-rename.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

138 lines
No EOL
4.9 KiB
TypeScript

/**
* 2026-07-06-order-items-collection-pool-rename.ts
*
* Rename `item_status` value `driver_pool` → `collection_pool` on every
* existing order_items row, so the persisted state matches the UI label
* ("Collection Pool") and the new write path in
* `purchase-orders-service.ts:recordPurchaseOrderSendOutcome`.
*
* Background:
* The internal `item_status` enum value was historically `driver_pool`
* because the related `collection_pool` table was the canonical
* source-of-truth for queued-for-collection lines. That table was
* later renamed to `DO_NOT_USE_collection_pool` (and is no longer
* used), but the `item_status` value on order_items was never
* renamed to match. Result: UI calls this state "Collection Pool"
* but the underlying value is `driver_pool`. New writes (post-rename
* code) produce `collection_pool`, so old rows show up on the wrong
* filter tab / count until migrated.
*
* This script does TWO things, both idempotent:
* 1. Ensure the schema's `item_status.values` includes `collection_pool`
* (and not `driver_pool`). If `driver_pool` is missing or the
* field's options are stale, PB rejects the row updates.
* 2. Update every existing row from `driver_pool` → `collection_pool`.
*
* Row updates are batched at 200 per call to stay under the 5MB payload
* limit on bulk updates.
*/
import { getAdminPocketBase } from "@/lib/server/pocketbase-admin";
const ORDER_ITEMS = "order_items";
const BATCH_SIZE = 200;
/** Target enum values for `item_status`. Must stay in sync with
* `src/lib/types.ts:ItemStatus` and `src/lib/server/order-item-status.ts`. */
const TARGET_ITEM_STATUS_VALUES = [
"pending",
"processed",
"po_generated",
"collection_pool",
"assigned",
"collected",
"stock_verified",
"picking",
"ready_delivery",
"delivered",
"cancelled",
];
export async function run(): Promise<void> {
const pb = await getAdminPocketBase();
// ── 1. Reconcile the schema's select options ───────────────────
// PB rejects row updates when the value being written isn't in
// the field's `values` array. Add `collection_pool` first (so the
// migration below can write it), and remove `driver_pool` from
// the enum so no NEW rows can land on the old name. PB keeps the
// existing `driver_pool` rows untouched until step 2 flips them.
const coll = await pb.collections.getOne(ORDER_ITEMS);
const fields = (coll.fields ?? []) as Array<{
name?: string;
values?: string[];
maxSelect?: number;
}>;
let schemaChanged = false;
for (const f of fields) {
if (f.name !== "item_status") continue;
const current = new Set(f.values ?? []);
const target = new Set(TARGET_ITEM_STATUS_VALUES);
const needsAdd = [...target].some((v) => !current.has(v));
const needsRemove = [...current].some((v) => !target.has(v));
if (needsAdd || needsRemove) {
f.values = [...target];
schemaChanged = true;
}
break;
}
if (schemaChanged) {
await pb.collections.update(ORDER_ITEMS, { fields });
console.log(
"[migration:collection-pool-rename] schema reconciled — " +
"item_status.values now: " +
JSON.stringify(TARGET_ITEM_STATUS_VALUES)
);
}
// ── 2. Update every row stuck on the old name ──────────────────
let page = 1;
let totalUpdated = 0;
let totalFailed = 0;
// eslint-disable-next-line no-constant-condition
while (true) {
const batch = await pb.collection(ORDER_ITEMS).getList(page, BATCH_SIZE, {
filter: `item_status = "driver_pool"`,
fields: "id,item_status",
});
if (batch.items.length === 0) break;
for (const row of batch.items) {
try {
await pb.collection(ORDER_ITEMS).update(row.id, {
item_status: "collection_pool",
});
totalUpdated++;
} catch (err) {
// Don't abort the whole migration on a single bad row — log
// and continue. PB's row-level errors are rare but possible
// (concurrent edit, missing field, etc.).
const msg = err instanceof Error ? err.message : String(err);
console.warn(
`[migration:collection-pool-rename] failed to update ${row.id}: ${msg}`
);
totalFailed++;
}
}
if (batch.items.length < BATCH_SIZE) break;
page++;
}
// ── 3. Verify ───────────────────────────────────────────────────
const remaining = await pb.collection(ORDER_ITEMS).getList(1, 1, {
filter: `item_status = "driver_pool"`,
});
console.log(
`[migration:collection-pool-rename] done — updated ${totalUpdated}, ` +
`failed ${totalFailed}, remaining on driver_pool: ${remaining.totalItems}`
);
}
if (require.main === module) {
run().catch((err) => {
console.error("[migration:collection-pool-rename] fatal:", err);
process.exit(1);
});
}