From fc6e73806ad9bb404a91f2f101331e23c806012e Mon Sep 17 00:00:00 2001 From: Edwin van den Houdt Date: Wed, 5 Aug 2026 15:34:10 +0200 Subject: [PATCH] refactor(backend): move aanvraag status lifecycle into the domain (WP-68 F3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The status was derived in Contracts/Mappers.ToStatusDto, not the domain; Concept was a magic "Concept" string with no AanvraagStatusTag member; and the besluit endpoint re-derived its own guard by reading the status back out of the DTO and Enum.Parse-ing it. New Domain/Applications/AanvraagStatus.cs models the full status (Concept included, via a null Tag rather than a sixth enum member) as a closed type, constructible only through its factories. Aanvraag.StatusAt(now) carries the logic verbatim; Mappers.ToStatusDto and ZgwZaakMapper's two status producers become one-line projections onto the same wire DTO, so the wire shape is unchanged (gen:api shows zero diff beyond F1's). The one remaining Enum.Parse (the beoordeling GET, which crosses the IZaakSource wire boundary) is now non-throwing on an unrecognised tag. Also, WP-68 F2: the besluit transition-legality check now runs inside ApplicationStore.RecordBesluit's write lock instead of in the endpoint beforehand — two concurrent besluiten used to both pass the check before either wrote, letting the second silently overwrite a terminal decision. RecordBesluit returns an Ok/NotFound/Conflict outcome, mirroring DocumentStore.DeleteResult. Also, WP-68 F6: the "toelichting required" rule moves from an inline endpoint check into BeoordelingRules.RequiresToelichting, alongside CanDecide. The three tests naming this refactor's regression net (AanvraagStatusTag_covers_the_published_lifecycle, AutoApprovable_flips_to_goedgekeurd_after_the_window, ZgwZaakMapperTests) pass unmodified. Co-Authored-By: Claude Opus 5 --- .../src/BigRegister.Api/Contracts/Mappers.cs | 37 ++++------- .../BigRegister.Api/Data/ApplicationStore.cs | 65 +++++++++++-------- .../src/BigRegister.Api/Data/IZaakSource.cs | 1 + .../BigRegister.Api/Data/LocalZaakSource.cs | 1 + .../Domain/Applications/AanvraagStatus.cs | 62 ++++++++++++++++++ .../Domain/Beoordeling/BeoordelingRules.cs | 21 ++++-- backend/src/BigRegister.Api/Program.cs | 36 +++++----- .../BigRegister.Api/Zgw/OpenZaakZaakSource.cs | 1 + .../src/BigRegister.Api/Zgw/ZgwZaakMapper.cs | 9 +-- .../BigRegister.Tests/ApplicationTests.cs | 1 + .../BeoordelingIdMismatchTests.cs | 1 + .../OpenZaakZaakSourceTests.cs | 1 + backend/tests/BigRegister.Tests/RuleTests.cs | 1 + 13 files changed, 159 insertions(+), 78 deletions(-) create mode 100644 backend/src/BigRegister.Api/Domain/Applications/AanvraagStatus.cs diff --git a/backend/src/BigRegister.Api/Contracts/Mappers.cs b/backend/src/BigRegister.Api/Contracts/Mappers.cs index eb37220..8ba9493 100644 --- a/backend/src/BigRegister.Api/Contracts/Mappers.cs +++ b/backend/src/BigRegister.Api/Contracts/Mappers.cs @@ -1,4 +1,5 @@ using BigRegister.Api.Data; +using BigRegister.Domain.Applications; using BigRegister.Domain.Diplomas; using BigRegister.Domain.Documents; using BigRegister.Domain.People; @@ -36,31 +37,17 @@ public static class Mappers public static DocumentCategoryDto ToDto(this DocumentCategory c) => new( c.CategoryId, c.Label, c.Description, c.Required, c.AcceptedTypes, c.MaxSizeMb, c.Multiple, c.AllowPostDelivery); - // Aanvraag status is COMPUTED ON READ: an auto-approvable submission reports - // Goedgekeurd once past the processing window, else In behandeling; a manual case - // stays In behandeling until a behandelaar records a decision (WP-65b — before that - // WP, it stayed In behandeling forever, awaiting the then-unbuilt backoffice). Pure — - // testable by passing different `now` values without waiting for the wall clock. - public static AanvraagStatusDto ToStatusDto(this Aanvraag a, DateTimeOffset now) - { - if (!a.Submitted) - return new("Concept", StepIndex: a.StepIndex, StepCount: a.StepCount); - if (a.Reden is not null) - return new(AanvraagStatusTag.Afgewezen.ToString(), Referentie: a.Referentie, Reden: a.Reden); - // A recorded decision (WP-65b) wins over the auto-approve computation below — a - // behandelaar's explicit besluit is authoritative once made. - if (a.BesluitStatus is { } besluit) - return besluit switch - { - Besluit.Goedkeuren => new(AanvraagStatusTag.Goedgekeurd.ToString(), Referentie: a.Referentie), - Besluit.Afwijzen => new(AanvraagStatusTag.Afgewezen.ToString(), Referentie: a.Referentie, Reden: a.BesluitToelichting), - Besluit.MeerInfoOpvragen => new(AanvraagStatusTag.MeerInfoGevraagd.ToString(), Referentie: a.Referentie, Reden: a.BesluitToelichting), - _ => throw new InvalidOperationException($"Unknown besluit {besluit}"), - }; - if (a.AutoApprovable && now > a.SubmittedAt!.Value + ApplicationStore.ProcessingWindow) - return new(AanvraagStatusTag.Goedgekeurd.ToString(), Referentie: a.Referentie); - return new(AanvraagStatusTag.InBehandeling.ToString(), Referentie: a.Referentie, Manual: !a.AutoApprovable); - } + /// Wire projection of an — the null "Concept" case + /// is the one place a status has no , so it becomes the wire + /// convention's magic string here at the boundary rather than living inside the domain type. + /// Shared by and ZgwZaakMapper, so both status producers + /// agree on the projection (WP-68 F3). + public static AanvraagStatusDto ToDto(this AanvraagStatus s) => new( + s.Tag?.ToString() ?? "Concept", s.StepIndex, s.StepCount, s.Referentie, s.Manual, s.Reden); + + // Aanvraag status is COMPUTED ON READ (see Aanvraag.StatusAt) — this is now a one-line + // projection of that domain method onto the wire DTO (WP-68 F3). + public static AanvraagStatusDto ToStatusDto(this Aanvraag a, DateTimeOffset now) => a.StatusAt(now).ToDto(); public static ApplicationSummaryDto ToSummaryDto(this Aanvraag a, DateTimeOffset now) => new( a.Id, a.Type, a.ToStatusDto(now), a.DocumentIds, diff --git a/backend/src/BigRegister.Api/Data/ApplicationStore.cs b/backend/src/BigRegister.Api/Data/ApplicationStore.cs index 8e64d04..904f9bd 100644 --- a/backend/src/BigRegister.Api/Data/ApplicationStore.cs +++ b/backend/src/BigRegister.Api/Data/ApplicationStore.cs @@ -1,31 +1,15 @@ using System.Text.Json; +using BigRegister.Domain.Applications; +using BigRegister.Domain.Beoordeling; using BigRegister.Domain.Submissions; namespace BigRegister.Api.Data; -/// -/// The post-submission aanvraag status lifecycle (ADR-0002, WP-63): Ingediend → In -/// behandeling → (Meer info gevraagd ⇄) → Goedgekeurd/Afgewezen. Concept (pre-submission, -/// the wizard draft) isn't part of this enum — see . -/// and are not reachable yet: no -/// endpoint sets them (that's WP-65's behandelaar-facing mutation) — modelled here so the -/// contract is ready when it does. -/// -public enum AanvraagStatusTag { Ingediend, InBehandeling, MeerInfoGevraagd, Goedgekeurd, Afgewezen } - -/// -/// A behandelaar's recorded decision (WP-65b) — the three actions the beoordeling screen -/// offers, each advancing and (via -/// ) the published -/// the FE renders. -/// -public enum Besluit { Goedkeuren, Afwijzen, MeerInfoOpvragen } - /// /// An application (aanvraag) — the system of record the dashboard reads. A wizard /// creates one as a Concept on its first step, syncs its draft snapshot per step, /// then submits it into the Concept → In behandeling → Goedgekeurd/Afgewezen -/// lifecycle (ADR-0002). Status is COMPUTED ON READ (see Mappers.ToStatusDto) so +/// lifecycle (ADR-0002). Status is COMPUTED ON READ (see ) so /// auto-approval is purely a function of stored timestamps — no timers, no jobs. /// public sealed class Aanvraag @@ -69,6 +53,26 @@ public sealed class Aanvraag /// The behandelaar's toelichting — required for Afwijzen/MeerInfoOpvragen (becomes /// the published status's Reden), optional for Goedkeuren. public string? BesluitToelichting { get; set; } + + /// The status at a point in time (WP-68 F3) — moved here from + /// Contracts.Mappers.ToStatusDto, which is now a one-line projection of this. A + /// recorded decision wins over the auto-approve computation below. + public AanvraagStatus StatusAt(DateTimeOffset now) + { + if (!Submitted) return AanvraagStatus.Concept(StepIndex, StepCount); + if (Reden is not null) return AanvraagStatus.Afgewezen(Referentie!, Reden); + if (BesluitStatus is { } besluit) + return besluit switch + { + Besluit.Goedkeuren => AanvraagStatus.Goedgekeurd(Referentie!), + Besluit.Afwijzen => AanvraagStatus.Afgewezen(Referentie!, BesluitToelichting), + Besluit.MeerInfoOpvragen => AanvraagStatus.MeerInfoGevraagd(Referentie!, BesluitToelichting), + _ => throw new InvalidOperationException($"Unknown besluit {besluit}"), + }; + if (AutoApprovable && now > SubmittedAt!.Value + ApplicationStore.ProcessingWindow) + return AanvraagStatus.Goedgekeurd(Referentie!); + return AanvraagStatus.InBehandeling(Referentie!, manual: !AutoApprovable); + } } /// @@ -270,23 +274,30 @@ public static class ApplicationStore } } - /// Record a behandelaar's decision (WP-65b). The endpoint has already checked - /// against the - /// freshly-read status before calling this — cross-owner like , - /// since a behandelaar decides on any citizen's case. Returns null only if the aanvraag - /// is gone (shouldn't happen — this runs right after the endpoint's own read found it). - public static Aanvraag? RecordBesluit(string id, Besluit besluit, string? toelichting) + public enum RecordBesluitOutcome { Ok, NotFound, Conflict } + + /// Record a behandelaar's decision (WP-65b) — cross-owner like + /// , since a behandelaar decides on any citizen's case. + /// WP-68 (F2): the transition-legality check () + /// now runs INSIDE this lock, against a status read fresh under the lock, rather than in + /// the endpoint beforehand — two concurrent besluiten used to both pass the endpoint's + /// check before either wrote, letting the second silently overwrite a terminal decision. + /// + public static (RecordBesluitOutcome Outcome, Aanvraag? Aanvraag) RecordBesluit(string id, Besluit besluit, string? toelichting, DateTimeOffset now) { lock (_gate) { using var db = Db.Create(); var a = db.Applications.Find(id); - if (a is null) return null; + if (a is null) return (RecordBesluitOutcome.NotFound, null); + var current = a.StatusAt(now).Tag; + if (current is null || !BeoordelingRules.CanDecide(current.Value)) + return (RecordBesluitOutcome.Conflict, null); a.BesluitStatus = besluit; a.BesluitToelichting = toelichting; a.UpdatedAt = DateTimeOffset.UtcNow; db.SaveChanges(); - return a; + return (RecordBesluitOutcome.Ok, a); } } } diff --git a/backend/src/BigRegister.Api/Data/IZaakSource.cs b/backend/src/BigRegister.Api/Data/IZaakSource.cs index 22436df..f6fb895 100644 --- a/backend/src/BigRegister.Api/Data/IZaakSource.cs +++ b/backend/src/BigRegister.Api/Data/IZaakSource.cs @@ -1,4 +1,5 @@ using BigRegister.Api.Contracts; +using BigRegister.Domain.Applications; using BigRegister.Domain.Authorization; namespace BigRegister.Api.Data; diff --git a/backend/src/BigRegister.Api/Data/LocalZaakSource.cs b/backend/src/BigRegister.Api/Data/LocalZaakSource.cs index 7326d68..5858cb1 100644 --- a/backend/src/BigRegister.Api/Data/LocalZaakSource.cs +++ b/backend/src/BigRegister.Api/Data/LocalZaakSource.cs @@ -1,4 +1,5 @@ using BigRegister.Api.Contracts; +using BigRegister.Domain.Applications; using BigRegister.Domain.Authorization; namespace BigRegister.Api.Data; diff --git a/backend/src/BigRegister.Api/Domain/Applications/AanvraagStatus.cs b/backend/src/BigRegister.Api/Domain/Applications/AanvraagStatus.cs new file mode 100644 index 0000000..7af6919 --- /dev/null +++ b/backend/src/BigRegister.Api/Domain/Applications/AanvraagStatus.cs @@ -0,0 +1,62 @@ +namespace BigRegister.Domain.Applications; + +/// +/// The post-submission aanvraag status lifecycle (ADR-0002, WP-63): Ingediend → In +/// behandeling → (Meer info gevraagd ⇄) → Goedgekeurd/Afgewezen. Concept (pre-submission, +/// the wizard draft) is deliberately NOT a member here — see , +/// which is null exactly when the aanvraag hasn't been submitted yet, instead of a sixth +/// "magic string" tag with no enum member to match it (WP-68 F3). +/// is reserved: no endpoint sets it yet (there is no state between +/// "just submitted" and "in behandeling" in this POC) — kept because the FE's status union +/// and $localize catalogue already declare it, and removing it would ripple into both. +/// +public enum AanvraagStatusTag { Ingediend, InBehandeling, MeerInfoGevraagd, Goedgekeurd, Afgewezen } + +/// +/// A behandelaar's recorded decision (WP-65b) — the three actions the beoordeling screen +/// offers, each advancing an aanvraag's . +/// +public enum Besluit { Goedkeuren, Afwijzen, MeerInfoOpvragen } + +/// +/// The domain projection of an aanvraag's status at a point in time (WP-68 F3) — the type +/// Aanvraag.StatusAt(now) returns, replacing the logic that used to live directly in +/// Contracts.Mappers.ToStatusDto. Constructible only via the factories below, so a +/// caller can never build e.g. a Referentie-less Goedgekeurd. is null only +/// for — the one construction path that used to be a bare "Concept" +/// string with no corresponding member. +/// +public sealed class AanvraagStatus +{ + public AanvraagStatusTag? Tag { get; } + public int? StepIndex { get; } + public int? StepCount { get; } + public string? Referentie { get; } + public bool? Manual { get; } + public string? Reden { get; } + + private AanvraagStatus(AanvraagStatusTag? tag, int? stepIndex, int? stepCount, string? referentie, bool? manual, string? reden) + { + Tag = tag; + StepIndex = stepIndex; + StepCount = stepCount; + Referentie = referentie; + Manual = manual; + Reden = reden; + } + + public static AanvraagStatus Concept(int stepIndex, int stepCount) => + new(null, stepIndex, stepCount, null, null, null); + + public static AanvraagStatus InBehandeling(string referentie, bool manual) => + new(AanvraagStatusTag.InBehandeling, null, null, referentie, manual, null); + + public static AanvraagStatus Goedgekeurd(string referentie) => + new(AanvraagStatusTag.Goedgekeurd, null, null, referentie, null, null); + + public static AanvraagStatus Afgewezen(string referentie, string? reden) => + new(AanvraagStatusTag.Afgewezen, null, null, referentie, null, reden); + + public static AanvraagStatus MeerInfoGevraagd(string referentie, string? reden) => + new(AanvraagStatusTag.MeerInfoGevraagd, null, null, referentie, null, reden); +} diff --git a/backend/src/BigRegister.Api/Domain/Beoordeling/BeoordelingRules.cs b/backend/src/BigRegister.Api/Domain/Beoordeling/BeoordelingRules.cs index d88d0a7..9b7e12e 100644 --- a/backend/src/BigRegister.Api/Domain/Beoordeling/BeoordelingRules.cs +++ b/backend/src/BigRegister.Api/Domain/Beoordeling/BeoordelingRules.cs @@ -1,19 +1,26 @@ -using BigRegister.Api.Data; +using BigRegister.Domain.Applications; namespace BigRegister.Domain.Beoordeling; /// -/// SERVER-OWNED rules for the behandelportal's case-treatment decision (WP-65). Read-side -/// today ( only, backing the beoordeling detail screen's decision -/// flag) — the decision-recording rules (which besluit is legal, whether it needs a -/// toelichting) land alongside the mutation endpoint in this WP's second half. +/// SERVER-OWNED rules for the behandelportal's case-treatment decision (WP-65). Used from +/// both the beoordeling read side ( backs the `canBesluiten` decision +/// flag) and the besluit write side (the SAME `CanDecide` gates the mutation, and — since +/// WP-68 F2 — runs inside the write lock, so the two can never drift and a concurrent besluit +/// can't race past the check). /// public static class BeoordelingRules { /// A behandelaar may record a decision while the aanvraag is in an open, non-terminal - /// status. Concept never reaches here (the endpoint 404s it before calling this); a case - /// already `Goedgekeurd`/`Afgewezen` is final. + /// status. Concept never reaches here (a null is checked + /// separately by callers); a case already `Goedgekeurd`/`Afgewezen` is final. public static bool CanDecide(AanvraagStatusTag current) => current is AanvraagStatusTag.Ingediend or AanvraagStatusTag.InBehandeling or AanvraagStatusTag.MeerInfoGevraagd; + + /// WP-68 F6: moved here from an inline check in the besluit endpoint. The + /// toelichting (behandelaar's explanation) is required for every besluit except an + /// approval — Afwijzen/MeerInfoOpvragen must justify why (becomes the published status's + /// Reden). + public static bool RequiresToelichting(Besluit besluit) => besluit != Besluit.Goedkeuren; } diff --git a/backend/src/BigRegister.Api/Program.cs b/backend/src/BigRegister.Api/Program.cs index 0724a9c..67caa9b 100644 --- a/backend/src/BigRegister.Api/Program.cs +++ b/backend/src/BigRegister.Api/Program.cs @@ -4,6 +4,7 @@ using System.Text.Json; using System.Text.Json.Serialization; using BigRegister.Api.Contracts; using BigRegister.Api.Data; +using BigRegister.Domain.Applications; using BigRegister.Domain.Authorization; using BigRegister.Domain.Beoordeling; using BigRegister.Domain.Diplomas; @@ -452,8 +453,10 @@ api.MapGet("/beoordeling/{id}", (string id, HttpContext ctx, IZaakSource zaken) var docs = DocumentStore.ByIds(c.DocumentIds) .Select(d => new BeoordelingDocumentDto(d.DocumentId, d.CategoryId, d.FileName)).ToList(); var masked = c with { Owner = MaskTail(c.Owner!, 3) }; - var decisions = new BeoordelingDecisionsDto( - BeoordelingRules.CanDecide(Enum.Parse(c.Status.Tag))); + // WP-68 (F3): non-throwing — c.Status.Tag crosses the IZaakSource wire boundary, so an + // unrecognised tag degrades to "cannot decide" instead of a 500. + var canBesluiten = Enum.TryParse(c.Status.Tag, out var tag) && BeoordelingRules.CanDecide(tag); + var decisions = new BeoordelingDecisionsDto(canBesluiten); return Results.Ok(new BeoordelingViewDto(masked, docs, decisions)); })) .Produces() @@ -464,14 +467,19 @@ api.MapGet("/beoordeling/{id}", (string id, HttpContext ctx, IZaakSource zaken) // lifecycle. The local write runs against ApplicationStore directly (not the IZaakSource // seam) — same reasoning as the GET above. The transition-legality check // (BeoordelingRules.CanDecide) is the SAME function the GET's canBesluiten flag uses, -// so the two can never drift. WP-66: once the local decision has committed, IZaakSource -// also gets a chance to advance the ZGW-side zaak status — LocalZaakSource no-ops, -// OpenZaakZaakSource POSTs a new Statussen entry (see its RecordBesluit). +// so the two can never drift — and (WP-68 F2) it now runs inside ApplicationStore.RecordBesluit's +// write lock rather than here, so two concurrent besluiten can't both pass it before either +// writes. WP-66: once the local decision has committed, IZaakSource also gets a chance to +// advance the ZGW-side zaak status — LocalZaakSource no-ops, OpenZaakZaakSource POSTs a new +// Statussen entry (see its RecordBesluit). api.MapPost("/beoordeling/{id}/besluit", (string id, RecordBesluitRequest req, HttpContext ctx, IZaakSource zaken) => Beoordelen(ctx, $"aanvraag/{id}/besluit", () => { if (!Enum.TryParse(req.Besluit, out var besluit)) return Results.Problem(detail: $"Onbekend besluit '{req.Besluit}'.", statusCode: StatusCodes.Status400BadRequest); + // WP-68 (F6): moved to BeoordelingRules.RequiresToelichting — same rule, now unit-testable. + if (BeoordelingRules.RequiresToelichting(besluit) && string.IsNullOrWhiteSpace(req.Toelichting)) + return Results.Problem(detail: "Toelichting is verplicht bij dit besluit.", statusCode: StatusCodes.Status400BadRequest); var now = DateTimeOffset.UtcNow; // Real bug fix (WP-66): `id` is the FE-facing case id from IZaakSource.ListCases — under @@ -481,17 +489,15 @@ api.MapPost("/beoordeling/{id}/besluit", (string id, RecordBesluitRequest req, H // (see ApplicationStore.GetByReferentie). var c = zaken.ListCases(now).FirstOrDefault(x => x.Id == id); var a = c?.Status.Referentie is { } referentie ? ApplicationStore.GetByReferentie(referentie) : null; - var statusTag = a?.ToStatusDto(now).Tag; - if (a is null || statusTag == "Concept") return Results.NotFound(); - var current = Enum.Parse(statusTag!); - if (!BeoordelingRules.CanDecide(current)) + if (a is null) return Results.NotFound(); + + var (outcome, updated) = ApplicationStore.RecordBesluit(a.Id, besluit, req.Toelichting, now); + if (outcome == ApplicationStore.RecordBesluitOutcome.NotFound) return Results.NotFound(); + if (outcome == ApplicationStore.RecordBesluitOutcome.Conflict) return Results.Problem( detail: "Deze aanvraag staat geen besluit meer toe in de huidige status.", statusCode: StatusCodes.Status409Conflict); - if (besluit != Besluit.Goedkeuren && string.IsNullOrWhiteSpace(req.Toelichting)) - return Results.Problem(detail: "Toelichting is verplicht bij dit besluit.", statusCode: StatusCodes.Status400BadRequest); - var updated = ApplicationStore.RecordBesluit(a.Id, besluit, req.Toelichting)!; app.Logger.LogInformation("aanvraag besluit id={Id} besluit={Besluit}", a.Id, besluit); // WP-60: the local decision above already committed — a ZGW failure here is caught and @@ -499,14 +505,14 @@ api.MapPost("/beoordeling/{id}/besluit", (string id, RecordBesluitRequest req, H // and document-link writes. try { - zaken.RecordBesluit(updated, besluit, req.Toelichting, now, ctx.Caller()); + zaken.RecordBesluit(updated!, besluit, req.Toelichting, now, ctx.Caller()); } catch (Exception ex) { - RecordZgwDivergence(ctx, a.Id, updated.Referentie ?? a.Id, ex); + RecordZgwDivergence(ctx, a.Id, updated!.Referentie ?? a.Id, ex); } - return Results.Ok(new RecordBesluitResponse(updated.ToStatusDto(now))); + return Results.Ok(new RecordBesluitResponse(updated!.ToStatusDto(now))); })) .Produces() .ProducesProblem(StatusCodes.Status400BadRequest) diff --git a/backend/src/BigRegister.Api/Zgw/OpenZaakZaakSource.cs b/backend/src/BigRegister.Api/Zgw/OpenZaakZaakSource.cs index bef4c7b..eda7a34 100644 --- a/backend/src/BigRegister.Api/Zgw/OpenZaakZaakSource.cs +++ b/backend/src/BigRegister.Api/Zgw/OpenZaakZaakSource.cs @@ -2,6 +2,7 @@ using System.Text.Json; using System.Text.Json.Serialization; using BigRegister.Api.Contracts; using BigRegister.Api.Data; +using BigRegister.Domain.Applications; using BigRegister.Domain.Authorization; namespace BigRegister.Api.Zgw; diff --git a/backend/src/BigRegister.Api/Zgw/ZgwZaakMapper.cs b/backend/src/BigRegister.Api/Zgw/ZgwZaakMapper.cs index 1569889..e54d478 100644 --- a/backend/src/BigRegister.Api/Zgw/ZgwZaakMapper.cs +++ b/backend/src/BigRegister.Api/Zgw/ZgwZaakMapper.cs @@ -1,5 +1,6 @@ using System.Text.Json.Serialization; using BigRegister.Api.Contracts; +using BigRegister.Domain.Applications; namespace BigRegister.Api.Zgw; @@ -33,9 +34,9 @@ public static class ZgwZaakMapper // ponytail: coarse status map — an open zaak (no einddatum) is In behandeling, a closed // one is Goedgekeurd. Real fidelity (statustype/resultaat lookups) is a later slice; the // Afgewezen path needs the resultaat resource. Enough to prove the seam end-to-end. - var status = z.Einddatum is null - ? new AanvraagStatusDto("InBehandeling", Referentie: z.Identificatie, Manual: true) - : new AanvraagStatusDto("Goedgekeurd", Referentie: z.Identificatie); + var status = (z.Einddatum is null + ? AanvraagStatus.InBehandeling(z.Identificatie, manual: true) + : AanvraagStatus.Goedgekeurd(z.Identificatie)).ToDto(); var created = Iso(z.Registratiedatum ?? z.Startdatum); var updated = Iso(z.Einddatum ?? z.Registratiedatum ?? z.Startdatum); @@ -59,5 +60,5 @@ public static class ZgwZaakMapper /// Status for a zaak that was JUST created (WP-50) — always the open/InBehandeling /// coarse status (no einddatum yet), same convention as . public static AanvraagStatusDto ToCreatedStatusDto(string identificatie) => - new("InBehandeling", Referentie: identificatie, Manual: true); + AanvraagStatus.InBehandeling(identificatie, manual: true).ToDto(); } diff --git a/backend/tests/BigRegister.Tests/ApplicationTests.cs b/backend/tests/BigRegister.Tests/ApplicationTests.cs index c8034a4..5ffc9e0 100644 --- a/backend/tests/BigRegister.Tests/ApplicationTests.cs +++ b/backend/tests/BigRegister.Tests/ApplicationTests.cs @@ -2,6 +2,7 @@ using System.Net; using System.Net.Http.Json; using BigRegister.Api.Contracts; using BigRegister.Api.Data; +using BigRegister.Domain.Applications; using Microsoft.AspNetCore.Mvc.Testing; namespace BigRegister.Tests; diff --git a/backend/tests/BigRegister.Tests/BeoordelingIdMismatchTests.cs b/backend/tests/BigRegister.Tests/BeoordelingIdMismatchTests.cs index a07f4a2..31fd155 100644 --- a/backend/tests/BigRegister.Tests/BeoordelingIdMismatchTests.cs +++ b/backend/tests/BigRegister.Tests/BeoordelingIdMismatchTests.cs @@ -1,6 +1,7 @@ using System.Net.Http.Json; using BigRegister.Api.Contracts; using BigRegister.Api.Data; +using BigRegister.Domain.Applications; using BigRegister.Domain.Authorization; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.TestHost; diff --git a/backend/tests/BigRegister.Tests/OpenZaakZaakSourceTests.cs b/backend/tests/BigRegister.Tests/OpenZaakZaakSourceTests.cs index 918898e..0db555b 100644 --- a/backend/tests/BigRegister.Tests/OpenZaakZaakSourceTests.cs +++ b/backend/tests/BigRegister.Tests/OpenZaakZaakSourceTests.cs @@ -1,6 +1,7 @@ using System.Net; using BigRegister.Api.Data; using BigRegister.Api.Zgw; +using BigRegister.Domain.Applications; using BigRegister.Domain.Authorization; namespace BigRegister.Tests; diff --git a/backend/tests/BigRegister.Tests/RuleTests.cs b/backend/tests/BigRegister.Tests/RuleTests.cs index c984c68..bd88643 100644 --- a/backend/tests/BigRegister.Tests/RuleTests.cs +++ b/backend/tests/BigRegister.Tests/RuleTests.cs @@ -1,4 +1,5 @@ using BigRegister.Api.Data; +using BigRegister.Domain.Applications; using BigRegister.Domain.Beoordeling; using BigRegister.Domain.Diplomas; using BigRegister.Domain.Documents;