The expiry worker asks the ACL to cancel the zaak before advancing the aggregate to VERLOPEN, so a lapsed document term is reflected in ZGW and not only in the domain. Ordered ACL-first for redelivery safety; guarded so a redelivered job neither re-saves nor re-cancels. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
46 lines
2.5 KiB
C#
46 lines
2.5 KiB
C#
using Big.Domain;
|
|
|
|
namespace Big.Application;
|
|
|
|
/// <summary>
|
|
/// Handles one acquired <c>RegistratieVerlopen</c> external-worker job (S-10a, ADR-0017): load the
|
|
/// registration the job correlates to and expire it to VERLOPEN — the 30-day document-wait timer fired
|
|
/// before the documents arrived, so the case is cancelled. Pure application logic over ports; it knows
|
|
/// nothing of Flowable. The polling loop that feeds it jobs lives in Infrastructure. Mirrors
|
|
/// <see cref="OpenZaakWorker"/>.
|
|
/// </summary>
|
|
public sealed class ExpireRegistrationWorker(IRegistrationStore store, IAclClient acl)
|
|
{
|
|
/// <summary>
|
|
/// Process the job. Idempotent and tolerant of races (§8.6, at-least-once delivery): a job whose
|
|
/// registration is already resolved — a redelivered expiry (VERLOPEN), or one withdrawn/decided
|
|
/// while it waited (INGETROKKEN/INGESCHREVEN/AFGEWEZEN) — is a no-op, so the job still completes
|
|
/// rather than throwing into a redelivery loop. Only a still-open registration is expired. An
|
|
/// unknown registration is an error: it throws, leaving the job un-completed for Flowable to redeliver.
|
|
/// </summary>
|
|
public async Task HandleAsync(RegistratieVerlopenJob job, CancellationToken ct = default)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(job);
|
|
|
|
var registration = await store.GetAsync(job.RegistrationId, ct)
|
|
?? throw new InvalidOperationException(
|
|
$"No registration {job.RegistrationId} for RegistratieVerlopen job {job.JobId}.");
|
|
|
|
// Only a still-open registration lapses; an already-resolved one (expired, or withdrawn/decided
|
|
// while it waited) is left untouched so the job can complete without violating the aggregate.
|
|
if (registration.Status is not (RegistrationStatus.Ingediend or RegistrationStatus.InBehandeling))
|
|
return;
|
|
|
|
// Cancel the ZGW zaak before advancing the aggregate (mirrors the approval path): if the ACL
|
|
// call fails it throws, the aggregate stays open, and the job is redelivered (§8.6) — rather
|
|
// than leaving the aggregate VERLOPEN while the zaak stays open. The status guard above stops a
|
|
// redelivered job from cancelling the zaak twice (a second resultaat would be a 400). A
|
|
// registration expired before its zaak was opened has nothing to cancel.
|
|
if (registration.ZaakUrl is not null)
|
|
await acl.CancelZaakAsync(registration.ZaakUrl, ct);
|
|
|
|
registration.Expire();
|
|
await store.SaveAsync(registration, ct);
|
|
}
|
|
}
|