This commit overhauls the order management system to use item-level status tracking instead of a single order-level status: - Remove deprecated `orders.status` field and add `invoiced` and quote match audit fields - Add `isOrderComplete` utility with tests to validate fully delivered orders - Add purchase order PDF rendering support and tooling - Add geo coordinate fields for companies/customers with map link UI - Add REST management APIs for customers, companies, and suppliers - Update customer/company services to use relation fields instead of free text - Add status pill and tab filter components for item status visualization - Add quote-to-order matching logic and unit tests - Update all pages, components, and tests to match the new schema - Include all required PocketBase migrations and schema updates
75 lines
No EOL
2.4 KiB
TypeScript
75 lines
No EOL
2.4 KiB
TypeScript
/**
|
|
* One-shot migration: add `rep` (text, optional) to the `orders` collection.
|
|
*
|
|
* Run with:
|
|
*
|
|
* npx tsx scripts/migrations/2026-06-26-add-orders-rep.ts
|
|
*
|
|
* What this does (idempotent — safe to re-run):
|
|
*
|
|
* 1. Reads the `orders` collection schema.
|
|
* 2. If a `rep` text field already exists, prints "already present" and exits.
|
|
* 3. Otherwise appends a new text field named `rep` (no max length,
|
|
* not required, default empty string) and PATCHes the collection.
|
|
*
|
|
* The field is intentionally free-text (not a relation) to keep it loose:
|
|
* reps can be referenced by name from the customer's `rep` field, or typed
|
|
* ad-hoc on company-only orders where no customer record exists. PB will
|
|
* accept null/empty values on existing rows.
|
|
*
|
|
* Rollback: delete the `rep` field from the orders collection in the PB
|
|
* admin UI. No data is lost because no rows have it yet.
|
|
*/
|
|
import PocketBase from "pocketbase";
|
|
|
|
const PB_URL = process.env.NEXT_PUBLIC_POCKETBASE_URL || "http://127.0.0.1:8090";
|
|
const ADMIN_EMAIL = process.env.PB_ADMIN_EMAIL;
|
|
const ADMIN_PASSWORD = process.env.PB_ADMIN_PASSWORD;
|
|
|
|
async function main() {
|
|
if (!ADMIN_EMAIL || !ADMIN_PASSWORD) {
|
|
throw new Error(
|
|
"Set PB_ADMIN_EMAIL and PB_ADMIN_PASSWORD env vars (e.g. sourced from .env.local)."
|
|
);
|
|
}
|
|
|
|
const pb = new PocketBase(PB_URL);
|
|
await pb.collection("_superusers").authWithPassword(ADMIN_EMAIL, ADMIN_PASSWORD);
|
|
console.log(`[orders-rep] Authenticated against ${PB_URL}`);
|
|
|
|
const orders = await pb.collections.getOne("orders");
|
|
const hasRep = orders.fields.some(
|
|
(f: { name: string }) => f.name === "rep"
|
|
);
|
|
|
|
if (hasRep) {
|
|
console.log("[orders-rep] orders.rep already exists — skipping.");
|
|
return;
|
|
}
|
|
|
|
await pb.collections.update(orders.id, {
|
|
fields: [
|
|
...orders.fields,
|
|
{
|
|
name: "rep",
|
|
type: "text",
|
|
required: false,
|
|
presentable: false,
|
|
// No max length — operator can type a full name or rep id.
|
|
// PB text fields without `max` accept long values; we don't
|
|
// need to constrain it for this field.
|
|
max: 0,
|
|
min: 0,
|
|
pattern: "",
|
|
autogeneratePattern: "",
|
|
},
|
|
],
|
|
});
|
|
|
|
console.log("[orders-rep] Added orders.rep (text, optional). Migration complete.");
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error("[orders-rep] Migration failed:", err);
|
|
process.exit(1);
|
|
}); |