diff --git a/apps/openbaar/src/app/register/register-page.html b/apps/openbaar/src/app/register/register-page.html
index 2051621..4f76ffb 100644
--- a/apps/openbaar/src/app/register/register-page.html
+++ b/apps/openbaar/src/app/register/register-page.html
@@ -43,7 +43,7 @@
@for (entry of entries(); track entry.id) {
- | {{ entry.id }} |
+ {{ entry.reference }} |
{{ entry.status }} |
}
diff --git a/apps/openbaar/src/app/register/register-page.spec.ts b/apps/openbaar/src/app/register/register-page.spec.ts
index d9dd6c9..b333cc9 100644
--- a/apps/openbaar/src/app/register/register-page.spec.ts
+++ b/apps/openbaar/src/app/register/register-page.spec.ts
@@ -5,8 +5,8 @@ import { axe } from 'vitest-axe';
import { RegisterPage } from './register-page';
const sample: OpenbaarEntry[] = [
- { id: 'zaak-abc', status: 'INGEDIEND' },
- { id: 'zaak-def', status: 'INGESCHREVEN' },
+ { id: 'zaak-abc', status: 'INGEDIEND', reference: 'REG-abc' },
+ { id: 'zaak-def', status: 'INGESCHREVEN', reference: 'REG-def' },
];
function providers(get = vi.fn().mockReturnValue(of(sample))) {
@@ -22,9 +22,11 @@ describe('RegisterPage', () => {
await render(RegisterPage, { providers: providers(get).providers });
expect(get).toHaveBeenCalled();
- expect(await screen.findByText(/zaak-abc/)).toBeTruthy();
+ // The Referentie column shows the citizen's reference (matches the submit confirmation, #78),
+ // not the internal zaak id.
+ expect(await screen.findByText(/REG-abc/)).toBeTruthy();
expect(screen.getByText(/INGEDIEND/)).toBeTruthy();
- expect(screen.getByText(/zaak-def/)).toBeTruthy();
+ expect(screen.getByText(/REG-def/)).toBeTruthy();
});
it('searches by the entered term', async () => {
diff --git a/libs/api-client/src/lib/generated/bff-api.ts b/libs/api-client/src/lib/generated/bff-api.ts
index 40a825d..5f76b6b 100644
--- a/libs/api-client/src/lib/generated/bff-api.ts
+++ b/libs/api-client/src/lib/generated/bff-api.ts
@@ -27,6 +27,8 @@ import {
export interface OpenbaarEntry {
id: string;
status: string;
+ /** @nullable */
+ reference: string | null;
}
export interface SubmitAccepted {
diff --git a/services/bff/Bff.Api/DownstreamClients.cs b/services/bff/Bff.Api/DownstreamClients.cs
index 2b66096..c03f166 100644
--- a/services/bff/Bff.Api/DownstreamClients.cs
+++ b/services/bff/Bff.Api/DownstreamClients.cs
@@ -6,11 +6,12 @@ namespace Bff.Api;
public sealed record SubmitAccepted(string RegistrationId, string Status);
/// A projection row as the projection-api serves it. Bsn/NaamPlaceholder are
-/// read but never surfaced by the openbaar endpoint (public-safe filtering, ADR-0010/S-09).
-public sealed record ProjectionEntry(string Id, string Status, string? Bsn, string? NaamPlaceholder);
+/// read but never surfaced by the openbaar endpoint (public-safe filtering, ADR-0010/S-09).
+/// Reference is the public-safe citizen reference (the zaak identificatie, #78).
+public sealed record ProjectionEntry(string Id, string Status, string? Reference, string? Bsn, string? NaamPlaceholder);
/// A public-safe openbaar register row — only non-sensitive fields leave the BFF.
-public sealed record OpenbaarEntry(string Id, string Status);
+public sealed record OpenbaarEntry(string Id, string Status, string? Reference);
/// Port to the Domain Service (§8.3: the BFF is the portals' only backend; it fans out).
public interface IDomainClient
diff --git a/services/bff/Bff.Api/OpenbaarProjection.cs b/services/bff/Bff.Api/OpenbaarProjection.cs
index 7f1ae33..050c327 100644
--- a/services/bff/Bff.Api/OpenbaarProjection.cs
+++ b/services/bff/Bff.Api/OpenbaarProjection.cs
@@ -11,8 +11,10 @@ public static class OpenbaarProjection
{
var filtered = string.IsNullOrWhiteSpace(q)
? entries
- : entries.Where(e => e.Id.Contains(q, StringComparison.OrdinalIgnoreCase));
+ : entries.Where(e =>
+ e.Id.Contains(q, StringComparison.OrdinalIgnoreCase) ||
+ (e.Reference?.Contains(q, StringComparison.OrdinalIgnoreCase) ?? false));
- return [.. filtered.Select(e => new OpenbaarEntry(e.Id, e.Status))];
+ return [.. filtered.Select(e => new OpenbaarEntry(e.Id, e.Status, e.Reference))];
}
}
diff --git a/services/bff/Bff.Tests/OpenbaarEndpointTests.cs b/services/bff/Bff.Tests/OpenbaarEndpointTests.cs
index 0fcb165..ee915b5 100644
--- a/services/bff/Bff.Tests/OpenbaarEndpointTests.cs
+++ b/services/bff/Bff.Tests/OpenbaarEndpointTests.cs
@@ -10,7 +10,7 @@ public class OpenbaarEndpointTests
public async Task Serves_public_safe_rows_anonymously()
{
using var factory = new BffFactory();
- factory.Projection.Entries.Add(new ProjectionEntry("abc-111", "INGEDIEND", "123456782", "Jan"));
+ factory.Projection.Entries.Add(new ProjectionEntry("abc-111", "INGEDIEND", "REG-abc", "123456782", "Jan"));
// No Authorization header — the openbaar register is a public lookup (ADR-0010/S-09).
var response = await factory.CreateClient().GetAsync("/openbaar/register");
@@ -19,7 +19,9 @@ public class OpenbaarEndpointTests
var body = await response.Content.ReadAsStringAsync();
Assert.Contains("abc-111", body);
Assert.Contains("INGEDIEND", body);
- // The bsn must never appear in a public response.
+ // The public reference is surfaced (matches the submit confirmation, #78)...
+ Assert.Contains("REG-abc", body);
+ // ...but the bsn must never appear in a public response.
Assert.DoesNotContain("123456782", body);
}
@@ -27,8 +29,8 @@ public class OpenbaarEndpointTests
public async Task Filters_by_the_query_parameter()
{
using var factory = new BffFactory();
- factory.Projection.Entries.Add(new ProjectionEntry("abc-111", "INGEDIEND", null, null));
- factory.Projection.Entries.Add(new ProjectionEntry("def-222", "INGEDIEND", null, null));
+ factory.Projection.Entries.Add(new ProjectionEntry("abc-111", "INGEDIEND", "REG-abc", null, null));
+ factory.Projection.Entries.Add(new ProjectionEntry("def-222", "INGEDIEND", "REG-def", null, null));
var rows = await factory.CreateClient()
.GetFromJsonAsync>("/openbaar/register?q=abc");
diff --git a/services/bff/Bff.Tests/OpenbaarProjectionTests.cs b/services/bff/Bff.Tests/OpenbaarProjectionTests.cs
index a385808..666b94d 100644
--- a/services/bff/Bff.Tests/OpenbaarProjectionTests.cs
+++ b/services/bff/Bff.Tests/OpenbaarProjectionTests.cs
@@ -6,8 +6,8 @@ public class OpenbaarProjectionTests
{
private static readonly ProjectionEntry[] Sample =
[
- new("abc-111", "INGEDIEND", "123456782", "Jan"),
- new("def-222", "INGEDIEND", "987654321", "Piet"),
+ new("abc-111", "INGEDIEND", Reference: "REG-A", Bsn: "123456782", NaamPlaceholder: "Jan"),
+ new("def-222", "INGESCHREVEN", Reference: "REG-B", Bsn: "987654321", NaamPlaceholder: "Piet"),
];
[Fact]
@@ -18,31 +18,47 @@ public class OpenbaarProjectionTests
Assert.Equal(2, view.Count);
Assert.Equal("abc-111", view[0].Id);
Assert.Equal("INGEDIEND", view[0].Status);
+ // The public reference (zaak identificatie) is surfaced so it matches the submit confirmation (#78).
+ Assert.Equal("REG-A", view[0].Reference);
}
[Fact]
- public void Public_view_exposes_only_id_and_status()
+ public void Public_view_exposes_only_id_status_and_reference()
{
- // OpenbaarEntry structurally carries only Id + Status — bsn/naam can never leak.
+ // OpenbaarEntry structurally carries only public-safe fields — bsn/naam can never leak.
var props = typeof(OpenbaarEntry).GetProperties().Select(p => p.Name).ToArray();
- Assert.Equal(["Id", "Status"], props);
+ Assert.Equal(["Id", "Status", "Reference"], props);
}
[Theory]
- [InlineData("abc", 1)]
- [InlineData("ABC", 1)]
- [InlineData("2", 1)]
- [InlineData("zzz", 0)]
+ [InlineData("abc", 1)] // by id
+ [InlineData("ABC", 1)] // by id, case-insensitive
+ [InlineData("def", 1)] // by id
+ [InlineData("zzz", 0)] // no match
public void Filters_by_id_containing_the_query_case_insensitively(string q, int expected)
- {
- var view = OpenbaarProjection.PublicView(Sample, q);
+ => Assert.Equal(expected, OpenbaarProjection.PublicView(Sample, q).Count);
- Assert.Equal(expected, view.Count);
- }
+ [Theory]
+ [InlineData("REG-A", 1)] // by reference — the citizen's confirmation reference
+ [InlineData("reg-a", 1)] // by reference, case-insensitive
+ [InlineData("REG", 2)] // both share the prefix
+ public void Filters_by_reference_containing_the_query_case_insensitively(string q, int expected)
+ => Assert.Equal(expected, OpenbaarProjection.PublicView(Sample, q).Count);
[Fact]
public void Blank_query_is_treated_as_no_filter()
{
Assert.Equal(2, OpenbaarProjection.PublicView(Sample, " ").Count);
}
+
+ [Fact]
+ public void A_row_without_a_reference_never_matches_a_query()
+ {
+ // Older rows can have a null reference (the column is additive, #78/ADR-0012). A search must
+ // simply not match them — it must not throw and must not treat "no reference" as a match.
+ var entries = new[] { new ProjectionEntry("xyz-999", "INGEDIEND", Reference: null, Bsn: null, NaamPlaceholder: null) };
+
+ Assert.Empty(OpenbaarProjection.PublicView(entries, "REG"));
+ Assert.Equal("xyz-999", Assert.Single(OpenbaarProjection.PublicView(entries, "xyz")).Id);
+ }
}
diff --git a/services/bff/openapi.json b/services/bff/openapi.json
index f1305b2..92a9120 100644
--- a/services/bff/openapi.json
+++ b/services/bff/openapi.json
@@ -67,7 +67,8 @@
"OpenbaarEntry": {
"required": [
"id",
- "status"
+ "status",
+ "reference"
],
"type": "object",
"properties": {
@@ -76,6 +77,12 @@
},
"status": {
"type": "string"
+ },
+ "reference": {
+ "type": [
+ "null",
+ "string"
+ ]
}
}
},
@@ -101,4 +108,4 @@
"name": "Bff.Api"
}
]
-}
\ No newline at end of file
+}
diff --git a/tests/acceptance/Steps/BffToegangSteps.cs b/tests/acceptance/Steps/BffToegangSteps.cs
index 7e19e04..165347b 100644
--- a/tests/acceptance/Steps/BffToegangSteps.cs
+++ b/tests/acceptance/Steps/BffToegangSteps.cs
@@ -31,7 +31,7 @@ public sealed class BffToegangSteps : IDisposable
[Given("the projection contains a zaak \"(.*)\" for BSN \"(.*)\"")]
public void GivenTheProjectionContains(string zaakId, string bsn)
{
- _host.Projection.Entries.Add(new ProjectionEntry(zaakId, "INGEDIEND", bsn, null));
+ _host.Projection.Entries.Add(new ProjectionEntry(zaakId, "INGEDIEND", "REG-" + zaakId, bsn, null));
_client = _host.CreateClient();
}