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.
68 lines
No EOL
2.7 KiB
TypeScript
68 lines
No EOL
2.7 KiB
TypeScript
// lib/vat-cadence.server.ts
|
|
// SERVER-ONLY PocketBase lookup for company VAT cadences.
|
|
//
|
|
// Split out from `vat-cadence.ts` because that module must stay client-safe
|
|
// (the Returns workbench is a client component and needs the period maths),
|
|
// while `lib/pb.ts` carries `import "server-only"`.
|
|
|
|
import { fetchCollection, PBRecord } from "./pb";
|
|
import { CompanyCadence, toCompanyCadence } from "./vat-cadence";
|
|
|
|
/**
|
|
* Load cadence for the named companies (or every VAT client when
|
|
* `allActive`), in ONE PB round-trip.
|
|
*
|
|
* Returns `null` when PB is unavailable — callers treat that as
|
|
* "cadence unknown" and fall back to the old unfiltered behaviour rather
|
|
* than failing the run outright. Failing soft here matters: a PB outage
|
|
* must not make it impossible to capture a return.
|
|
*/
|
|
export async function loadCompanyCadences(
|
|
names: string[] | null,
|
|
allActive: boolean
|
|
): Promise<{ byName: Map<string, CompanyCadence>; all: CompanyCadence[] } | null> {
|
|
try {
|
|
const wanted = new Set((names ?? []).map((n) => n.trim().toUpperCase()).filter(Boolean));
|
|
let data: { items: PBRecord[] };
|
|
if (allActive) {
|
|
// VAT201 applies to VAT-registered entities — mirror the .py's gate.
|
|
data = await fetchCollection<PBRecord>("ysterbaard_companies", {
|
|
perPage: 500,
|
|
filter: "is_client=true",
|
|
fields: "id,enterprise_name,sars_vat_no,sars_vat_returns_period",
|
|
});
|
|
} else {
|
|
if (wanted.size === 0) return { byName: new Map(), all: [] };
|
|
const filter = [...wanted]
|
|
.map((n) => `enterprise_name = "${n.replace(/"/g, '\\"')}"`)
|
|
.join(" || ");
|
|
data = await fetchCollection<PBRecord>("ysterbaard_companies", {
|
|
perPage: 500,
|
|
filter,
|
|
fields: "id,enterprise_name,sars_vat_no,sars_vat_returns_period",
|
|
});
|
|
}
|
|
|
|
const all: CompanyCadence[] = data.items
|
|
.map((r) =>
|
|
toCompanyCadence({
|
|
id: (r.id as string) || "",
|
|
enterprise_name: (r.enterprise_name as string) || "",
|
|
sars_vat_no: (r.sars_vat_no as string) ?? "",
|
|
sars_vat_returns_period: (r.sars_vat_returns_period as number | null) ?? null,
|
|
})
|
|
)
|
|
// An explicit selection keeps clients with no VAT number so the caller
|
|
// can report WHY each one was skipped ("no SARS VAT number") instead of
|
|
// implying the record is missing. For --all-active the selection means
|
|
// "every VAT client", so that filter does apply.
|
|
.filter((c) => c.name && (allActive ? c.hasVatNo : true));
|
|
|
|
const byName = new Map<string, CompanyCadence>();
|
|
for (const c of all) byName.set(c.name.trim().toUpperCase(), c);
|
|
return { byName, all };
|
|
} catch (err) {
|
|
console.error("[vat-cadence] failed to load company cadences:", err);
|
|
return null;
|
|
}
|
|
} |