Workflow-App/Workflow/scripts/_capture_periods.py
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

140 lines
5.9 KiB
Python

"""
Direct capture script for JS MINING AND ENGINEERING EMP201 periods.
Uses .click() on the EMP201 link (form loads on same page via JS).
"""
import base64
import json
import os
import re
import subprocess
import tempfile
import time
AB = "C:\\Users\\SJ\\AppData\\Roaming\\npm\\agent-browser.cmd"
PERIOD_URLS = {
"202603": "https://secure.sarsefiling.co.za/EFDotNet/LoadPage.aspx?PageCode=EMP2013LANDING&ReturnTypeID=36&ReturnGroupID=168198289&TaxPayerID=31142387&ReadOnly=1&Period=202603&TPReturnITID=0&TPReturnPITID=79672519&HistoryInd=1",
"202604": "https://secure.sarsefiling.co.za/EFDotNet/LoadPage.aspx?PageCode=EMP2013LANDING&ReturnTypeID=36&ReturnGroupID=168885394&TaxPayerID=31142387&ReadOnly=1&Period=202604&TPReturnITID=0&TPReturnPITID=80251983&HistoryInd=1",
"202605": "https://secure.sarsefiling.co.za/EFDotNet/LoadPage.aspx?PageCode=EMP2013LANDING&ReturnTypeID=36&ReturnGroupID=169701876&TaxPayerID=31142387&ReadOnly=1&Period=202605&TPReturnITID=0&TPReturnPITID=80776676&HistoryInd=1",
"202606": "https://secure.sarsefiling.co.za/EFDotNet/LoadPage.aspx?PageCode=EMP2013LANDING&ReturnTypeID=36&ReturnGroupID=170268751&TaxPayerID=31142387&ReadOnly=1&Period=202606&TPReturnITID=0&TPReturnPITID=81270492&HistoryInd=1",
"202607": "https://secure.sarsefiling.co.za/EFDotNet/LoadPage.aspx?PageCode=EMP2013LANDING&ReturnTypeID=36&ReturnGroupID=170816319&TaxPayerID=31142387&ReadOnly=1&Period=202607&TPReturnITID=0&TPReturnPITID=81856371&HistoryInd=1",
"202608": "https://secure.sarsefiling.co.za/EFDotNet/LoadPage.aspx?PageCode=EMP2013LANDING&ReturnTypeID=36&ReturnGroupID=171605060&TaxPayerID=31142387&ReadOnly=1&Period=202608&TPReturnITID=0&TPReturnPITID=82341489&HistoryInd=1",
}
def run_ab(*args, timeout=30):
tmp = tempfile.NamedTemporaryFile(mode="w", delete=False, encoding="utf-8", errors="replace", prefix="ab_", suffix=".out")
tmp_path = tmp.name
tmp.close()
try:
result = subprocess.run([AB, *args], stdout=open(tmp_path, "w", encoding="utf-8", errors="replace"), stderr=subprocess.DEVNULL, timeout=timeout)
with open(tmp_path, "r", encoding="utf-8", errors="replace") as f:
out = f.read().strip()
finally:
try: os.unlink(tmp_path)
except OSError: pass
if result.returncode != 0:
raise RuntimeError(f"agent-browser {args!r} failed: {out}")
return out
def eval_js(js):
b64 = base64.b64encode(js.encode("utf-8")).decode("ascii")
out = run_ab("eval", "-b", b64)
if out == "null" or not out:
return None
if out.startswith('"') and out.endswith('"'):
out = json.loads(out)
return out
def navigate_iframe_to(url):
js = f"document.querySelectorAll('iframe')[0].src = {json.dumps(url)}; 'ok'"
eval_js(js)
def click_emp201_link():
"""Click the EMP201 link inside the iframe; form loads on same page via JS."""
js = """(() => {
const ifr = document.querySelectorAll('iframe')[0];
const doc = ifr.contentDocument;
const links = Array.from(doc.querySelectorAll('a'));
// Find link with text 'EMP201' or href with BPEMP201Form
let target = links.find(a => a.href && a.href.includes('BPEMP201Form'));
if (!target) target = links.find(a => a.textContent.trim() === 'EMP201');
if (!target) return JSON.stringify({ clicked: false, reason: 'no link' });
target.click();
return JSON.stringify({ clicked: true, href: target.href });
})()"""
return eval_js(js)
def read_input_xml():
js = """(() => {
const ifr = document.querySelectorAll('iframe')[0];
const doc = ifr.contentDocument;
const all = Array.from(doc.querySelectorAll('input'));
const x = all.find(i => i.name === 'InputXML');
return x ? x.value : '';
})()"""
res = eval_js(js)
return res or ""
def parse_input_xml(xml_str):
xml_str = (xml_str.replace("&lt;", "<").replace("&gt;", ">")
.replace("&quot;", '"').replace("&amp;", "&"))
def get(tag):
m = re.search(rf"<{tag}>([^<]*)</{tag}>", xml_str)
return m.group(1) if m else ""
return {
"company_name": get("TRADING_NAME"),
"paye_no": get("PAYE_REF_NO"),
"payment_period": get("PAYMENT_PERIOD"),
"paye_payable": get("PAYE_AMT"),
"sdl_payable": get("SDL_AMT"),
"uif_payable": get("UIF_AMT"),
"total_payable": get("TOTAL_PAYMENT"),
"payment_ref_no": get("PAYMENT_REF_NO"),
}
def main():
periods_wanted = ["202603", "202604", "202605", "202606", "202607", "202608"]
results = []
for period in periods_wanted:
if period not in PERIOD_URLS:
continue
href = PERIOD_URLS[period]
print(f"\n{period}: navigating to work page...", flush=True)
navigate_iframe_to(href)
time.sleep(3.5)
print(f" clicking EMP201 link...", flush=True)
click_emp201_link()
# Wait longer for form to load
for i in range(6):
time.sleep(2)
xml = read_input_xml()
if xml:
break
if not xml:
print(f" {period}: no InputXML after 12s", flush=True)
continue
fields = parse_input_xml(xml)
fields["period"] = period
results.append(fields)
print(f" {period}: PAYE={fields['paye_payable']} SDL={fields['sdl_payable']} UIF={fields['uif_payable']} Total={fields['total_payable']} Ref={fields['payment_ref_no']}", flush=True)
# Navigate back to work page (back to landing) so next iteration's navigate works
# The navigate_iframe_to sets a new URL, so we don't need to click Back
print("\n\n=== FINAL RESULTS ===", flush=True)
print(json.dumps(results, indent=2), flush=True)
out_path = r"C:\Users\SJ\Downloads\js_mining_emp201_results.json"
with open(out_path, "w", encoding="utf-8") as f:
json.dump(results, f, indent=2)
print(f"\nSaved to {out_path}", flush=True)
if __name__ == "__main__":
main()