Add Booking module: model, create/read/cancel API, Filament resource (T4.1-T4.7)
- Booking model with booking_vehicle_options line items (supports mixing vehicle options like front_seat + back_seat in one booking), price snapshot, status machine, and driver/car assignment fields - BookingService: front-seat max, disabled-option toggles, duplicate-option and whole-vehicle-exclusivity guards - CreateBookingAction, CancelBookingAction, AssignDriverAction - BookingRefGenerator: sequential EVB-AAAAA1-style refs via row lock - POST/GET/cancel booking API endpoints (Sanctum, ownership + admin policy) - BookingPlugin + Filament BookingResource: list, detail view, Cancel and Assign Driver actions (shared between table and detail page) - domain.md updated for multi-vehicle-option bookings (§2) and driver/ vehicle assignment (§5a)
This commit is contained in:
@@ -10,12 +10,11 @@ Reference doc for business rules and domain vocabulary. Pull this up alongside `
|
||||
|---|---|
|
||||
| **EV Company** | A vehicle operator/fleet owner. Plain reference data — not a tenant (see §4). |
|
||||
| **Destination** | A city/town served by routes. Used as both origin and endpoint. |
|
||||
| **Pickup Location** | A physical point within a Destination where customers board. |
|
||||
| **Dropoff Location** | A physical point within a Destination where customers alight. |
|
||||
| **Departure Time Slot** | A shared catalog of times (e.g. "06:00 AM"); attached to routes via a pivot, not owned by one route. |
|
||||
| **EV Route** | Company + From Destination + To Destination + Pickup + Dropoff + round-trip flag + one or more Time Slots + pricing per Vehicle Option. |
|
||||
| **EV Route** | Company + From Destination + To Destination + round-trip flag + one or more Time Slots + pricing per Vehicle Option. |
|
||||
| **Vehicle Option** | What the customer books: `front_seat`, `back_seat`, or `whole_vehicle`. Not a numbered seat — see §2. |
|
||||
| **Booking** | A customer's reservation of one Vehicle Option on one Route + Date + Time Slot. |
|
||||
| **Pickup/Dropoff Address** | Free-text address (+ optional lat/lng) the customer supplies when booking — where the EV meets/drops them. Captured per Booking, not a catalog entity — see §2a. |
|
||||
| **Booking** | A customer's reservation on one Route + Date + Time Slot, with customer-supplied pickup/dropoff addresses. Covers one or more Vehicle Option selections (e.g. `front_seat` + `back_seat` together), each with its own passenger count — see `booking_vehicle_options` in §2. Once `confirmed`, staff assign a driver/vehicle to it — see §5a. |
|
||||
| **Payment** | One attempt to pay for a Booking through a gateway (may retry after failure). |
|
||||
| **Refund** | A reversal against a specific successful Payment (not against the Booking directly). |
|
||||
|
||||
@@ -25,19 +24,35 @@ Reference doc for business rules and domain vocabulary. Pull this up alongside `
|
||||
|
||||
Unlike a bus-booking system, **there is no seat map and no capacity tracking in v1**:
|
||||
|
||||
- Any number of customers can book the same Route + Date + Time Slot. The system does not check whether a "Whole Vehicle" or "Back Seat" is already taken by someone else.
|
||||
- The **only rule enforced in code** is: **max 1 Front Seat per booking** (a single booking cannot request more than one front seat — this is a per-booking constraint, not a per-trip inventory check).
|
||||
- A Booking can select **more than one Vehicle Option** in the same booking (e.g. `front_seat` + `back_seat` for a customer traveling with a companion) — stored as one row per selected option in `booking_vehicle_options` (`booking_id`, `vehicle_option`, `passenger_count`, `unit_price`, `line_total`), not a single column on `bookings`. `bookings.price` is the sum of every line's `line_total`.
|
||||
- Each Vehicle Option can appear **at most once per booking** (no two separate `front_seat` lines — bump `passenger_count` instead). `whole_vehicle` cannot be combined with any other option in the same booking, since it already covers the entire vehicle.
|
||||
- Any number of *different bookings* can book the same Route + Date + Time Slot. The system does not check whether a "Whole Vehicle" or "Back Seat" is already taken by someone else.
|
||||
- The **only inventory rule enforced in code** is: **max Front Seats per booking**, checked against `passenger_count` on the `front_seat` line (`BOOKING_FRONT_SEAT_MAX_PER_BOOKING`, currently `1` — a per-booking constraint, not a per-trip inventory check).
|
||||
- Back Seat and Whole Vehicle availability are controlled by **blunt config toggles**, not database rows:
|
||||
- `BOOKING_BACK_SEAT_ENABLED` — whether Back Seat can be selected at all right now.
|
||||
- `BOOKING_WHOLE_VEHICLE_ENABLED` — whether Whole Vehicle can be selected at all right now.
|
||||
- `BOOKING_FRONT_SEAT_MAX_PER_BOOKING` — currently `1`, expressed as config in case it ever needs to change.
|
||||
- This is a **deliberate v1 simplification**, not an oversight. Real per-route/date/time-slot capacity holding (e.g. "only 1 Whole Vehicle booking allowed per trip") is an explicitly deferred future phase — see §7.
|
||||
- This is a **deliberate v1 simplification**, not an oversight. Real per-route/date/time-slot capacity holding (e.g. "only 1 Whole Vehicle booking allowed per trip") is an explicitly deferred future phase — see §7. `booking_vehicle_options` is deliberately shaped so that phase can be built as a new query against it (`sum(passenger_count) group by vehicle_option` for a route/date/time-slot) rather than a schema rework.
|
||||
- Consequence: double-booking of "Whole Vehicle" is possible by design until that future phase ships. Admins reconcile manually via the Filament Booking list (filterable by route + date + time).
|
||||
|
||||
**Do not build seat inventory, seat locking, or availability-checking logic against real capacity in current-phase tickets** unless a ticket explicitly says so — it's out of scope until the "Future Scalability" phase.
|
||||
|
||||
---
|
||||
|
||||
## 2a. Pickup & Dropoff (door-to-door, v1)
|
||||
|
||||
The real-world business model is **door-to-door**: the EV drives to wherever the customer requests, not a fixed depot. So, unlike a bus system, `EvRoute` does **not** carry a pickup/dropoff location — those live on `Booking` itself:
|
||||
|
||||
- `pickup_address` (free text) + optional `pickup_lat`/`pickup_lng`.
|
||||
- `dropoff_address` (free text) + optional `dropoff_lat`/`dropoff_lng`.
|
||||
- Captured at booking time (customer types/pins it), not selected from a catalog.
|
||||
|
||||
**Deliberate v1 simplification**: there is no fixed "meet-up checkpoint" catalog and no automatic "customer is too far" detection (no geofencing/service-radius check). In reality, dispatch sometimes asks a too-far customer to meet at a fixed checkpoint instead of door-to-door — that checkpoint catalog (`pickup_locations`/`dropoff_locations` tied to `EvRoute`) is **deferred**, see §7. Until then, "meet at a checkpoint" is handled operationally (dispatch calls the customer), not in the schema.
|
||||
|
||||
**Do not build a `pickup_locations`/`dropoff_locations` catalog or attach pickup/dropoff FKs to `EvRoute`** in current-phase tickets — `Booking` carries the address directly instead.
|
||||
|
||||
---
|
||||
|
||||
## 3. Pricing
|
||||
|
||||
- `RoutePricing` holds one price per (Route, Vehicle Option) pair.
|
||||
@@ -67,6 +82,17 @@ pending_payment ──(TTL expiry, future phase)──▶ expired
|
||||
|
||||
---
|
||||
|
||||
## 5a. Driver & Vehicle Assignment
|
||||
|
||||
Once a Booking is `confirmed` (paid), dispatch assigns who's actually doing the trip — a real driver and a real EV, not a catalog lookup:
|
||||
|
||||
- `bookings.driver_name`, `driver_phone`, `car_plate_number`, `car_model` — plain nullable columns directly on `Booking`, not a separate `drivers`/`vehicles` catalog. Null until assigned; `car_model` stays nullable even after assignment (optional detail).
|
||||
- Filled in via `AssignDriverAction`, gated to `confirmed` bookings only — assigning a driver to a `pending_payment`/`cancelled`/`expired` booking is rejected (`DriverAssignmentNotAllowedException`). Staff can re-run it to reassign a different driver/vehicle as long as the booking is still `confirmed`.
|
||||
- Filament-only for now: the "Assign Driver" action on the admin Booking list/detail page (`manage_bookings` permission), no customer-facing write path. The values are exposed read-only on the booking API response (`GET /api/v1/bookings*`) so a confirmed customer can see who's picking them up.
|
||||
- **Deliberate v1 simplification**: no `drivers`/`vehicles` catalog, no driver scheduling/availability, no linking a driver to an `EvCompany`. If driver roster management becomes a real need, this is the natural point to introduce a `Driver`/`Vehicle` catalog and swap these free-text columns for FKs — not scoped now.
|
||||
|
||||
---
|
||||
|
||||
## 6. Payment Domain (ported from `bnf_event`, refined)
|
||||
|
||||
The existing KBZ Mini App payment code at `/home/marcspecta/company_projects/bnf_event` (`app/Strategies/Payments/KBZMiniApp.php`, `KBZPay.php`, `BasePayment.php`, `app/Services/PaymentService.php`) is the reference implementation being ported and refined — **read it before starting any Payment-module ticket.**
|
||||
@@ -92,6 +118,7 @@ The existing KBZ Mini App payment code at `/home/marcspecta/company_projects/bnf
|
||||
|
||||
## 7. Deferred / Future (do not build yet)
|
||||
|
||||
- Fixed pickup/dropoff checkpoint catalog (`pickup_locations`/`dropoff_locations`, FK'd from `EvRoute`) for when a customer is too far for door-to-door — v1 is pure free-text `pickup_address`/`dropoff_address` on `Booking` (see §2a). Revisit if checkpoint meet-ups become common enough to need a curated, reusable list instead of ad-hoc dispatch calls.
|
||||
- Real per-route/date/time-slot capacity holding + availability checks (see §2).
|
||||
- DB row locking (`SELECT ... FOR UPDATE`) for booking concurrency — only needed once real inventory exists.
|
||||
- Multi-tenant admin isolation (Spatie Permission "teams").
|
||||
|
||||
Reference in New Issue
Block a user