Availability windows were stored and served as a single bookable row, so a 9:00 AM-4:00 PM window showed to students as one giant slot and booking it consumed the whole day; past and multi-day windows also leaked into the booking page as nonsense entries. - Split windows into consecutive lesson-length slots on save (REST and admin form); each chunk is independently bookable and weekly recurrence creates a series per chunk so "reserve this time weekly" holds the same hour each week - Reject windows spanning multiple days or shorter than the lesson length (400 invalid_window) - Never return slots whose start has passed from GET /availability - Migrate pre-split rows: Plugin::boot re-runs the Installer on version change and AvailabilityRepository::splitOversizedWindows() rewrites unbooked same-day oversized windows in place - Display all times in 12-hour AM/PM form (booking page, wp-admin lists, editor previews) - Add a List | Week view toggle to the student booking page and the instructor availability page, with previous/next-week navigation honouring the site's start_of_week option (new WeekCalendar helper) Co-Authored-By: Claude Fable 5 <[email protected]>
5.4 KiB
Feature: Lesson Booking
Overview
Students register for a private lesson by choosing an offering, picking a time (or reserving a weekly slot for the term), answering the offering's intake questions, accepting current policies, and paying. Instructors confirm or cancel from wp-admin or via the REST API. A lesson becomes confirmed only once its payment is paid (or the student is comp'd).
Data Model — {prefix}us_lessons
| Column | Type | Notes |
|---|---|---|
id |
BIGINT UNSIGNED | Primary key |
slot_id |
BIGINT UNSIGNED | FK → us_availability.id |
offering_id |
BIGINT UNSIGNED | FK → us_offerings.id (the private-lesson type booked) |
student_id |
BIGINT UNSIGNED | WordPress user ID |
instructor_id |
BIGINT UNSIGNED | WordPress user ID (denormalised for fast queries) |
recurrence |
VARCHAR(10) | single or weekly |
series_id |
BIGINT UNSIGNED | Nullable — groups the lesson rows of one weekly reservation |
status |
VARCHAR(20) | pending / confirmed / cancelled |
payment_id |
BIGINT UNSIGNED | Nullable FK → us_payments.id |
notes |
TEXT | Optional student notes |
created_at |
DATETIME | Insertion time |
Registration Flow
- Student opens the page with the
[us_booking]shortcode and browses open slots as an agenda list or a weekly calendar (view toggle with previous/next-week navigation; times shown in 12-hour AM/PM form). - Student picks an offering (a 30 or 60-minute private-lesson type) and a slot.
- For a
weeklyreservation, the same weekday/time is held for the rest of the offering's term. - Student answers the offering's questions (
GET /offerings/{id}/questions). - Student accepts the current published policy versions (
GET /policies) — required to continue. - Payment is taken per the student's billing method (card by default;
pendingfor e-transfer; skipped for comp). Seepayments.md. POST /bookingscreates the lesson row(s) (status = pending), records answers and policy acceptances, marksus_availability.is_booked = 1, and links the payment.- On successful payment (or comp) the lesson is
confirmedand a receipt is emailed. - Instructor sees the booking under My Lessons and may update status via
PATCH /bookings/{id}/status.
Weekly Reservations
A weekly reservation creates one series_id shared across N lesson rows (one per
week in the term) and reserves the matching availability windows. It is billed
full-term upfront as a single payment (billing_mode = full_term on the
offering).
REST API
| Method | Endpoint | Permission |
|---|---|---|
GET |
/wp-json/us-scheduler/v1/bookings |
Any logged-in user |
POST |
/wp-json/us-scheduler/v1/bookings |
book_lesson |
PATCH |
/wp-json/us-scheduler/v1/bookings/{id}/status |
manage_availability or admin |
POST /bookings body: offering_id, slot_id, recurrence, answers[]
(question_id → value), accepted_policy_version_ids[], and payment data
(see payments.md).
GET /bookings returns the caller's own lessons (student view) or upcoming lessons for the instructor if the caller has manage_availability.
Group classes follow the same registration flow but enrol against an offering of
kind group_class; see group-classes.md.
Admin Interface
- Scheduler (
view_all_lessons— studio admin / administrators): all upcoming lessons across all instructors - My Lessons (
view_own_lessons): upcoming lessons for the logged-in instructor
Frontend Shortcodes
[us_booking]— student calendar + registration flow; requiresbook_lessoncapability[us_student_login]— front-end login form for students
Implementation
- Repository:
Unsupervised\Schedular\Booking\BookingRepository(insertSeries()builds a weekly series sharing aseries_id) - Model:
Unsupervised\Schedular\Booking\Lesson - Registration gate:
Unsupervised\Schedular\Registration\RegistrationGate— validates and records intake answers + booking-scoped policy acceptances; shared with group enrolment - Admin controller:
Unsupervised\Schedular\Booking\LessonController - REST endpoint:
Unsupervised\Schedular\Booking\BookingEndpoint - Frontend:
Unsupervised\Schedular\Booking\BookingPage,Unsupervised\Schedular\Auth\LoginPage
Payment seam: payment is deferred to the Payments feature (#7). For now a booking is created with
status = pendingandpayment_id = null; the instructor confirms viaPATCH /bookings/{id}/status. When payments land, the pay→confirm + receipt step plugs into this seam.GET /policies?scope=bookingreturns just the booking-gate policies the form must collect.
Tests
tests/Unit/Booking/BookingRepositoryTest.phptests/Unit/Booking/LessonTest.php