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,
IRegisterRecordGateway register,
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,
defaults.Vertrouwelijkheidaanduiding,
await catalog.GetZaaktypeUrlAsync(ct),
clock.Today,
registration.Reference);
return await gateway.OpenZaakAsync(request, ct);
}
///
/// Approve a zaak: set it to the eindstatus of the BIG zaaktype (resolved by identificatie, S-27),
/// then write the register record to Objecten (S-19a). The domain hands over only the zaak URL; the
/// ACL owns which statustype means "approved" and what the register record looks like (§8.1).
///
///
/// OpenZaak holds the process, Objecten holds the register (ADR-0028), so approval is two writes
/// across two modules and is eventually consistent by construction. Both are idempotent — a status
/// is a log entry, the record upsert is keyed on the zaak id — so a caller that retries a failed
/// approval converges rather than duplicating.
///
public async Task ApproveZaakAsync(Uri zaakUrl, CancellationToken ct = default)
{
ArgumentNullException.ThrowIfNull(zaakUrl);
await gateway.SetZaakToEindstatusAsync(zaakUrl, await catalog.GetZaaktypeUrlAsync(ct), clock.Today, ct);
await register.UpsertAsync(
new RegisterRecord(
ZaakId(zaakUrl),
RegisterRecordStatus.Ingeschreven,
await gateway.GetZaakIdentificatieAsync(zaakUrl, ct)),
ct);
}
/// The zaak's UUID — the key the register record and the read projection rows share.
private static string ZaakId(Uri zaakUrl) => zaakUrl.Segments[^1].TrimEnd('/');
///
/// Cancel a zaak on document-timeout expiry (S-10c): set it to the BIG zaaktype's cancellation
/// statustype + resultaat. The domain hands over only the zaak URL; the ACL owns which
/// statustype/resultaat means "cancelled" (§8.1).
///
public async Task CancelZaakAsync(Uri zaakUrl, CancellationToken ct = default)
{
ArgumentNullException.ThrowIfNull(zaakUrl);
await gateway.SetZaakToCancellationStatusAsync(zaakUrl, await catalog.GetZaaktypeUrlAsync(ct), clock.Today, ct);
}
/// The published zaaktypen, for the beheer catalogus viewer (S-15a). Read-only passthrough:
/// no default-fill, the ACL is simply the only code allowed to read ZGW (§8.1).
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)
{
ArgumentNullException.ThrowIfNull(zaakUrl);
return gateway.GetZaakIdentificatieAsync(zaakUrl, ct);
}
///
/// Store an uploaded diploma against the zaak (S-10b): default-fill the ZGW-mandatory document
/// fields (informatieobjecttype, bronorganisatie, vertrouwelijkheidaanduiding, taal, creatiedatum)
/// and hand the file to the gateway, which creates the informatieobject and relates it to the zaak.
/// The domain supplies only the zaak, the bytes, and the file's name/type (§8.1).
///
public async Task StoreDiplomaAsync(Uri zaakUrl, byte[] content, string fileName, string contentType, CancellationToken ct = default)
{
ArgumentNullException.ThrowIfNull(zaakUrl);
ArgumentNullException.ThrowIfNull(content);
ArgumentException.ThrowIfNullOrWhiteSpace(fileName);
ArgumentException.ThrowIfNullOrWhiteSpace(contentType);
var defaults = fill.Current;
var request = new DocumentRequest(
defaults.Bronorganisatie,
await catalog.GetInformatieobjecttypeUrlAsync(ct),
defaults.Vertrouwelijkheidaanduiding,
zaakUrl,
clock.Today,
Titel: "Diploma",
Auteur: "zorgprofessional",
Taal: "nld",
Bestandsnaam: fileName,
Formaat: contentType,
Inhoud: content);
return await gateway.StoreDocumentAsync(request, ct);
}
}