feat(domain): pass the registrationId to the ACL as the zaak reference (refs #78)

OpenZaakWorker forwards registration.Id to IAclClient.OpenZaakAsync so the zaak's
identificatie equals the reference the citizen sees on the submit confirmation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-14 14:46:40 +02:00
parent ffbd8072b2
commit 7b841d6e62
7 changed files with 23 additions and 10 deletions

View File

@@ -28,7 +28,7 @@ public sealed class OpenZaakWorker(IRegistrationStore store, IAclClient acl)
if (registration.ZaakUrl is not null) if (registration.ZaakUrl is not null)
return registration.ZaakUrl; return registration.ZaakUrl;
var zaakUrl = await acl.OpenZaakAsync(registration.Bsn, ct); var zaakUrl = await acl.OpenZaakAsync(registration.Bsn, registration.Id.ToString(), ct);
registration.AttachZaak(zaakUrl); registration.AttachZaak(zaakUrl);
await store.SaveAsync(registration, ct); await store.SaveAsync(registration, ct);
return zaakUrl; return zaakUrl;

View File

@@ -24,7 +24,10 @@ public interface IWorkflowClient
/// </summary> /// </summary>
public interface IAclClient public interface IAclClient
{ {
Task<Uri> OpenZaakAsync(string bsn, CancellationToken ct = default); /// <summary>Open a zaak for the registration. <paramref name="reference"/> is the registration's
/// own reference (its id); the ACL records it as the zaak's identificatie so the openbaar register
/// can show the same reference the zorgprofessional was given (S-09b follow-up, adr-proposal #78).</summary>
Task<Uri> OpenZaakAsync(string bsn, string reference, CancellationToken ct = default);
/// <summary> /// <summary>
/// Record the approval on the given zaak. The ACL translates this to the ZGW concept — setting /// Record the approval on the given zaak. The ACL translates this to the ZGW concept — setting

View File

@@ -11,10 +11,10 @@ namespace Big.Infrastructure;
/// </summary> /// </summary>
public sealed class AclHttpClient(HttpClient http, AclOptions options) : IAclClient public sealed class AclHttpClient(HttpClient http, AclOptions options) : IAclClient
{ {
public async Task<Uri> OpenZaakAsync(string bsn, CancellationToken ct = default) public async Task<Uri> OpenZaakAsync(string bsn, string reference, CancellationToken ct = default)
{ {
using var response = await http.PostAsJsonAsync( using var response = await http.PostAsJsonAsync(
new Uri(options.BaseUrl, "zaken"), new OpenZaakRequest(bsn), ct); new Uri(options.BaseUrl, "zaken"), new OpenZaakRequest(bsn, reference), ct);
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
var opened = await response.Content.ReadFromJsonAsync<OpenZaakResponse>(ct) var opened = await response.Content.ReadFromJsonAsync<OpenZaakResponse>(ct)
@@ -31,7 +31,9 @@ public sealed class AclHttpClient(HttpClient http, AclOptions options) : IAclCli
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
} }
private sealed record OpenZaakRequest([property: JsonPropertyName("bsn")] string Bsn); private sealed record OpenZaakRequest(
[property: JsonPropertyName("bsn")] string Bsn,
[property: JsonPropertyName("reference")] string Reference);
private sealed record OpenZaakResponse([property: JsonPropertyName("zaakUrl")] string ZaakUrl); private sealed record OpenZaakResponse([property: JsonPropertyName("zaakUrl")] string ZaakUrl);

View File

@@ -15,12 +15,13 @@ public class AclHttpClientTests
var client = Client(capture.Responds(HttpStatusCode.OK, var client = Client(capture.Responds(HttpStatusCode.OK,
"""{"zaakUrl":"http://openzaak/zaken/api/v1/zaken/abc"}""")); """{"zaakUrl":"http://openzaak/zaken/api/v1/zaken/abc"}"""));
var url = await client.OpenZaakAsync("123456782"); var url = await client.OpenZaakAsync("123456782", "reg-42");
Assert.Equal("http://openzaak/zaken/api/v1/zaken/abc", url.ToString()); Assert.Equal("http://openzaak/zaken/api/v1/zaken/abc", url.ToString());
Assert.Equal(HttpMethod.Post, capture.Seen!.Method); Assert.Equal(HttpMethod.Post, capture.Seen!.Method);
Assert.Equal("http://acl/zaken", capture.Seen.RequestUri!.ToString()); Assert.Equal("http://acl/zaken", capture.Seen.RequestUri!.ToString());
Assert.Contains("\"bsn\":\"123456782\"", capture.Body); Assert.Contains("\"bsn\":\"123456782\"", capture.Body);
Assert.Contains("\"reference\":\"reg-42\"", capture.Body);
} }
[Fact] [Fact]
@@ -29,7 +30,7 @@ public class AclHttpClientTests
var capture = new RequestCapture(); var capture = new RequestCapture();
var client = Client(capture.Responds(HttpStatusCode.BadGateway)); var client = Client(capture.Responds(HttpStatusCode.BadGateway));
await Assert.ThrowsAsync<HttpRequestException>(() => client.OpenZaakAsync("123456782")); await Assert.ThrowsAsync<HttpRequestException>(() => client.OpenZaakAsync("123456782", "reg-1"));
} }
[Fact] [Fact]
@@ -38,7 +39,7 @@ public class AclHttpClientTests
var capture = new RequestCapture(); var capture = new RequestCapture();
var client = Client(capture.Responds(HttpStatusCode.OK, "null")); var client = Client(capture.Responds(HttpStatusCode.OK, "null"));
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => client.OpenZaakAsync("123456782")); var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => client.OpenZaakAsync("123456782", "reg-1"));
Assert.Contains("empty", ex.Message, StringComparison.OrdinalIgnoreCase); Assert.Contains("empty", ex.Message, StringComparison.OrdinalIgnoreCase);
} }

View File

@@ -50,15 +50,17 @@ internal sealed class FakeAclClient(Uri? zaakUrl = null) : IAclClient
private readonly Uri _zaakUrl = zaakUrl ?? DefaultZaakUrl; private readonly Uri _zaakUrl = zaakUrl ?? DefaultZaakUrl;
public string? OpenedForBsn { get; private set; } public string? OpenedForBsn { get; private set; }
public string? OpenedWithReference { get; private set; }
public int CallCount { get; private set; } public int CallCount { get; private set; }
public Uri? ApprovedZaakUrl { get; private set; } public Uri? ApprovedZaakUrl { get; private set; }
public int ApproveCallCount { get; private set; } public int ApproveCallCount { get; private set; }
public Task<Uri> OpenZaakAsync(string bsn, CancellationToken ct = default) public Task<Uri> OpenZaakAsync(string bsn, string reference, CancellationToken ct = default)
{ {
CallCount++; CallCount++;
OpenedForBsn = bsn; OpenedForBsn = bsn;
OpenedWithReference = reference;
return Task.FromResult(_zaakUrl); return Task.FromResult(_zaakUrl);
} }

View File

@@ -25,6 +25,9 @@ public class OpenZaakWorkerTests
Assert.Equal(FakeAclClient.DefaultZaakUrl, zaakUrl); Assert.Equal(FakeAclClient.DefaultZaakUrl, zaakUrl);
Assert.Equal("123456782", acl.OpenedForBsn); Assert.Equal("123456782", acl.OpenedForBsn);
// The registration's own id is handed to the ACL as the zaak reference (identificatie),
// so the openbaar register can show the reference the zorgprofessional received (#78).
Assert.Equal(registration.Id.ToString(), acl.OpenedWithReference);
var saved = await store.GetAsync(registration.Id); var saved = await store.GetAsync(registration.Id);
Assert.Equal(FakeAclClient.DefaultZaakUrl, saved!.ZaakUrl); Assert.Equal(FakeAclClient.DefaultZaakUrl, saved!.ZaakUrl);
Assert.Equal(1, store.SaveCount); Assert.Equal(1, store.SaveCount);

View File

@@ -26,11 +26,13 @@ public sealed class InMemoryAclClient : IAclClient
public static readonly Uri OpenedZaakUrl = new("http://openzaak/zaken/api/v1/zaken/acc-zaak"); public static readonly Uri OpenedZaakUrl = new("http://openzaak/zaken/api/v1/zaken/acc-zaak");
public string? OpenedForBsn { get; private set; } public string? OpenedForBsn { get; private set; }
public string? OpenedWithReference { get; private set; }
public Uri? ApprovedZaakUrl { get; private set; } public Uri? ApprovedZaakUrl { get; private set; }
public Task<Uri> OpenZaakAsync(string bsn, CancellationToken ct = default) public Task<Uri> OpenZaakAsync(string bsn, string reference, CancellationToken ct = default)
{ {
OpenedForBsn = bsn; OpenedForBsn = bsn;
OpenedWithReference = reference;
return Task.FromResult(OpenedZaakUrl); return Task.FromResult(OpenedZaakUrl);
} }