Two backlog trees are complete: `docs/project/backlog/` (75 files, every WP done) and `docs/project/refactor-backlog-setup/` (the arc before it). Move both under `docs/project/archive/` with `git mv`, so history stays intact through `git log --follow`. `SHOWCASE-ROADMAP.md` moves with them, because it points at the now-archived backlog README. Add `docs/project/archive/README.md`. It states that these trees are historical and names the two directories that are still live. Repoint every inbound reference named in RD-30's Files table: CLAUDE.md, the root README, both backend READMEs, `LetterHtml.cs`, `a11y.mdx`, the `document-feature` and `new-ssp` skills, and the readable-codebase PLAN, README, and RD-19 ticket. Fix two upward-relative links inside the moved WP files (WP-68, WP-69) that gained a directory level and would otherwise break. Repoint `.prettierignore`'s two agent-prompt exclusions to their new path, so prettier keeps leaving those files' exact wording alone. Mark RD-30 done and check off its acceptance criteria; flip its README row to done. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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-endpointskill (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/casesendpoint, 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-featurerecipe.
Files
New backend endpoint + DTO in BigRegister.Api; new behandeling context in the
behandelportal app (domain/infrastructure/application/ui per the house layering).
Steps
- Backend: new screen-shaped endpoint returning aanvragen needing treatment
(status =
InBehandeling/Ingediend), gated by WP-62's capability. - FE: scaffold the
behandelingcontext (domain → infrastructure → application → ui), followingnew-feature. - UI: a list page (queue), composed from the shared
shared/uikernel — no new atoms unless nothing existing fits. - Storybook story for the new list component/page, a11y-checked.
Acceptance criteria
- Werkvoorraad screen lists aanvragen needing treatment for an authenticated medewerker.
npm run cigreen 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.