feat(domain): record inscription date + herregistratie reminder-due rule (refs #18)

Approve stores the inscription moment; HerregistratieVoor derives the deadline
(inscription + 5-year validity); HerregistratieReminderDue(asOf) is true once the
16-week window before the deadline opens for a still-un-reminded inscription;
MarkHerregistratieReminderVerstuurd is idempotent and guards against reminding a
non-inscribed registration.

refs #18
This commit is contained in:
not
2026-07-23 11:51:47 +02:00
parent dc532dee00
commit 22215865b3
+22 -6
View File
@@ -105,7 +105,7 @@ public sealed class Registration
RequireOpenForDecision(nameof(Approve)); RequireOpenForDecision(nameof(Approve));
Status = RegistrationStatus.Ingeschreven; Status = RegistrationStatus.Ingeschreven;
// RED stub: the inscription moment is not stored yet. IngeschrevenOp = ingeschrevenOp;
} }
// --- Herregistratie (S-17) — RED stubs, implemented in the green commit --------------------- // --- Herregistratie (S-17) — RED stubs, implemented in the green commit ---------------------
@@ -125,19 +125,35 @@ public sealed class Registration
/// <summary>The date by which herregistratie must happen: inscription + validity. Null until /// <summary>The date by which herregistratie must happen: inscription + validity. Null until
/// inscribed.</summary> /// inscribed.</summary>
public DateTimeOffset? HerregistratieVoor => null; // RED stub public DateTimeOffset? HerregistratieVoor =>
IngeschrevenOp is DateTimeOffset ingeschrevenOp ? ingeschrevenOp + HerregistratieGeldigheid : null;
/// <summary>Whether the herregistratie reminder has been sent for this inscription (S-17).</summary> /// <summary>Whether the herregistratie reminder has been sent for this inscription (S-17).</summary>
public bool HerregistratieReminderVerstuurd { get; private set; } public bool HerregistratieReminderVerstuurd { get; private set; }
/// <summary>Whether, as of <paramref name="asOf"/>, this registration is due a herregistratie /// <summary>Whether, as of <paramref name="asOf"/>, this registration is due a herregistratie
/// reminder: it is inscribed, the reminder window before its deadline has opened, and it has not /// reminder: it is inscribed, the reminder window before its deadline has opened, and it has not
/// already been reminded.</summary> /// already been reminded. Once inside the window it stays due until reminded (an overdue inscription
public bool HerregistratieReminderDue(DateTimeOffset asOf) => false; // RED stub /// is still due). This is the single rule the store query and the sweep both build on.</summary>
public bool HerregistratieReminderDue(DateTimeOffset asOf) =>
Status == RegistrationStatus.Ingeschreven
&& !HerregistratieReminderVerstuurd
&& IngeschrevenOp is DateTimeOffset ingeschrevenOp
&& asOf >= ingeschrevenOp + HerregistratieGeldigheid - Herinneringstermijn;
/// <summary>Record that the herregistratie reminder has been sent. Idempotent — a re-sweep is a /// <summary>Record that the herregistratie reminder has been sent. Idempotent — a re-sweep is a
/// no-op; only an inscribed registration can be reminded.</summary> /// no-op (§8.6); only an inscribed registration can be reminded.</summary>
public void MarkHerregistratieReminderVerstuurd() { } // RED stub public void MarkHerregistratieReminderVerstuurd()
{
if (HerregistratieReminderVerstuurd)
return;
if (Status != RegistrationStatus.Ingeschreven)
throw new InvalidOperationException(
$"Registration {Id} is {Status}; only an INGESCHREVEN registration can be sent a herregistratie reminder.");
HerregistratieReminderVerstuurd = true;
}
/// <summary> /// <summary>
/// Reject the registration — the behandelaar's decision not to enter it in the register. Advances a /// Reject the registration — the behandelaar's decision not to enter it in the register. Advances a