Files
atomic-design-poc/backend/src/BigRegister.Api/Domain/Applications/AanvraagStatus.cs
T
ehoandClaude Opus 5 fc6e73806a 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>
2026-08-05 15:34:10 +02:00

63 lines
2.9 KiB
C#

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);
}