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:
Nyan Lin Paing
2026-08-08 21:43:15 +07:00
parent 4da9ecfe7d
commit 5b68f4fa38
51 changed files with 2698 additions and 42 deletions
+23 -13
View File
@@ -10,7 +10,7 @@ app-modules/
composer.json # requires filament/filament, registers provider
src/
Providers/CatalogServiceProvider.php # repository bindings, event/listener registration
Models/ (EvCompany, Destination, PickupLocation, DropoffLocation, DepartureTimeSlot)
Models/ (EvCompany, Destination, DepartureTimeSlot — PickupLocation/DropoffLocation deferred, see "Deferred Tickets")
Http/Controllers/ Http/Requests/ Http/Resources/
Filament/
Resources/ Pages/ Widgets/
@@ -110,11 +110,9 @@ Cross-module domain code (e.g. Booking module calling Payment module's `RefundBo
- **Description**: Migration, model, factory for `destinations` (`name`, `region`, `is_active`). `DestinationResource` in `app-modules/catalog/src/Filament/Resources/`.
- **Domain reference**: domain.md §1
### T2.3 — Pickup & Dropoff Locations
- **Module**: Catalog
- **Depends on**: T2.2
- **Description**: Migrations, models, factories for `pickup_locations` / `dropoff_locations` (`destination_id` FK, `name`, `address`, `lat`, `lng`, `is_active`). Two Filament resources (or one resource with a type toggle — prefer two for clarity given the architecture plan's separate tables), both under `app-modules/catalog/src/Filament/Resources/`.
- **Domain reference**: domain.md §1
### T2.3 — Deferred (see "Deferred Tickets" at bottom)
Pickup & Dropoff Locations was originally scoped here. The real business model is door-to-door (customer supplies a free-text pickup/dropoff address on `Booking`, see domain.md §2a), so a fixed checkpoint catalog isn't needed for v1. The original ticket is kept at the bottom of this file for when checkpoint meet-ups get built.
### T2.4 — Departure Time Slot
- **Module**: Catalog
@@ -134,9 +132,9 @@ Cross-module domain code (e.g. Booking module calling Payment module's `RefundBo
### T3.1 — EvRoute
- **Module**: Routing
- **Depends on**: T2.1, T2.2, T2.3
- **Description**: Migration, model, factory for `ev_routes` (`ev_company_id`, `from_destination_id`, `to_destination_id`, `pickup_location_id`, `dropoff_location_id`, `is_round_trip`, `is_active`). Eloquent relations: `belongsTo` company/fromDestination/toDestination/pickup/dropoff.
- **Domain reference**: domain.md §1
- **Depends on**: T2.1, T2.2
- **Description**: Migration, model, factory for `ev_routes` (`ev_company_id`, `from_destination_id`, `to_destination_id`, `is_round_trip`, `is_active`). Eloquent relations: `belongsTo` company/fromDestination/toDestination. No pickup/dropoff FK on the route — those are captured per-booking as free-text addresses (domain.md §2a).
- **Domain reference**: domain.md §1, §2a
### T3.2 — Route ↔ Time Slot pivot
- **Module**: Routing
@@ -159,13 +157,13 @@ Cross-module domain code (e.g. Booking module calling Payment module's `RefundBo
### T3.5 — RoutingPlugin + Filament EvRouteResource
- **Module**: Routing/Filament
- **Depends on**: T3.1T3.3, T1.3
- **Description**: `app-modules/routing/src/RoutingPlugin.php` (same `Plugin` contract shape as `CatalogPlugin`, T2.0), added to `AdminPanelProvider`'s `->plugins([...])`. `EvRouteResource` in `app-modules/routing/src/Filament/Resources/` — form with relation selects (company, from/to destination, pickup/dropoff), multi-select for time slots, nested `RoutePricing` relation manager (one row per vehicle option, enforce all 3 present before route can be activated — validation, not a DB constraint).
- **Description**: `app-modules/routing/src/RoutingPlugin.php` (same `Plugin` contract shape as `CatalogPlugin`, T2.0), added to `AdminPanelProvider`'s `->plugins([...])`. `EvRouteResource` in `app-modules/routing/src/Filament/Resources/` — form with relation selects (company, from/to destination), multi-select for time slots, nested `RoutePricing` relation manager (one row per vehicle option, enforce all 3 present before route can be activated — validation, not a DB constraint).
- **Domain reference**: domain.md §1, §3
### T3.6 — Routes read API
- **Module**: Routing
- **Depends on**: T3.1T3.4
- **Description**: `GET /api/v1/routes` (filters: `from`, `to`, `date`, `company`), `GET /api/v1/routes/{route}`, `GET /api/v1/routes/{route}/pricing`, `GET /api/v1/routes/{route}/time-slots`. `EvRouteResource` includes nested pickup/dropoff/company/timeSlots/pricing per architecture plan §11 (AI-agent-friendly shape). Feature tests including the filter combinations.
- **Description**: `GET /api/v1/routes` (filters: `from`, `to`, `date`, `company`), `GET /api/v1/routes/{route}`, `GET /api/v1/routes/{route}/pricing`, `GET /api/v1/routes/{route}/time-slots`. `EvRouteResource` includes nested company/timeSlots/pricing per architecture plan §11 (AI-agent-friendly shape). Feature tests including the filter combinations.
- **Domain reference**: domain.md §8 (this is what the AI agent's `route:read` ability consumes)
### T3.7 — Route/pricing caching
@@ -181,8 +179,8 @@ Cross-module domain code (e.g. Booking module calling Payment module's `RefundBo
### T4.1 — Booking model
- **Module**: Booking
- **Depends on**: T3.1, T3.2, T3.3
- **Description**: Migration, model, factory for `bookings` (`booking_code` unique, `user_id` nullable FK, `ev_route_id`, `departure_time_slot_id`, `travel_date`, `vehicle_option` enum, `passenger_name`, `passenger_phone`, `price` decimal snapshot, `status` enum, `is_round_trip`, `return_travel_date` nullable, `created_by_channel` enum). `BookingStatus` enum (`pending_payment`, `confirmed`, `cancelled`, `expired`).
- **Domain reference**: domain.md §3 (price snapshot — critical, write a test asserting price doesn't change after a later `RoutePricing` edit), §5 (status machine)
- **Description**: Migration, model, factory for `bookings` (`booking_code` unique, `user_id` nullable FK, `ev_route_id`, `departure_time_slot_id`, `travel_date`, `vehicle_option` enum, `passenger_name`, `passenger_phone`, `pickup_address`, `pickup_lat` nullable, `pickup_lng` nullable, `dropoff_address`, `dropoff_lat` nullable, `dropoff_lng` nullable, `price` decimal snapshot, `status` enum, `is_round_trip`, `return_travel_date` nullable, `created_by_channel` enum). `BookingStatus` enum (`pending_payment`, `confirmed`, `cancelled`, `expired`).
- **Domain reference**: domain.md §2a (door-to-door pickup/dropoff addresses live here, not on the route), §3 (price snapshot — critical, write a test asserting price doesn't change after a later `RoutePricing` edit), §5 (status machine)
### T4.2 — BookingService::validateSelection
- **Module**: Booking
@@ -347,3 +345,15 @@ Cross-module domain code (e.g. Booking module calling Payment module's `RefundBo
- **Depends on**: all above
- **Description**: Production compose file (queue worker service, scheduler cron), `config:cache`/`route:cache`/`view:cache` in deploy steps, mTLS certs delivered via deployment secrets (not committed).
- **Domain reference**: domain.md §6 (cert handling)
---
## Deferred Tickets (not scheduled — pick up if the business need reappears)
### T2.3 (deferred) — Pickup & Dropoff Checkpoint Locations
- **Module**: Catalog
- **Depends on**: T2.2
- **Description**: Migrations, models, factories for `pickup_locations` / `dropoff_locations` (`destination_id` FK, `name`, `address`, `lat`, `lng`, `is_active`). Two Filament resources (or one resource with a type toggle — prefer two for clarity), both under `app-modules/catalog/src/Filament/Resources/`.
- **Why deferred**: the actual business model is door-to-door — the EV goes to whatever address the customer gives at booking time (see domain.md §2a), not a fixed catalog point. This ticket models the *exception* case (customer too far, so they and the car meet at a fixed checkpoint instead), which isn't built for v1.
- **To revive this later**: re-add `pickup_location_id`/`dropoff_location_id` nullable FKs (route-level default checkpoint) or put them directly on `Booking` (per-booking checkpoint choice — more likely, since door-to-door is also per-booking) alongside the existing `pickup_address`/`dropoff_address` free-text fields from T4.1, so a booking can be *either* a free-text address *or* a checkpoint reference. Update `EvRouteResource` (T3.5) and the routes read API (T3.6) if checkpoints end up route-scoped.
- **Domain reference**: domain.md §2a, §7