57 lines
3 KiB
JavaScript
57 lines
3 KiB
JavaScript
|
|
// Create the `cash_float_entries` PocketBase collection (idempotent).
|
||
|
|
//
|
||
|
|
// The cash-float CARD: one opening balance, then every slip subtracts from it. Stored as a
|
||
|
|
// ledger — the balance is the SUM of entries, never a number that is overwritten — so every
|
||
|
|
// rand on the card can be traced to a slip.
|
||
|
|
//
|
||
|
|
// kind "opening" the balance on the card when tracking began (set once)
|
||
|
|
// kind "topup" money loaded onto the card later
|
||
|
|
// kind "expense" a slip: supplier, cost code, comment, amount and the photo of the slip
|
||
|
|
//
|
||
|
|
// `amount` is always positive; the kind decides the sign. Not wiped by the seed.
|
||
|
|
//
|
||
|
|
// Usage: node scripts/setup-cash-float-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 assetsId = (await pb.collections.getOne("assets")).id;
|
||
|
|
const fields = [
|
||
|
|
{ name: "kind", type: "text", required: true },
|
||
|
|
{ name: "amount", type: "number" },
|
||
|
|
// Full instant (ISO). `created` is not auto-filled on API-made collections.
|
||
|
|
{ name: "occurred_on", type: "text" },
|
||
|
|
{ name: "supplier", type: "text" },
|
||
|
|
{ name: "cost_code", type: "text" },
|
||
|
|
// Set when the cost code names a vehicle in the plant register; never guessed.
|
||
|
|
{ name: "asset", type: "relation", collectionId: assetsId, minSelect: 0, maxSelect: 1, cascadeDelete: false },
|
||
|
|
{ name: "comment", type: "text" },
|
||
|
|
{ name: "slip_photo", type: "file", maxSelect: 1, maxSize: 10485760, mimeTypes: ["image/jpeg", "image/png", "image/webp", "image/heic", "image/heif"], protected: true },
|
||
|
|
{ name: "recorded_by", type: "text" },
|
||
|
|
];
|
||
|
|
const existing = await pb.collections.getFullList({ filter: 'name = "cash_float_entries"', perPage: 1 });
|
||
|
|
if (existing.length) {
|
||
|
|
const have = new Set(existing[0].fields.map((f) => f.name));
|
||
|
|
const absent = fields.filter((f) => !have.has(f.name));
|
||
|
|
if (absent.length) {
|
||
|
|
await pb.send("/api/collections/cash_float_entries", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ fields: [...existing[0].fields, ...absent] }) });
|
||
|
|
console.log(` ✓ added: ${absent.map((f) => f.name).join(", ")}`);
|
||
|
|
} else console.log(" · cash_float_entries already present");
|
||
|
|
} else {
|
||
|
|
const c = await pb.send("/api/collections", {
|
||
|
|
method: "POST", headers: { "Content-Type": "application/json" },
|
||
|
|
body: JSON.stringify({
|
||
|
|
name: "cash_float_entries", type: "base", fields,
|
||
|
|
listRule: ref.listRule ?? "", viewRule: ref.viewRule ?? "",
|
||
|
|
createRule: ref.createRule ?? "", updateRule: ref.updateRule ?? "", deleteRule: ref.deleteRule ?? "",
|
||
|
|
}),
|
||
|
|
});
|
||
|
|
console.log(` ✓ created cash_float_entries (${c.id})`);
|
||
|
|
}
|