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:
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user