Add group-class scheduling, instructor assignment, and details/invite management
CI / Tests (PHP 8.2) (pull_request) Successful in 39s
CI / Tests (PHP 8.1) (pull_request) Successful in 46s
CI / No Debug Code (pull_request) Successful in 2s
CI / Coding Standards (pull_request) Successful in 2m52s
CI / PHPStan (pull_request) Successful in 2m50s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m36s
CI / Build Plugin Zip (pull_request) Skipped
CI / Tests (PHP 8.2) (pull_request) Successful in 39s
CI / Tests (PHP 8.1) (pull_request) Successful in 46s
CI / No Debug Code (pull_request) Successful in 2s
CI / Coding Standards (pull_request) Successful in 2m52s
CI / PHPStan (pull_request) Successful in 2m50s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m36s
CI / Build Plugin Zip (pull_request) Skipped
Group classes now carry a specific class time (alongside date and duration) and an assigned instructor: - Schema: add `class_time` (TIME) to `us_offerings`; `Offering` gains `normalizeTime`/`sessionWindows`. (Rides the pending 1.0.0->1.1.0 dbDelta upgrade, so no version bump.) - Offering form: class-time field, plus a studio-admin instructor picker (plain instructors always own their own classes). - `ClassSlotReconciler`: assigning an instructor clears their open booking slots overlapping each session and flags already-booked lessons that clash (a booked lesson is never deleted). Uses new `AvailabilityRepository::findOverlapping`. - Front end: `GET /offerings` exposes `instructor_name`; the enrolment page shows who teaches each class and when it meets. Back-office group-class views redesigned: - Instructor **My Group Classes** and studio-admin **Group Classes** are now per-class summaries with enrolment counts, not flat student lists. - Each links through (`?class_id=<id>`) to a per-class **details page** (schedule panel, roster with payment status, and — for invite-only classes — the add/make-available/invite-by-email controls). Invite-only membership is managed entirely from this page. - Invite actions are allowed for the class's owning instructor or any `view_all_lessons` studio admin, so an owner-operator (studio admin who also teaches) can reach every class's roster and invites from the Group Classes page. Tests: composer test (508), composer lint, composer cs all pass. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Unsupervised\Schedular\Offering;
|
||||
|
||||
use Unsupervised\Schedular\Availability\AvailabilityRepository;
|
||||
|
||||
/**
|
||||
* Keeps an instructor's open availability out of the way of the group classes
|
||||
* they teach. When a group class is scheduled (an assigned instructor plus a
|
||||
* date, time, and duration), each session occupies the instructor: any open
|
||||
* private-booking slot that overlaps a session is removed so students cannot
|
||||
* book the instructor at the class time, and any already-booked slot that
|
||||
* overlaps is reported as a conflict for the studio to resolve by hand — a
|
||||
* booked lesson is never silently deleted.
|
||||
*/
|
||||
class ClassSlotReconciler {
|
||||
|
||||
public function __construct( private AvailabilityRepository $availability ) {}
|
||||
|
||||
/**
|
||||
* Reconcile the assigned instructor's availability with the class schedule.
|
||||
*
|
||||
* @return array{removed: int, conflicts: list<string>} The number of open
|
||||
* slots cleared, and the start datetime (`Y-m-d H:i:s`) of each booked
|
||||
* slot that still clashes with a session.
|
||||
*/
|
||||
public function reconcile( Offering $offering ): array {
|
||||
if ( Offering::KIND_GROUP_CLASS !== $offering->kind || $offering->instructorId <= 0 ) {
|
||||
return [
|
||||
'removed' => 0,
|
||||
'conflicts' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$removed = 0;
|
||||
$conflicts = [];
|
||||
|
||||
foreach ( $offering->sessionWindows() as $window ) {
|
||||
foreach ( $this->availability->findOverlapping( $offering->instructorId, $window['start'], $window['end'] ) as $slot ) {
|
||||
if ( $slot->isBooked ) {
|
||||
$conflicts[] = $slot->startDt;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( null !== $slot->id && $this->availability->delete( $slot->id ) ) {
|
||||
++$removed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'removed' => $removed,
|
||||
'conflicts' => $conflicts,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user