Files
unsupervised-scheduler/docs/features/offerings.md
T
thatguygriffandClaude Opus 4.8 8b90b8d78d
CI / Tests (PHP 8.1) (pull_request) Successful in 41s
CI / Tests (PHP 8.2) (pull_request) Successful in 53s
CI / No Debug Code (pull_request) Successful in 2s
CI / PHPStan (pull_request) Successful in 2m51s
CI / Coding Standards (pull_request) Successful in 2m54s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m38s
CI / Build Plugin Zip (pull_request) Skipped
Add cancellation cutoff limiting how close to a lesson a student can cancel
Students can no longer cancel their own lesson online once it starts within a
configured window; instructors and studio admins can always cancel.

- Studio default `us_cancellation_cutoff_hours` (stored/computed in hours,
  entered and displayed in days under Studio Settings → Cancellations).
- Optional per-offering override `cancellation_cutoff_hours` (entered in hours);
  blank inherits the studio default, 0 allows anytime cancellation.
- `Booking\CancellationPolicy` resolves the effective window and decides;
  `BookingEndpoint::cancel()` returns a 403 `cancellation_closed` when too late.
  The instructor status endpoint and studio-admin student actions bypass it.

Closes #93

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-23 11:57:18 -03:00

5.1 KiB
Raw Blame History

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)
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). See payments.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 + (N1) 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 posts usc_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_active flag).

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.php
  • tests/Unit/Offering/OfferingRepositoryTest.php
  • tests/Unit/Offering/OfferingTest.php