Files
unsupervised-scheduler/docs/features/group-classes.md
T
thatguygriffandClaude Fable 5 5888032ed7
CI / No Debug Code (pull_request) Successful in 2s
CI / Tests (PHP 8.2) (pull_request) Successful in 53s
CI / PHPStan (pull_request) Successful in 2m46s
CI / Tests (PHP 8.1) (pull_request) Successful in 42s
CI / Coding Standards (pull_request) Successful in 47s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m35s
CI / Build Plugin Zip (pull_request) Has been skipped
Skip payment step for unpriced bookings, confirm them immediately, show students their lessons
Booking a slot with no priced offering created the lesson but no payment,
yet the front end still called POST /payments/intent, which 400ed with
"Could not start payment for this registration" — the student saw an error
while the backend held a claimed slot and a lesson stuck at pending.

- POST /bookings and POST /enrollments now return a `payment` summary
  ({id, method, status}) or null when nothing is owed; the JS only runs
  the payment step when a payment exists.
- Bookings with nothing owed are confirmed at creation — there is no
  payment step that would ever confirm them later.
- The booking page now shows the student's upcoming lessons (GET /bookings,
  now scoped to upcoming non-cancelled lessons with slot start/end times)
  with a pending-payment/confirmed status badge.

Fixes #53

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-05 17:02:38 -03:00

4.1 KiB

Feature: Group Classes

Overview

Students enrol in a group class — an offering of kind group_class — as a commitment for the year. Enrolment is capacity-enforced and billed full-term upfront. Registration reuses the same flow as private lessons (intake questions + policy acceptance + payment).

Data Model — {prefix}us_group_enrollments

Column Type Notes
id BIGINT UNSIGNED Primary key
offering_id BIGINT UNSIGNED FK → us_offerings.id (kind = group_class)
student_id BIGINT UNSIGNED WordPress user ID
instructor_id BIGINT UNSIGNED WordPress user ID (denormalised from the offering)
status VARCHAR(20) active / cancelled / completed
payment_id BIGINT UNSIGNED Nullable FK → us_payments.id
enrolled_at DATETIME Insertion time

Enrolment Flow

  1. Student opens a group class from the offering catalog.
  2. Student answers the offering's questions (GET /offerings/{id}/questions).
  3. Student accepts the current published policy versions (GET /policies) — required to continue.
  4. Full-term payment is taken per the student's billing method (card by default; pending for e-transfer; skipped for comp). See payments.md.
  5. POST /enrollments creates the enrolment (status = active), records answers and policy acceptances, and links the payment — but only if the offering's capacity has not been reached.
  6. On successful payment (or comp) a receipt is emailed.

Capacity is enforced at enrolment time by counting active rows for the offering; a class at capacity rejects further enrolments.

REST API

Method Endpoint Permission
GET /wp-json/us-scheduler/v1/enrollments Any logged-in user
POST /wp-json/us-scheduler/v1/enrollments book_lesson

POST /enrollments body: offering_id, answers[] (question_id → value), accepted_policy_version_ids[], and payment data (see payments.md). The response includes id, status, and payment — a {id, method, status} summary, or null when the class is free (the front end then skips the payment step).

GET /enrollments returns the caller's own enrolments, or all enrolments for the instructor's group classes if the caller has view_own_lessons on those offerings.

Admin Interface

  • Group Classes (manage_options / studio admin): all enrolments across instructors
  • Instructors see enrolments for their own group classes under My Lessons

Implementation

  • Repository: Unsupervised\Schedular\GroupClass\EnrollmentRepository (countActiveForOffering/hasActiveEnrollment enforce capacity and prevent duplicates)
  • Model: Unsupervised\Schedular\GroupClass\Enrollment
  • Admin controller: Unsupervised\Schedular\GroupClass\GroupClassController (gated on view_all_lessons)
  • REST endpoint: Unsupervised\Schedular\GroupClass\EnrollmentEndpoint
  • Frontend: Unsupervised\Schedular\GroupClass\GroupClassPage ([us_group_classes] shortcode)
  • Reuses Registration\RegistrationGate (intake answers + booking-scoped policy acceptance, type enrollment)

Payment seam: payment is deferred to #7. An enrolment is created with status = active and payment_id = null; the pay→confirm + receipt step plugs in later. Instructor-specific enrolment views (the spec's "under My Lessons") are a follow-up — this iteration ships the studio-admin Group Classes page (view_all_lessons) plus per-student/per-instructor REST queries.

Tests

  • tests/Unit/GroupClass/EnrollmentTest.php
  • tests/Unit/GroupClass/EnrollmentRepositoryTest.php