Keep payment holds independent of workflow and margin approval. Require proof before collection readiness; deduplicate uploads transactionally in PocketBase and preserve payment history. Includes schema migration, deployment runbook, browser checks and isolated PocketBase regression coverage. Full suite has 5156 passing tests and retains two pre-existing script-import errors.
58 lines
3.7 KiB
TypeScript
58 lines
3.7 KiB
TypeScript
/**
|
|
* Supplier-payment metadata and item projections. Manual live step; idempotent.
|
|
* Documents remain on order_items.order_item_attachements. API mutations are
|
|
* null: the superuser-only transactional hook command is the only writer.
|
|
*/
|
|
import "./_env-loader";
|
|
import PocketBase from "pocketbase";
|
|
|
|
type Field = Record<string, unknown> & { name: string };
|
|
const relation = (name: string, collectionId: string, maxSelect = 1): Field => ({
|
|
name, type: "relation", collectionId, maxSelect, cascadeDelete: false, required: false,
|
|
});
|
|
async function main() {
|
|
const { PB_ADMIN_EMAIL, PB_ADMIN_PASSWORD } = process.env;
|
|
if (!PB_ADMIN_EMAIL || !PB_ADMIN_PASSWORD) throw new Error("Set PB_ADMIN_EMAIL and PB_ADMIN_PASSWORD.");
|
|
const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL || process.env.PB_URL || "http://127.0.0.1:8090");
|
|
await pb.collection("_superusers").authWithPassword(PB_ADMIN_EMAIL, PB_ADMIN_PASSWORD);
|
|
const [orders, items, suppliers] = await Promise.all([
|
|
pb.collections.getOne("orders"), pb.collections.getOne("order_items"), pb.collections.getOne("suppliers"),
|
|
]);
|
|
const fields: Field[] = [
|
|
relation("order", orders.id), relation("supplier", suppliers.id), relation("items", items.id, 100),
|
|
{ name: "status", type: "select", maxSelect: 1, values: ["draft", "pending", "paid", "cancelled"] },
|
|
...["amount", "paid_amount", "revision"].map((name) => ({ name, type: "number", min: 0, required: false })),
|
|
...["reference", "payment_reference", "requested_by", "paid_by"].map((name) => ({ name, type: "text", max: 200, required: false })),
|
|
{ name: "note", type: "text", max: 4000 },
|
|
{ name: "cancellation_reason", type: "text", max: 2000 },
|
|
{ name: "paid_at", type: "date", required: false },
|
|
...["documents", "item_snapshot", "history"].map((name) => ({ name, type: "json", maxSize: 5000000, required: false })),
|
|
{ name: "created", type: "autodate", onCreate: true, onUpdate: false },
|
|
{ name: "updated", type: "autodate", onCreate: true, onUpdate: true },
|
|
];
|
|
const all = await pb.collections.getFullList();
|
|
let requests = all.find((c) => c.name === "supplier_payment_requests");
|
|
if (!requests) {
|
|
requests = await pb.collections.create({
|
|
name: "supplier_payment_requests", type: "base", fields,
|
|
listRule: null, viewRule: null, createRule: null, updateRule: null, deleteRule: null,
|
|
indexes: ["CREATE INDEX idx_supplier_payment_requests_order ON supplier_payment_requests (`order`, status)"],
|
|
});
|
|
console.log("Created supplier_payment_requests.");
|
|
} else {
|
|
const existing = new Set(requests.fields.map((f: { name: string }) => f.name));
|
|
const missing = fields.filter((f) => !existing.has(f.name));
|
|
if (missing.length) requests = await pb.collections.update(requests.id, { fields: [...requests.fields, ...missing] });
|
|
await pb.collections.update(requests.id, { listRule: null, viewRule: null, createRule: null, updateRule: null, deleteRule: null });
|
|
console.log("Verified supplier_payment_requests fields and locked API rules.");
|
|
}
|
|
const projections: Field[] = [
|
|
{ name: "payment_status", type: "select", maxSelect: 1, values: ["required", "paid"], required: false },
|
|
relation("payment_request", requests.id),
|
|
];
|
|
const existing = new Set(items.fields.map((f: { name: string }) => f.name));
|
|
const missing = projections.filter((f) => !existing.has(f.name));
|
|
if (missing.length) await pb.collections.update(items.id, { fields: [...items.fields, ...missing] });
|
|
console.log("Supplier payment schema ready. Publish the payment hooks before enabling the app feature.");
|
|
}
|
|
main().catch((error) => { console.error("Supplier payment migration failed:", error); process.exitCode = 1; });
|