# Services: Orders & Dispatch

These six services are the entire order/dispatch/pricing core. Every action on an order goes
through one of them — nowhere else changes order state.

## `OrderService`
*(depends on `PricingService`)*

| Method | Does |
|---|---|
| `getDriverOrders(Driver, array $data = [])` | Paginated, optional `status` filter |
| `getOrder(Order)` | Loads restaurant, client, driver, products |
| `fetchRestaurantOrders(Restaurant, array $data)` | Paginated, optional `status` filter — the restaurant's order list |
| `clientOrders(Client, array $data = [])` | Paginated |
| `storeOrder(array $data, Client)` | Creates the order, re-prices everything server-side (never trusts client-sent prices), notifies the restaurant owner + staff, broadcasts `OrderStatusUpdated` |
| `getCalculationFees()` | `{settings, peak_hours, demand_fee}` |

## `OrderStatusService`
*(depends on `NotificationService`, `DispatchService`, `TransactionService`)*

**The only place `orders.status` changes.** Every method locks the row, validates the transition
against `OrderStatusEnum::canTransitionTo()`, and broadcasts `OrderStatusUpdated`. An illegal
transition throws.

| Method | Does |
|---|---|
| `accept(Order)` | `pending` → `preparing`, notifies the client, opens driver dispatch |
| `decline(Order)` | `pending` → `cancelled` (`restaurant_declined`), notifies the client |
| `cancel(Order, OrderCancelReasonEnum)` | System-driven cancellation (timeout / no driver) |
| `markOnTheWay(Order)` | `preparing` → `on_the_way` |
| `markDelivered(Order)` | `on_the_way` → `delivered`, computes prep/delivery time, runs `TransactionService::processOrderDelivery()` |

## `DispatchService`

FCFS driver dispatch, plus a manual-offer path.

| Method | Does |
|---|---|
| `eligibleDrivers(Restaurant)` | Online, available, active, seen in the last 5 minutes, within the restaurant's dispatch radius |
| `open(Order)` | Offers the order to every eligible driver at once. Called automatically by `OrderStatusService::accept()` |
| `claim(Order, Driver)` | First driver to hit this wins — locked, rejects if already claimed |
| `offerToDriver(Order, Driver)` | Restaurant offers to one specific driver — still requires that driver to `claim()` it |
| `rejectOffer(Order, Driver)` | Driver declines a manual offer, order stays open for FCFS |
| `escalateToManual(Order)` | Flags `requires_manual_driver_assignment` — called by a scheduled timeout sweep |

## `DriverProximityService`

`evaluate(Driver)` — runs on every driver location ping (HTTP, not a socket). Broadcasts
`DriverLocationUpdated`, and fires a one-time `DriverPromptRequested` when the driver enters the
pickup/arrival radius.

## `PricingService`
*(depends on `GoogleMapsService`)*

`quote(Restaurant, array $items, float $lat, float $lng)` is the single source of truth for order
money — subtotal, distance, night/peak/demand multipliers, delivery fee, total. `priceItems()` and
`getMultipliers()` are its building blocks.

## `TransactionService`

`processOrderDelivery(Order)` — runs the restaurant→admin commission transfer and records the
driver's earning row. Called once, from `OrderStatusService::markDelivered()`.
