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
106 lines
3.5 KiB
TypeScript
106 lines
3.5 KiB
TypeScript
/**
|
|
* Migration — add `assigned_at` text field to `order_items`.
|
|
*
|
|
* Why:
|
|
* The assignment flow (operator picks a driver for a non-in-stock
|
|
* order item) needs a server-side timestamp that other surfaces —
|
|
* the driver app, the order detail page, future reporting — can
|
|
* sort and filter on without trusting `updated`. We already have
|
|
* `drv_id` (relation) and `drv_status` on the row; adding a
|
|
* dedicated `assigned_at` keeps the data model honest.
|
|
*
|
|
* `item_status` and `drv_status` already cover the boolean-ish
|
|
* state ("is this assigned right now"). `assigned_at` is the
|
|
* "when did the most recent assignment happen" timestamp — needed
|
|
* for downstream reports (per-driver collection velocity, SLA
|
|
* monitoring) and for re-assignment history if we add a log table
|
|
* later.
|
|
*
|
|
* Per-line status lifecycle stays on `item_status` (the existing
|
|
* enum: po_generated → assigned → collected). This field is pure
|
|
* metadata. Do not use it to derive "is this assigned" — check
|
|
* `item_status === "assigned"` for that.
|
|
*
|
|
* Idempotent — skips if `assigned_at` already exists.
|
|
*
|
|
* Run with:
|
|
* npx tsx scripts/migrations/2026-07-06-order-items-assigned-at.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-assigned-at] 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 === "assigned_at")) {
|
|
console.log(
|
|
"[order-items-assigned-at] assigned_at already exists — skip",
|
|
);
|
|
console.log("[order-items-assigned-at] Migration complete (no-op).");
|
|
return;
|
|
}
|
|
|
|
fields.push({
|
|
name: "assigned_at",
|
|
type: "date",
|
|
required: false,
|
|
options: {},
|
|
});
|
|
|
|
await pb.collections.update(col.id, { fields });
|
|
console.log(
|
|
"[order-items-assigned-at] added `assigned_at` (date, optional) to order_items",
|
|
);
|
|
|
|
const fresh = await pb.collections.getOne("order_items");
|
|
const verify = (fresh.fields as Array<{ name: string; type: string }>)
|
|
.filter((f) => f.name === "assigned_at")
|
|
.map((f) => `${f.name}:${f.type}`)
|
|
.join(", ");
|
|
console.log(`[order-items-assigned-at] Post-update → ${verify}`);
|
|
console.log("[order-items-assigned-at] Migration complete.");
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error("[order-items-assigned-at] Migration failed:", err);
|
|
process.exit(1);
|
|
});
|