Files
thatguygriff 19e663d6fa
CI / Coding Standards (pull_request) Successful in 50s
CI / PHPStan (pull_request) Successful in 1m2s
CI / Tests (PHP 8.1) (pull_request) Successful in 47s
CI / Tests (PHP 8.2) (pull_request) Successful in 48s
CI / Tests (PHP 8.3) (pull_request) Successful in 46s
CI / No Debug Code (pull_request) Successful in 2s
CI / Build Plugin Zip (pull_request) Has been skipped
Extend availability (durations, weekly recurrence, calendar); price offerings in dollars
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 <noreply@anthropic.com>
2026-06-05 15:43:48 -03:00

56 lines
3.7 KiB
Markdown

# Feature: Offerings
## Overview
An offering is anything a student can register for: a private-lesson type (30 or 60 minutes, single or weekly) or a group class. Offerings carry pricing, billing mode, and the intake questions a registrant must answer. They are the catalog the booking calendar and group-class pages are built from.
## Data Model — `{prefix}us_offerings`
| Column | Type | Notes |
|--------------------|------------------|-------------------------------------------------------------|
| `id` | BIGINT UNSIGNED | Primary key |
| `instructor_id` | BIGINT UNSIGNED | WordPress user ID of the owning instructor |
| `kind` | VARCHAR(20) | `private_lesson` or `group_class` |
| `title` | VARCHAR(191) | Display name |
| `description` | TEXT | Optional longer description |
| `duration_minutes` | SMALLINT | Private lessons only (e.g. 30, 60); NULL for group classes |
| `price` | DECIMAL(10,2) | Price in dollars |
| `currency` | VARCHAR(3) | ISO 4217, e.g. `CAD` |
| `billing_mode` | VARCHAR(20) | `one_time` (single booking) or `full_term` (weekly / group) |
| `allow_weekly` | TINYINT(1) | Private only — may be reserved weekly for the term |
| `capacity` | SMALLINT | Group only — max enrolments; NULL for private |
| `term_start` | DATE | Group / term offerings — first day; NULL otherwise |
| `term_end` | DATE | Group / term offerings — last day; NULL otherwise |
| `schedule_note` | VARCHAR(191) | Group only — human-readable schedule, e.g. "Tuesdays 4:00pm"|
| `is_active` | TINYINT(1) | 0 = hidden from registration, 1 = bookable |
| `created_at` | DATETIME | Insertion time |
## Billing Mode
- `one_time` — charged once at booking (a single private lesson).
- `full_term` — charged in full upfront at registration (a weekly private reservation or a year-long group class). See `payments.md`.
## Admin Interface
Studio admin and instructors manage offerings under **Offerings** in wp-admin.
- Studio admin (`manage_offerings`) manages offerings for any instructor.
- Instructor (`manage_offerings`) manages only their own.
- Each offering's intake questions are edited from the offering screen (see `registration-questions.md`).
## REST API
| Method | Endpoint | Permission |
|----------|---------------------------------------------|----------------------------------|
| `GET` | `/wp-json/us-scheduler/v1/offerings` | Public (active offerings only) |
| `POST` | `/wp-json/us-scheduler/v1/offerings` | `manage_offerings` |
| `PATCH` | `/wp-json/us-scheduler/v1/offerings/{id}` | `manage_offerings` + owner |
| `DELETE` | `/wp-json/us-scheduler/v1/offerings/{id}` | `manage_offerings` + owner |
`GET` supports query params: `instructor_id`, `kind`.
## Implementation
- Repository: `Unsupervised\Schedular\Offering\OfferingRepository`
- Model: `Unsupervised\Schedular\Offering\Offering`
- Admin controller: `Unsupervised\Schedular\Offering\OfferingController`
- REST endpoint: `Unsupervised\Schedular\Offering\OfferingEndpoint`
## Tests
- `tests/Unit/Offering/OfferingRepositoryTest.php`
- `tests/Unit/Offering/OfferingTest.php`