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
103 lines
No EOL
3.6 KiB
TypeScript
103 lines
No EOL
3.6 KiB
TypeScript
/* eslint-disable */
|
|
/**
|
|
* Migration: add `coordinates` (geoPoint) to the `companies` collection.
|
|
*
|
|
* Why this exists
|
|
* ───────────────
|
|
* Companies need a location for future routing / delivery work (we'll
|
|
* either query nearest-company for order allocation, or build a small
|
|
* internal delivery app so the driver team can see where they're going).
|
|
* Today the field is unstored; tomorrow it's the foundation of those
|
|
* features.
|
|
*
|
|
* Schema
|
|
* ──────
|
|
* companies.coordinates geoPoint (optional in form)
|
|
*
|
|
* Why `geoPoint` and not `text "lat, lng"`
|
|
* ────────────────────────────────────────
|
|
* Decision (operator): use the native PB `geoPoint` type. Customers +
|
|
* suppliers store coordinates as combined text (`"-26.123, 28.456"`)
|
|
* with a `parseCoordinates` helper on the supplier map. Companies
|
|
* diverge on purpose so we can use PB's `within` filter for radius
|
|
* queries without re-parsing. If the project later wants a single
|
|
* convention across all three collections, that's a separate migration.
|
|
*
|
|
* PB geoPoint gotcha
|
|
* ──────────────────
|
|
* The field is non-nullable with a zero default of `{"lon":0,"lat":0}`
|
|
* ("Null Island" in the Atlantic). UI must treat `{0, 0}` as empty,
|
|
* otherwise every company shows up at sea. Form submits also send
|
|
* `{0, 0}` for blank — the UI is what hides it.
|
|
*
|
|
* Idempotent — safe to re-run.
|
|
*
|
|
* Run with:
|
|
* npx tsx scripts/migrations/2026-06-28-add-companies-coordinates.ts
|
|
*/
|
|
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 {
|
|
// missing file — fine
|
|
}
|
|
}
|
|
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. from .env.local)."
|
|
);
|
|
}
|
|
|
|
const pb = new PocketBase(url);
|
|
await pb.collection("_superusers").authWithPassword(email, password);
|
|
console.log(`[companies-coordinates] Authenticated against ${url}`);
|
|
|
|
const companies = await pb.collections.getOne("companies");
|
|
|
|
if (companies.fields.some((f) => f.name === "coordinates")) {
|
|
console.log("[companies-coordinates] companies.coordinates already present — skipping.");
|
|
return;
|
|
}
|
|
|
|
await pb.collections.update(companies.id, {
|
|
fields: [
|
|
...companies.fields,
|
|
{
|
|
// Native PB geoPoint. Stored as JSON {"lon": <num>, "lat": <num>}.
|
|
// Zero default is Null Island {0,0}; the UI treats that as empty.
|
|
// No `options` block — geoPoint doesn't take one in PB 0.26+.
|
|
name: "coordinates",
|
|
type: "geoPoint",
|
|
required: false,
|
|
presentable: false,
|
|
},
|
|
],
|
|
});
|
|
console.log("[companies-coordinates] Added companies.coordinates (geoPoint).");
|
|
|
|
console.log("[companies-coordinates] Migration complete.");
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error("[companies-coordinates] Migration failed:", err);
|
|
process.exit(1);
|
|
}); |