# 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`). `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`