Notify instructors on new bookings and enrolments
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
This commit is contained in:
2026-09-18 16:18:43 -03:00
co-authored by anthropic/claude-opus-4-8
parent 56fc0bd57d
commit 12765d8f13
16 changed files with 583 additions and 3 deletions
@@ -5,6 +5,7 @@ namespace Unsupervised\Schedular\Tests\Unit\Availability;
use Brain\Monkey\Functions;
use Mockery;
use Unsupervised\Schedular\Auth\InstructorNotificationPref;
use Unsupervised\Schedular\Availability\AvailabilityController;
use Unsupervised\Schedular\Availability\AvailabilityRepository;
use Unsupervised\Schedular\Availability\AvailabilitySlot;
@@ -16,6 +17,7 @@ class AvailabilityControllerTest extends TestCase
{
private AvailabilityRepository&Mockery\MockInterface $repository;
private OfferingRepository&Mockery\MockInterface $offerings;
private InstructorNotificationPref&Mockery\MockInterface $notifyPref;
private AvailabilityController $controller;
protected function setUp(): void
@@ -24,7 +26,12 @@ class AvailabilityControllerTest extends TestCase
$this->repository = Mockery::mock(AvailabilityRepository::class);
$this->offerings = Mockery::mock(OfferingRepository::class);
$this->controller = new AvailabilityController($this->repository, $this->offerings, new WindowValidator($this->offerings));
// The notification preference is exercised in its own tests; here it simply
// reads off and accepts any save, so availability tests stay about slots.
$this->notifyPref = Mockery::mock(InstructorNotificationPref::class);
$this->notifyPref->shouldReceive('wants')->andReturn(false)->byDefault();
$this->notifyPref->shouldReceive('set')->byDefault();
$this->controller = new AvailabilityController($this->repository, $this->offerings, new WindowValidator($this->offerings), $this->notifyPref);
$_POST = [];
$_GET = [];
@@ -286,6 +293,29 @@ class AvailabilityControllerTest extends TestCase
self::assertStringContainsString('1 slot could not be deleted', $html);
}
public function testTickingTheNotificationBoxSavesAnOptIn(): void
{
$_POST = ['usc_action' => 'save_notify', 'notify_on_booking' => '1'];
$this->notifyPref->shouldReceive('set')->once()->with(3, true);
$this->repository->shouldReceive('findByInstructor')->once()->with(3)->andReturn([]);
$html = $this->render();
self::assertStringContainsString('notice-success', $html);
self::assertStringContainsString('Notification preference saved.', $html);
}
public function testLeavingTheNotificationBoxUntickedSavesAnOptOut(): void
{
$_POST = ['usc_action' => 'save_notify'];
$this->notifyPref->shouldReceive('set')->once()->with(3, false);
$this->repository->shouldReceive('findByInstructor')->once()->with(3)->andReturn([]);
$this->render();
}
private function render(): string
{
ob_start();