Adds the full Next.js app (app/, components/, lib/, scripts/, assets/, docs/, Dockerfile, compose, configs) to version control. Security: - CIPC BizPortal customer-code logins (Login 1 + Login 3) now read CIPC_LOGIN1_CODE / CIPC_LOGIN1_PASSWORD / CIPC_LOGIN3_CODE / CIPC_LOGIN3_PASSWORD from .env.local instead of source. - BizPortal personal login in apply-bizportal route reads BIZPORTAL_ID_NO / BIZPORTAL_PASSWORD. - Added .env.example as the committed template. .gitignore excludes .env*, node_modules, .next, logs, sqlite, Playwright storage_state.json (live CIPC cookies), scratch dirs, client PDFs and debug screenshots.
130 lines
5.6 KiB
JavaScript
130 lines
5.6 KiB
JavaScript
// scripts/_create_vat_v2.js
|
|
// PB v0.22 schema-create via PATCH /api/collections/:id with the
|
|
// `fields` key (not `schema`). Field names must be snake_case — the
|
|
// SARS form-field numbers survive as suffixes (e.g. standard_rate_1).
|
|
|
|
const fs = require("node:fs");
|
|
const path = require("node:path");
|
|
|
|
const ENV_PATH = path.join(__dirname, "..", ".env.local");
|
|
for (const line of fs.readFileSync(ENV_PATH, "utf-8").split(/\r?\n/)) {
|
|
const m = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
|
|
if (m && !process.env[m[1]]) process.env[m[1]] = m[2].trim();
|
|
}
|
|
|
|
const PB_URL = process.env.PB_URL;
|
|
const PB_TOKEN = process.env.PB_TOKEN;
|
|
const COLLECTION_ID = "pbc_3919993741";
|
|
|
|
const fields = [
|
|
{ name: "period", type: "text", required: true, options: { min: 6, max: 6, pattern: "^\\d{6}$" } },
|
|
{ name: "standard_rate_1", type: "number", required: false, options: {} },
|
|
{ name: "zero_rate_2", type: "number", required: false, options: {} },
|
|
{ name: "exempt_and_non_supplies_3", type: "number", required: false, options: {} },
|
|
{ name: "capital_goods_and_services_supplied_to_you_14", type: "number", required: false, options: {} },
|
|
{ name: "other_goods_and_services_supplied_to_you_15", type: "number", required: false, options: {} },
|
|
{ name: "total_payable", type: "number", required: false, options: {} },
|
|
{ name: "interest_d", type: "number", required: false, options: {} },
|
|
{ name: "penalties_d", type: "number", required: false, options: {} },
|
|
{
|
|
name: "status",
|
|
type: "select",
|
|
required: true,
|
|
options: { maxSelect: 1, values: ["draft", "submitted", "paid"] },
|
|
},
|
|
{ name: "date_submitted", type: "date", required: false, options: { min: "", max: "" } },
|
|
{ name: "date_paid", type: "date", required: false, options: { min: "", max: "" } },
|
|
{ name: "due_date", type: "date", required: false, options: { min: "", max: "" } },
|
|
{ name: "payment_reference", type: "text", required: false, options: { min: 0, max: 100, pattern: "" } },
|
|
// Hidden upsert key — mirrors EMP201
|
|
{ name: "company_period_key", type: "text", required: false, options: { min: 0, max: 0, pattern: "" }, hidden: true },
|
|
{
|
|
name: "company",
|
|
type: "relation",
|
|
required: true,
|
|
options: {
|
|
collectionId: "pbc_3748838150", // ysterbaard_companies
|
|
cascadeDelete: false,
|
|
minSelect: 1,
|
|
maxSelect: 1,
|
|
},
|
|
},
|
|
{ name: "Return_Filed", type: "bool", required: false, options: {} },
|
|
{ name: "last_updated", type: "date", required: false, options: { min: "", max: "" } },
|
|
{ name: "notes", type: "text", required: false, options: { min: 0, max: 0, pattern: "" } },
|
|
{ name: "first_name", type: "text", required: false, options: { min: 0, max: 0, pattern: "" } },
|
|
{ name: "surname", type: "text", required: false, options: { min: 0, max: 0, pattern: "" } },
|
|
{ name: "capacity", type: "text", required: false, options: { min: 0, max: 0, pattern: "" } },
|
|
{ name: "bus_tel_no", type: "number", required: false, options: {} },
|
|
{ name: "cell_no", type: "number", required: false, options: {} },
|
|
{ name: "contact_email", type: "email", required: false, options: { exceptDomains: [], onlyDomains: [] } },
|
|
{ name: "is_active", type: "bool", required: false, options: {} },
|
|
{
|
|
name: "documents",
|
|
type: "file",
|
|
required: false,
|
|
options: {
|
|
maxSelect: 10,
|
|
maxSize: 10485760,
|
|
mimeTypes: ["application/pdf", "image/png", "image/jpeg"],
|
|
thumbs: null,
|
|
protected: false,
|
|
},
|
|
},
|
|
];
|
|
|
|
const indexes = [
|
|
"CREATE UNIQUE INDEX idx_vat201_company_period ON ysterbaard_SARS_VAT_VAT201 (company_period_key)",
|
|
"CREATE INDEX idx_vat201_period ON ysterbaard_SARS_VAT_VAT201 (period)",
|
|
"CREATE INDEX idx_vat201_status ON ysterbaard_SARS_VAT_VAT201 (status)",
|
|
];
|
|
|
|
(async () => {
|
|
console.log(`[1/3] PATCH /api/collections/${COLLECTION_ID} (add ${fields.length} fields, key=schema)`);
|
|
const r1 = await fetch(`${PB_URL}/api/collections/${COLLECTION_ID}`, {
|
|
method: "PATCH",
|
|
headers: { Authorization: PB_TOKEN, "Content-Type": "application/json" },
|
|
body: JSON.stringify({ schema: fields }),
|
|
});
|
|
const t1 = await r1.text();
|
|
console.log(` HTTP ${r1.status}`);
|
|
if (!r1.ok) {
|
|
console.error(` Failed: ${t1.slice(0, 600)}`);
|
|
process.exit(1);
|
|
}
|
|
const persisted = (JSON.parse(t1).fields || []).filter(f => !f.system).length;
|
|
console.log(` fields persisted: ${persisted}`);
|
|
|
|
console.log(`[2/3] PATCH /api/collections/${COLLECTION_ID} (add ${indexes.length} indexes)`);
|
|
const r2 = await fetch(`${PB_URL}/api/collections/${COLLECTION_ID}`, {
|
|
method: "PATCH",
|
|
headers: { Authorization: PB_TOKEN, "Content-Type": "application/json" },
|
|
body: JSON.stringify({ indexes }),
|
|
});
|
|
const t2 = await r2.text();
|
|
console.log(` HTTP ${r2.status}`);
|
|
if (!r2.ok) {
|
|
console.error(` Failed: ${t2.slice(0, 600)}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log(`[3/3] GET /api/collections/${COLLECTION_ID} (verify)`);
|
|
const r3 = await fetch(`${PB_URL}/api/collections/${COLLECTION_ID}`, { headers: { Authorization: PB_TOKEN } });
|
|
const final = await r3.json();
|
|
const userFields = (final.fields || final.schema || []).filter(f => !f.system);
|
|
console.log(` name: ${final.name}`);
|
|
console.log(` user-defined fields: ${userFields.length}`);
|
|
console.log(` indexes: ${(final.indexes || []).length}`);
|
|
console.log("");
|
|
console.log("Field list:");
|
|
for (const f of userFields) {
|
|
console.log(` - ${f.name.padEnd(50)} ${f.type}${f.required ? " (required)" : ""}`);
|
|
}
|
|
console.log("");
|
|
console.log("Indexes:");
|
|
for (const ix of final.indexes || []) console.log(` - ${ix}`);
|
|
process.exit(0);
|
|
})().catch((err) => {
|
|
console.error("Unexpected error:", err);
|
|
process.exit(1);
|
|
});
|