Files
unsupervised-scheduler/docs/features/group-classes.md
T
thatguygriffandClaude Fable 5 da9a449d55
CI / Tests (PHP 8.2) (pull_request) Successful in 38s
CI / Tests (PHP 8.1) (pull_request) Successful in 44s
CI / No Debug Code (pull_request) Successful in 1s
CI / Coding Standards (pull_request) Successful in 2m49s
CI / PHPStan (pull_request) Successful in 2m54s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m42s
CI / Build Plugin Zip (pull_request) Skipped
Update README and group-classes doc to reflect shipped Stripe payments
The README still listed Payments as partial with the Stripe card charge
pending, and group-classes.md still described the pre-#7 payment seam.
Both are behind the code: StripeGateway/PaymentEndpoint ship the live
card charge, and enrolments create and link payments via PaymentService.

Fixes #73

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-18 18:06:58 -03:00

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

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 (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; 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. Instructor-specific enrolment views (the spec's "under My Lessons") are a follow-up (#71) — 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
  • tests/Unit/GroupClass/GroupClassPageTest.php