# Kapitano Captain Application — Codebase Audit & Build Plan

- **Date:** 2026-09-09
- **Project root:** `C:\xampp\htdocs\kapitano_logistic`
- **Purpose:** Read-only audit of the existing Kapitano Logistic backend as the foundation for the **Kapitano Captain Application** (captain/driver mobile app).
- **Ground rules honored:**
  - The existing repository is the **source of truth** for what already exists; the Captain App spec is the source of truth for required product behavior.
  - This document makes **no code changes**. Nothing here was implemented; notes are facts + a plan to be approved before work starts.
  - Where the spec is not yet confirmed, assumptions are explicitly flagged in **Section 19 (Open Questions)**.

---

## 1. Executive Summary

The existing backend is a clean, modern **Laravel 13 API** focused on a narrow slice of the delivery business:

- **Two identities:** `Admin` (back-office, password + roles/permissions) and `Driver`/Captain (phone + OTP, document & vehicle onboarding, admin review).
- **Strong, consistent engineering:** thin controllers → services → repositories → models, DTOs (spatie/laravel-data), enums for state, events/listeners, a uniform JSON envelope (`ApiResponder`), structured exception handling, and rate limiting.
- **Infrastructure for push (FCM) and WhatsApp (ISHAAR) is wired but unconfigured** (`FIREBASE_CREDENTIALS`, `ISHAAR_API_KEY` empty in `.env`).
- **The entire delivery core is missing:** there are **no** orders, deliveries, customers, stores/vendors, addresses, payments/wallets, location tracking, attendance/shifts, or ratings tables, routes, or models. The Captain App therefore requires building the order lifecycle from scratch **on top of** the existing captain-onboarding and admin identity foundation.

**Bottom line:** The existing code is a strong, conventional foundation (auth, RBAC, onboarding, media, notifications, envelope). The Captain app is greenfield for the order domain. We can build the MVP without rewriting any existing working code.

---

## 2. Existing Architecture

**Stack (composer.json, verified):**
- Laravel framework `^13.8`, PHP `^8.3` (local PHP 8.4.25 at `C:\xampp\php`).
- `laravel/sanctum ^4.0` (API tokens).
- `spatie/laravel-permission ^8.3` (roles/permissions).
- `spatie/laravel-data ^4.23` (request DTOs), `spatie/laravel-medialibrary ^11.23` (file uploads).
- `kreait/laravel-firebase ^7.2` + `laravel-notification-channels/fcm ^6.1` (push).
- `predis/predis ^3.5` (redis client available; Redis not running — effective store is DB-backed, see Section 4).

**Layering / conventions (verified class names):**
- `app/Http/Controllers/{Dashboard,Mobile}` — back-office vs captain-app controllers.
- `app/Http/Requests` — `BaseFormRequest` (authorize = true; validation via rules on request classes) + `Concerns/NormalizesPhoneNumber`.
- `app/Http/Resources` — API resources (AdminResource, DriverResource, VehicleResource, etc.).
- `app/Services` — domain services: `BaseService`, `BaseJsonService`; `Driver/{DriverAuthService,DriverService}`; `Admin/{AdminAuthService,AdminService}`; `General/{OtpService,IshaarWhatsAppService,FileService,JsonFileService}`; `Vehicle`, `Permission` services.
- `app/Repositories` — repository pattern with interfaces + `Traits/CacheableRepository`; repositories for Admin, Driver, Vehicle, Permission, Role.
- `app/DTOs` — spatie/laravel-data DTOs (e.g., `DriverPhoneData`, `DriverOtpData`, `AdminCredentialsData`, vehicle data).
- `app/Enums` — domain enums: `Driver/DriverStatus` (pending/approved/rejected), `Driver/DriverSignInStatus`, `Driver/DriverEmploymentType`, `Vehicle/VehicleOwnership`, `Permission/AdminPermission` (12 permission names), `Admin/AdminAuthStatus`.
- `app/Events` + `app/Listeners` — `DriverRegistered` (no listener yet), `DriverApplicationReviewed` → queued `NotifyDriverOfApplicationDecision`; `NotificationFailed` → `DeleteExpiredNotificationTokens` (**dead code**, see Risks).
- `app/Notifications` — `OtpNotification` (Log in local/testing, WhatsApp otherwise), `BaseFcmNotification`, `NewFcmNotification` (unused), `Channels/{WhatsAppChannel,LogChannel}`, `Messages/WhatsAppMessage`.
- `app/Http/Middleware/CheckApiHeaderMiddleware` — the only custom middleware; gates `Accept: application/json` + `Accept-Language`; sets locale (en/ar).
- `app/Http/Responses/ApiResponder` — uniform envelope (Section 5).
- `app/Exceptions/{Handler,ApiExceptionHandler}` — class → handler map for `api/*`.
- `app/Providers` — `AppServiceProvider` (super-admin `Gate::before`, paginator, strict models), `RateLimiterProvider`, `EventServiceProvider`.
- `app/Models/Traits/HasFile` — photo upload helper auto-deleting on delete.

**Identifier strategy:** `Admin`, `Vehicle`, `Role`, `Permission` use `HasUuids` (`uuid` route key); `Driver` uses `HasUlids` (route key `uuid`, ULID value). Route param constraints use `whereUuid` / `whereUlid` accordingly.

**Routing (bootstrap/app.php):** `web` + `api` + `commands` + health `/up`. Middleware aliases: `role`, `permission`, `role_or_permission`. `api` group appends `throttle:api` + `CheckApiHeaderMiddleware` (prepended before auth). JSON rendering enabled for all `api/*`.

**Environment-effective settings (.env):** `APP_ENV=local`, `CACHE_STORE=database`, `QUEUE_CONNECTION=database`, `SESSION_DRIVER=database`, `FILESYSTEM_DISK=local`, `APP_URL=http://localhost/kapitano_logistic`, MySQL `root`/empty password, DB `kapitano_logistic`. Strict models on in local.

---

## 3. Existing API Inventory

All JSON APIs require headers `Accept: application/json` and `Accept-Language: ar|en`. All responses use the ApiResponder envelope. Global `api` throttle: 30/min.

### Captain (Driver) side — `routes/driver.php` → `/api/driver/*` (guard `driver`)

| Method | URI | Name | Middleware | Notes |
|---|---|---|---|---|
| POST | `/api/driver/auth/register` | `api.driver.auth.register` | (api throttle) | 201; `status=Pending`; uploads license + photo + vehicle via media library |
| POST | `/api/driver/auth/login` | `api.driver.auth.login` | `throttle:otp` | sends OTP; refusal → 404/423/403 |
| POST | `/api/driver/auth/verify` | `api.driver.auth.verify` | `throttle:otp` | returns `{token, driver}`; single-use code |
| POST | `/api/driver/auth/resend` | `api.driver.auth.resend` | `throttle:otp` | issues new code, invalidates old |
| POST | `/api/driver/auth/logout` | `api.driver.auth.logout` | `auth:driver` | deletes ALL tokens |
| GET | `/api/driver/profile/` | `api.driver.profile.show` | `auth:driver` | profile + vehicle |
| POST | `/api/driver/profile/` | `api.driver.profile.update` | `auth:driver` | update profile fields |

### Admin side — `/api/dashboard/*` (guard `admin`)

**Auth (`routes/admin.php: prefix auth`, `throttle:admin-auth` 5/min/email+ip):**
| Method | URI | Notes |
|---|---|---|
| POST | `/api/dashboard/auth/login` | email+password → token; 401 invalid / 403 disabled |
| POST | `/api/dashboard/auth/forgot-password` | sends OTP to admin phone |
| POST | `/api/dashboard/auth/resend-code` | |
| POST | `/api/dashboard/auth/reset-password` | OTP + new password; end any other guard mismatch 401 |
| POST | `/api/dashboard/auth/logout` | `auth:admin`; deletes all tokens |

**Profile (`auth:admin`):** `GET/POST /api/dashboard/profile/`, `POST /api/dashboard/profile/photo`, `POST /api/dashboard/profile/password`.

**Admins CRUD (`auth:admin` + `permission:admins.*,admin`):** `GET/POST /api/dashboard/admins/`, `GET/PUT /api/dashboard/admins/{uuid}`, `PATCH /api/dashboard/admins/{uuid}/activation`.

**Roles/Permissions (`auth:admin` + `permission:roles.*,admin`):** `GET/POST /api/dashboard/roles/`, `GET/PUT /api/dashboard/roles/{uuid}`, `GET /api/dashboard/permissions/` (behind `roles.view|roles.create|roles.update`).

**Driver management (`auth:admin` + `permission:drivers.*,admin`):** `GET /api/dashboard/drivers/`, `GET/PUT /api/dashboard/drivers/{uuid}` (ULID), `PATCH .../activation`, `PATCH .../approve`, `PATCH .../reject`.

**Web (`routes/web.php`):** `GET /` → health-up welcome view; `GET /up` → framework health.

> **No endpoint exists for:** device-token registration, order/delivery anything, captain availability, captain location, earnings/payments, ratings, notifications list.

---

## 4. Existing Database Inventory

**Effective storage (.env):** MySQL `kapitano_logistic`; cache=**database** (table `cache`/`cache_locks`); queue=**database** (`jobs`); session=**database**. Redis/phpredis listed but Redis not used in `.env` today.

**Existing tables (18 app tables):**

| Table | Purpose |
|---|---|
| `admins` | Back-office users; uuid, unique phone/email, `password` hashed, `is_active` |
| `drivers` | Captains; ulid, unique phone/national_id, `employment_type` enum, `status` enum (pending/approved/rejected), license fields, `reviewed_by`→admins, `is_active`; **no password column** |
| `vehicles` | driver_id→drivers (cascade), `ownership_type` enum (company_owned/personal), plate/brand/model/year/color, `is_active` |
| `device_tokens` | polymorphic user (Admin/Driver), `fcm_token`, unique(user_type,user_id,fcm_token) |
| `media` | Spatie media library (license, profile_photo, vehicle_image, mechanics_image) |
| `personal_access_tokens` | Sanctum tokens, `expires_at` indexed |
| `permissions`, `roles`, `model_has_permissions`, `model_has_roles`, `role_has_permissions` | Spatie (guard `admin`; teams off) |
| `password_reset_tokens`, `sessions`, `jobs`, `job_batches`, `failed_jobs`, `cache`, `cache_locks`, `migrations` | Framework/infra |

**Domain tables that DO NOT exist (verified — no migration, no model):**
- ❌ `orders`, `order_items`
- ❌ `deliveries` / `trips` / `assignments`
- ❌ `customers`, `stores` / `vendors`
- ❌ `addresses`
- ❌ `payments`, `payment_methods`, `transactions`, `wallets`, `balances`
- ❌ `locations` / live position tracking / geofences
- ❌ `attendance` / `shifts` / `schedules`
- ❌ `ratings` / `reviews`
- ❌ product/catalog tables

**Seeders (`DatabaseSeeder` order):** `OptimizeSeeder → PermissionSeeder → RoleSeeder → AdminSeeder → DriverSeeder → VehicleSeeder`.
- Permissions (guard `admin`): `roles.view|create|update`, `admins.view|create|update`, `drivers.view|update|review`, `vehicles.view|create|update` (12 total, from `AdminPermission` enum).
- Roles: `super-admin` (no perms; Gate bypass), `operations-manager` (drivers+vehicles mgmt), `support` (drivers.view, vehicles.view).
- Admin: `super-admin@kapitano-logiistic.com` / `123456` (dev default) — sync email "logiistic" typo is the actual seeded value.
- 5 sample drivers + 5 sample vehicles (statuses across pending/approved/rejected/disabled to exercise flows).

**Factories:** `AdminFactory` (incl. `superAdmin()` state), `DriverFactory` (incl. `approved/rejected/employee/inactive` states), `VehicleFactory` (incl. `companyOwned/awaitingAssignment/inactive` states), `RoleFactory`, `PermissionFactory`.

---

## 5. Existing Authentication Flow

- **Guards:** `driver` (Sanctum + `Driver`) and `admin` (Sanctum + `Admin`). **Default guard = `driver`** (`env('AUTH_GUARD','driver')`), so admin routes always name the guard explicitly (`auth:admin`, `permission:...,admin`).
- **Owner model traits:** `HasApiTokens`, `Notifiable`; `Driver` additionally `HasUlids`, `InteractsWithMedia`; `Admin` additionally `HasRoles`, `HasFile`, `HasUuids`.
- **Tokens:** `createToken(device_name ?? fallback)` with default abilities `['*']`; expiration **43200 min = 30 days** (`sanctum.expiration`). **No refresh-token flow**; logout deletes all of the principal's tokens.
- **Captain login (passwordless OTP):**
  1. `POST /api/driver/auth/login` (phone) → `DriverAuthService::requestOtp` → checks sign-in rules → `OtpService::send`.
  2. OTP stored in **cache** (`otp_<sha256(phone)>`) as `{hash, expires_at}`; 6-digit, `OTP_EXPIRES` = 10 min; **single-use** (consumed on match); resend overwrites (only newest valid).
  3. `POST /api/driver/auth/verify` (phone + code) → `OtpStatus::Matched` → sets `last_login_at` → returns `{token, driver}`.
  4. Delivery channels: **local/testing → LogChannel** (prints code to log); **otherwise WhatsApp via ISHAAR** (`services.ishaar`, template id fallback `913046878122992`). `.env` keys **empty** (see Risks).
  5. Refusal mapping (`DriverSignInStatus`): NotRegistered→404, Pending→423, Disabled→403, Rejected→403; reflection messages localized.
- **Admin login:** email+password, bcrypt (`BCRYPT_ROUNDS=12`, `password` cast `hashed`), anti-enumeration (401 `auth.failed` for both bad email and bad password), 403 if disabled. Password reset uses the same cache OTP flow to the admin's phone; reset logs out all sessions.
- **Rate limiters (`RateLimiterProvider`):** `api` 30/min; `web` 30/min; `otp` 4/min per phone **and** per IP; `admin-auth` 5/min per email **and** per IP. 429 responses via `ApiExceptionHandler`.
- **Header gate (`CheckApiHeaderMiddleware`):** rejects with 401 if not `Accept: application/json` or missing `Accept-Language`; resolves locale (ar/en, fallback en).
- **RBAC:** route middleware `permission:<name>,admin`; `Gate::before` lets `super-admin` pass everything; service-level guards (super-admin role protection, cannot deactivate self/other super-admin).

> **Gaps for Captain app:** no device-token registration endpoint (tokens only inserted via tests); FCM creds empty; `NotificationFailed` cleanup listener targets a non-existent `notificationTokens()`/`push_token` (dead code).

---

## 6. Existing Order Flow

**There is no order/delivery flow in the codebase.** Verified: no `orders*`, `deliveries*`, `trips*`, `assignments*` routes, controllers, services, models, or migrations exist anywhere under `app/` or `routes/` or `database/`.

The **closest existing lifecycle** (and the pattern to mirror) is driver onboarding:

```
register (Pending) → admin review → approve/reject → DriverApplicationReviewed event
   → queued listener → FCM notification to captain
```

This proves the project’s ability to model a stateful domain flow with events + push, but the Captain app's **order lifecycle (assign → accept → pickup → delivered) is greenfield.**

---

## 7. Captain App Requirement Mapping

Assumed core captain-app capabilities (validated in Section 19) mapped to what exists today:

| Captain App capability (assumed) | Existing? | Evidence |
|---|---|---|
| Register / onboard (docs, license, vehicle, employment type) | ✅ | Driver module (Section 3/4) |
| OTP login / logout / profile | ✅ | Driver auth + profile APIs |
| Receive push notifications | 🟡 | FCM infra wired, **credentials absent**; no device-token API |
| Availability (on/off duty) | ❌ | missing |
| Order list / assignment | ❌ | missing |
| Accept / decline order | ❌ | missing |
| Pickup / deliver status transitions | ❌ | missing |
| Proof of delivery (photo/signature) | 🟡 | media library can host files; no POD flow |
| Live location reporting | ❌ | missing |
| Earnings / commission / wallet | ❌ | missing |
| Ratings (whoever-rated) | ❌ | missing |
| Attendance / shift / schedule | ❌ | missing |
| Admin order ops + dispatch + stats | ❌ | missing (admin CRUD/permissions pattern exists to reuse) |

---

## 8. Gap Analysis

| # | Capability | State today | Gap | Needed build |
|---|---|---|---|---|
| 1 | Push to captains | **Closed (2026-09-16).** FCM channel, `device_tokens` table, register/unregister endpoint, credentials in `storage/app/firebase/`, `firebase:test` smoke command, working token pruning | — | Done; a real-device delivery test is still outstanding |
| 2 | OTP delivery in production | ISHAAR WhatsApp channel exists | `ISHAAR_API_KEY`/`NUMBER_ID`/`TEMPLATE_ID` empty; errors only logged | Provide keys (or swap provider) |
| 3 | Orders core | — | entire domain | Schema + models + status enum + services + APIs (Section 13) |
| 4 | Delivery lifecycle | — | — | Status machine: assigned→accepted→at-pickup→picked-up→delivered / canceled / failed; timeouts |
| 5 | Assignment / dispatch | — | — | Manual admin dispatch first; have idle-captain/auto-assign pattern ready |
| 6 | Captain availability + location | — | — | `driver_availability`/`driver_locations` tables + throttled update endpoint |
| 7 | Proof of delivery | media library works | no flow | POD photo/image/signature collection on delivery; store via media morph |
| 8 | Customer / vendor data | — | — | decide scope: basic `customers` + inline addresses |
| 9 | Payments / wallet | — | — | defer to post-MVP (Section 9), architecture-ready |
| 10 | Admin ops screens/endpoints | RBAC + envelope + patterns exist | no order domain | Orders index/show/assign/cancel + per-captain stats; new permissions in `AdminPermission` + roles |
| 11 | Earnings/attendance/ratings | — | — | post-MVP, DB-designed-now |
| 12 | Realtime | none (no websockets, no polling) | — | MVP: REST + FCM + background polling; Reverb later |
| 13 | Seed/factory data for tests | existing seeders | no order fixtures | Order/Driver factories + seeders; tests in existing test suite |

---

## 9. MVP Scope (proposal — confirm in Section 19)

**MVP (captain app v1):**
1. Push wiring: device-token register/unregister endpoint + Firebase JSON credentials.
2. Captain availability toggle (online/offline) with location update (throttled).
3. Orders: admin creates order (with pickup + dropoff locations/addresses, items summary), captain assigned.
4. Captain: order list assigned& active; order detail; **accept / decline** with countdown auto-expire (decline → re-dispatch); **pickup**; **delivered with POD proof** (photo + optional note); cancel with reason (limited).
5. Notifications: FCM on new assignment, status changes.
6. Admin: minimal order management (create, list, assign manually, cancel, view status timeline) + driver status overview (already available).
7. Earnings: **summary only** (per-order fee computed, running total column) — no wallet/payout engine in MVP.

**Explicitly post-MVP:** wallets/payouts, customer/marketplace self-serve, ratings & reviews, attendance/shifts, ride-hailing passenger flow, geofencing/fare estimation, auto-assignment intelligence, multi-store product catalog.

---

## 10. MVP Dependency Map

Order of construction (an item may only start when its dependencies are done →):

```
F0 Foundation (prereq: none)
 ├─ Firebase credentials file + FCM token registration endpoint      (enables all push)
 ├─ Orders schema migration (orders, order_items, deliveries, driver_locations/availability, addresses)
 ├─ OrderStatus enum + DriverAvailability enum + models/relations + DTOs
 └─ OrderStatusService (state machine + guards)                      [reuse service/repo/DTO/enum patterns]

F1 Dispatch (depends F0)
 ├─ Order assignment (manual admin assign; idle-captain list)
 ├─ Accept/Decline endpoints + auto-expiry job
 └─ FCM notifications on assign/accept/decline                       [reuse BaseFcmNotification]

F2 Execution (depends F1)
 ├─ Pickup / Delivered transitions
 ├─ POD upload (media library morph) + POD link in order payload
 └─ Cancel-with-reason + status timeline endpoint

F3 Captains context (depends F0)
 ├─ Availability toggle + location update (throttle:api-friendly)
 └─ Driver earnings summary (derived columns/tables added in F1)

F4 Admin ops (depends F1, F2)
 ├─ Admin order CRUD + assign + cancel (new AdminPermission cases + role sync)
 └─ Dashboard stats: orders/day, per-captain, on-time/cancel rates

F5 (post-MVP) wallets, ratings, attendance, auto-assignment, realtime via Reverb
```

> Rule honored: **no existing module is modified unless architecturally necessary.** New code lands as new modules mirroring existing patterns (DTO + Service + Repository + Resource + Event/Listener).

---

## 11. Recommended Architecture

Keep everything that exists; extend with the same idioms:

- **Controllers stay thin.** New `Mobile\Order\OrderController`, `Mobile\Driver\DriverAvailabilityController`, `Mobile\Driver\DeviceTokenController`, `Dashboard\Order\OrderController` (+ `Dashboard\Driver\DriverEarningsController`).
- **One source of truth for order state:** `Enums\Order\OrderStatus` (+ `DispatchStatus`), with a single `Services\Order\OrderStateMachine` enforcing legal transitions; do not allow scattered status writes.
- **DTO + Request pattern:** `OrderCreateData`, `OrderAcceptData`, `ProofOfDeliveryData`, `LocationUpdateData` (spatie/laravel-data), validation in `app/Http/Requests`.
- **Repositories:** `OrderRepository`, `OrderItemRepository`, `DriverLocationRepository` implementing interfaces, aligned with `CacheableRepository` trait.
- **Events/Listeners for side effects:** `OrderAssigned`, `OrderAccepted`, `OrderDeclined`, `OrderDelivered` → queued listeners send FCM (extending `BaseFcmNotification`) and write history rows; audit via `order_status_history` table.
- **Async:** queue = database (works locally); notifications queued.
- **Realtime (MVP):** REST polling (30 s) + FCM. Defer Laravel Reverb/websockets until scale demands.
- **Location:** `POST /api/driver/location` throttled (e.g., every 30–60 s client-side), storing latest point per driver (single row upsert), geocoding via maps provider adapter (config-driven, swap-friendly).
- **Media for POD:** reuse `InteractsWithMedia`; add `Order` model media collection `proof_of_delivery`.
- **Auth for new routes:** `auth:driver` (captain) / `auth:admin` + new `permission:orders.*,admin` (admin); keep header-gate + envelope.
- **Guard against touching working code:** new document uses only additive migrations/models/services; no edits to `Driver*`, `Admin*` behavior.

---

## 12. Database Changes (additive only, MVP slice)

1. `orders`
   - `customer name/phone` (inline for MVP), `pickup_address`, `dropoff_address`, `note`
   - `pickup_lat/lng`, `dropoff_lat/lng` (nullable, decimal)
   - `order_status` enum, `dispatch_status`, `fee`/`total` decimal, `currency`
   - `driver_id` FK→drivers (nullable until assigned), `created_by` FK→admins
   - `accepted_at`, `picked_up_at`, `delivered_at`, `canceled_at`, `cancel_reason`, `timeout deadline` (accept/arrive), timestamps
2. `order_items` — `order_id` FK, `name`, `quantity`, `unit_price` (summary-level for MVP)
3. `order_status_history` — `order_id` FK, `status`, `note`, `actor_type`/`actor_id`, `created_at` (audit/state machine)
4. `driver_availabilities` (or column on drivers — separate table recommended) — driver_id FK, `is_online`, `last_seen_at`, upsert
5. `driver_locations` — driver_id FK unique, `lat`, `lng`, `accuracy`, `captured_at` (upsert latest)
6. `device_tokens` — already exists (reuse); add unique index cleanup + `is_active` if needed later
7. (Post-MVP) `wallets`, `wallet_transactions`, `ratings`, `attendance` — design names now, skip creation

Style details to match migrations: `utf8mb4`; uuid/ulid per entity style (order → `uuid` + `HasUuids`); FKs with `cascadeOnDelete`/`nullOnDelete`; `is_active` boolean pattern where applicable.

---

## 13. API Changes (additive, contract proposal)

**Captain (`auth:driver` unless noted):**
- `POST /api/driver/device-token` (register FCM) + `DELETE /api/driver/device-token` (unregister) — *unauthenticated register optional; prefer authenticated.*
- `PATCH /api/driver/availability` (online/offline)
- `POST /api/driver/location` (throttled)
- `GET /api/driver/orders` (assigned active list, filter by status, paginated — envelope pagination exists)
- `GET /api/driver/orders/{uuid}` (detail + status timeline)
- `POST /api/driver/orders/{uuid}/accept`
- `POST /api/driver/orders/{uuid}/decline` (reason)
- `POST /api/driver/orders/{uuid}/pickup`
- `POST /api/driver/orders/{uuid}/deliver` (multipart: POD photo + note)
- `POST /api/driver/orders/{uuid}/cancel` (reason; captain-initiated cases controlled by state machine)

**Admin (`auth:admin` + new `orders.*` permissions):**
- `GET /api/dashboard/orders/` (filters: status, driver, date range — reuse pagination envelope)
- `GET /api/dashboard/orders/{uuid}` (detail + timeline + POD)
- `POST /api/dashboard/orders/` (create) — assign optional
- `PATCH /api/dashboard/orders/{uuid}/assign` `{driver_uuid}`
- `PATCH /api/dashboard/orders/{uuid}/cancel`
- `GET /api/dashboard/drivers/{uuid}/earnings` (summary) (post-POD basic for MVP)

All: envelope shape, `Accept` headers, `throttle:api` base, `throttle:otp`-like custom limiter for location updates if needed, permission middleware, Events → FCM. Response examples should mirror existing DriverResource/ApiResponder usage.

---

## 14. Mobile Screens (Captain App)

Proposed screen map (backend already supports several):

1. **Splash / Language & Region selection** (ar / en) — sets `Accept-Language`.
2. **Sign-in** — phone → OTP (existing `/login` + `/verify`) → stores token.
3. **Onboarding** (first run) — profile docs, license, vehicle, employment type → `/register` (existing), status banner (pending/approved/rejected).
4. **Home (orders)** — availability toggle; active order list; offline empty state.
5. **Order detail** — pickup/dropoff (address + map), items, fee, status timeline, action buttons.
6. **Dispatch dialog** — incoming order alert (FCM data + polling), accept/decline with countdown.
7. **Pickup screen** — confirm arrived/picked up.
8. **Deliver screen** — POD photo capture + optional note → submit.
9. **Earnings** — running total + per-order list (summary only in MVP).
10. **Notifications** — FCM-driven list.
11. **Profile** — existing profile endpoints; vehicle info; logout.

Map provider for address/coordinates: config-driven adapter (start with Google Maps; OpenStreetMap fallback).

---

## 15. Dashboard Requirements (Admin)

1. **Orders** — list w/ filters (status, driver, date), detail with live status timeline + POD file; create order; manual assign; cancel.
2. **Drivers** — existing view/review management (reuse); add online/offline + last location per captain; per-captain orders/earnings summary.
3. **Statistics (MVP-lite)** — orders per day, active captains, accept/cancel/on-time metrics; deliver via a `Dashboard\StatsController` (aggregate queries, no heavy BI).
4. **Settings** — reuse existing roles/permissions admin to grant `orders.*` permissions; no new settings screens in MVP.

New permissions to add to `AdminPermission` enum + `PermissionSeeder`/`RoleSeeder` sync: `orders.view`, `orders.create`, `orders.update`, `orders.assign`, `orders.cancel` (assign/cancel under `orders.update` optional). Assign sensible defaults to `operations-manager`; keep `super-admin` bypass.

---

## 16. Implementation Task List

T-01 `[x]` Configure Firebase service account JSON; point `FIREBASE_CREDENTIALS` (or `GOOGLE_APPLICATION_CREDENTIALS`) at it; smoke-test `NewFcmNotification`. *(2026-09-16: credentials live in `storage/app/firebase/`; `php artisan firebase:test` is the smoke test — see `docs/runbook.md` §1.)*
T-02 `[x]` Add device-token register/unregister controller+DTO+service+route using existing `DeviceToken` model (reuse). Remove/fix dead `DeleteExpiredNotificationTokens` listener + stale `school_id` cast. *(2026-09-16: the listener now prunes by `fcm_token` on unknown/invalid-token reports only.)*
T-03 Order schema migration set (orders, order_items, order_status_history, driver_availabilities, driver_locations) + rollback definitions.
T-04 `Order` (HasUuids, media POD), `OrderItem`, `OrderStatusHistory`, `DriverAvailability`, `DriverLocation` models + relations + factories + order seeders.
T-05 Enums `OrderStatus`, `DispatchStatus`; DTOs; `OrderStateMachine` service with transition guards; unit tests for legal/illegal transitions.
T-06 Order services + repositories (Order, OrderItem, DriverLocation) following existing interfaces/trait patterns.
T-07 Captain order endpoints (list/detail/accept/decline/pickup/deliver+POD/cancel) + Requests + Resources + envelope responses.
T-08 Dispatch: admin assign endpoint; idle-captain suggestion query; accept-countdown job (auto-expire → re-dispatch).
T-09 Events + queued listeners → FCM (`OrderAssigned`, `OrderAccepted`, `OrderDelivered`); extend `BaseFcmNotification`.
T-10 Availability + location endpoints (PATCH availability, POST location with throttle + upsert).
T-11 Earnings summary (derived) API for captain + admin per-driver.
T-12 Admin orders CRUD + assign/cancel + stats minimal; new `AdminPermission` cases + RoleSeeder sync; tests for admin permission gates.
T-13 API integration tests for full journey (register→admin approve→create order→assign→accept→pickup→deliver + POD) matching existing test conventions (`tests/Feature/Driver/...`).
T-14 Feature-flag/env doc: expected `.env` additions (Firebase, ISHAAR, maps key, coordinates rounding).

---

## 17. Development Order

Follow Section 10 dependency order, concretely:

1. **T-01 + T-02** (push plumbing) — unlocks all notifications.
2. **T-03 → T-06** (schema → models → state machine → services) — the core, test-first.
3. **T-07 → T-09** (captain execution + dispatch + FCM) — vertical slice: assign → accept → deliver with POD.
4. **T-10** (availability + location).
5. **T-11** (earnings summary).
6. **T-12** (admin ops + permissions) + **T-13** (journey tests).
7. **T-14** (docs/env checklist) then MVP release.

Ship the vertical slice (3) end-to-end before adding breadth (10–12). Do not start post-MVP (wallets/ratings/realtime) before MVP ships.

---

## 18. Risks

1. **Push dead without credentials:** `FIREBASE_CREDENTIALS` empty → any FCM send throws. Mitigation: T-01 first; dev-mode log channel fallback.
2. **OTP dead in production:** `ISHAAR_API_KEY`/`NUMBER_ID`/`TEMPLATE_ID` empty; failures only logged → captains get no codes. Mitigation: obtain keys or swap to SMS provider; keep LogChannel for local.
3. **No device-token registration API** — mobile can’t enroll for push → must ship T-02.
4. **Dead/broken existing code to clean:** `DeleteExpiredNotificationTokens` expects non-existent relation/column; `DeviceToken` has stray `school_id` cast; `NewFcmNotification` unused; `DriverRegistered` event has no listener. Clean only where it blocks the new work; otherwise leave.
5. **Default-guard footgun:** app default guard is `driver` — every new admin route/permission middleware must pass `,admin` explicitly; forgetting yields driver-scoped auth failures.
6. **Envelope/header coupling:** every new client call must send `Accept: application/json` + `Accept-Language` or it 401s pre-auth — new app screens must set headers from day one.
7. **DB-backed cache for OTP:** cache store is database; high OTP volume adds DB writes; acceptable for MVP; move to Redis later (predis already in composer).
8. **Queue = database:** queued notifications could add load; fine locally; plan `queue:work`/Redis for production.
9. **No refresh tokens:** 30-day tokens, all-token wipe on logout — fine for MVP; revisit with real client.
10. **Doc/scope assumptions (Section 19)** could reorder sections 9–15 — confirm spec before development starts.
11. **Local Apache quirks:** Apache must run via the XAMPP console (`apache_start.bat`) — forced `Stop-Process` of `httpd` destabilizes it; note for the team.
12. **`config/cors.php` absent** — framework defaults (any origin for api/*, no credentials). Fine for mobile; pin down before web-dashboard/BI tooling.

---

## 19. Open Questions (must confirm with the Captain App spec before/while implementing)

1. **Order source:** are orders created by dashboard staff only (MVP assumption), or ingested from store/vendor partners via API/webhook?
2. **Customer model:** inline-only (name/phone/note) for MVP, or full `customers` entity + address book?
3. **Payment:** capture only fee + COD flag in MVP, or include wallet/instant-payout? Need the actual payout model (fee per order vs percentage, minimum balance, payout cadence).
4. **Package type:** parcels/goods only, or also food/multi-stop/passenger rides?
5. **Assignment:** manual only for MVP, or requirement for auto-assign by proximity/load?
6. **Realtime expectation:** is 30 s polling + FCM acceptable for v1, or must we include WebSockets (Reverb) from day one?
7. **Location:** which maps provider (Google/OSM) and what precision/interval; is background location required (OS permission implications)?
8. **Doc of POD:** photo only, or signature/PIN/NA-form as alternatives.
9. **Cancel policy:** who can cancel, at which statuses, which reasons, and any penalty.
10. **Earnings/ratings/attendance:** confirm they are post-MVP (this plan) or needed in v1.
11. **Device-token registration:** is unauthenticated registration acceptable (token by device_id) or must it always be authenticated?
12. **ISHAAR & Firebase credentials:** available from the client? If not, which OTP/push providers are preferred?
13. **Languages:** confirm en/ar only and exact default locale per app.
14. **Order IDs shown to users:** raw UUID/ULID or a human-friendly sequential order number (recommend adding `order_number`).

---

## 20. Recommended Next Step

1. **Confirm Section 19** against the Captain App spec (fastest as a shared doc review).
2. Upon confirmation, execute in Section 17 order: start with **T-01/T-02 (push plumbing)** and **T-03/T-04/T-05 (order schema + state machine, test-first)** — both additive and non-breaking.
3. Vertical slice **assign → accept → pickup → deliver (POD) → FCM**, then breadth (availability/location, earnings, admin ops).
4. Do **not** modify existing `Driver*`, `Admin*`, or auth modules; follow existing DTO/Service/Repository/Event/Envelope patterns so the new code matches by construction.

---

## Appendix A — Local Environment Notes (reproducibility)

- OS: Windows 10/11; PHP 8.4.25 at `C:\xampp\php` (old incompatible build at `C:\xampp\php-backup-82`).
- Apache + MySQL run from XAMPP. Keep `apache_start.bat` console window open; avoid `Stop-Process httpd` (destabilizes XAMPP control).
- Composer 2.10.3 hangs on Windows → use phar `composer-2.7.7.phar` for installs.
- App reachable at `http://localhost/kapitano_logistic` (no `/public`) via:
  - root `.htaccess` rewrite into `public/` with `RewriteBase /kapitano_logistic/`
  - `public/index.php` setting `SCRIPT_NAME`/`PHP_SELF` to `/kapitano_logistic/index.php` so Symfony strips the prefix
  - `routes/web.php` `/` route rendering the health-up view; `APP_URL=http://localhost/kapitano_logistic`
- Health: `GET /` and `GET /up` → 200. Old `/public/...` URLs 404 by design.
- DB: MySQL `kapitano_logistic`, user `root`, empty password; `utf8mb4` collation.
- Dev admin seed: `super-admin@kapitano-logiistic.com` / `123456` (development only — change in non-local).
- Known benign Apache log line: `Unable to load dynamic library 'curl' ... php_curl.dll (The specified procedure could not be found)`; does not block requests.

## Appendix B — Existing Routes Quick Reference

Full route tables with methods, middleware, and permissions are in **Section 3**. The two route registrars: `routes/api.php` (loads `driver.php`, `admin.php`, `permission.php`, `driver-management.php`) and `routes/web.php`.

## Appendix C ? Implemented Foundation (Captain MVP) - Completed

Status: implemented and green (164 tests / 703 assertions). No UI built yet, per scope.

### 1. Schema analysis conclusion
- The order domain was greenfield; everything else (auth, RBAC, onboarding, media, envelope) already existed and was reused. No duplicate Driver/Order/User/auth models were created.
- Status columns are MySQL `ENUM`s (they snapshot their case list), so the new enum states required real column widening. Everything else was additive tables only.

### 2. Required schema changes (additive only) - implemented
- `orders`: uuid, order_number (unique), customer name/phone, pickup/dropoff address + lat/lng, note, fee, currency, `order_status` (indexed, enum cast), `driver_id` FK to captains (`orders.driver_id` = captain/order relationship), `created_by` FK to admins, timestamps for each captain timestamp (`picked_up_at`, `on_the_way_at`, `delivered_at`, `failed_at`), `failed_reason`.
- `order_items`: line items under an order.
- `order_status_history`: one immutable row per lifecycle step (morph actor = captain or admin, no `updated_at`).
- `driver_availabilities`: one row per captain (`is_online` indexed, `last_seen_at`) for the availability gate.
- `driver_locations`: one row per captain (latest point only; lat/lng/accuracy/captured_at indexed).
- `2026_09_09_140006_backfill_delivery_failed_status`: data backfill mapping old `failed` rows to `delivery_failed` (backward compatible).
- `2026_09_09_140007_widen_status_enums`: widens `drivers.status` to the five captain states, and `orders.order_status` / `order_status_history.status` to the renamed `delivery_failed` lifecycle (widens to a superset, folds legacy rows, then narrows; no-op on SQLite).

### 3. States and enumerations
- Captain approval status (`DriverStatus`): `pending`, `documents_required` (added), `approved`, `rejected`, `suspended` (added). Only `approved` may sign in; `suspended` maps to a 403/disabled sign-in response, `documents_required` to locked-awaiting-documents.
- Employment type (`DriverEmploymentType`): `employee` / `freelance` (already existed).
- Vehicle type (`VehicleOwnership`): `company_owned` / `personal` (already existed; reused).
- Delivery status (`OrderStatus`): **extended the existing order-status column + history table** rather than creating a separate delivery state machine. Lifecycle: `pending -> assigned -> picked_up -> on_the_way -> delivered | delivery_failed` (`delivery_failed` always carries a reason). Single source of truth: the state machine in `app/Services/Order/OrderStateMachine.php`.

### 4. Documentation for captains and notifications
- Driver documents stay on the existing Spatie media collections (driving license, profile photo).
- Notification reference: `device_tokens` (per captain per device) with register/unregister API; FCM send library wired later (task T-01, awaits `FIREBASE_CREDENTIALS`).

### 5. API impact analysis
- **No breaking changes.** All additions are new additive endpoints, all under the existing `driver` guard and header envelope:
  - `POST /api/driver/device-token`, `DELETE /api/driver/device-token`
  - `POST /api/driver/availability`
  - `POST /api/driver/location`
  - Order endpoint slice (captain list/detail/pickup/on-the-way/deliver/fail and admin create/assign + push) is the next slice.
- Existing dashboard + mobile routes, permissions, and responses are untouched; full suite was green before and after.

### 6. Seed data
- `DriverSeeder`: two new captains covering the added states (`documents_required`, `suspended`).
- `OrderSeeder`: ORD-100001..100004 across assigned / on-the-way / delivered / pending, tied to approved active captains.
