-
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:Claude Opus 5 (1M context) <noreply@anthropic.com>
5c5d1330