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
138 lines
No EOL
4.9 KiB
TypeScript
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);
|
|
});
|
|
} |