Files
unsupervised-scheduler/docs/features/availability-management.md
T
thatguygriffandClaude Fable 5 266f572884
CI / Tests (PHP 8.2) (pull_request) Successful in 38s
CI / Tests (PHP 8.1) (pull_request) Successful in 48s
CI / No Debug Code (pull_request) Successful in 3s
CI / Coding Standards (pull_request) Successful in 2m49s
CI / PHPStan (pull_request) Successful in 2m53s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m38s
CI / Build Plugin Zip (pull_request) Skipped
Default the availability admin page to the week view too
Follow-up demo feedback: the My Availability page now opens in its weekly
calendar (usc_view=list opts back into the table, which keeps the bulk-delete
form), matching the new lessons defaults.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-22 11:09:23 -03:00

86 lines
5.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Feature: Availability Management
## Overview
Instructors define same-day date/time windows during which they are available for private lessons. On save, a window is split into consecutive lesson-length slots (09:0016:00 with 60-minute lessons becomes seven rows), each independently bookable by students. Windows may be generated as a weekly-recurring series.
## Data Model — `{prefix}us_availability`
| Column | Type | Notes |
|--------------------|------------------|-------------------------------------------------------------|
| `id` | BIGINT UNSIGNED | Primary key |
| `instructor_id` | BIGINT UNSIGNED | WordPress user ID |
| `offering_id` | BIGINT UNSIGNED | Nullable FK → `us_offerings.id` (private-lesson type) |
| `start_dt` | DATETIME | Slot start — stored as `Y-m-d H:i:s` |
| `end_dt` | DATETIME | Slot end — always `start_dt + duration_minutes` |
| `duration_minutes` | SMALLINT | Lesson length (e.g. 30, 60) |
| `is_booked` | TINYINT(1) | 0 = available, 1 = booked |
| `recurrence_group` | BIGINT UNSIGNED | Nullable — weekly-recurring windows share one group id |
| `created_at` | DATETIME | Insertion time |
A slot's `duration_minutes` is matched against the offering a student picks: a
30-minute private offering can only be booked into a slot whose
`duration_minutes` accommodates it.
## Window Splitting
`AvailabilitySlot::splitByDuration()` chunks a submitted window into consecutive
`duration_minutes` slots; `AvailabilityRepository::createFromWindow()` persists
one row per chunk. A trailing remainder shorter than the lesson length is
dropped. Windows must start and end on the same day and fit at least one lesson
(REST responds `400 invalid_window` otherwise; the admin form is a no-op).
`AvailabilityRepository::splitOversizedWindows()` is a data migration (run by
`Installer` on activation or version change) that rewrites pre-split rows.
## Weekly-Recurring Windows
Instructors may generate a window weekly across a date range. Each lesson-length
chunk becomes its own weekly series: occurrences of the same time-of-day share
one `recurrence_group` id, so a recurring set can be added or removed together
while individual occurrences are still booked independently.
## Admin Interface
Instructors access **My Availability** in wp-admin (`?page=us-availability`).
- Add availability: provide a same-day start/end window, lesson length, and (optionally) a linked private-lesson offering
- Add a weekly series: tick weekly repeat and choose the number of weeks
- Delete a slot: only allowed if `is_booked = 0`
- Bulk delete: the list view has a checkbox per unbooked slot (with a select-all header checkbox) and a **Delete selected** button (`usc_action=bulk_delete`, `slot_ids[]`); each id is ownership-checked, and booked slots are refused at the repository level
- Current slots can be shown as a **weekly calendar** (the default, navigated with `usc_week=Y-m-d`) or a **list** (`usc_view=list`); the grid honours the site's `start_of_week` option via `Availability\WeekCalendar`
## Public Calendar
The front-end booking shortcode renders open slots from `GET /availability`
either as an agenda-style list grouped by day or as a **weekly calendar** with
previous/next-week navigation (toggle rendered by `assets/js/booking.js`; the
site's `start_of_week` option is passed through the `usScheduler` JS config).
## REST API
| Method | Endpoint | Permission |
|----------|-----------------------------------------------|------------------------------------|
| `GET` | `/wp-json/us-scheduler/v1/availability` | `book_lesson` |
| `POST` | `/wp-json/us-scheduler/v1/availability` | `manage_availability` |
| `DELETE` | `/wp-json/us-scheduler/v1/availability/{id}` | `manage_availability` + slot owner |
`GET` supports query params: `instructor_id`, `offering_id`, `duration_minutes`, `from` (datetime), `to` (datetime).
Slots whose start has already passed are never returned.
`POST` validates `start_dt`/`end_dt` (admin form and REST alike) via
`AvailabilitySlot::normalizeDateTime()`: the canonical `Y-m-d H:i[:s]` and HTML
`datetime-local` (`Y-m-d\TH:i[:s]`) forms are normalised to `Y-m-d H:i:s`;
anything else — or an end not after the start — is rejected (REST responds
`400 invalid_datetime`; the admin form is a no-op). A valid window is stored as
lesson-length slots and `201` returns `{ "ids": [...] }` for every row created.
Times are displayed in 12-hour AM/PM form in the booking calendar and wp-admin
lists.
## Implementation
- Repository: `Unsupervised\Schedular\Availability\AvailabilityRepository`
- Model: `Unsupervised\Schedular\Availability\AvailabilitySlot`
- Week bucketing: `Unsupervised\Schedular\Availability\WeekCalendar`
- Admin controller: `Unsupervised\Schedular\Availability\AvailabilityController`
- REST endpoint: `Unsupervised\Schedular\Availability\AvailabilityEndpoint`
## Tests
- `tests/Unit/Availability/AvailabilityControllerTest.php`
- `tests/Unit/Availability/AvailabilityRepositoryTest.php`
- `tests/Unit/Availability/AvailabilitySlotTest.php`
- `tests/Unit/Availability/AvailabilityEndpointTest.php`
- `tests/Unit/Availability/WeekCalendarTest.php`