# Feature 06 — Full Order Details & the Captain's Orders

## What this feature does

It builds on Feature 04 (manual order assignment) so that three requirements hold end to
end:

| Requirement | How it is met |
| --- | --- |
| Once assigned, the captain immediately receives a new-order notification | `OrderService::assign()` fires `OrderAssigned`; the queued `NotifyCaptainOfAssignment` listener pushes `NewOrderAssignedNotification` (FCM, `data.type=order_assigned`, `data.order_uuid`). The captain app opens that UUID with `GET /api/driver/orders/{uuid}`. **Needs a running queue worker and Firebase credentials. See "Making the notification immediate".** |
| Orders are assigned manually by the operations team via the dashboard | `PATCH /api/dashboard/orders/{uuid}/assign` (`orders.assign` permission) is the only code path that assigns an order. There is no automatic dispatch. |
| Full order details are displayed: customer name, phone number, delivery address, product list, customer notes, payment method | On the dashboard (`GET /api/dashboard/orders/{uuid}`) **and** in the captain app (`GET /api/driver/orders/{uuid}`). |

## New order fields

| Column | Type | Notes |
| --- | --- | --- |
| `customer_note` | text, nullable | What the customer asked for. Shown to the captain. |
| `payment_method` | enum `cash_on_delivery` / `prepaid`, default `cash_on_delivery` | Older orders are read as cash on delivery. |
| `amount_to_collect` | decimal(10,2), nullable | Cash the captain collects. Always `null` for prepaid. |

The existing `note` column is now the **internal note** for the operations team. The
dashboard shows it; the captain app never does.

Migration: `2026_09_13_090000_add_payment_and_customer_note_to_orders_table`.

### Creating an order (dashboard): breaking change

`POST /api/dashboard/orders` now **requires `payment_method`**. For `cash_on_delivery` it
also requires `amount_to_collect`. For `prepaid`, any `amount_to_collect` sent is dropped.
A dashboard client that creates orders without these fields now gets **422**.

Code that calls `OrderService::create()` directly (seeders, tests, the future third-party
feed) defaults to cash on delivery.

## Captain app endpoints (under `/api/driver`, `auth:driver`)

| Method | Path | Purpose |
| --- | --- | --- |
| GET | `/api/driver/orders?rows&page[&status]` | My orders, newest first. Only orders assigned to this captain. |
| GET | `/api/driver/orders/{uuid}` | Full detail. This is what the push opens. |
| PATCH | `/api/driver/orders/{uuid}/picked-up` | Assigned → Picked up |
| PATCH | `/api/driver/orders/{uuid}/on-the-way` | Picked up → On the way |
| PATCH | `/api/driver/orders/{uuid}/delivered` | On the way → Delivered (optional `note`, saved on the timeline) |
| PATCH | `/api/driver/orders/{uuid}/failed` | On the way → Delivery failed (`reason` required) |

The detail (`CaptainOrderResource`) contains:
- order number, status, and `next_statuses` (the buttons to show right now)
- customer name, phone and customer note
- pickup and delivery address with coordinates
- the product list (name, quantity, unit price, item note)
- payment method and amount to collect, currency
- step timestamps and the failure reason

It does **not** contain the internal note or a captain block.

Rules, all enforced in `OrderService`, the same code the rest of the order lifecycle uses:
- Another captain's order gets **403** (`not_assigned_to_you`), on view and on every step.
- A step out of order gets **422** (`invalid_transition`), and nothing is written.
- `failed` without a reason gets **422**.
- Each step records its timestamp and adds a timeline row naming the captain.
- Once an order is delivered or failed, the captain is free for the next assignment.

## Making the notification immediate

The code sends the push the moment the assignment is saved, **but the listener runs on the
queue** (`QUEUE_CONNECTION=database`). A push reaches the phone only when all four of these
are true:

1. **A queue worker is running.**
   - Locally: `php artisan queue:work`.
   - On the server: keep `php artisan queue:work --tries=3` alive with Supervisor, or the
     host's equivalent.
   - Without a worker, notifications pile up in the `jobs` table.
2. **Firebase is configured.** `FIREBASE_CREDENTIALS` must point to the Firebase
   service-account JSON. It is empty in the current `.env`, so FCM cannot send.
3. **The captain registered their phone.** The app calls `POST /api/driver/device-token`
   after sign-in. A captain with no token is skipped, with no error.
4. **The app handles the push.** It reads `data.order_uuid` and calls
   `GET /api/driver/orders/{uuid}`.

> **Before starting a worker on an existing database, check `jobs`.** On the development
> machine it already held 23 old notifications (17 application decisions, 6 order
> assignments) queued since 2026-09-10. Starting a worker would try to send them all. Clear
> them first with `php artisan queue:clear` if they should not go out.

## Permissions

No new permissions. The dashboard keeps `orders.view` / `orders.create` / `orders.assign`.
The captain endpoints are protected by `auth:driver`, and ownership is checked per order.

## Tests

- `tests/Feature/Order/CaptainOrderApiTest.php`:
  - only own orders listed, and the status filter
  - full detail, with the internal note and captain block hidden
  - prepaid shows nothing to collect
  - another captain's order gets 403
  - full delivery through the three steps, with timestamps and timeline
  - a step out of order gets 422
  - `failed` needs a reason
  - unknown order gets 404
  - admin token and missing headers get 401
- `tests/Feature/Order/OrderAssignmentApiTest.php`:
  - create with payment and notes
  - prepaid drops the amount
  - payment method required, and amount required for cash
  - dashboard detail shows every required field
  - assign → push names the order → the captain opens it

## Not included

- Proof-of-delivery photo upload. The `proof_of_delivery` media collection exists, but no
  endpoint writes to it yet.
- Cancelling or re-assigning an order.
- Notifying the operations team when a captain completes or fails a delivery.
