CI / Coding Standards (pull_request) Successful in 27s
CI / Tests (PHP 8.1) (pull_request) Successful in 37s
CI / No Debug Code (pull_request) Successful in 9s
CI / Tests (PHP 8.3) (pull_request) Successful in 36s
CI / Tests (PHP 8.5) (pull_request) Successful in 40s
CI / Tests (PHP 8.2) (pull_request) Successful in 42s
CI / Static Analysis (pull_request) Successful in 48s
CI / Build Plugin Zip (pull_request) Skipped
Add an opt-in, per-instructor email notice sent when someone books one of their lessons or enrols in one of their group classes. Off by default and set from My Availability → Notifications; covers both the student/guardian REST flows and the studio's wp-admin "book/add for a student" forms. The opt-in check lives in InstructorNotificationMailer so no booking path can drift on who is mailed; lessons fire from LessonBooker::settle (the one step both booking paths reach), enrolments from EnrollmentEndpoint::enroll and GroupClassController::addDirect. The notice is a courtesy and never fails a booking or enrolment that otherwise succeeded. Co-authored-by: anthropic/claude-opus-4-8
85 lines
4.4 KiB
Markdown
85 lines
4.4 KiB
Markdown
# Feature: Instructor Booking Notifications
|
|
|
|
## Overview
|
|
An instructor can ask to be emailed whenever someone books one of their private
|
|
lessons or enrols in one of their group classes. It is **off by default** and set
|
|
per instructor — the people it emails decide whether they want it.
|
|
|
|
The notice fires whichever way the booking or enrolment was made: a student
|
|
(or a guardian for their child) doing it themselves through the front-end, or the
|
|
studio doing it on their behalf from wp-admin. There is no separate "the studio
|
|
booked it" case to forget — every path funnels through one place per registration
|
|
type, and the opt-in check lives in the mailer, not at each call site, so no path
|
|
can drift on who gets mailed.
|
|
|
|
## Preference `us_notify_on_booking` (user meta)
|
|
`'1'` or `'0'`, stored against the instructor's WordPress user. Absent — the
|
|
default for every account — reads as off. Stored as `'0'` rather than deleted when
|
|
an instructor turns it off, so a deliberate "no" is told apart from never having
|
|
chosen.
|
|
|
|
Default off because the notification is a new capability: turning it on for every
|
|
instructor on an existing site the day it ships would mail people who never asked.
|
|
|
|
## Admin Interface
|
|
**My Availability → Notifications** (`manage_availability` — every instructor sees
|
|
their own availability page):
|
|
|
|
- **Email me when someone books a lesson or enrols in one of my group classes** —
|
|
a single checkbox, off by default, saved on its own form (`usc_action=save_notify`).
|
|
|
|
The page an instructor sets availability on is the one they already visit to shape
|
|
their teaching schedule, so the preference about that schedule lives beside it.
|
|
|
|
## What triggers a notice
|
|
| Registration | Path | Where the notice fires |
|
|
|---|---|---|
|
|
| Private lesson | student/guardian REST **and** studio wp-admin form | `Booking\LessonBooker::settle()` — the single step both paths reach once a slot is claimed |
|
|
| Group class | student/guardian REST | `GroupClass\EnrollmentEndpoint::enroll()`, after the roster row is written |
|
|
| Group class | studio "Add students directly" (wp-admin) | `GroupClass\GroupClassController::addDirect()`, per student added |
|
|
|
|
A weekly lesson reservation reports the number of occurrences claimed, so a single
|
|
booking and a term booked at once each read correctly.
|
|
|
|
The notice is an opt-in courtesy, never a step a booking or enrolment depends on:
|
|
a missing or failed send can never fail a booking that otherwise succeeded, and
|
|
the mailer returns false (without sending) when the instructor has not opted in,
|
|
their account is gone, or it carries no email.
|
|
|
|
## Implementation
|
|
- `Unsupervised\Schedular\Auth\InstructorNotificationPref` — the per-instructor
|
|
user-meta preference (`wants()` / `set()`), default off
|
|
- `Unsupervised\Schedular\Auth\InstructorNotificationMailer` — `notifyLessonBooked()`
|
|
and `notifyEnrollment()`; the opt-in check and recipient resolution live here
|
|
- `Unsupervised\Schedular\Booking\LessonBooker::settle()` — fires the lesson notice
|
|
for both booking paths
|
|
- `Unsupervised\Schedular\GroupClass\EnrollmentEndpoint::enroll()` — student/guardian
|
|
enrolment notice
|
|
- `Unsupervised\Schedular\GroupClass\GroupClassController::addDirect()` — studio
|
|
enrolment notice
|
|
- `Unsupervised\Schedular\Availability\AvailabilityController` — reads the preference
|
|
for the page and saves the toggle (`save_notify`)
|
|
- `templates/admin/availability.php` — the Notifications checkbox
|
|
|
|
All three consumers take the mailer (and the controller its preference) as a
|
|
constructor dependency defaulting to a fresh instance, so existing wiring in
|
|
`Plugin`, `RestRegistrar` and `AdminMenu` is unchanged.
|
|
|
|
## Tests
|
|
- `tests/Unit/Auth/InstructorNotificationPrefTest.php` — default-off, opt-in read,
|
|
string-boolean write, non-user guards
|
|
- `tests/Unit/Auth/InstructorNotificationMailerTest.php` — sends to an opted-in
|
|
instructor, weekly occurrence count, and sends nothing when opted out / account
|
|
gone / no email
|
|
- `tests/Unit/GroupClass/EnrollmentEndpointTest.php` — a successful enrolment
|
|
notifies the class instructor
|
|
- `tests/Unit/Availability/AvailabilityControllerTest.php` — the toggle saves an
|
|
opt-in and an opt-out
|
|
- The booking-path tests (`BookingEndpointTest`, `AdminBookingTest`) inject a mock
|
|
mailer, keeping them about booking
|
|
|
|
## Related
|
|
- `lesson-booking.md` — the booking core the lesson notice hangs off
|
|
- `group-classes.md` — the enrolment paths the class notice hangs off
|
|
- `user-roles.md` — the instructor role and `manage_availability` capability
|