Implements #2 (Availability) — durations, weekly-recurring windows, and a public agenda calendar — and slips in a requested change to price offerings in dollars (plus a $wpdb format bugfix found along the way).
AvailabilityRepository: createWeeklySeries() generates N weekly rows sharing a recurrence_group (= the first row's id); findAvailable() filters by offering_id and duration_minutes.
REST: GET filters by offering/duration; POST accepts duration_minutes, offering_id, recurrence (single|weekly) + weeks.
Admin: the My Availability form adds a duration, an offering picker (the instructor's private-lesson offerings), and one-off / weekly-for-N-weeks options.
Front end: booking.js renders an agenda calendar (slots grouped by day, with duration). The richer booking UX (offering pick, questions, policy acceptance) lands with the booking-flow work (#3).
Offering price in dollars
us_offerings.price_cents (INT) → price DECIMAL(10,2); Offering uses float $price. Admin form and REST take dollars.
The Offering insert/update $wpdbformat arrays were misaligned with the columns — e.g. billing_mode (a string) mapped to %d, which $wpdb coerces to 0, corrupting the stored value on a real insert. Replaced both with a single, correctly-aligned COLUMN_FORMATS constant. (Unit tests missed it because they assert the format arg via Mockery::type('array').)
Notes
Date math uses DateTimeImmutable::modify() because the CI no-debug regex dd\( matches ->add( — worth giving that regex word boundaries later.
Bumps PHPStan to --memory-limit=1G in the lint script; 128M now crashes analysis as the codebase has grown.
Verification
Rebased on current main (includes the merged Policies #15). composer test (91 tests), composer lint, composer cs, and the no-debug check all pass.
Implements **#2 (Availability)** — durations, weekly-recurring windows, and a public agenda calendar — and slips in a requested change to **price offerings in dollars** (plus a `$wpdb` format bugfix found along the way).
## Availability (#2)
- **Schema**: `us_availability` gains `offering_id` (nullable FK), `duration_minutes` (default 60), `recurrence_group` (nullable), with indexes.
- **`AvailabilitySlot`**: carries the new fields.
- **`AvailabilityRepository`**: `createWeeklySeries()` generates N weekly rows sharing a `recurrence_group` (= the first row's id); `findAvailable()` filters by `offering_id` and `duration_minutes`.
- **REST**: GET filters by offering/duration; POST accepts `duration_minutes`, `offering_id`, `recurrence` (`single`|`weekly`) + `weeks`.
- **Admin**: the My Availability form adds a duration, an offering picker (the instructor's private-lesson offerings), and one-off / weekly-for-N-weeks options.
- **Front end**: `booking.js` renders an agenda calendar (slots grouped by day, with duration). The richer booking UX (offering pick, questions, policy acceptance) lands with the booking-flow work (#3).
## Offering price in dollars
- `us_offerings.price_cents` (INT) → **`price DECIMAL(10,2)`**; `Offering` uses `float $price`. Admin form and REST take dollars.
## Bugfix (pre-existing, from #10)
The Offering insert/update `$wpdb` **format arrays were misaligned** with the columns — e.g. `billing_mode` (a string) mapped to `%d`, which `$wpdb` coerces to `0`, corrupting the stored value on a real insert. Replaced both with a single, correctly-aligned `COLUMN_FORMATS` constant. (Unit tests missed it because they assert the format arg via `Mockery::type('array')`.)
## Notes
- Date math uses `DateTimeImmutable::modify()` because the CI `no-debug` regex `dd\(` matches `->add(` — worth giving that regex word boundaries later.
- Bumps PHPStan to `--memory-limit=1G` in the lint script; 128M now crashes analysis as the codebase has grown.
## Verification
Rebased on current `main` (includes the merged Policies #15). `composer test` (91 tests), `composer lint`, `composer cs`, and the no-debug check all pass.
Refs #2
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Availability (#2):
- us_availability gains offering_id, duration_minutes (default 60), and
recurrence_group; AvailabilitySlot carries the new fields.
- AvailabilityRepository::createWeeklySeries() generates N weekly rows
sharing a recurrence_group; findAvailable() filters by offering and
duration. Date math uses DateTimeImmutable::modify() (the no-debug CI
regex `dd\(` matches `->add(`).
- REST GET filters by offering_id/duration_minutes; POST accepts
duration_minutes, offering_id, recurrence (single|weekly) + weeks.
- Admin form adds duration, an offering picker, and one-off/weekly options
(OfferingRepository wired into AvailabilityController).
- booking.js renders an agenda calendar (slots grouped by day, with
duration). The richer booking UX lands with the booking-flow work.
Offering price in dollars:
- Switch us_offerings.price_cents (INT) to price DECIMAL(10,2); Offering
uses float $price. Admin form and REST take dollars.
- Fix a pre-existing misalignment in the Offering insert/update $wpdb
format arrays (billing_mode/capacity/is_active were mapped to the wrong
specifiers, which would corrupt values) via a single COLUMN_FORMATS list.
Also bump PHPStan to --memory-limit=1G in the lint script; 128M now
crashes analysis as the codebase has grown.
Refs #2
Co-Authored-By: Claude Opus 4.8 <[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.
Implements #2 (Availability) — durations, weekly-recurring windows, and a public agenda calendar — and slips in a requested change to price offerings in dollars (plus a
$wpdbformat bugfix found along the way).Availability (#2)
us_availabilitygainsoffering_id(nullable FK),duration_minutes(default 60),recurrence_group(nullable), with indexes.AvailabilitySlot: carries the new fields.AvailabilityRepository:createWeeklySeries()generates N weekly rows sharing arecurrence_group(= the first row's id);findAvailable()filters byoffering_idandduration_minutes.duration_minutes,offering_id,recurrence(single|weekly) +weeks.booking.jsrenders an agenda calendar (slots grouped by day, with duration). The richer booking UX (offering pick, questions, policy acceptance) lands with the booking-flow work (#3).Offering price in dollars
us_offerings.price_cents(INT) →price DECIMAL(10,2);Offeringusesfloat $price. Admin form and REST take dollars.Bugfix (pre-existing, from #10)
The Offering insert/update
$wpdbformat arrays were misaligned with the columns — e.g.billing_mode(a string) mapped to%d, which$wpdbcoerces to0, corrupting the stored value on a real insert. Replaced both with a single, correctly-alignedCOLUMN_FORMATSconstant. (Unit tests missed it because they assert the format arg viaMockery::type('array').)Notes
DateTimeImmutable::modify()because the CIno-debugregexdd\(matches->add(— worth giving that regex word boundaries later.--memory-limit=1Gin the lint script; 128M now crashes analysis as the codebase has grown.Verification
Rebased on current
main(includes the merged Policies #15).composer test(91 tests),composer lint,composer cs, and the no-debug check all pass.Refs #2
🤖 Generated with Claude Code