# Order Status & Dispatch Flow

What actually happens end to end, on the mobile side, for one order. This is the flow any
restaurant-facing real-time view is a window into.

## Status state machine

```
pending ──accept──> preparing ──driver picks up──> on_the_way ──driver delivers──> delivered
   │                    │
   └──decline/cancel──> cancelled <──cancel (no driver found, restaurant timeout)──┘
```

- `pending` → `preparing` or `cancelled`
- `preparing` → `on_the_way` or `cancelled`
- `on_the_way` → `delivered` only
- `delivered` / `cancelled` are terminal

This table lives in code as `OrderStatusEnum::canTransitionTo()` — it's the single source of
truth, enforced by `OrderStatusService` on every transition.

`cancelled` orders carry a `cancel_reason` (`restaurant_declined`, `restaurant_timeout`,
`no_driver`) so "restaurant said no" is distinguishable from "system gave up" without a separate
status value.

## The flow, step by step

1. **Client places an order** (mobile only). Status starts at `pending`. Pricing is computed
   server-side and snapshotted onto the order row — it's never recalculated later.
2. **Restaurant accepts.** Transitions to `preparing`, stamps `accepted_at`, notifies the client,
   and **opens driver dispatch** — this happens as part of accepting, not a separate step.
3. **Dispatch opens (FCFS).** Every eligible driver (online, available, active, seen recently,
   within the restaurant's dispatch radius) gets offered the order at once. A broadcast announces
   the offer went out.
4. **A driver claims it** (mobile only). First to hit this wins — the order's `driver_id` gets set.
   Two broadcasts fire: one because the order itself changed (driver info now visible to the
   client), one specifically for the dispatch board so the offer can be pulled off every other
   driver's screen the instant it's claimed.
5. **Manual assignment (alternative to step 4).** The restaurant can pick a driver directly from
   the same eligible-drivers list instead of waiting on FCFS. That's still just an *offer* — the
   driver still has to claim it (or reject it) the same as an FCFS offer.
6. **No driver claims in time.** A scheduled sweep flags the order as needing manual assignment.
   FCFS dispatch stays open regardless — this is additive, not a replacement.
7. **Driver picks up** (mobile only). `preparing` → `on_the_way`.
8. **Live tracking while en route.** Every driver location ping broadcasts the driver's position on
   the order's channel.
9. **Driver delivers** (mobile only). `on_the_way` → `delivered`. Prep/delivery time gets computed,
   and the restaurant→admin commission transfer + driver earning record happen at this point.

Full method-level detail for each step is in
[03-services-orders-and-dispatch.md](03-services-orders-and-dispatch.md); the channels/events fired
at each step are in [02-broadcast-reference.md](02-broadcast-reference.md).
