Skip to content

feat(events): event closure job (attended/no-show) #247

@danielhe4rt

Description

@danielhe4rt

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:
    1. Load event with policy
    2. Determine attendance_requirement from policy
    3. 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
    4. For each enrollment with status confirmed (never checked in): → no_show
    5. Record all transitions in audit trail (triggered_by=system)
    6. 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

  • Job transitions checked_in → attended when attendance requirement is met
  • Job transitions confirmed → no_show for participants who never checked in
  • attendance_requirement is correctly evaluated:
    • all_days: requires check-in on every day of the event
    • any_day: any single check-in suffices
    • minimum_days(N): requires N or more check-in days
  • Job is idempotent — running twice produces same result (no duplicate transitions)
  • ShouldBeUnique prevents duplicate job execution for same event
  • ParticipantAttended domain event is dispatched for each newly attended enrollment
  • Admin can override status (no_show → attended) with reason
  • All transitions are recorded in audit trail with triggered_by=system
  • Feature tests: all_days met, all_days not met, any_day, minimum_days, no_show marking, admin override, idempotency
  • Pint passes

Blocked by

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions