Files
unsupervised-scheduler/docs/features/group-classes.md
T
thatguygriff 9cb5207dcd
CI / Tests (PHP 8.1) (pull_request) Successful in 45s
CI / Coding Standards (pull_request) Successful in 50s
CI / PHPStan (pull_request) Successful in 1m4s
CI / No Debug Code (pull_request) Successful in 2s
CI / Tests (PHP 8.2) (pull_request) Successful in 42s
CI / Tests (PHP 8.3) (pull_request) Successful in 42s
CI / Build Plugin Zip (pull_request) Has been skipped
Add group-class enrolment (year commitment, capacity, registration gate)
Implements #4: students enrol in a group_class offering via the same
registration gate as private lessons (intake questions + booking-scoped
policy acceptance). Enrolment is capacity-enforced and prevents duplicates.

- Schema: us_group_enrollments table.
- Enrollment value object + EnrollmentRepository (countActiveForOffering,
  hasActiveEnrollment, per-student/instructor/all-active queries, status).
- EnrollmentEndpoint: GET /enrollments (scoped) and POST /enrollments
  (validates group_class, capacity, no-duplicate; reuses RegistrationGate;
  records answers/acceptances type enrollment).
- GroupClassController + admin page (view_all_lessons): all active enrolments.
- Front-end: [us_group_classes] shortcode (GroupClassPage) + group-classes.js
  enrol flow (list classes -> questions + policies -> POST /enrollments).
- Wiring in Plugin, RestRegistrar, AdminMenu, ShortcodeRegistrar.

Payment is the deferred seam (#7): enrolment lands active, payment_id null.
JS left untested for parity with the repo's no-build vanilla-JS posture.

Tests: tests/Unit/GroupClass/ (Enrollment, EnrollmentRepository).
composer test (121), cs, and PHPStan level 6 all pass.

Refs #4

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 11:43:33 -03:00

4.0 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).

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