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.
94 lines
3.5 KiB
JavaScript
94 lines
3.5 KiB
JavaScript
#!/usr/bin/env node
|
|
// scripts/backfill_client_status.mjs
|
|
//
|
|
// One-shot backfill of ysterbaard_companies.client_status from existing
|
|
// is_client (bool) + enterprise_status (text) values.
|
|
//
|
|
// Rules (added 2026-08-20):
|
|
// is_client=true AND enterprise_status contains "Deregistered" -> "deregistered"
|
|
// is_client=true else -> "active_client"
|
|
// is_client=false AND enterprise_status contains "In Process" -> "non_client"
|
|
// is_client=false else -> "non_client"
|
|
//
|
|
// Dormant clients are a manual judgement call — this script does NOT
|
|
// set anyone to dormant_client. Operators must toggle dormant manually.
|
|
//
|
|
// Idempotent: re-running overwrites previous values with the same derivation.
|
|
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
|
// Load PB_TOKEN from .env.local
|
|
const envPath = path.resolve(__dirname, "..", ".env.local");
|
|
const env = {};
|
|
for (const line of fs.readFileSync(envPath, "utf8").split("\n")) {
|
|
const m = line.match(/^([A-Z_]+)=(.*)$/);
|
|
if (m) env[m[1]] = m[2].trim().replace(/^["']|["']$/g, "");
|
|
}
|
|
const PB_URL = env.PB_URL || "https://pb.agent.apphub.co.za";
|
|
const PB_TOKEN = env.PB_TOKEN;
|
|
|
|
function derive(isClient, enterpriseStatus) {
|
|
const status = (enterpriseStatus || "").toLowerCase();
|
|
if (isClient === true && status.includes("deregistered")) return "deregistered";
|
|
if (isClient === true) return "active_client";
|
|
return "non_client";
|
|
}
|
|
|
|
async function main() {
|
|
if (!PB_TOKEN) {
|
|
console.error("PB_TOKEN missing in .env.local");
|
|
process.exit(1);
|
|
}
|
|
|
|
// Page through all companies
|
|
let page = 1;
|
|
let totalProcessed = 0;
|
|
let totalUpdated = 0;
|
|
const buckets = { active_client: 0, dormant_client: 0, deregistered: 0, non_client: 0 };
|
|
|
|
while (true) {
|
|
const url = `${PB_URL}/api/collections/pbc_3748838150/records?perPage=200&page=${page}&fields=id,enterprise_no,enterprise_name,is_client,enterprise_status,client_status`;
|
|
const res = await fetch(url, { headers: { Authorization: PB_TOKEN } });
|
|
if (!res.ok) {
|
|
console.error(`PB list HTTP ${res.status}: ${(await res.text()).slice(0, 300)}`);
|
|
process.exit(1);
|
|
}
|
|
const data = await res.json();
|
|
const items = data.items || [];
|
|
if (items.length === 0) break;
|
|
|
|
for (const row of items) {
|
|
totalProcessed++;
|
|
const target = derive(row.is_client, row.enterprise_status);
|
|
buckets[target]++;
|
|
if (row.client_status === target) continue; // already correct
|
|
const patch = await fetch(`${PB_URL}/api/collections/pbc_3748838150/records/${row.id}`, {
|
|
method: "PATCH",
|
|
headers: { Authorization: PB_TOKEN, "Content-Type": "application/json" },
|
|
body: JSON.stringify({ client_status: target }),
|
|
});
|
|
if (!patch.ok) {
|
|
console.error(`PATCH failed for ${row.id} (${row.enterprise_no}): HTTP ${patch.status}`);
|
|
continue;
|
|
}
|
|
totalUpdated++;
|
|
console.log(` ${row.enterprise_no || row.id} ${row.enterprise_name || "(no name)"} -> ${target}`);
|
|
}
|
|
|
|
if (items.length < 200) break;
|
|
page++;
|
|
}
|
|
|
|
console.log(`\nDone. Processed ${totalProcessed}, updated ${totalUpdated}.`);
|
|
console.log("Buckets:", buckets);
|
|
console.log("\nNote: dormant_client requires manual judgement. Review the active_client list and set dormant_client where appropriate.");
|
|
}
|
|
|
|
main().catch((e) => {
|
|
console.error(e);
|
|
process.exit(1);
|
|
});
|