Group classes can now be marked invite-only (us_offerings.access_mode). Invite-only classes are hidden from the public catalog and reachable only when the instructor lets someone in via one of three paths, managed from My Lessons -> My Group Classes: - Add students directly: enrols them now with a pending payment. - Make available: grants registered students access to self-enrol through the normal paid flow (multi-select, emailed a notice). - Invite by email: tokenised registration invite tied to the class for a non-account address; after they register the class becomes enrollable. Reuses an existing pending invite instead of sending a second link. New us_group_access table records grants; GET /offerings merges granted invite-only classes for the caller; enrolment requires a grant (403 invite_required) and flips it to enrolled on success. composer test (487), composer lint, composer cs all pass. Co-Authored-By: Claude Opus 4.8 <[email protected]>
5.3 KiB
Feature: Offerings
Overview
An offering is anything a student can register for: a private-lesson type (30 or 60 minutes, single or weekly) or a group class. Offerings carry pricing, billing mode, and the intake questions a registrant must answer. They are the catalog the booking calendar and group-class pages are built from.
Data Model — {prefix}us_offerings
| Column | Type | Notes |
|---|---|---|
id |
BIGINT UNSIGNED | Primary key |
instructor_id |
BIGINT UNSIGNED | WordPress user ID of the owning instructor |
kind |
VARCHAR(20) | private_lesson or group_class |
title |
VARCHAR(191) | Display name |
description |
TEXT | Optional longer description |
duration_minutes |
SMALLINT | Private lessons only (e.g. 30, 60); NULL for group classes |
price |
DECIMAL(10,2) | Price in dollars |
currency |
VARCHAR(3) | ISO 4217, e.g. CAD |
billing_mode |
VARCHAR(20) | one_time (single booking) or full_term (weekly / group) |
allow_weekly |
TINYINT(1) | Private only — may be reserved weekly for the term |
capacity |
SMALLINT | Group only — max enrolments; NULL for private |
term_start |
DATE | Group / term offerings — first day; NULL otherwise |
term_end |
DATE | Group / term offerings — last day; NULL otherwise |
schedule_note |
VARCHAR(191) | Group only — human-readable schedule, e.g. "Tuesdays 4:00pm" |
cancellation_cutoff_hours |
SMALLINT UNSIGNED | Optional per-offering cancellation cutoff in hours; NULL inherits the studio default (see cancellation-cutoff.md) |
access_mode |
VARCHAR(20) | public (listed in the catalog) or invite_only (group classes hidden from the catalog — see group-classes.md) |
is_active |
TINYINT(1) | 0 = hidden from registration, 1 = bookable |
created_at |
DATETIME | Insertion time |
Billing Mode
one_time— charged once at booking (a single private lesson).full_term— charged in full upfront at registration (a weekly private reservation or a year-long group class). Seepayments.md.
Term Dates
Group classes carry a term: term_start is the date of the first class and
term_end the last. The add-offering form takes a start date plus a sessions
control — one-off (the term ends the day it starts) or weekly for N
sessions (term_end = term_start + (N−1) weeks, computed by
Offering::weeklyTermEnd()). Dates are validated by Offering::normalizeDate()
(strict Y-m-d); an invalid start date leaves both term columns NULL. The
student-facing class card shows the date (one-off) or the date range with the
weekly session count.
Admin Interface
Studio admin and instructors manage offerings under Offerings in wp-admin.
- Studio admin (
manage_offerings) manages offerings for any instructor. - Instructor (
manage_offerings) manages only their own. - Each offering's intake questions are edited from the offering screen (see
registration-questions.md). - The offerings list shows each offering's ID (needed for
[us_group_classes offering="…"]) and its term dates. - Edit on a row reloads the page (
?usc_edit=<id>) with the form prefilled; saving postsusc_action=update. Owner and currency are always preserved on update, so a form submission can never reassign an offering. Non-admin instructors can only load and update their own offerings. - The form includes a description textarea and an Active — open for registration checkbox (unchecking hides the offering from students without deleting it — the admin-UI counterpart of the REST
is_activeflag).
REST API
| Method | Endpoint | Permission |
|---|---|---|
GET |
/wp-json/us-scheduler/v1/offerings |
Public (active offerings only) |
POST |
/wp-json/us-scheduler/v1/offerings |
manage_offerings |
PATCH |
/wp-json/us-scheduler/v1/offerings/{id} |
manage_offerings + owner |
DELETE |
/wp-json/us-scheduler/v1/offerings/{id} |
manage_offerings + owner |
GET supports query params: instructor_id, kind.
Implementation
- Repository:
Unsupervised\Schedular\Offering\OfferingRepository - Model:
Unsupervised\Schedular\Offering\Offering - Admin controller:
Unsupervised\Schedular\Offering\OfferingController - REST endpoint:
Unsupervised\Schedular\Offering\OfferingEndpoint
Tests
tests/Unit/Offering/OfferingControllerTest.phptests/Unit/Offering/OfferingRepositoryTest.phptests/Unit/Offering/OfferingTest.php