Workflow-App/Workflow/lib/sars-runners.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

146 lines
5.8 KiB
TypeScript

// lib/sars-runners.ts
// Registry of SARS return types the workbench knows how to run.
//
// One entry per return type. Adding a new return type = one entry here.
// The form, the API, the results table, and the .py lookup all read
// from this — the page and components stay generic.
//
// Per return type we record:
// - The Python script that knows how to capture it (in scripts/).
// - The period format the user types (e.g. "YYYYMM" for monthly).
// - The period granularity, which drives the period-UX component.
// - The columns to render in the results table.
// - The PB collection that future per-row writes will target.
//
// EMP201 + VAT201 are both enabled today. The other 3 are registered
// disabled so the dropdown can show them as "coming soon". The VAT201
// capture and push scripts are in scripts/ (mirrors of the EMP201
// pair) and target PB collection ysterbaard_SARS_VAT_VAT201 (note the
// double-VAT in the collection name — that's the actual schema name).
export type PeriodGranularity = "monthly" | "annual" | "bi-monthly";
export interface ResultColumn {
/** JSON key in the .py's per-row payload. */
key: string;
/** Column header. */
label: string;
/** Optional Tailwind class for the cell (e.g. text-right for numbers). */
cellClassName?: string;
}
export interface SarsRunner {
/** Machine code, matches the SARS form code (also the .py CLI value). */
code: string;
/** Human-readable label shown in the dropdown. */
label: string;
/** Script filename inside the scripts/ directory. */
script: string;
/** Free-text period format the user types. Shown as a hint. */
periodFormat: string;
/** Drives the period-UX component. */
periodGranularity: PeriodGranularity;
/** Columns to render in the results table. */
resultSchema: ResultColumn[];
/** PB collection for future per-row writes (null until wired up). */
pbCollection: string | null;
/** Push-to-PB script filename in scripts/. null until a push tool exists. */
pushScript: string | null;
/** Whether the runner is currently wired up and runnable. */
enabled: boolean;
}
export const SARS_RUNNERS: SarsRunner[] = [
{
code: "EMP201",
label: "EMP201 (PAYE)",
script: "SARS Retun EMP201 single or all pty.py",
periodFormat: "YYYYMM",
periodGranularity: "monthly",
resultSchema: [
{ key: "period", label: "Period" },
{ key: "date_submitted", label: "Date Submitted", cellClassName: "font-mono text-muted" },
{ key: "paye_payable", label: "PAYE", cellClassName: "text-right tabular-nums" },
{ key: "sdl_payable", label: "SDL", cellClassName: "text-right tabular-nums" },
{ key: "uif_payable", label: "UIF", cellClassName: "text-right tabular-nums" },
{ key: "total_payable", label: "Total", cellClassName: "text-right tabular-nums font-semibold" },
{ key: "payment_ref_no", label: "Payment Ref" },
],
pbCollection: "ysterbaard_SARS_EMP201_PAYE",
pushScript: "SARS retun EMP201 push to PB.py",
enabled: true,
},
// ── Coming soon ───────────────────────────────────────────────
{
code: "VAT201",
label: "VAT201",
script: "SARS Retun VAT201 single or all pty.py",
periodFormat: "YYYYMM",
// VAT201 is NOT monthly for every company. Each client's SARS
// "VAT Return Every" setting (`sars_vat_returns_period`) decides which
// months exist — e.g. an uneven-months client has no Feb/Apr/Jun
// returns at all, they are non-existent rather than unfiled.
//
// `monthly` here describes the period *entry format* (the user still
// types YYYYMM), not the filing frequency. The per-company cadence is
// resolved from PocketBase in the run route and passed to the .py as
// `--cadence`; the UI narrows the previewed months with the same table
// in lib/vat-cadence.ts.
periodGranularity: "monthly",
resultSchema: [
{ key: "period", label: "Period" },
{ key: "date_filed", label: "Date Filed", cellClassName: "font-mono text-muted" },
{ key: "total_payable", label: "Total (38)", cellClassName: "text-right tabular-nums font-semibold" },
{ key: "standard_rate_1", label: "Std rate (1)", cellClassName: "text-right tabular-nums" },
{ key: "zero_rate_2", label: "Zero rate (2)", cellClassName: "text-right tabular-nums" },
{ key: "exempt_and_non_supplies_3", label: "Exempt & non (3)", cellClassName: "text-right tabular-nums" },
{ key: "capital_goods_and_services_supplied_to_you_14", label: "Capital goods (14)", cellClassName: "text-right tabular-nums" },
{ key: "other_goods_and_services_supplied_to_you_15", label: "Other inputs (15)", cellClassName: "text-right tabular-nums" },
{ key: "payment_reference", label: "Pay Ref" },
],
pbCollection: "ysterbaard_SARS_VAT_VAT201",
pushScript: "SARS retun VAT201 push to PB.py",
enabled: true,
},
{
code: "IRP6",
label: "Provisional Tax (IRP6)",
script: "",
periodFormat: "YYYYMM",
periodGranularity: "bi-monthly",
resultSchema: [],
pbCollection: null,
pushScript: null,
enabled: false,
},
{
code: "ITR14",
label: "Income Tax (ITR14/ITR12T/IT12EI)",
script: "",
periodFormat: "YYYY",
periodGranularity: "annual",
resultSchema: [],
pbCollection: null,
pushScript: null,
enabled: false,
},
{
code: "STC",
label: "STC",
script: "",
periodFormat: "YYYY-MM-DD",
periodGranularity: "annual",
resultSchema: [],
pbCollection: null,
pushScript: null,
enabled: false,
},
];
export function getRunner(code: string): SarsRunner | undefined {
return SARS_RUNNERS.find((r) => r.code === code);
}
export function listEnabledRunners(): SarsRunner[] {
return SARS_RUNNERS.filter((r) => r.enabled);
}