Files
unsupervised-scheduler/docs/features/group-classes.md
T
thatguygriffandClaude Opus 4.8 5f9d5ffc4f
CI / Tests (PHP 8.2) (pull_request) Successful in 47s
CI / Tests (PHP 8.1) (pull_request) Successful in 38s
CI / No Debug Code (pull_request) Successful in 2s
CI / Coding Standards (pull_request) Successful in 2m50s
CI / PHPStan (pull_request) Successful in 2m54s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m42s
CI / Build Plugin Zip (pull_request) Skipped
Add instructor group-class roster view under My Lessons
Instructors can now see their own group classes under My Lessons →
My Group Classes (view_own_lessons): each class shows its active
enrolment count against capacity plus a roster of enrolled students
with enrolment and payment status.

GroupClassController gains renderInstructorPage(), backed by the
existing per-instructor enrolment query and a newly injected
PaymentRepository for payment status. Wired as a submenu under the
existing My Lessons menu, inside the same !view_all_lessons guard so
owner-operators don't get a duplicate item.

Closes #71

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-23 12:52:27 -03:00

5.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

Class Dates

A group class offering carries term_start/term_end (see offerings.md): one-off classes end the day they start; weekly classes run a set number of sessions. The class card on the enrolment page shows the date or date range with the session count.

Enrolment Flow

The class list is loaded together with the student's own enrolments (GET /enrollments); a class the student already has an active enrolment in shows "You are enrolled in this class." instead of the Enrol button (the server would reject the duplicate with 409 already_enrolled regardless — a cancelled enrolment does not block re-enrolling).

  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 (view_all_lessons / studio admin): all active enrolments across instructors
  • My Lessons → My Group Classes (view_own_lessons / instructor): the instructor's own group classes, each showing its active-enrolment count against capacity and a per-class roster of enrolled students with enrolment and payment status

Implementation

  • Repository: Unsupervised\Schedular\GroupClass\EnrollmentRepository (countActiveForOffering/hasActiveEnrollment enforce capacity and prevent duplicates)
  • Model: Unsupervised\Schedular\GroupClass\Enrollment
  • Admin controller: Unsupervised\Schedular\GroupClass\GroupClassControllerrenderPage (studio admin, view_all_lessons) and renderInstructorPage (instructor, view_own_lessons)
  • REST endpoint: Unsupervised\Schedular\GroupClass\EnrollmentEndpoint
  • Frontend: Unsupervised\Schedular\GroupClass\GroupClassPage ([us_group_classes] shortcode; offering="…" restricts it to a single class for embedding on a dedicated page — the block equivalent is the offeringId attribute)
  • Reuses Registration\RegistrationGate (intake answers + booking-scoped policy acceptance, type enrollment)

Payment: a priced enrolment creates a payment via Payment\PaymentService (registration_type = enrollment) and links it as payment_id; unpriced enrolments return payment: null and skip the payment step. See payments.md for the card/e-transfer/comp flows.

Tests

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