# VIP Booking module — project plan

Client: **VIP HOLIDAYS & TRAVEL SDN BHD**, chauffeur and car booking.
Source: `DATABASE/Holiday/VIP_HOLIDAYS_Booking_Dispatch_TEXT_ONLY_PRICE_BLANK.xlsx`; the analysis is in `DATABASE/Holiday/VIP_Booking_System_Analysis.md`.
Status 15 Nov 2026: **clickable prototype built** on sample data. **No tables yet**: they wait for your approval of §5.

Open it at: http://localhost:81/08_ScanartWarranty/public/vip-booking (rail icon **VIP**, superadmin, or a role given module `vip_booking`).

**v2 BUILT 15 Nov 2026 (user: "ok go ahead and build")** — real tables, no longer a prototype:
- Tables (migration `2026_11_15_000001`): vip_car_type, vip_vehicle, vip_driver, vip_customer, vip_place, vip_booking, vip_booking_stop,
  vip_booking_passenger, vip_booking_event, vip_payment, vip_message, vip_booking_number. Settings = `settings` key vip_settings;
  audit = workflow_audit_log module VIP. Defaults (`…000002`): the 5 car types (luggage / wheelchair room not set) + 11 quick places.
- Code: models `app/Models/Vip/*`; services `VipRules` (ID, checks, clash, texts, audit), `VipBookings` (every change), `VipWhatsApp`
  (rules, Hermes send, tick), `VipBoard` (Today / Dispatch / calendar), `VipReports` (overview, pay rule B, car usage), `VipSettings`;
  controllers `VipBooking\*` (+ public `VipJobController`); middleware `VipLocalTime` (VIP runs on Malaysia time, the app stays UTC).
- Menu: Today · Bookings (list / calendar / board) · Dispatch · Drivers · Cars · Customers · Reports (Overview, Driver trips & pay,
  Car usage, WhatsApp log, Audit) · Guide · Settings. Driver job link `/vip/job/{token}` (no login).
- WhatsApp: rules auto / ask first / off; **sending is OFF until switched on in Settings**; sample bookings never sent. Hermes cron
  job `vip-whatsapp` (every 5 min, --no-agent, deliver local) runs `%LOCALAPPDATA%\hermes\scripts\vip_whatsapp_tick.py` →
  `php artisan vip:whatsapp-tick`.
- Sample data: `php artisan vip:demo` / `--remove` (or Settings → Sample data); bookings DEMO-…, is_sample = 1.
- Tests: 38 pages 200 (+404 for a bad job link); 46/46 logic checks rolled back (ID counter, clash edges, workshop, expired permit,
  accept → steps → km, payments, cancel, tick, no-reply escalation, pay rule B, usage, validation).
- **Drafts entered 15 Nov 2026 (user: "auto enter the demo and draft")** — to be confirmed by the client:
  - Driver pay (RM, flag `pay.draft` = true → the pay report shows "Draft rates"): transfer 50 · cross-border 80 · hourly 20 / h ·
    tour 120 · cancelled after "on the way" 20 · toll & parking paid back. Untick "Draft" in Settings once agreed.
  - Luggage (large bags) / folded wheelchair room per car type, copied to the cars: Vios 2 / no · Alphard 3 / yes · Vellfire 3 / yes ·
    Staria 4 / yes · Van 8 / yes. Now that cars are marked, a wheelchair passenger on a car without room is a red block.
  - Role access: `role_permissions` vip_booking — admin view/add/edit/delete, user view/add/edit.
  - **Free map** (setting `free_map`, on; used when no Google key): Leaflet 1.9.4 + OpenStreetMap tiles, address search by Photon
    (komoot), pin-drop address by Nominatim, drive km / minutes by the public OSRM server. No key, no cost, fair use; drive times have
    no traffic or checkpoint queues. A Google key in Settings replaces it.
  - Tests after the drafts: all pages 200 as superadmin, admin and user; logic 48/48 (wheelchair check updated: room → no block,
    no room → red; 4 bags → Vios not offered).
- Still open: company WhatsApp number paired in Hermes + staff / manager numbers (not invented), public hosting (driver links point
  at APP_URL = localhost today), the client confirming the draft rates and capacities.

**v2 proposal (15 Nov 2026, approved and built — see above):** design canvas https://claude.ai/artifact/AobmXp3N8HNgGwS1Z1WdED
with 7 screens: 1 Ops dashboard "Today" (4 KPIs, dispatch timeline with drag-to-assign, "Do next" queue, fleet strip) · 2 menu
without numbered steps (Today · Bookings [list / calendar / board] · Dispatch · Drivers · Cars · Customers · Reports · Settings) and
what to take from the pasted CodeCanyon-style brief · 3 one-screen booking (service type transfer / hourly / fixed route, adults /
children / luggage) + driver badges (No driver → Assigned, waiting reply → Accepted / Declined / No reply 15 min → On the road) ·
4 driver phone job link (Accept / Decline, On the way → Arrived → On board → Done, odometer) · 5 driver profile with photo + fixed
car photo + documents expiry · 6 car usage (trips, busy %, km, under-used / high use / service due) · 7 driver trips & pay by month
(pay rule A–E to choose). Revised the same day: booking with Google Maps address search / pin / saved places and optional
per-passenger name, phone, IC/passport (masked), special care · dashboard with 3 charts · 8 WhatsApp automation through Hermes
(rules auto / ask first / scheduled, send log; would replace D7) · 9 Management overview graphs · 0 Recommended answers (pay rule B
in RM, link reminders 10/15 min, 12 h/day, <30% / >80%, service per car, odometer per job, payments simple in scope, WooCommerce
later, block hours per service type, company WhatsApp number, schedule via the Hermes gateway). Waiting for the user's OK.

---

## 1. Decisions

| # | Topic | Decision |
|---|---|---|
| D1 | Car type | A field on the booking. The customer picks it, or the system suggests the smallest that fits. Only types with **seats ≥ pax** can be chosen (6 pax → never a 4-seater). |
| D2 | Car types | Toyota Vios 4 · Toyota Alphard 7 · Toyota Vellfire 7 · Staria 7 · Van 10 |
| D3 | Bigger car | Staff **may** assign a bigger car than booked (amber check). A car with fewer seats is never allowed. |
| D4 | Booking ID | `VIP-YYMM-NNNNNN`: YYMM from the **booking (created) date**, 6-digit running number, **restarts every month**. E.g. `VIP-2611-000001`. |
| D5 | Clash | Each trip blocks the driver for a **fixed default number of hours** from pickup. Staff **may change it per booking** (full-day hire). |
| D6 | Fixed vehicle | Each driver has one fixed vehicle (from the workbook). The vehicle is never typed on a booking. |
| D7 | WhatsApp | Sent by staff, by hand. The system builds the text and the wa.me link. **Sent Confirmed** is clicked by staff only. |
| D8 | Prices | SGD and RM are typed separately. No conversion, no exchange rate. |

**Still open (please answer):**
- **O1** Default block hours: how many? The prototype uses **3 as a sample**.
- **O2** Same seat count but a different model than booked (e.g. Staria for an Alphard booking): allowed with an amber warning, like a bigger car? The prototype says yes.
- **O3** Price rule: does **Confirm** need at least one of SGD / RM?
- **O4** Users: how many staff? Do drivers log in to see their jobs, or only get WhatsApp?
- **O5** Company: run VIP Holidays as **its own company** in Nexora (own database, shared logins; *recommended*, since it is a separate business), or inside the current company?
- **O6** Hosting: staff and drivers must reach it from outside this PC. Where will it run?
- **O7** Out of scope for now: payment collected, deposits, driver commission, costs, profit. Agree?
- **O8** Driver fee / monthly fee: how is a driver paid — per trip, % of the booking, a monthly salary, or does the driver pay a monthly car fee? The Month tab shows "not set" until this is given.
- **O9** Workshop: who sets a car "In repair" / "Going to repair", and should a booking already given to that car be flagged automatically?

---

## 2. Modules and functions

| Module | Functions |
|---|---|
| **M1 Booking entry** | New booking form (Apple-style, 5 steps) · customer by phone (auto-fills the name) · **pax ≥ 2 → asks "same pickup?" / "same destination?" and takes one line per pickup / drop-off with the pax there** · car type by pax (suggest + block too-small) · price SGD / RM · block hours · validation · duplicate warning (same phone + date + time) · Booking ID on save (D4) |
| **M2 Booking management** | List with search and filters · booking card (view → Edit → Save) · Confirm · Cancel with reason · Mark completed · Print job sheet · every change logged · edits after a message was Ready set it to **Needs Regeneration** |
| **M3 Driver assignment** | Bookings of the day · driver checks (active, vehicle active, seats ≥ pax, car type vs booked, clash) · day timeline per driver · Assign / Change driver · fixed vehicle copied on assign |
| **M4 WhatsApp messages** | Queue per day · customer confirmation and driver job order (workbook format, fixed) · Copy text · Open in WhatsApp (status Opened) · **✓ I sent it** (Sent Confirmed) · Regenerate · blocked until confirmed + driver + car type |
| **M5 Daily dispatch** | Day board by pickup time, cancelled left out · alerts column · previous / next day · print · Close finished trips |
| **M6 Control centre** | Live KPI strip (trips, waiting confirmation, no driver, messages to send, drivers free now, cars available, booking value day / month) · alerts · tabs **Board** (kanban: waiting confirmation → no driver → to notify → ready → on the road → completed) · **Calendar** (month: trips per day, no driver, pending, to notify, ready, cars in repair; next 7 days) · **Drivers & fleet** (free / on job / no car; available / on job / going to repair / in repair) · **Month** (per driver: trips, hours, booking value, driver fee) |
| **M7 Master data** | Drivers (code, name, phone, status, fixed vehicle) · Vehicles (plate, car type, model, seats, colour, status) · Car types (code, name, seats, status) · Customers (from bookings, phone = key) |
| **M8 Reports** | By day / driver / car type / status for any period (day, week, month quick buttons) · counts + booking value · print · Excel |
| **M9 Audit history** | Who, when, booking, action, field, old → new, note · filter · open the booking · Excel |
| **M10 Settings & help** | ID prefix and digits · default block hours · phone country codes · statuses · both message templates · Guide (daily procedure) |

---

## 3. Menu (rail workspace "VIP")

Order follows the rule for every module: dashboard → numbered flow → master data → reports → help → settings last.

```
Overview      Control centre
Work          1 · New booking
              2 · Bookings            (list → booking card)
              3 · Assign drivers
              4 · WhatsApp messages
              5 · Daily dispatch
Master data   Drivers & vehicles      (tabs: Drivers · Vehicles · Car types)
              Customers
Reports       Booking reports         (tabs: By day · By driver · By car type · By status)
              Audit history
Help          Guide: daily procedure
Settings      Booking settings
```

---

## 4. Pages: layout and entry

All pages follow `docs/UI_STANDARD.md` (x-page-head, x-section, x-field-grid, x-edit-form, x-data-grid with column picker).

### 4.1 Control centre · `vip-booking.index` · `/vip-booking` (rebuilt 15 Nov: KPI strip, alerts, tabs Board · Calendar · Drivers & fleet · Month — see M6)
```
┌ Booking dashboard ─────────────── [date] [Dispatch board] [+ New booking] ┐
│ ①Receive&confirm  ②Assign driver  ③Send WhatsApp  ④Dispatch  ⑤Complete  │  ← "Next" highlighted
│ [Bookings] [No driver yet] [Messages not sent drv·cust] [Value SGD / RM]   │
│ ┌ Needs attention ──────────────┐ ┌ Tomorrow at a glance ─────────────┐   │
│ │ 08:30 VIP-2611-000002 clash…  │ │ bookings · no driver · pending    │   │
│ └───────────────────────────────┘ └───────────────────────────────────┘   │
│ Today's trips by pickup time (grid → click opens the booking)              │
└────────────────────────────────────────────────────────────────────────────┘
```
Entry: date only.

### 4.2 1 · New booking · `vip-booking.create` · `/vip-booking/new`
```
┌ VIP-2611-000012 (next ID) ──── [← Bookings] (Pending) [Save] [Save & confirm] ┐
│ 1 Customer: Phone* (type-ahead) · Name*        │ Car type for this pax       │
│ 2 Journey: Date* · Time* · Pickup 1* · Pickup 2 │ Vios 4   too small          │
│   Destination* · Pax* · Car type · Block hours  │ Alphard 7  fits ← suggested │
│   Notes                                          │ …                           │
│ 3 Price: SGD · RM (separate)                     │ Checks before save          │
└────────────────────────────────────────────────────────────────────────────────┘
```
| Field | Rule |
|---|---|
| Customer phone * | `+` and country code (+60 / +65 in Settings), 8–15 digits. A known phone fills the name |
| Customer name * | text |
| Service date * | not in the past |
| Pickup time * | HH:MM |
| Pickup location (address 1) * | text |
| Pickup address 2 | optional |
| Destination * | text |
| Pax * | 1–60 |
| Car type | only seats ≥ pax; smallest is suggested; empty allowed while Pending |
| Block driver for (hours) | default from Settings (D5) |
| Booking notes | sent to the driver |
| Price (SGD) / Price (RM) | ≥ 0, 2 decimals, independent (D8) |

Save → status Pending Confirmation; Save & confirm → Confirmed. Same phone + date + time already booked → "possible duplicate" warning.

### 4.3 2 · Bookings · `vip-booking.bookings` → card `vip-booking.show`
List: filters (search, service date, order status, driver) → grid (17 columns, picker) → click opens the card.

Card:
```
┌ VIP-2611-000005 · Mdm Wong · Sun 15 Nov 2:00 PM ── [← Bookings][Print](Confirmed)[Edit][Cancel booking][Mark completed] ┐
│ ⚠ Driver message: Needs Regeneration  ⚠ Customer message: Needs Regeneration                                           │
│ [Details] [Driver & vehicle] [WhatsApp] [History n]                                                                     │
│ Details: Customer · Journey · Price (view → Edit → Save)                                                               │
│ Driver & vehicle: code, name, phone, plate, model, seats, colour, blocked from–to  [Change driver]                    │
│ WhatsApp: both messages side by side with status, or why they can't be made yet                                       │
│ History: audit lines of this booking                                                                                   │
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
```
The main button follows the status: Pending → **Confirm**; Confirmed without a driver → **Assign driver**; Confirmed with a driver → **Mark completed**.

### 4.4 3 · Assign drivers · `vip-booking.assign`
```
┌ Assign drivers ── [date] ──────────────────────────────────────────────┐
│ Bookings to assign (pickup, ID, pax, │ Driver for VIP-2611-000006         │
│ car type, driver) — click one        │ P Driver P · Alphard 7 [Assign]    │
│                                      │  OK active · OK seats · OK no clash│
│                                      │ S Driver S  Blocked inactive …     │
├ Driver day timeline 00–24 h: jobs (blue), the selected booking (amber), clash (red) ┤
└────────────────────────────────────────────────────────────────────────┘
```
Checks: driver active · vehicle active · seats ≥ pax · car type vs booked (bigger or same seats = amber, fewer = red) · clash with the driver's other jobs (pickup + block hours). Any red → Assign disabled.

### 4.5 4 · WhatsApp messages · `vip-booking.messages`
Left: queue of the day (customer status, driver status; click a pill). Right: To · WhatsApp number · Status, then the text, then **[Copy text] [Open in WhatsApp] [Regenerate] [✓ I sent it — mark Sent Confirmed]**.
The message cannot be made until the booking is Confirmed and has a driver and a car type (no empty lines are ever sent).

Message fixes against the workbook: time with minutes (`8:30 AM`, the workbook printed "8AM"); prices with 2 decimals; empty Pickup 2 / SGD / RM / Notes lines are left out.

### 4.6 5 · Daily dispatch · `vip-booking.dispatch`
KPIs (trips, pending, no driver, completed) and a grid by pickup time with 22 columns, all in the picker: pickup, blocked until, ID, customer, phones, pickups, destination, pax, car type, prices, driver, vehicle, model, colour, notes, the 3 statuses and alerts. Buttons: previous / next day, Print, **Close finished trips**. Cancelled trips are left out.

### 4.7 Drivers & vehicles (tabs) · `vip-booking.masters.*`
- **Drivers:** code, name, phone, status, fixed vehicle, model, seats, colour, jobs today · [Edit]
- **Vehicles:** plate, car type, model, seats, colour, status, driver · [Edit]
- **Car types:** code, name, seats, fits pax, vehicles, status · [Edit]

Entry (card): Driver = code*, name*, phone*, active, fixed vehicle* (one vehicle → one driver). Vehicle = plate*, car type*, model, seats* (from car type, editable), colour, active. Car type = code*, name*, seats*, active.

### 4.8 Customers · `vip-booking.customers`
Name, phone, bookings, last trip, notes; click → their bookings. Created and updated from bookings.

### 4.9 Booking reports (tabs) · `vip-booking.reports.*`
From / To with Day · Week · Month buttons → 4 KPIs → grid: group, bookings, confirmed, pending, completed, cancelled, no driver, value SGD, value RM. Cancelled bookings are left out of the value. The page says it is booking value, not cash or profit.

### 4.10 Audit history · `vip-booking.audit`
Filters: Booking ID, action, from, to. Grid: time, user, booking, action, field, old, new, note. Read-only.

### 4.11 Guide · `vip-booking.guide`
The staff procedure: start of day, new booking, assign, WhatsApp, changes, end of day.

### 4.12 Booking settings · `vip-booking.settings`
Booking ID (prefix, digits, month from, restart, next ID) · driver schedule (default block hours, bigger car rule) · phones (country codes) · the status lists · both message templates as a preview.

---

## 5. Data design (to approve, not created)

Names are **proposals**. Per the project rule, nothing is created until you approve or rename them.

| Table | Key | Columns |
|---|---|---|
| `vip_car_type` | code | name, seats, active |
| `vip_vehicle` | plate | car_type_code, model, seats, colour, active, notes, service_status (ok / scheduled / repair), service_from, service_until, service_note |
| `vip_driver` | code | name, phone, active, vehicle_plate (unique), notes |
| `vip_customer` | id | phone (unique), name, notes |
| `vip_booking` | booking_id (`VIP-YYMM-NNNNNN`, unique) | created_at, created_by, service_date, pickup_time, block_hours, customer_id, customer_name, customer_phone, pickup_1, pickup_2, destination, pax, car_type_code, price_sgd, price_rm, notes, driver_code, *copy at send:* plate, model, colour, seats, order_status, cancel_reason, driver_msg_status, customer_msg_status, updated_at, updated_by |
| `vip_booking_stop` | booking_id + seq | type (pickup / drop-off), address, pax (null = everyone) — the route; pickup 1 / 2 of the workbook become rows here |
| `vip_booking_number` | yymm | last_no (the monthly running number, taken inside a lock so two staff never get the same ID) |
| `vip_message_log` | id | booking_id, recipient (customer / driver), phone, text, status, by, at |
| Audit | — | reuse `workflow_audit_log` with module `VIP` (as the Agent module does), or a new `vip_audit_log` |
| Settings | — | reuse the existing settings store (as `agent_settings` does) |

Rules in the database: one driver ↔ one vehicle (unique plate on the driver) · booking_id unique · money as decimal(12,2).

---

## 6. Build plan

| Phase | Work | Done when | Test |
|---|---|---|---|
| **B0** ✅ | Prototype of every page on sample data, menu, permission `vip_booking` | You click through and approve the layout | 27 pages 200 |
| **B1** | Answer O1–O7; approve or rename the tables in §5 | Signed off | — |
| **B2** | Tables, migrations, models; Settings; masters (car types, vehicles, drivers) with Edit; import the 3 drivers / vehicles from the workbook | Masters editable, fixed vehicle enforced | Rolled-back tests: unique plate, inactive rules |
| **B3** | New booking + booking card: validation, Booking ID per month with a lock, customer by phone, car type by pax, duplicate warning, audit on every change | Bookings saved; ID restarts in a new month | Tests: ID 000001 on the 1st, two saves at once, 6 pax ≠ Vios |
| **B4** | Assign: checks, clash with block hours, timeline, copy of the vehicle on assign | Wrong assignments blocked | Tests: clash edges (end = next start is not a clash), bigger and smaller car |
| **B5** | WhatsApp: message log, statuses, Opened on link, Sent Confirmed by staff, Needs Regeneration after edits | Matches the workbook text | Text tests for both messages, empty-line rules |
| **B6** | Dispatch board, Close finished trips, dashboard live | Ops runs a day from it | Counts equal the bookings list |
| **B7** | Reports, Excel export, Audit page, Customers | Figures match | Totals = sum of bookings, cancelled excluded |
| **B8** | UAT with staff on real bookings, user guide, go-live; the workbook kept read-only | Signed off | UAT script |

---

## 7. Prototype files (B0)

| File | What |
|---|---|
| `app/Http/Controllers/VipBooking/VipBookingController.php` | All pages; `demo()` takes every button and saves nothing |
| `app/Services/VipBooking/VipRules.php` | The rules and data access: Booking ID, car type by pax, checks (incl. workshop), clash, lanes, driver / vehicle status, both messages, stats |
| `app/Services/VipBooking/VipBoard.php` | Control-centre figures: KPIs, kanban, calendar, next 7 days, drivers, fleet, month by driver, alerts |
| `app/Services/VipBooking/VipSample.php` | Sample data only (fictional): 6 drivers, 6 vehicles (one in repair, one going), 75 bookings over Oct–Nov, sample now = 15 Nov 11:00 |
| `resources/views/vip-booking/_styles.blade.php` | The vx-* design layer (Apple-style cards, icon headers, segmented controls, steppers, tiles, kanban, calendar) |
| `.claude/skills/chauffeur-booking-system/SKILL.md` | Domain skill: call-centre intake, lifecycle, dispatch rules, monitoring of bookings / drivers / fleet / month |
| `resources/views/vip-booking/*.blade.php` | 13 pages + partials (`_banner`, `_demo`, `_booking_row`) |
| `routes/web.php` | `vip-booking.*` routes |
| `config/navigation.php` | Workspace `vip_booking` (rail "VIP") |
| `app/Services/PermissionService.php` | Module `vip_booking`, prefix `vip-booking.` |
| `app/Http/Controllers/UserColumnPreferenceController.php` | Grids `vip_bookings`, `vip_dispatch`, `vip_drivers`, `vip_vehicles`, `vip_car_types`, `vip_customers`, `vip_audit`, `vip_report` |

The sample day is **15 Nov 2026**; pages open on that date. The sample includes, on purpose: a driver clash (VIP-2611-000002 and VIP-2611-000004, both driver P), a confirmed trip with no driver, a booking with no price, a booking with no car type (6 pax → Alphard suggested), messages Needs Regeneration, a cancelled trip, a 10-hour full-day hire, and an inactive driver / vehicle.
