Files
unsupervised-scheduler/docs/features/availability-management.md
T
thatguygriffandClaude Fable 5 c743ed5459
CI / No Debug Code (pull_request) Successful in 2s
CI / Coding Standards (pull_request) Successful in 1m40s
CI / Tests (PHP 8.1) (pull_request) Successful in 46s
CI / Tests (PHP 8.2) (pull_request) Successful in 45s
CI / Tests (PHP 8.3) (pull_request) Successful in 1m34s
CI / PHPStan (pull_request) Successful in 2m52s
CI / Build Plugin Zip (pull_request) Has been skipped
Bulk delete availability slots from the admin list view
The list view of Current Slots gets a checkbox per unbooked slot, a
select-all header checkbox, and a Delete selected button submitting a
new bulk_delete form action. Each id is ownership-checked through the
same path as single delete; the repository's is_booked guard refuses
booked slots as a second layer. Row checkboxes attach to the bulk form
via the HTML form attribute because the table already contains the
per-row delete forms and forms cannot nest.

Closes #57

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-05 22:25:35 -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 **list** or a **weekly calendar** (`usc_view=week`, navigated with `usc_week=Y-m-d`); 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`