Workflow-App/Workflow/lib/vat-cadence.server.ts
sjcoetzeesj-coder 11d0178ab1 feat: commit Workflow app source; move CIPC credentials to env
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.
2026-10-18 15:21:45 +02:00

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;
}
}