mega-plant/scripts/setup-suppliers-collection.mjs
David cfd54385b3 Add Suppliers list; book purchase orders and petty-cash slips against it
- /expenses/suppliers: terms (cash / 30 / 60 days), PO + petty cash totals per supplier,
  period filters, add/edit/delete, due dates
- POs and slips resolve to a supplier by code then folded name; new PO suppliers start
  with terms unset, new slip suppliers as cash
- Full-user guard on cash-float and job-card write routes

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 15:43:57 +02:00

52 lines
2.9 KiB
JavaScript

// Create the `suppliers` collection and link purchase orders + cash-float slips to it (idempotent).
//
// One supplier list for the whole business. Every purchase order and every petty-cash slip is
// booked against a supplier, so "how much did we spend at X" is one query.
//
// terms "cash" paid on the spot (petty cash)
// "30" account, payable in 30 days
// "60" account, payable in 60 days
// "" not set yet — a supplier that appeared on a PO before anyone classified it
//
// `key` is the supplier's name folded (case, punctuation, PTY/LTD/CC removed) and is UNIQUE, so
// "Lube Force Traders cc" and "LUBE FORCE TRADERS" cannot become two suppliers.
//
// Usage: node scripts/setup-suppliers-collection.mjs
import PocketBase from "pocketbase";
import { readFileSync } from "node:fs";
for (const l of readFileSync(new URL("../.env.local", import.meta.url), "utf8").split("\n")) {
const i = l.indexOf("="); if (i > 0 && !l.trim().startsWith("#")) process.env[l.slice(0, i).trim()] = l.slice(i + 1).trim();
}
const pb = new PocketBase(process.env.NEXT_PUBLIC_PB_URL);
pb.autoCancellation(false);
await pb.collection("_superusers").authWithPassword(process.env.PB_ADMIN, process.env.PB_PASS);
const ref = await pb.collections.getOne("monthly_income");
const rules = {
listRule: ref.listRule ?? "", viewRule: ref.viewRule ?? "",
createRule: ref.createRule ?? "", updateRule: ref.updateRule ?? "", deleteRule: ref.deleteRule ?? "",
};
const fields = [
{ name: "name", type: "text", required: true },
{ name: "key", type: "text", required: true },
{ name: "code", type: "text" },
{ name: "terms", type: "text" },
{ name: "notes", type: "text" },
{ name: "source", type: "text" },
];
const indexes = ["CREATE UNIQUE INDEX idx_suppliers_key ON suppliers (key)"];
let suppliers = (await pb.collections.getFullList({ filter: 'name = "suppliers"', perPage: 1 }))[0];
if (!suppliers) {
suppliers = await pb.send("/api/collections", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: "suppliers", type: "base", fields, indexes, ...rules }) });
console.log(` ✓ created suppliers (${suppliers.id})`);
} else console.log(" · suppliers already present");
for (const target of ["purchases", "cash_float_entries"]) {
const c = await pb.collections.getOne(target).catch(() => null);
if (!c) { console.log(` ! ${target} missing — skipped`); continue; }
if (c.fields.some((f) => f.name === "supplier_ref")) { console.log(` · ${target}.supplier_ref already present`); continue; }
await pb.send(`/api/collections/${target}`, { method: "PATCH", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ fields: [...c.fields, { name: "supplier_ref", type: "relation", collectionId: suppliers.id, minSelect: 0, maxSelect: 1, cascadeDelete: false }] }) });
console.log(` ✓ added ${target}.supplier_ref`);
}