diff --git a/services/acl/Acl.Infrastructure/OpenZaakGateway.cs b/services/acl/Acl.Infrastructure/OpenZaakGateway.cs
index 2dcffd4..5f33ba8 100644
--- a/services/acl/Acl.Infrastructure/OpenZaakGateway.cs
+++ b/services/acl/Acl.Infrastructure/OpenZaakGateway.cs
@@ -8,6 +8,12 @@ namespace Acl.Infrastructure;
/// The only code that talks to OpenZaak's Zaken API (ADR-0001).
public sealed class OpenZaakGateway(HttpClient http, OpenZaakOptions options) : IZaakGateway
{
+ // The ACL owns which ZGW statustype/resultaat carries each domain outcome (ยง8.1). These
+ // omschrijvingen match the seeded BIG catalogus (infra/openzaak/seed_catalogus.py).
+ private const string GeregistreerdResultaat = "Geregistreerd"; // approval outcome
+ private const string GeannuleerdStatus = "Geannuleerd"; // document-timeout cancellation status (S-10c)
+ private const string VervallenResultaat = "Vervallen"; // document-timeout cancellation outcome (S-10c)
+
public async Task OpenZaakAsync(ZaakRequest request, CancellationToken ct = default)
{
ArgumentNullException.ThrowIfNull(request);
@@ -48,7 +54,9 @@ public sealed class OpenZaakGateway(HttpClient http, OpenZaakOptions options) :
ArgumentNullException.ThrowIfNull(zaaktypeUrl);
var eindstatus = await ResolveEindstatusAsync(zaaktypeUrl, ct);
- var resultaattype = await ResolveResultaattypeAsync(zaaktypeUrl, ct);
+ // Resolve the approval resultaat by name: once S-10c adds the Vervallen resultaattype, taking
+ // the first would be ambiguous (the Zaken API does not guarantee order).
+ var resultaattype = await ResolveResultaattypeByOmschrijvingAsync(zaaktypeUrl, GeregistreerdResultaat, ct);
// OpenZaak refuses to set a zaak's eindstatus unless the zaak has a resultaat
// ("resultaat-does-not-exist"), so record the resultaat first, then the status.
@@ -62,8 +70,26 @@ public sealed class OpenZaakGateway(HttpClient http, OpenZaakOptions options) :
"Setting the zaak status", ct);
}
- public Task SetZaakToCancellationStatusAsync(Uri zaakUrl, Uri zaaktypeUrl, DateOnly datumStatusGezet, CancellationToken ct = default)
- => throw new NotImplementedException();
+ public async Task SetZaakToCancellationStatusAsync(Uri zaakUrl, Uri zaaktypeUrl, DateOnly datumStatusGezet, CancellationToken ct = default)
+ {
+ ArgumentNullException.ThrowIfNull(zaakUrl);
+ ArgumentNullException.ThrowIfNull(zaaktypeUrl);
+
+ // Distinct from approval: resolve the cancellation statustype + resultaat by name (Geannuleerd
+ // is a non-terminal statustype, so it is never the eindstatus the approval path resolves).
+ var cancellationStatus = await ResolveStatustypeByOmschrijvingAsync(zaaktypeUrl, GeannuleerdStatus, ct);
+ var cancellationResultaat = await ResolveResultaattypeByOmschrijvingAsync(zaaktypeUrl, VervallenResultaat, ct);
+
+ // As with approval, OpenZaak wants the resultaat recorded before the status.
+ await PostAsync("/zaken/api/v1/resultaten",
+ new ResultaatDto(zaakUrl.ToString(), cancellationResultaat.ToString()),
+ "Setting the zaak cancellation resultaat", ct);
+
+ await PostAsync("/zaken/api/v1/statussen",
+ new StatusDto(zaakUrl.ToString(), cancellationStatus.ToString(),
+ datumStatusGezet.ToDateTime(TimeOnly.MinValue, DateTimeKind.Utc).ToString("yyyy-MM-ddTHH:mm:ssZ")),
+ "Setting the zaak cancellation status", ct);
+ }
public async Task GetZaakIdentificatieAsync(Uri zaakUrl, CancellationToken ct = default)
{
@@ -177,13 +203,23 @@ public sealed class OpenZaakGateway(HttpClient http, OpenZaakOptions options) :
return new Uri(eindstatus.Url);
}
- /// Resolve the zaaktype's resultaattype from the catalogus (the seed defines one).
- private async Task ResolveResultaattypeAsync(Uri zaaktypeUrl, CancellationToken ct)
+ /// Resolve a specific statustype from the catalogus by its omschrijving (e.g. "Geannuleerd").
+ private async Task ResolveStatustypeByOmschrijvingAsync(Uri zaaktypeUrl, string omschrijving, CancellationToken ct)
+ {
+ var page = await GetCatalogusAsync("statustypen", zaaktypeUrl, "statustypen", ct);
+ var match = (page.Results ?? []).FirstOrDefault(s => s.Omschrijving == omschrijving)
+ ?? throw new InvalidOperationException($"No '{omschrijving}' statustype found for zaaktype {zaaktypeUrl}");
+ return new Uri(match.Url);
+ }
+
+ /// Resolve a specific resultaattype from the catalogus by its omschrijving (the seed defines
+ /// "Geregistreerd" for approval and "Vervallen" for a document-timeout cancellation).
+ private async Task ResolveResultaattypeByOmschrijvingAsync(Uri zaaktypeUrl, string omschrijving, CancellationToken ct)
{
var page = await GetCatalogusAsync("resultaattypen", zaaktypeUrl, "resultaattypen", ct);
- var resultaattype = (page.Results ?? []).FirstOrDefault()
- ?? throw new InvalidOperationException($"No resultaattypen found for zaaktype {zaaktypeUrl}");
- return new Uri(resultaattype.Url);
+ var match = (page.Results ?? []).FirstOrDefault(r => r.Omschrijving == omschrijving)
+ ?? throw new InvalidOperationException($"No '{omschrijving}' resultaattype found for zaaktype {zaaktypeUrl}");
+ return new Uri(match.Url);
}
// GETs a catalogus collection filtered by zaaktype (status=alles includes concept + published).
@@ -227,7 +263,8 @@ public sealed class OpenZaakGateway(HttpClient http, OpenZaakOptions options) :
private sealed record StatustypeDto(
[property: JsonPropertyName("url")] string Url,
[property: JsonPropertyName("volgnummer")] int Volgnummer,
- [property: JsonPropertyName("isEindstatus")] bool IsEindstatus);
+ [property: JsonPropertyName("isEindstatus")] bool IsEindstatus,
+ [property: JsonPropertyName("omschrijving")] string? Omschrijving);
private sealed record ResultaatDto(
[property: JsonPropertyName("zaak")] string Zaak,
@@ -237,7 +274,8 @@ public sealed class OpenZaakGateway(HttpClient http, OpenZaakOptions options) :
[property: JsonPropertyName("results")] IReadOnlyList? Results);
private sealed record ResultaattypeDto(
- [property: JsonPropertyName("url")] string Url);
+ [property: JsonPropertyName("url")] string Url,
+ [property: JsonPropertyName("omschrijving")] string? Omschrijving);
private sealed record CreatedDto(
[property: JsonPropertyName("url")] string Url);
diff --git a/services/acl/Acl.Tests/OpenZaakGatewayTests.cs b/services/acl/Acl.Tests/OpenZaakGatewayTests.cs
index c570fc0..a94f783 100644
--- a/services/acl/Acl.Tests/OpenZaakGatewayTests.cs
+++ b/services/acl/Acl.Tests/OpenZaakGatewayTests.cs
@@ -410,7 +410,7 @@ public class OpenZaakGatewayTests
Gateway(ApprovalStub(rec, new OzRoutes { ResultaattypenJson = "{}" }))
.SetZaakToEindstatusAsync(new Uri(ZaakUrl), Zaaktype, new DateOnly(2026, 6, 4)));
- Assert.Contains("No resultaattypen found", ex.Message);
+ Assert.Contains("'Geregistreerd' resultaattype", ex.Message);
// Resolved the eindstatus + queried resultaattypen, but posted nothing.
Assert.Equal(-1, rec.IndexOf("/resultaten"));
Assert.Equal(-1, rec.IndexOf("/statussen"));