diff --git a/services/acl/Acl.Api/Program.cs b/services/acl/Acl.Api/Program.cs index 21509c8..9b1b7bc 100644 --- a/services/acl/Acl.Api/Program.cs +++ b/services/acl/Acl.Api/Program.cs @@ -33,6 +33,15 @@ builder.Services.AddSingleton(sp => sp.GetRequiredService() builder.Services.AddSingleton(sp => sp.GetRequiredService() .GetSection("Acl:OpenZaak").Get() ?? throw new InvalidOperationException("Missing configuration section 'Acl:OpenZaak'")); +// The default-fill values are held in a runtime-mutable store (S-15b, ADR-0026), seeded from the +// configured Acl:Defaults. The beheer portal edits it; the worker reads it per zaak. The S-27 +// resolution keys stay on AclDefaults (static) — see DefaultFillSettings. +builder.Services.AddSingleton(sp => +{ + var d = sp.GetRequiredService(); + return new InMemoryDefaultFillStore( + new DefaultFillSettings(d.Bronorganisatie, d.VerantwoordelijkeOrganisatie, d.Vertrouwelijkheidaanduiding)); +}); builder.Services.AddHttpClient(); // Singleton so the resolved zaaktype/informatieobjecttype URLs are cached across requests (S-27). builder.Services.AddSingleton(); @@ -91,6 +100,22 @@ app.MapPost("/documenten", async (StoreDocumentRequest body, AclService acl, Can app.MapGet("/catalogi/zaaktypen", async (AclService acl, CancellationToken ct) => Results.Ok(await acl.ListZaaktypenAsync(ct))); +// Read the current default-fill settings (beheer config viewer, S-15b). +app.MapGet("/default-fill", (AclService acl) => Results.Ok(acl.GetDefaultFill())); + +// Update the default-fill settings from the beheer portal (S-15b). Behind beheerder authorization at +// the BFF; the ACL validates the values are present (the three ZGW-mandatory fields). +app.MapPut("/default-fill", (DefaultFillSettings body, AclService acl) => +{ + if (string.IsNullOrWhiteSpace(body.Bronorganisatie) || + string.IsNullOrWhiteSpace(body.VerantwoordelijkeOrganisatie) || + string.IsNullOrWhiteSpace(body.Vertrouwelijkheidaanduiding)) + return Results.BadRequest(new { error = "bronorganisatie, verantwoordelijkeOrganisatie and vertrouwelijkheidaanduiding are all required." }); + + acl.UpdateDefaultFill(body); + return Results.NoContent(); +}); + app.Run(); public sealed record OpenZaakRequest(string Bsn, string Reference); diff --git a/services/acl/Acl.Application/AclService.cs b/services/acl/Acl.Application/AclService.cs index 52bb9d7..28e83f9 100644 --- a/services/acl/Acl.Application/AclService.cs +++ b/services/acl/Acl.Application/AclService.cs @@ -2,12 +2,15 @@ namespace Acl.Application; /// The ACL's single operation: open a zaak from a domain payload, /// default-filling the ZGW-mandatory fields (ADR-0003). -public sealed class AclService(IZaakGateway gateway, AclDefaults defaults, IZaaktypeCatalog catalog, IClock clock) +public sealed class AclService(IZaakGateway gateway, IDefaultFillStore fill, IZaaktypeCatalog catalog, IClock clock) { public async Task OpenZaakAsync(DomainRegistration registration, CancellationToken ct = default) { ArgumentNullException.ThrowIfNull(registration); + // Read the current default-fill per zaak (not at construction), so a beheerder edit (S-15b) + // takes effect on the next zaak without a restart. + var defaults = fill.Current; var request = new ZaakRequest( defaults.Bronorganisatie, defaults.VerantwoordelijkeOrganisatie, @@ -47,6 +50,17 @@ public sealed class AclService(IZaakGateway gateway, AclDefaults defaults, IZaak public Task> ListZaaktypenAsync(CancellationToken ct = default) => gateway.ListZaaktypenAsync(ct); + /// The current default-fill settings, for the beheer config viewer (S-15b). + public DefaultFillSettings GetDefaultFill() => fill.Current; + + /// Replace the default-fill settings from the beheer portal (S-15b). Takes effect on the + /// next zaak (the fill is read per zaak, not cached). + public void UpdateDefaultFill(DefaultFillSettings settings) + { + ArgumentNullException.ThrowIfNull(settings); + fill.Update(settings); + } + /// The zaak's reference (its ZGW identificatie), for the read projection (#78). public Task GetZaakReferenceAsync(Uri zaakUrl, CancellationToken ct = default) { @@ -68,6 +82,7 @@ public sealed class AclService(IZaakGateway gateway, AclDefaults defaults, IZaak ArgumentException.ThrowIfNullOrWhiteSpace(fileName); ArgumentException.ThrowIfNullOrWhiteSpace(contentType); + var defaults = fill.Current; var request = new DocumentRequest( defaults.Bronorganisatie, await catalog.GetInformatieobjecttypeUrlAsync(ct), diff --git a/services/acl/Acl.Tests/AclServiceTests.cs b/services/acl/Acl.Tests/AclServiceTests.cs index 905dc9f..6b726f8 100644 --- a/services/acl/Acl.Tests/AclServiceTests.cs +++ b/services/acl/Acl.Tests/AclServiceTests.cs @@ -84,8 +84,11 @@ public class AclServiceTests InformatieobjecttypeOmschrijving = "Diploma", }; + private static InMemoryDefaultFillStore FillFrom(AclDefaults d) => + new(new DefaultFillSettings(d.Bronorganisatie, d.VerantwoordelijkeOrganisatie, d.Vertrouwelijkheidaanduiding)); + private static AclService ServiceWith(FakeGateway gateway, AclDefaults defaults, DateOnly today) => - new(gateway, defaults, new CachedZaaktypeCatalog(gateway, defaults), new FixedClock(today)); + new(gateway, FillFrom(defaults), new CachedZaaktypeCatalog(gateway, defaults), new FixedClock(today)); private sealed class FixedClock(DateOnly today) : IClock { @@ -113,6 +116,22 @@ public class AclServiceTests Assert.Equal("reg-77", req.Identificatie); } + [Fact] + public async Task Opening_a_zaak_reflects_a_default_fill_update(/* S-15b */) + { + var gateway = new FakeGateway(); + var service = ServiceWith(gateway, Defaults(), new DateOnly(2026, 6, 4)); + + // A beheerder edits the default-fill; the very next zaak must use the new values (read per zaak). + service.UpdateDefaultFill(new DefaultFillSettings("999999999", "888888888", "vertrouwelijk")); + await service.OpenZaakAsync(new DomainRegistration("123456782", "reg-1")); + + var req = gateway.Captured!; + Assert.Equal("999999999", req.Bronorganisatie); + Assert.Equal("888888888", req.VerantwoordelijkeOrganisatie); + Assert.Equal("vertrouwelijk", req.Vertrouwelijkheidaanduiding); + } + [Fact] public async Task Rejects_a_null_registration_without_calling_the_gateway() { diff --git a/tests/acceptance/Steps/EenZaakOpenenSteps.cs b/tests/acceptance/Steps/EenZaakOpenenSteps.cs index 493fb55..b65a04f 100644 --- a/tests/acceptance/Steps/EenZaakOpenenSteps.cs +++ b/tests/acceptance/Steps/EenZaakOpenenSteps.cs @@ -44,7 +44,9 @@ public sealed class EenZaakOpenenSteps [When("the domain asks the ACL to open a zaak")] public async Task WhenTheDomainAsksTheAclToOpenAZaak() { - var service = new AclService(_gateway, _defaults!, new CachedZaaktypeCatalog(_gateway, _defaults!), new FixedClock(_today)); + var fill = new InMemoryDefaultFillStore(new DefaultFillSettings( + _defaults!.Bronorganisatie, _defaults.VerantwoordelijkeOrganisatie, _defaults.Vertrouwelijkheidaanduiding)); + var service = new AclService(_gateway, fill, new CachedZaaktypeCatalog(_gateway, _defaults!), new FixedClock(_today)); _returnedUrl = await service.OpenZaakAsync(_registration!); }