Closes#167. Found on a live deployment, on the studio's first real booking.
The bug
A weekly booking reserves a series of lessons, but the student answers the intake and ticks the policy boxes once, for the whole reservation — so BookingEndpoint::book() records both against the anchor lesson alone:
LessonDetail then looked them up by whichever lesson id was being viewed, so every occurrence after the first showed no intake answers and no policy acceptances — reading as though the student had agreed to nothing.
The fix
LessonDetail now takes the Lesson rather than a bare id and resolves the registration to series_id ?? id, so each occurrence reads the anchor's records. LessonController already had the Lesson loaded and passes it straight through.
This is the same seam PaymentService::creditForCancelledLesson() already uses to find a series lesson's payment on the anchor — series members carry no payment_id, and by the same token no registration of their own.
The intake answers had the identical bug from the same call site, so both are fixed together.
Upgrade safety
Read-path only. The acceptance rows were always written correctly, so existing bookings — including the customer's — display properly the moment this deploys. No schema change, no version bump, no migration, and nothing to re-collect from anyone.
Tests
composer test — 908 tests, 2576 assertions, all passing. New LessonDetailTest case covers a series occurrence reading the anchor's answers and acceptances; LessonControllerTest updated for the changed signature.
composer lint — no errors.
composer cs — clean.
Not included
Only the admin lesson detail view read the registration by lesson id, so nothing else needed the same treatment. Group-class enrolments record against the enrolment itself and were never affected.
Closes #167. Found on a live deployment, on the studio's first real booking.
## The bug
A weekly booking reserves a series of lessons, but the student answers the intake and ticks the policy boxes **once**, for the whole reservation — so `BookingEndpoint::book()` records both against the anchor lesson alone:
```php
$this->gate->record( PolicyAcceptance::REG_LESSON, $anchorId, ... );
```
`LessonDetail` then looked them up by whichever lesson id was being viewed, so every occurrence after the first showed no intake answers and no policy acceptances — reading as though the student had agreed to nothing.
## The fix
`LessonDetail` now takes the `Lesson` rather than a bare id and resolves the registration to `series_id ?? id`, so each occurrence reads the anchor's records. `LessonController` already had the `Lesson` loaded and passes it straight through.
This is the same seam `PaymentService::creditForCancelledLesson()` already uses to find a series lesson's payment on the anchor — series members carry no `payment_id`, and by the same token no registration of their own.
The intake answers had the identical bug from the same call site, so both are fixed together.
## Upgrade safety
Read-path only. The acceptance rows were always written correctly, so existing bookings — including the customer's — display properly the moment this deploys. No schema change, no version bump, no migration, and nothing to re-collect from anyone.
## Tests
- `composer test` — 908 tests, 2576 assertions, all passing. New `LessonDetailTest` case covers a series occurrence reading the anchor's answers and acceptances; `LessonControllerTest` updated for the changed signature.
- `composer lint` — no errors.
- `composer cs` — clean.
## Not included
Only the admin lesson detail view read the registration by lesson id, so nothing else needed the same treatment. Group-class enrolments record against the enrolment itself and were never affected.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
A weekly booking reserves a series of lessons, but the student answers
the intake and ticks the policy boxes once — so BookingEndpoint records
both against the anchor lesson alone. The admin detail view looked them
up by whichever lesson id was being viewed, so every occurrence after
the first showed no answers and no acceptances at all.
LessonDetail now takes the Lesson rather than a bare id and resolves the
registration to `series_id ?? id`, so each occurrence reads the anchor's
records. This is the same seam PaymentService already uses to find a
series lesson's payment on the anchor.
Nothing was ever missing from the database, so existing bookings read
correctly with no migration and no schema change.
Closes#167
Co-Authored-By: Claude Opus 5 <[email protected]>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #167. Found on a live deployment, on the studio's first real booking.
The bug
A weekly booking reserves a series of lessons, but the student answers the intake and ticks the policy boxes once, for the whole reservation — so
BookingEndpoint::book()records both against the anchor lesson alone:LessonDetailthen looked them up by whichever lesson id was being viewed, so every occurrence after the first showed no intake answers and no policy acceptances — reading as though the student had agreed to nothing.The fix
LessonDetailnow takes theLessonrather than a bare id and resolves the registration toseries_id ?? id, so each occurrence reads the anchor's records.LessonControlleralready had theLessonloaded and passes it straight through.This is the same seam
PaymentService::creditForCancelledLesson()already uses to find a series lesson's payment on the anchor — series members carry nopayment_id, and by the same token no registration of their own.The intake answers had the identical bug from the same call site, so both are fixed together.
Upgrade safety
Read-path only. The acceptance rows were always written correctly, so existing bookings — including the customer's — display properly the moment this deploys. No schema change, no version bump, no migration, and nothing to re-collect from anyone.
Tests
composer test— 908 tests, 2576 assertions, all passing. NewLessonDetailTestcase covers a series occurrence reading the anchor's answers and acceptances;LessonControllerTestupdated for the changed signature.composer lint— no errors.composer cs— clean.Not included
Only the admin lesson detail view read the registration by lesson id, so nothing else needed the same treatment. Group-class enrolments record against the enrolment itself and were never affected.
🤖 Generated with Claude Code