Parent
#237
What to build
Implement the scheduled job that runs after an event ends and automatically marks final states: checked_in → attended (success) and confirmed → no_show (absent).
Job: ProcessEventClosureJob
- Implements
ShouldQueue, ShouldBeUnique (uniqueId = event_id)
$backoff = [1, 5, 10] for exponential retry
- Implements
failed() with logging
- Logic:
- Load event with policy
- Determine attendance_requirement from policy
- For each enrollment with status
checked_in:
- Count check-in records for this enrollment
- Calculate event days from
starts_at to ends_at (inclusive date range)
- Apply requirement:
all_days: check-in count == total event days → attended
any_day: check-in count >= 1 → attended (always true if checked_in)
minimum_days(N): check-in count >= N → attended
- If requirement NOT met: →
no_show (was present some days but not enough)
- If met: transition to
attended, set attended_at
- For each enrollment with status
confirmed (never checked in): → no_show
- Record all transitions in audit trail (triggered_by=system)
- Dispatch
ParticipantAttended domain event for each newly attended enrollment (implements ShouldDispatchAfterCommit)
Scheduler:
- Register job to run periodically (every 15 minutes or hourly)
- Only processes events where
ends_at < now() AND has enrollments in non-terminal states
- Or: dispatch job once per event via
event:closure artisan command
Admin panel — Status override:
- "Override Status" action on enrollment records
- Allows:
no_show → attended (admin corrects mistake), confirmed → checked_in (late arrival that missed formal check-in)
- Requires reason text field
- Records transition with triggered_by=admin
Acceptance criteria
Blocked by
Parent
#237
What to build
Implement the scheduled job that runs after an event ends and automatically marks final states:
checked_in → attended(success) andconfirmed → no_show(absent).Job:
ProcessEventClosureJobShouldQueue,ShouldBeUnique(uniqueId = event_id)$backoff = [1, 5, 10]for exponential retryfailed()with loggingchecked_in:starts_attoends_at(inclusive date range)all_days: check-in count == total event days →attendedany_day: check-in count >= 1 →attended(always true if checked_in)minimum_days(N): check-in count >= N →attendedno_show(was present some days but not enough)attended, setattended_atconfirmed(never checked in): →no_showParticipantAttendeddomain event for each newly attended enrollment (implementsShouldDispatchAfterCommit)Scheduler:
ends_at < now()AND has enrollments in non-terminal statesevent:closureartisan commandAdmin panel — Status override:
no_show → attended(admin corrects mistake),confirmed → checked_in(late arrival that missed formal check-in)Acceptance criteria
checked_in → attendedwhen attendance requirement is metconfirmed → no_showfor participants who never checked inattendance_requirementis correctly evaluated:all_days: requires check-in on every day of the eventany_day: any single check-in sufficesminimum_days(N): requires N or more check-in daysShouldBeUniqueprevents duplicate job execution for same eventParticipantAttendeddomain event is dispatched for each newly attended enrollmentBlocked by
checked_inenrollments)