fix(training): give generated sessions a branch, and reserve the pitch they...
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>
Showing
Please register or sign in to comment