• Mahmoud Aglan's avatar
    fix(training): give generated sessions a branch, and reserve the pitch they... · 5c5d1330
    Mahmoud Aglan authored
    fix(training): give generated sessions a branch, and reserve the pitch they were actually scheduled on
    
    Three defects on one path, all silent, all found while auditing a tenant whose
    sessions were invisible on every branch screen.
    
    BranchContext::branchIdForStamping() returns null in console and queue context —
    deliberately, because a cron that silently billed one branch would be worse than
    an unfiltered read. But that is exactly where sessions are born: the 02:00
    sessions:generate-upcoming run, and the TrainingSchedule::saved() hook. So
    SessionGeneratorService and AttendanceGenerationService, which both relied on the
    BelongsToBranch creating hook, have been writing branch_id NULL since they were
    written. On a strictly-scoped table that is not a leak but a disappearance: the
    row is in the database, counted by SQL, and on no screen in the product. It is
    also why attendance:backfill exists at 02:30 — it repairs the attendance half
    nightly, with the same session -> group attribution now applied at the source.
    
    CreateAutoReservation resolved the schedule by (training_group_id, day_of_week)
    and took ->first(): no start_time match, no is_active filter, no ordering. A
    group that trains twice on one weekday, or that has a deactivated row sitting
    beside a live one, booked whichever row sorted first — the wrong hour, or another
    branch's pitch entirely. The session already records which schedule row produced
    it, so use it; the (group, weekday) lookup survives only as a fallback for a
    session with no schedule_id, and now matches on time and is_active.
    
    The listener swallows its own exceptions into a log line, so none of this ever
    surfaced as an error.
    Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
    5c5d1330
SessionGeneratorService.php 7.45 KB