Group class offerings now carry real dates: the add/edit form takes a start date plus a sessions control (one-off, or weekly for N sessions; the end date is computed as start + (N-1) weeks via Offering::weeklyTermEnd). Dates are validated strictly (Y-m-d) and shown in the offerings list and on the student-facing class card, including the weekly session count. [us_group_classes offering="<id>"] (block attribute offeringId, chosen from a dropdown of active classes fetched from the public offerings endpoint) restricts the page to a single class so the enrolment flow can be embedded on a page dedicated to that class; a pinned class that is no longer offered reports itself closed instead of falling back to the catalog. Offerings are now editable from the admin screen: an Edit button prefills the shared add/edit form and saving posts usc_action=update. Updates always preserve the original owner and currency, and non-admin instructors can only load and update their own offerings. The form also gains the previously missing description field and an Active toggle (the admin-UI counterpart of the REST is_active flag) so an edit cannot wipe data the form never collected. Closes #59 Co-Authored-By: Claude Fable 5 <[email protected]>
4.6 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
- Student opens a group class from the offering catalog.
- Student answers the offering's questions (
GET /offerings/{id}/questions). - Student accepts the current published policy versions (
GET /policies) — required to continue. - Full-term payment is taken per the student's billing method (card by default;
pendingfor e-transfer; skipped for comp). Seepayments.md. POST /enrollmentscreates the enrolment (status = active), records answers and policy acceptances, and links the payment — but only if the offering'scapacityhas not been reached.- 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/hasActiveEnrollmentenforce capacity and prevent duplicates) - Model:
Unsupervised\Schedular\GroupClass\Enrollment - Admin controller:
Unsupervised\Schedular\GroupClass\GroupClassController(gated onview_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 theofferingIdattribute) - Reuses
Registration\RegistrationGate(intake answers + booking-scoped policy acceptance, typeenrollment)
Payment seam: payment is deferred to #7. An enrolment is created with
status = activeandpayment_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.phptests/Unit/GroupClass/EnrollmentRepositoryTest.phptests/Unit/GroupClass/GroupClassPageTest.php