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]>
58 lines
1.7 KiB
PHP
58 lines
1.7 KiB
PHP
<?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,
|
|
];
|
|
}
|
|
}
|