using Big.Domain; namespace Big.Application; /// /// Handles one acquired RegistratieVerlopen 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 /// . /// public sealed class ExpireRegistrationWorker(IRegistrationStore store, IAclClient acl) { /// /// 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. /// 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); } }