refactor(backend): move aanvraag status lifecycle into the domain (WP-68 F3)
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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);
|
||||
}
|
||||
/// <summary>Wire projection of an <see cref="AanvraagStatus"/> — the null "Concept" case
|
||||
/// is the one place a status has no <see cref="AanvraagStatusTag"/>, so it becomes the wire
|
||||
/// convention's magic string here at the boundary rather than living inside the domain type.
|
||||
/// Shared by <see cref="ToStatusDto"/> and <c>ZgwZaakMapper</c>, so both status producers
|
||||
/// agree on the projection (WP-68 F3).</summary>
|
||||
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,
|
||||
|
||||
@@ -1,31 +1,15 @@
|
||||
using System.Text.Json;
|
||||
using BigRegister.Domain.Applications;
|
||||
using BigRegister.Domain.Beoordeling;
|
||||
using BigRegister.Domain.Submissions;
|
||||
|
||||
namespace BigRegister.Api.Data;
|
||||
|
||||
/// <summary>
|
||||
/// 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 <see cref="Aanvraag.Submitted"/>.
|
||||
/// <see cref="Ingediend"/> and <see cref="MeerInfoGevraagd"/> 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.
|
||||
/// </summary>
|
||||
public enum AanvraagStatusTag { Ingediend, InBehandeling, MeerInfoGevraagd, Goedgekeurd, Afgewezen }
|
||||
|
||||
/// <summary>
|
||||
/// A behandelaar's recorded decision (WP-65b) — the three actions the beoordeling screen
|
||||
/// offers, each advancing <see cref="Aanvraag.BesluitStatus"/> and (via
|
||||
/// <see cref="BigRegister.Api.Contracts.Mappers.ToStatusDto"/>) the published
|
||||
/// <see cref="AanvraagStatusTag"/> the FE renders.
|
||||
/// </summary>
|
||||
public enum Besluit { Goedkeuren, Afwijzen, MeerInfoOpvragen }
|
||||
|
||||
/// <summary>
|
||||
/// 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 <see cref="StatusAt"/>) so
|
||||
/// auto-approval is purely a function of stored timestamps — no timers, no jobs.
|
||||
/// </summary>
|
||||
public sealed class Aanvraag
|
||||
@@ -69,6 +53,26 @@ public sealed class Aanvraag
|
||||
/// <summary>The behandelaar's toelichting — required for Afwijzen/MeerInfoOpvragen (becomes
|
||||
/// the published status's Reden), optional for Goedkeuren.</summary>
|
||||
public string? BesluitToelichting { get; set; }
|
||||
|
||||
/// <summary>The status at a point in time (WP-68 F3) — moved here from
|
||||
/// <c>Contracts.Mappers.ToStatusDto</c>, which is now a one-line projection of this. A
|
||||
/// recorded decision wins over the auto-approve computation below.</summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -270,23 +274,30 @@ public static class ApplicationStore
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Record a behandelaar's decision (WP-65b). The endpoint has already checked
|
||||
/// <see cref="BigRegister.Domain.Beoordeling.BeoordelingRules.CanDecide"/> against the
|
||||
/// freshly-read status before calling this — cross-owner like <see cref="DeleteAny"/>,
|
||||
/// 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).</summary>
|
||||
public static Aanvraag? RecordBesluit(string id, Besluit besluit, string? toelichting)
|
||||
public enum RecordBesluitOutcome { Ok, NotFound, Conflict }
|
||||
|
||||
/// <summary>Record a behandelaar's decision (WP-65b) — cross-owner like
|
||||
/// <see cref="DeleteAny"/>, since a behandelaar decides on any citizen's case.
|
||||
/// WP-68 (F2): the transition-legality check (<see cref="BeoordelingRules.CanDecide"/>)
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using BigRegister.Api.Contracts;
|
||||
using BigRegister.Domain.Applications;
|
||||
using BigRegister.Domain.Authorization;
|
||||
|
||||
namespace BigRegister.Api.Data;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using BigRegister.Api.Contracts;
|
||||
using BigRegister.Domain.Applications;
|
||||
using BigRegister.Domain.Authorization;
|
||||
|
||||
namespace BigRegister.Api.Data;
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
namespace BigRegister.Domain.Applications;
|
||||
|
||||
/// <summary>
|
||||
/// 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 <see cref="AanvraagStatus.Tag"/>,
|
||||
/// 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).
|
||||
/// <see cref="Ingediend"/> 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.
|
||||
/// </summary>
|
||||
public enum AanvraagStatusTag { Ingediend, InBehandeling, MeerInfoGevraagd, Goedgekeurd, Afgewezen }
|
||||
|
||||
/// <summary>
|
||||
/// A behandelaar's recorded decision (WP-65b) — the three actions the beoordeling screen
|
||||
/// offers, each advancing an aanvraag's <see cref="AanvraagStatus"/>.
|
||||
/// </summary>
|
||||
public enum Besluit { Goedkeuren, Afwijzen, MeerInfoOpvragen }
|
||||
|
||||
/// <summary>
|
||||
/// The domain projection of an aanvraag's status at a point in time (WP-68 F3) — the type
|
||||
/// <c>Aanvraag.StatusAt(now)</c> returns, replacing the logic that used to live directly in
|
||||
/// <c>Contracts.Mappers.ToStatusDto</c>. Constructible only via the factories below, so a
|
||||
/// caller can never build e.g. a Referentie-less Goedgekeurd. <see cref="Tag"/> is null only
|
||||
/// for <see cref="Concept"/> — the one construction path that used to be a bare "Concept"
|
||||
/// string with no corresponding <see cref="AanvraagStatusTag"/> member.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
@@ -1,19 +1,26 @@
|
||||
using BigRegister.Api.Data;
|
||||
using BigRegister.Domain.Applications;
|
||||
|
||||
namespace BigRegister.Domain.Beoordeling;
|
||||
|
||||
/// <summary>
|
||||
/// SERVER-OWNED rules for the behandelportal's case-treatment decision (WP-65). Read-side
|
||||
/// today (<see cref="CanDecide"/> 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 (<see cref="CanDecide"/> 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).
|
||||
/// </summary>
|
||||
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 <see cref="AanvraagStatus.Tag"/> 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;
|
||||
|
||||
/// <summary>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).</summary>
|
||||
public static bool RequiresToelichting(Besluit besluit) => besluit != Besluit.Goedkeuren;
|
||||
}
|
||||
|
||||
@@ -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<AanvraagStatusTag>(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<AanvraagStatusTag>(c.Status.Tag, out var tag) && BeoordelingRules.CanDecide(tag);
|
||||
var decisions = new BeoordelingDecisionsDto(canBesluiten);
|
||||
return Results.Ok(new BeoordelingViewDto(masked, docs, decisions));
|
||||
}))
|
||||
.Produces<BeoordelingViewDto>()
|
||||
@@ -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<Besluit>(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<AanvraagStatusTag>(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<RecordBesluitResponse>()
|
||||
.ProducesProblem(StatusCodes.Status400BadRequest)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
/// <summary>Status for a zaak that was JUST created (WP-50) — always the open/InBehandeling
|
||||
/// coarse status (no einddatum yet), same convention as <see cref="ToSummaryDto"/>.</summary>
|
||||
public static AanvraagStatusDto ToCreatedStatusDto(string identificatie) =>
|
||||
new("InBehandeling", Referentie: identificatie, Manual: true);
|
||||
AanvraagStatus.InBehandeling(identificatie, manual: true).ToDto();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user