feat(registratie): WP-36 — admin cases page + admin delete

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>
This commit is contained in:
eho
2026-07-23 12:23:34 +02:00
co-authored by Claude Opus 4.8
parent d1abd35b0d
commit 446ea9474b
23 changed files with 786 additions and 9 deletions
+49
View File
@@ -0,0 +1,49 @@
# WP-36 — Admin cases page + admin delete
Status: done
Phase: 7 — refinements
## Why
Admins can maintain stamdata and org-templates but have no view of the cases (aanvragen) in the
register, and no way to remove an erroneous one. This WP adds an admin-only overview of **all**
cases across owners and an admin **delete** that can remove any case — the back-office counterpart
of the user's own dashboard.
## Decisions (made while building — no spec existed; flagged for review)
- **Single capability `cases:manage`** covers both the list and the delete (one back-office
concern), following the `orgtemplate:edit` / `stamdata:edit` precedent exactly (Authz role→cap +
a `CanManageCases` gate + a `CasesAdmin(ctx,…)` helper; FE `Capability` union + `me.adapter`
`KNOWN` + `capabilityGuard` + nav item + `role.interceptor` prefix).
- **Page lives in `registratie` (not `beheer`).** `registratie` owns the `Aanvraag` aggregate, so
the admin view reuses its `aanvraag-view` labels + `parseApplications` trust boundary instead of
duplicating them — and it respects the layer boundary (`beheer` may not import `registratie`).
This matches the existing pattern (stamdata-admin lives in `beheer` because `beheer` owns
stamdata; org-template-admin in `brief`). Routed at `/beheer/zaken` for a legible admin URL.
- **Admin delete removes ANY case** — any owner, submitted or not — unlike the user-facing
`DELETE /applications/{id}` (owner-scoped, 409 on a submitted case). That is the admin power.
- **Native `confirm()` guards the delete.** No confirm-dialog component exists (the only precedent
is a native `confirm()` in behandel-scherm); the delete is irreversible, so it gets a prompt
rather than the dashboard's no-confirm optimistic cancel.
- **Single owner in practice.** Only `DemoOwner` exists, so the list shows that owner's cases with
an Owner column; no fake multi-user seed was added (the endpoint is cross-owner-capable —
`ListAll()` — so real multi-owner data would just appear).
## Files
- Backend: `ApplicationStore.ListAll()` + `DeleteAny(id)`; `ApplicationSummaryDto.Owner` +
`ToAdminSummaryDto`; `Authz` cap + `CanManageCases`; `Program.cs` `CasesAdmin` gate + `GET
/admin/cases` + `DELETE /admin/cases/{id}`; `AdminCasesTests` (+ update the org-template `/me`
cap-list assertion). SQLite can't `ORDER BY DateTimeOffset` → `ListAll` sorts client-side.
- FE: `capability.ts` + `me.adapter` `KNOWN` + `role.interceptor` (`/api/v1/admin/cases`);
`aanvraag.ts` `owner?`; `applications.adapter` `listAll`/`deleteAny` + parse owner;
`registratie/application/admin-cases.store.ts` (+spec); `registratie/ui/admin-cases.page.ts`;
route in `app.routes.ts`; nav item in `site-header`; new `$localize` ids + English targets.
## Acceptance criteria
- [x] Admin-only page at `/beheer/zaken` lists all cases (owner + type + status), gated by
`cases:manage` (denial alert for non-admins; server re-enforces via `CasesAdmin`).
- [x] Admin delete removes any case (incl. submitted); confirmed, optimistic with rollback.
- [x] `npm run ci` green (336 FE tests, backend 129, localized build, drift clean after commit).