Files
unsupervised-scheduler/docs/features/availability-management.md
T
thatguygriffandClaude Opus 5 9d11cc3b01
CI / Tests (PHP 8.1) (pull_request) Successful in 54s
CI / Tests (PHP 8.2) (pull_request) Successful in 54s
CI / No Debug Code (pull_request) Successful in 2s
CI / Coding Standards (pull_request) Successful in 2m52s
CI / PHPStan (pull_request) Successful in 3m3s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m41s
CI / Build Plugin Zip (pull_request) Skipped
Collapse the lesson-type filter behind a Show Only button
The filter took a row of the booking calendar before a student had asked for
it. The view toggle and a new "Show Only" button now share one control row,
and the lesson-type list is revealed between that row and the calendar.

The list stays open across re-renders once revealed, and collapsing it leaves
the filter applied — the button keeps its active styling and carries the
number of ticked types, so a collapsed filter is never invisible.

Closes #119

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-07-28 12:47:24 -03:00

88 lines
5.7 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).
Both views can be narrowed to the slots bookable as chosen private-lesson types
with the **Show Only** lesson-type filter — see `lesson-booking.md`.
## 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`