// 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})`); }