using System.Text.Json;
namespace BigRegister.Domain.Applications;
///
/// The aanvraag lifecycle as a closed union: (the pre-submission
/// wizard draft) → (awaiting a behandelaar's decision, or already
/// auto-rejected at submission time — see ) →
/// (a behandelaar's outcome recorded). Each variant carries only the fields that make sense for
/// it; the private base constructor closes the hierarchy to the nested sealed records below, so
/// a caller can never construct a fourth variant, a with no referentie, or
/// an Afwijzen/MeerInfoGevraagd with no toelichting — each is a compile error (a missing
/// `required` member, CS9035), not a runtime null-check the way the old flat, mutable
/// Aanvraag needed one.
///
/// is the EF-mapped persistence row this maps to/from
/// (Api.Data.AanvraagMapper's ToDomain/ApplyTo/ToEntity) — it stays a
/// flat, mutable bag with no invariants of its own (SQLite needs exactly that shape); this type
/// is what production code actually reads and writes everywhere else. The wire-facing,
/// point-in-time a screen renders is a further, time-dependent
/// projection (StatusAt, in Api.Data) — the auto-approval window is a function of
/// wall-clock time, not of this stored shape, so it stays a derived read rather than a fourth
/// member of this union.
///
public abstract record Aanvraag
{
public required string Id { get; init; }
public required string Type { get; init; } // registratie | herregistratie | intake
public required string Owner { get; init; }
public required IReadOnlyList DocumentIds { get; init; }
public required DateTimeOffset CreatedAt { get; init; }
public required DateTimeOffset UpdatedAt { get; init; }
/// The OpenZaak zaak's URL, set once CreateZaak registers one — null under
/// the local source, or before a zaak has been registered at all.
public string? ZaakUrl { get; init; }
/// Non-null means the ZGW side of this aanvraag's last write did not
/// complete — see Api.Data.ApplicationStore.SetZgwError.
public string? ZgwError { get; init; }
private Aanvraag() { }
/// Pre-submission wizard draft.
public sealed record Concept : Aanvraag
{
public JsonElement? Draft { get; init; }
public int StepIndex { get; }
public int StepCount { get; }
/// 0 <= <= — the
/// non-strict upper bound, not the strict "<" a wizard's own step cursor uses, because
/// ApplicationStore.CreateConcept's freshly-created row is (StepIndex: 0, StepCount:
/// 0) before the wizard's first draft sync ever runs, and that has to stay constructible.
///
public Concept(int stepIndex, int stepCount)
{
if (stepIndex < 0 || stepCount < 0 || stepIndex > stepCount)
throw new ArgumentOutOfRangeException(
nameof(stepIndex), stepIndex, $"StepIndex must be within [0, StepCount ({stepCount})].");
StepIndex = stepIndex;
StepCount = stepCount;
}
}
/// Submitted, no behandelaar decision recorded yet. non-null
/// means SubmissionRules rejected it automatically at submission time (e.g. a manually
/// entered diploma) — terminal in practice (BeoordelingRules.CanDecide refuses a
/// besluit once the projected status is already Afgewezen) but structurally still "no besluit
/// was ever recorded", hence it lives here rather than in .
public sealed record Submitted : Aanvraag
{
public required string Referentie { get; init; }
public required DateTimeOffset SubmittedAt { get; init; }
public required bool AutoApprovable { get; init; }
public string? Reden { get; init; }
}
/// A behandelaar's decision — closed by besluit: only
/// / require a toelichting
/// (BeoordelingRules.RequiresToelichting's rule, now also a type, not just an endpoint
/// check) — omitting it is a compile error, not merely a 400 the type happens to also let
/// slip through at runtime.
public abstract record Decided : Aanvraag
{
public required string Referentie { get; init; }
public required DateTimeOffset SubmittedAt { get; init; }
private Decided() { }
public sealed record Goedgekeurd : Decided;
public sealed record Afgewezen : Decided
{
public required string Toelichting { get; init; }
}
public sealed record MeerInfoGevraagd : Decided
{
public required string Toelichting { get; init; }
}
}
}