## What & why S-15b, second of the S-15 (#16) split, on top of S-15a (#133). A beheerder edits the ACL's ZGW **default-fill** values from the beheer portal, and the next zaak is stamped with the new values — no restart. Closes #131 ### The vertical portal → BFF `GET/PUT /beheer/default-fill` (medewerker realm + `beheerder` role) → ACL `GET/PUT /default-fill` → a runtime-mutable in-memory store the ACL reads **per zaak**. - **ACL**: `IDefaultFillStore` / `InMemoryDefaultFillStore` (thread-safe, seeded from `Acl:Defaults`); `AclService` reads `fill.Current` per zaak (not cached at construction); `GET`/`PUT /default-fill` with required-field validation. - **BFF**: `IAclClient` gains `GetDefaultFillAsync`/`UpdateDefaultFillAsync`; `GET`/`PUT /beheer/default-fill` behind the `beheerder` policy. OpenAPI + generated client regenerated. - **Frontend**: a *Default-fill* editor page in the beheer app (load → edit → save, with saved/failure states) + nav between Catalogus and Default-fill. ### Scope decision → ADR-0026 Only the **three ZGW fill fields** (bronorganisatie, verantwoordelijke organisatie, vertrouwelijkheidaanduiding) are editable. The S-27 catalog-resolution keys stay **static config** — editing them would desync the zaaktype-URL cache (ADR-0021), and they're catalogus wiring, not "default fill". The store is **in-memory** (seeded from config): an edit reverts on restart. That's the reference-app-appropriate ceiling (no DB added to the stateless ACL); upgrade path documented. Recorded in **ADR-0026**. ## Verified locally lint (`dotnet format`) ✓ · .NET unit — acl 60 / bff 45 / domain 152 / event-subscriber 19 / acceptance 17 ✓ · frontend lint+test (8 projects) ✓ · beheer build ✓. Clean full-solution build (caught + fixed the acceptance `AclService` ctor drift). TDD red→green per layer (ACL store, ACL endpoints, BFF, frontend). ## Definition of Done - [x] Failing test committed before each implementation (red→green per layer). - [x] Conventional Commits referencing #131. - [ ] CI green — see note below. - [x] Docs: ADR-0026 + S-15b demo note. - [x] Demo note in `docs/demo-script.md`. ## Note on CI The bulk validates in the fast jobs (lint/build/unit/frontend/mutation). The **verify-stack e2e** (incl. the new `default-fill.spec.ts`) can't go green until the pre-existing **verify-stack bring-up failure on the 1.27/2.0.0 runner** is resolved (that fails on plain `main` too — unrelated to this PR). Additive change; no existing e2e touched. 🤖 Generated with [Claude Code](https://claude.com/claude-code)Reviewed-on: #138
This commit was merged in pull request #138.
This commit is contained in:
@@ -59,12 +59,26 @@ public interface IProjectionClient
|
||||
/// internal reference, not shown in the portal.</summary>
|
||||
public sealed record BeheerZaaktype(string Identificatie, string Omschrijving);
|
||||
|
||||
/// <summary>Port to the ACL for read-only catalogus queries (beheer portal, S-15a). The BFF reaches the
|
||||
/// ACL directly for this read: the catalogus isn't a domain concern, and the ACL is the only code
|
||||
/// allowed to read the ZGW Catalogi API (§8.1, ADR-0025).</summary>
|
||||
/// <summary>The ACL default-fill settings the beheer portal reads + edits (S-15b): the three ZGW-mandatory
|
||||
/// fields the ACL stamps on every zaak (ADR-0003).</summary>
|
||||
public sealed record BeheerDefaultFill(
|
||||
string Bronorganisatie,
|
||||
string VerantwoordelijkeOrganisatie,
|
||||
string Vertrouwelijkheidaanduiding);
|
||||
|
||||
/// <summary>Port to the ACL for beheer queries (beheer portal). The BFF reaches the ACL directly: these
|
||||
/// aren't a domain concern, and the ACL is the only code allowed to read/own the ZGW-facing config
|
||||
/// (§8.1, ADR-0025).</summary>
|
||||
public interface IAclClient
|
||||
{
|
||||
/// <summary>The published catalogus zaaktypen, read-only (S-15a).</summary>
|
||||
Task<IReadOnlyList<BeheerZaaktype>> GetZaaktypenAsync(CancellationToken ct = default);
|
||||
|
||||
/// <summary>The current default-fill settings (S-15b).</summary>
|
||||
Task<BeheerDefaultFill> GetDefaultFillAsync(CancellationToken ct = default);
|
||||
|
||||
/// <summary>Replace the default-fill settings (S-15b).</summary>
|
||||
Task UpdateDefaultFillAsync(BeheerDefaultFill settings, CancellationToken ct = default);
|
||||
}
|
||||
|
||||
/// <summary>Calls the Domain Service's <c>POST /registrations</c>.</summary>
|
||||
@@ -141,4 +155,14 @@ public sealed class AclClient(HttpClient http) : IAclClient
|
||||
{
|
||||
public async Task<IReadOnlyList<BeheerZaaktype>> GetZaaktypenAsync(CancellationToken ct = default)
|
||||
=> await http.GetFromJsonAsync<List<BeheerZaaktype>>("catalogi/zaaktypen", ct) ?? [];
|
||||
|
||||
public async Task<BeheerDefaultFill> GetDefaultFillAsync(CancellationToken ct = default)
|
||||
=> await http.GetFromJsonAsync<BeheerDefaultFill>("default-fill", ct)
|
||||
?? throw new InvalidOperationException("The ACL returned an empty default-fill response.");
|
||||
|
||||
public async Task UpdateDefaultFillAsync(BeheerDefaultFill settings, CancellationToken ct = default)
|
||||
{
|
||||
using var response = await http.PutAsJsonAsync("default-fill", settings, ct);
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,6 +228,25 @@ app.MapGet("/beheer/catalogi/zaaktypen", async (IAclClient acl, CancellationToke
|
||||
.Produces(StatusCodes.Status401Unauthorized)
|
||||
.Produces(StatusCodes.Status403Forbidden);
|
||||
|
||||
// Beheer default-fill config (S-15b): read + edit the ACL's default-fill values. Behind medewerker-
|
||||
// realm + beheerder authorization; the BFF proxies the ACL (ADR-0025). The ACL validates the values.
|
||||
app.MapGet("/beheer/default-fill", async (IAclClient acl, CancellationToken ct) =>
|
||||
Results.Ok(await acl.GetDefaultFillAsync(ct)))
|
||||
.RequireAuthorization(BeheerAuth.Policy)
|
||||
.Produces<BeheerDefaultFill>(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status401Unauthorized)
|
||||
.Produces(StatusCodes.Status403Forbidden);
|
||||
|
||||
app.MapPut("/beheer/default-fill", async (BeheerDefaultFill body, IAclClient acl, CancellationToken ct) =>
|
||||
{
|
||||
await acl.UpdateDefaultFillAsync(body, ct);
|
||||
return Results.NoContent();
|
||||
})
|
||||
.RequireAuthorization(BeheerAuth.Policy)
|
||||
.Produces(StatusCodes.Status204NoContent)
|
||||
.Produces(StatusCodes.Status401Unauthorized)
|
||||
.Produces(StatusCodes.Status403Forbidden);
|
||||
|
||||
app.Run();
|
||||
|
||||
/// <summary>The behandelaar's decision on a registration.</summary>
|
||||
|
||||
Reference in New Issue
Block a user