Files
atomic-design-poc/docs/project/backlog/WP-64-behandelportal-werkvoorraad.md
ehoandClaude Sonnet 5 4133b30e5d
CI / changes (pull_request) Successful in 17s
CI / lint (pull_request) Failing after 54s
CI / frontend (pull_request) Successful in 2m38s
CI / storybook-a11y (pull_request) Failing after 3m28s
CI / backend (pull_request) Successful in 2m1s
CI / semgrep (pull_request) Successful in 1m9s
CI / e2e (pull_request) Successful in 2m55s
CI / api-client-drift (pull_request) Successful in 2m1s
feat(behandelportal): WP-65a beoordeling detail (read) + fix unreachable medewerker login
New GET /beoordeling/{id} shows one aanvraag's status, linked documents, and a
canBesluiten decision flag, gated by the same CanBeoordelen capability as the
werkvoorraad list. Reads through IZaakSource.ListCases rather than a new seam
method (WP-66 needs one anyway for the real write); owner BSN is masked.

Fixes a real gap found while wiring this up: the behandelportal's login was still
WP-61's copied citizen/BSN DigiD flow, so nothing ever sent X-Medewerker and the
werkvoorraad screen (WP-64) always denied in a real browser. A dev-only
medewerkerInterceptor (mirrors the existing ?role= stand-in as ?rollen=) fixes that.

WP-65's own Risks note authorized splitting read from write across sessions given
its size; this is the read half. The decision-recording mutation is next (65b).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-03 09:01:09 +02:00

4.8 KiB

WP-64 — Behandelportal: werkvoorraad (queue) screen

Status: done Phase: 11 — Behandelportal

Why

First real screen in the new app — a read-only list of aanvragen needing treatment (the "werkvoorraad"), gated by the medewerker identity from WP-62 and backed by the real status DTO from WP-63. This is the smallest useful vertical slice of actual case-treatment functionality — usable and demoable on its own, even before any decision can be recorded (WP-65).

Read first

  • WP-61/62/63 outcomes
  • bff-endpoint skill (screen-shaped decision DTO recipe)
  • src/app/registratie/ui/admin-cases.page.ts (the existing cross-owner list, for what to avoid repeating — that page is audit/delete, this one is a queue)

Decisions (pre-made, don't relitigate)

  • New BFF-lite endpoint (decision-enriched DTO) shaped for a werkvoorraad screen — not a reuse of the existing /admin/cases endpoint, which is audit-shaped, not queue-shaped (per CLAUDE.md's per-screen endpoint discipline).
  • Gated by the canBeoordelen-style capability from WP-62, not a new ad hoc role check.
  • Domain first, then infrastructure, application, UI — per the house new-feature recipe.

Files

New backend endpoint + DTO in BigRegister.Api; new behandeling context in the behandelportal app (domain/infrastructure/application/ui per the house layering).

Steps

  1. Backend: new screen-shaped endpoint returning aanvragen needing treatment (status = InBehandeling/Ingediend), gated by WP-62's capability.
  2. FE: scaffold the behandeling context (domain → infrastructure → application → ui), following new-feature.
  3. UI: a list page (queue), composed from the shared shared/ui kernel — no new atoms unless nothing existing fits.
  4. Storybook story for the new list component/page, a11y-checked.

Acceptance criteria

  • Werkvoorraad screen lists aanvragen needing treatment for an authenticated medewerker.
  • npm run ci green in the behandelportal app; Storybook story present.
  • Endpoint follows BFF-lite discipline (decision-enriched, not raw passthrough).

Outcome

GET /werkvoorraad reuses the existing ApplicationSummaryDto/IZaakSource.ListCases — no new DTO — filtered server-side to Status.Tag is "Ingediend" or "InBehandeling". Gated by a new Werkvoorraad local-function twin of CasesAdmin, but checking Authz.CanBeoordelen(ctx.Caller()) (the CallerIdentity directly) rather than a role-only Principal — a zorgverlener with X-Role=admin is still denied (covered by a test).

One course correction beyond the pre-made decisions: GET /me didn't expose any capability a medewerker could hold (RoleCapabilities only switches on PrincipalRole, which every MedewerkerCaller also carries but doesn't determine CanBeoordelen). Added one line — if (Authz.CanBeoordelen(ctx.Caller())) caps.Add("aanvraag:beoordelen") — and a matching Capability union member in libs/shared, so the FE page can use the same deny-by-default AccessStore/capabilityGuard idiom every other gated page uses (avoids a wasted 403 GET and a denial flash), instead of inventing a second gating mechanism.

FE: the queue item's domain type (behandeling/domain/werkvoorraad-item.ts) is deliberately narrower than the SSP's full AanvraagStatus union — only Ingediend/InBehandeling — so a case the backend's filter let through with any other tag is a parse error, not a silently-rendered row. Composed entirely from existing libs/shared/ui molecules (ApplicationListComponent + ApplicationLinkComponent, the same ones ssp's dashboard uses) via one new organism, WerkvoorraadListComponent — no new atom. The stopgap behandeling.page.ts/BehandelingPage (WP-61's scaffold placeholder, its own TODO said to replace it) is gone; /dashboard now loads WerkvoorraadPage directly, and the redundant /behandeling route (same placeholder, two paths) was dropped.

Correction (found during WP-65): this WP's Verification line ("manual: log in as a stub medewerker, see the queue populated") could not actually have passed — the behandelportal's login was still WP-61's copy-pasted citizen/BSN DigiD flow, nothing sent X-Medewerker, so WerkvoorraadPage always rendered its denial alert in a real browser. CI stayed green regardless (none of this WP's tests exercise the browser gate). Fixed in WP-65 with a dev-only medewerkerInterceptor — see that WP's Progress notes.

Verification

npm run ci in the behandelportal app; cd backend && dotnet test; manual: log in as a stub medewerker, see the queue populated from seeded aanvragen.

Out of scope

Viewing a single zaak's detail (WP-65); recording any decision (WP-65).

Risks

None major — this is a read-only composition slice once WP-61-63 exist.

Depends on: WP-61, WP-62, WP-63.