diff --git a/infra/openzaak/seed_catalogus.py b/infra/openzaak/seed_catalogus.py index 1c913c0..a9aa4c6 100644 --- a/infra/openzaak/seed_catalogus.py +++ b/infra/openzaak/seed_catalogus.py @@ -18,6 +18,16 @@ SECRET = os.environ.get("OZ_SECRET", "insecure-dev-secret-change-me") ZTC = f"{BASE}/catalogi/api/v1" RSIN = "517439943" # elfproef-valid test RSIN +# Opt-in: also publish the zaaktype so OpenZaak's Zaken API accepts a zaak against +# it (a concept zaaktype is rejected with `not-published`). Off by default — the +# S-01 compose seed keeps it a concept (ADR-0002). The ACL integration test +# (S-04a, #46) sets OZ_PUBLISH=1. Publishing requires ≥2 statustypen, ≥1 roltype +# and ≥1 resultaattype; the resultaattype is validated against the external +# Selectielijst reference API, so this path needs outbound access to it. See ADR-0006. +PUBLISH = os.environ.get("OZ_PUBLISH", "").lower() in ("1", "true", "yes") +SELECTIELIJST = os.environ.get( + "OZ_SELECTIELIJST", "https://selectielijst.openzaak.nl/api/v1").rstrip("/") + def token(): b64 = lambda b: base64.urlsafe_b64encode(b).rstrip(b"=") @@ -52,6 +62,68 @@ def find(path): return body.get("results", []) +def selectielijst(path): + """GET the external Selectielijst reference API (no auth). Used only when publishing.""" + req = urllib.request.Request(f"{SELECTIELIJST}{path}", headers={"Accept": "application/json"}) + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()) + + +def publish_zaaktype(zt): + """Add the relations OpenZaak requires to publish, then publish (idempotent). + + Publish validation (verified against OpenZaak 1.28.2) demands: ≥2 statustypen + (begin + eind), ≥1 roltype, ≥1 resultaattype. A resultaattype needs a + Selectielijst `selectielijstklasse` whose procestype matches the zaaktype's + `selectielijstProcestype`, plus a `resultaattypeomschrijving`. + """ + have_st = {s.get("volgnummer") for s in find(f"/statustypen?zaaktype={zt['url']}&status=alles")} + for volgnummer, omschrijving in [(1, "Ontvangen"), (2, "Afgehandeld")]: + if volgnummer not in have_st: + st, body = api("POST", "/statustypen", { + "omschrijving": omschrijving, "zaaktype": zt["url"], "volgnummer": volgnummer}) + if st != 201: + sys.exit(f"create statustype {volgnummer} -> {st}: {json.dumps(body, indent=2)}") + print(f"create statustype {volgnummer} ({omschrijving})") + + if find(f"/roltypen?zaaktype={zt['url']}&status=alles"): + print("skip roltype Aanvrager") + else: + st, body = api("POST", "/roltypen", { + "zaaktype": zt["url"], "omschrijving": "Aanvrager", "omschrijvingGeneriek": "initiator"}) + if st != 201: + sys.exit(f"create roltype -> {st}: {json.dumps(body, indent=2)}") + print("create roltype Aanvrager") + + if find(f"/resultaattypen?zaaktype={zt['url']}&status=alles"): + print("skip resultaattype Geregistreerd") + else: + resultaat = selectielijst("/resultaten?pageSize=1")["results"][0] + omschrijvingen = selectielijst("/resultaattypeomschrijvingen") + oms = (omschrijvingen if isinstance(omschrijvingen, list) else omschrijvingen["results"])[0]["url"] + # The selectielijstklasse and the zaaktype must share a procestype. + st, body = api("PATCH", zt["url"], {"selectielijstProcestype": resultaat["procesType"]}) + if st != 200: + sys.exit(f"set procestype -> {st}: {json.dumps(body, indent=2)}") + st, body = api("POST", "/resultaattypen", { + "zaaktype": zt["url"], "omschrijving": "Geregistreerd", + "resultaattypeomschrijving": oms, "selectielijstklasse": resultaat["url"], + "archiefnominatie": "blijvend_bewaren", + "brondatumArchiefprocedure": {"afleidingswijze": "afgehandeld"}, + }) + if st != 201: + sys.exit(f"create resultaattype -> {st}: {json.dumps(body, indent=2)}") + print("create resultaattype Geregistreerd") + + if zt.get("concept", True): + st, body = api("POST", f"{zt['url']}/publish") + if st != 200: + sys.exit(f"publish zaaktype -> {st}: {json.dumps(body, indent=2)}") + print(f"publish zaaktype BIG-REGISTRATIE ({zt['url']})") + else: + print("skip publish (already published)") + + def main(): # 1. Catalogus existing = [c for c in find(f"/catalogussen?domein=BIG") if c.get("domein") == "BIG"] @@ -121,16 +193,24 @@ def main(): else: print("warn zaaktype already published; cannot add bsn eigenschap") - # Intentionally NOT published. Publishing requires roltypen, resultaattypen - # and statustypen, which go beyond the "lean / schema-mandatory" zaaktype this - # slice asks for; they arrive with the workflow/zaak slices. See ADR-0002. + # 4. Optionally publish. By default the zaaktype stays a concept: publishing + # requires roltypen, resultaattypen and statustypen, beyond the "lean / + # schema-mandatory" zaaktype S-01 asks for (ADR-0002). Set OZ_PUBLISH=1 to add + # those relations and publish — needed so a real zaak POST is accepted, which + # the ACL integration test (S-04a, #46) exercises. See ADR-0006. + if PUBLISH: + # Re-fetch: the bsn-eigenschap branch above may hold a stale concept flag. + zt = next(z for z in find(f"/zaaktypen?catalogus={cat['url']}&status=alles") + if z.get("identificatie") == "BIG-REGISTRATIE") + publish_zaaktype(zt) - # 4. Verify the JWT client can list the zaaktype (concepts included). + # 5. Verify the JWT client can list the zaaktype (concepts included). zaaktypen = find(f"/zaaktypen?catalogus={cat['url']}&status=alles") names = [z.get("identificatie") for z in zaaktypen] print(f"zaaktypen in BIG: {names}") assert "BIG-REGISTRATIE" in names, "BIG-REGISTRATIE not listed" - print("OK — BIG catalogus seeded (BIG-REGISTRATIE concept + bsn eigenschap)") + state = "published" if PUBLISH else "concept" + print(f"OK — BIG catalogus seeded (BIG-REGISTRATIE {state} + bsn eigenschap)") if __name__ == "__main__":