Two work packages in one commit because both edit Program.cs and splitting
them would leave a commit that does not build.
WP-72 — deletes POST /api/v1/intakes and /herregistraties. Both were dead
from the UI (the wizard submits via /applications/{id}/submit) and strictly
less capable: they minted a bare reference and wrote no Aanvraag, made no
ZGW call, and did no document-ownership check. The shared Submit(...) helper
survives — /registrations and /change-requests still use it. WP-69 hardened
/intakes with a 400 last session; removing the surface is the stronger fix,
and WP-69's /applications/{id}/submit enforcement is untouched.
WP-73 — RegistrationStatus becomes an abstract record with three sealed
variants behind a private base ctor, so only Geregistreerd carries a
herregistratie deadline and reden is required on Geschorst/Doorgehaald
(matching the FE union, which was already right). HerregistratieRule
.IsStatusConsistent and its test are deleted: the type now guarantees what
the runtime check was for, and the test could no longer construct the
illegal state it existed to catch.
Aanvraag splits into a Concept | Submitted | Decided union with the EF row
demoted to AanvraagEntity behind a two-way mapper. Submitted carries a
non-null Referentie and SubmittedAt, and Decided.Afgewezen/MeerInfoGevraagd
require a Toelichting — so the five Referentie! null-forgiving derefs in
StatusAt are gone, not merely suppressed. IZaakSource.CreateZaak narrows to
Aanvraag.Submitted, removing the same class of deref in both zaak sources.
Draft is now cleared on submit rather than lingering: ApplicationStore's
doc-comment claimed "Concept only" but Submit never cleared it. Verified
nothing reads a submitted aanvraag's draft (draft-sync's applyResume only
resumes unsubmitted wizards), so the comment is now true instead of
aspirational.
No migration, no schema change, no wire change — RegistrationStatusDto and
the application DTOs are byte-identical, confirmed against a live swagger.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The status was derived in Contracts/Mappers.ToStatusDto, not the domain; Concept was
a magic "Concept" string with no AanvraagStatusTag member; and the besluit endpoint
re-derived its own guard by reading the status back out of the DTO and Enum.Parse-ing
it. New Domain/Applications/AanvraagStatus.cs models the full status (Concept
included, via a null Tag rather than a sixth enum member) as a closed type,
constructible only through its factories. Aanvraag.StatusAt(now) carries the logic
verbatim; Mappers.ToStatusDto and ZgwZaakMapper's two status producers become
one-line projections onto the same wire DTO, so the wire shape is unchanged (gen:api
shows zero diff beyond F1's). The one remaining Enum.Parse (the beoordeling GET,
which crosses the IZaakSource wire boundary) is now non-throwing on an unrecognised
tag.
Also, WP-68 F2: the besluit transition-legality check now runs inside
ApplicationStore.RecordBesluit's write lock instead of in the endpoint beforehand —
two concurrent besluiten used to both pass the check before either wrote, letting
the second silently overwrite a terminal decision. RecordBesluit returns an
Ok/NotFound/Conflict outcome, mirroring DocumentStore.DeleteResult.
Also, WP-68 F6: the "toelichting required" rule moves from an inline endpoint check
into BeoordelingRules.RequiresToelichting, alongside CanDecide.
The three tests naming this refactor's regression net
(AanvraagStatusTag_covers_the_published_lifecycle,
AutoApprovable_flips_to_goedgekeurd_after_the_window, ZgwZaakMapperTests) pass
unmodified.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds POST /beoordeling/{id}/besluit: a Besluit enum (Goedkeuren/Afwijzen/
MeerInfoOpvragen) backed by new Aanvraag.BesluitStatus/BesluitToelichting
columns, gated by the same BeoordelingRules.CanDecide the read side's
canBesluiten flag already uses (409 on an illegal transition, 400 on a
missing required toelichting). Mappers.ToStatusDto gains the "a recorded
decision wins" branch. FE: besluit.machine.ts + besluit-form organism
(same form idiom as change-request-form), wired into the beoordeling page
behind the server's canBesluiten flag.
Completes WP-65 (65a + 65b) — verified end-to-end against a running
backend (werkvoorraad -> beoordeling -> besluit -> status reflected back).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Model the full ADR-0002 lifecycle (Ingediend/InBehandeling/
MeerInfoGevraagd/Goedgekeurd/Afgewezen) as a backend enum backing the
existing AanvraagStatusDto.Tag string, and widen the FE union/parse
boundary/switches to match. Ingediend/MeerInfoGevraagd aren't reachable
yet (no behandelaar transition exists) — that's WP-65. Zero DTO shape
change, so gen:api has no drift.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Admin-only overview of all cases across owners + an admin delete, gated by a new
`cases:manage` capability (Authz role→cap + CanManageCases + CasesAdmin gate;
FE capability + guard + nav + role.interceptor prefix — the org-template/stamdata
recipe). Backend adds ApplicationStore.ListAll()/DeleteAny() and GET /admin/cases +
DELETE /admin/cases/{id}; admin delete removes ANY case incl. submitted. Page lives
in registratie/ui (owns the Aanvraag aggregate; reuses aanvraag-view + parse),
routed /beheer/zaken; delete guarded by a native confirm, optimistic with rollback.
Typed client regenerated (documents the new endpoints + owner field).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds the backend-owned Aanvraag aggregate (PRD 0001, phase A) — the system of
record the dashboard will read. In-memory static store mirroring DocumentStore.
- ApplicationStore: create/get/list/draft-sync/cancel/submit; status COMPUTED ON
READ (Mappers.ToStatusDto(now)) so auto-approval is pure timestamp arithmetic,
no timers/jobs (ProcessingWindow = 8s).
- Endpoints: GET /applications, GET/POST/PUT/DELETE /applications/{id},
POST /applications/{id}/submit.
- Lifecycle: registratie duo -> auto (Goedgekeurd after window), handmatig ->
manual pending (no 422); herregistratie/intake 0 uren -> Afgewezen else auto.
Cancel blocks submitted aanvragen (409, no withdrawal in scope).
- Old /registrations endpoint + RejectRegistratie 422 left intact (retire in E).
- ApplicationTests: lifecycle + auto-approve window boundary (pure). 54/54 green.
Also checks in the PRD (docs/prd/0001).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the authoritative business rules off the frontend into a real backend,
realising the BFF-lite + decision-DTO design (ADR-0001) that until now lived
only in static mock JSON.
Backend (backend/):
- ASP.NET Core (.NET 10) minimal API, contract-first, Swagger UI at /swagger.
- DDD Domain/ rules layer: profession derivation + applicable policy questions
(DiplomaRules), herregistratie eligibility + reason (HerregistratieRule),
scholing threshold (IntakePolicy), submit rejections + reference generation
(SubmissionRules). In-memory seeded data, ProblemDetails (RFC 7807) errors.
- 27 xUnit tests: rule units + endpoint integration incl. BRP no-address and
DUO not-found fallbacks and 422 submit paths.
Frontend (only infrastructure/ + contracts/ change, as the architecture promised):
- NSwag-generated typed client (api-client.ts), routed through Angular HttpClient
via a small fetch adapter so the ?scenario= interceptor still applies.
- GET adapters use resource({ loader: client.x }); submit commands call the client
and map ProblemDetails -> err. The hardcoded uren==0 / manual-diploma rules are
deleted (now server-side). Domain, stores, UI and format validators unchanged.
- Deleted the now-dead public/mock/*.json.
Tooling/docs:
- npm start proxies /api -> backend; npm run gen:api regenerates the client;
docker compose up runs both (bind mounts use :z for SELinux/Fedora).
- backend/README.md walkthrough: adding a policy question is a one-file backend
change, no FE change, no client regen. Updated CLAUDE.md + ARCHITECTURE.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>