feat(portal-beheer): ACL default-fill configuration editor (closes #131) #138
@@ -33,6 +33,15 @@ builder.Services.AddSingleton(sp => sp.GetRequiredService<IConfiguration>()
|
||||
builder.Services.AddSingleton(sp => sp.GetRequiredService<IConfiguration>()
|
||||
.GetSection("Acl:OpenZaak").Get<OpenZaakOptions>()
|
||||
?? 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<IDefaultFillStore>(sp =>
|
||||
{
|
||||
var d = sp.GetRequiredService<AclDefaults>();
|
||||
return new InMemoryDefaultFillStore(
|
||||
new DefaultFillSettings(d.Bronorganisatie, d.VerantwoordelijkeOrganisatie, d.Vertrouwelijkheidaanduiding));
|
||||
});
|
||||
builder.Services.AddHttpClient<IZaakGateway, OpenZaakGateway>();
|
||||
// Singleton so the resolved zaaktype/informatieobjecttype URLs are cached across requests (S-27).
|
||||
builder.Services.AddSingleton<IZaaktypeCatalog, CachedZaaktypeCatalog>();
|
||||
@@ -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);
|
||||
|
||||
@@ -2,12 +2,15 @@ namespace Acl.Application;
|
||||
|
||||
/// <summary>The ACL's single operation: open a zaak from a domain payload,
|
||||
/// default-filling the ZGW-mandatory fields (ADR-0003).</summary>
|
||||
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<Uri> 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<IReadOnlyList<ZaaktypeSummary>> ListZaaktypenAsync(CancellationToken ct = default) =>
|
||||
gateway.ListZaaktypenAsync(ct);
|
||||
|
||||
/// <summary>The current default-fill settings, for the beheer config viewer (S-15b).</summary>
|
||||
public DefaultFillSettings GetDefaultFill() => fill.Current;
|
||||
|
||||
/// <summary>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).</summary>
|
||||
public void UpdateDefaultFill(DefaultFillSettings settings)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(settings);
|
||||
fill.Update(settings);
|
||||
}
|
||||
|
||||
/// <summary>The zaak's reference (its ZGW identificatie), for the read projection (#78).</summary>
|
||||
public Task<string> 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),
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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!);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user