Files
atomic-design-poc/docs/project/backlog/WP-29-stamdata-beheer-editor.md
T
ehoandClaude Opus 4.8 5761b13dd2 style: format the repo with prettier (green format:check)
`npm run format:check` (a CI gate) had drifted red across 44 files — pre-existing
files plus recently-added ones committed without formatting. Ran `prettier --write .`;
no logic changes. Also regenerates documentation.json (compodoc reflects the reformatted
component sources).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 17:20:42 +02:00

5.5 KiB

WP-29 — Stamdata beheer editor (low-code, PR-emitting)

Status: done (0e77faf) Phase: follow-on — ADR-0004 realization (not part of the 2026-07-02 showcase audit)

Why

ADR-0004 (stamdata-as-code) named a future low-code editor that commits a PR as its mitigation for "a non-developer may need dev assistance to edit C#", and floated a data-file format as the alternative to typed-C# constants when hand-editing ergonomics outweigh maximal compile-time safety. This WP realizes both: an admin-only maintenance editor that reads the stamdata catalog, edits rows in a grid, and produces the edited JSON data-file the admin drops into the repo — the existing CI build + StamdataValidationTests stay the authority. No production database, no runtime write path (ADR-0004 unchanged).

Read first

  • ADR-0004 (docs/reference/architecture/0004-stamdata-as-code.md) — the model this obeys.
  • ADR-0001 (BFF-lite + decision DTOs) — the endpoints are screen-shaped, admin-gated reads.
  • src/app/brief/** (WP-23/26) — the org-template admin editor is the closest prior art (root store + machine + capability guard + admin role).

Decisions (pre-made, don't relitigate)

  • Read-only endpoints only. GET /stamdata (catalog) and GET /stamdata/{table}?peildatum= (schema + rows). No POST/PUT/DELETE — the edit lands as a reviewed PR, not a write. The stamdata:edit capability / CanEditStamdata gate the reads (naming is the enforce-twin of a future edit capability; deliberate).
  • Generic, schema-driven. One endpoint pair + one grid editor serve every table. Columns are reflected from the typed record (StamdataTable.Of<T>); the FE renders inputs by column type (date/number/enum/text). A new table is one line in StamdataCatalog — no new endpoint, UI, or test. (Catalog of one today; this is the shape ADR-0004 prescribed.)
  • Data-file format for professions. professions.json (embedded resource) replaces the hardcoded C# dictionary, deserialized into ProfessionMapping. This trades compile-time value checking (gate #1) for editor ergonomics — the value gate becomes StamdataValidationTests (gate #2), exactly the trade-off ADR-0004's consequences listed.
  • Valid-time. geldigVan/geldigTot (half-open [van, tot)); a table is temporal iff it has both columns. peildatum previews "which rows applied on date X". Professions.ByProgram preserves pre-valid-time behaviour by filtering to rows active today.
  • Apply path = download → PR. The editor's download() serializes the draft to {table}.json; the admin commits it. mutation-command is intentionally not used.
  • Admin-only, resource-independent authz — same shape as org-template management (role IS the decision), denials audited (no PII).

Files

  • Backend: backend/src/BigRegister.Api/Stamdata/{StamdataCatalog,StamdataTable,StamdataFile,ProfessionMapping}.cs (new), Professions.cs (now loads the data-file), professions.json (new), BigRegister.Api.csproj (embed Stamdata\*.json); Program.cs (two GET endpoints + StamdataAdmin gate), Contracts/Dtos.cs (3 DTOs), Domain/Authorization/Authz.cs (stamdata:edit + CanEditStamdata); tests StamdataEndpointTests.cs (new), StamdataValidationTests.cs (generic Every_catalog_table_is_valid).
  • Frontend: src/app/beheer/** (contracts / domain + specs / infrastructure + spec / application / ui + organism story); app.routes.ts (guarded lazy route), shared/domain/capability.ts + shared/infrastructure/me.adapter.ts (stamdata:edit), eslint.config.mjs (beheer boundary rules), tsconfig.json (@beheer/* alias); regenerated backend/swagger.json + src/app/shared/infrastructure/api-client.ts.

Acceptance criteria

  • GET /stamdata and GET /stamdata/{table} return admin-only (403 + audit otherwise).
  • professions served from professions.json; ProfessionMapping typed; behaviour of Professions.ByProgram unchanged for all-current rows.
  • Generic build gate: Every_catalog_table_is_valid covers every catalog table (keys non-blank, no overlapping validity, well-formed windows).
  • beheer/stamdata route capability-guarded; page shows denial for non-admin; grid renders from reflected schema; edits update dirty/change-count; download() yields a valid {table}.json; peildatum before 2000-01-01 → zero rows.
  • Full gate GREEN both sides; npm run gen:api leaves no drift; new stamdata story passes axe.

Verification

cd backend && dotnet test && dotnet format --verify-no-changes; npm run lint && npm run check:tokens && npm test && npm run build && npm run build-storybook && npm run test-storybook:ci; npm run gen:api && git diff --exit-code -- backend/swagger.json src/app/shared/infrastructure/api-client.ts. Live: /beheer/stamdata?role=admin renders + edits + downloads; without ?role=admin denies.

Out of scope

  • Runtime persistence of edits (would contradict ADR-0004) — the download-to-PR path is the design.
  • Migrating PolicyQuestions / document-categories into the catalog (each is a later one-liner).
  • A write-back "commit a PR on the admin's behalf" integration (the download() seam is where it slots in).

Risks

  • Data-file weakens compile-time value safety for professions — mitigated by StamdataValidationTests running in CI (a bad value fails the build, never prod).
  • Generic reflection assumes the record's first property is the key and camelCase JSON names — documented conventions in StamdataTable; covered by the endpoint schema test.