# Booking

Appointment booking for Slate. Services + providers + weekly hours
+ a public `/book` widget customers walk through to pick a time.
Confirmation email on creation; 24h + 1h reminders sent by cron.

## What it does

- Services (name, duration, buffer, price, colour, active flag)
- Providers (name, email, timezone, bio) linked many-to-many to services
- Weekly recurring hours per provider (one row per day-of-week)
- Slot engine that respects hours + buffer + existing bookings
- Public widget at `/book` (multi-step, server-side state) + iframe embed
- Admin appointments list + right-rail detail view (Slate design system)
- Race-safe creation using `SELECT … FOR UPDATE` inside a transaction
- Cron handler for 24h + 1h reminder emails
- Customer dashboard widget showing upcoming appointments

## Public URLs

| URL | What |
|---|---|
| `/book` | Step 1 — pick service |
| `/book?service=N` | Step 2 — pick provider |
| `/book?service=N&provider=M` | Step 3 — pick date + slot |
| `/book?service=N&provider=M&date=…&slot=…` | Step 4 — confirm form |
| `/book?embed=1` (with any params) | Strips canvas + footer for iframe embeds |

## Setup checklist

1. Create services (`/admin/booking/services`).
2. Create providers (`/admin/booking/providers`).
3. On each provider, link the services they offer + tick the days +
   set start/end times.
4. Wire core cron — see below.
5. Open `/book` and walk through it; the appointment lands in the
   admin inbox and the customer gets a confirmation email.

## Cron (reminders)

Slate ships a generic cron entry point at `/cron.php` (see
`docs/PLUGIN-API.md` for details). Booking listens on the
`frequent_cron` action. Hit cron every ~5 minutes:

```cron
*/5 * * * * curl -fsS 'https://yoursite/cron.php?key=YOUR_CRON_SECRET' > /dev/null
```

The reminder dispatch is idempotent (`reminder_24h_sent` and
`reminder_1h_sent` flags on each row) — running the cron twice
won't send duplicate reminders.

## Hooks

| Hook | Type | Args |
|---|---|---|
| `booking_created` | action | `(int $appointmentId, int $serviceId, int $providerId)` |
| `booking_status_changed` | action | `(int $appointmentId, string $newStatus)` |
| `booking_cancelled` | action | `(int $appointmentId)` |

## Permissions

| Key | Allows |
|---|---|
| `booking.view` | See the booking dashboard, appointments list, details |
| `booking.manage_services` | CRUD services |
| `booking.manage_providers` | CRUD providers + hours + service links |
| `booking.manage_appointments` | Cancel / reconfirm / complete / no-show |

## Tables

- `booking_services` — name, slug, duration_min, buffer_min, price_cents, …
- `booking_providers` — name, email, timezone, bio, is_active
- `booking_provider_services` — m:n link (provider_id, service_id)
- `booking_provider_hours` — (provider, day_of_week, start_time, end_time)
- `booking_appointments` — ref, service+provider+customer, starts_at/ends_at, status, reminder flags

All tenant-scoped via `tenant_id`.

## What's NOT in v0.1

- Membership plans / paid gating (depends on Phase 2 generic payments)
- Promo codes
- Star ratings + post-appointment prompt
- QR check-in
- Waitlist
- Recurring appointments
- Google Calendar / iCal sync
- Multi-timezone slot computation (currently always server-local)
- Group bookings (capacity > 1 per slot)

The schema is intentionally minimal so the next session can add
`booking_memberships`, `booking_plans`, `booking_promo_codes`,
`booking_ratings` without touching what's already here.
