---
feature: Order status updates and service completion
slug: order-execution
surface: provider
consumer: flutter
version: 1.4.0
status: ready
brd_version: "1.3"
brd_ids: [PROV-019, PROV-020, CUST-019, DRIV-015]
business_rules: [BR-03, BR-19, BR-22, BR-23, BR-28]
decisions: [DEC-08, TD-149, DEC-19, TD-16, TD-19, TD-92, TD-96, TD-100, TD-101, TD-102, TD-103, TD-104, TD-109, TD-111]
schema_tables: [orders, order_status_history, reviews, notifications]
screens: [provider--session--01-*, provider--session--05-*, provider--session--06-*]
related_docs: [orders.md, ../client/orders.md]
last_updated: 2026-11-09
---

# Order status updates and service completion — Flutter Integration Guide (provider)

> **Base URL:** `{HOST}/api/v1/provider` · [Shared API conventions](../API_CONVENTIONS.md).

## Version history

| Version | Date | Type | Summary | App action required |
|---------|------|------|---------|---------------------|
| 1.4.0 | 2026-11-09 | MINOR | New `POST /orders/{order}/location` (`provider.orders.location`): while she travels herself and the order is `on_the_way` she sends `{lat, lng}`; the customer's live view receives it (R08, TD-183). 422 `INVALID_STATE_TRANSITION` outside self travel on the way, 404 foreign order, 429 `RATE_LIMITED` (shares the `dispatch-availability` budget). | Send a position every few seconds while the app is in the foreground and she is travelling herself; stop at arrival. |

| 1.3.0 | 2026-11-01 | MINOR | `POST /orders/{order}/confirm-arrival` confirms the driver's arrival; `transport.trip` adds `arrival_confirmed_by` and `can_confirm_arrival`; starting the service confirms an unconfirmed arrival (TD-149). | Show "Confirm driver arrival" from `transport.trip.can_confirm_arrival`; no change to the status flow. |
| 1.2.0 | 2026-10-28 | MINOR | Driver trips now synchronize departure and arrival; provider arrival requires trip arrival (DEC-08, TD-139). | Handle driver-managed travel and the arrival boundary. |
| 1.1.0 | 2026-10-27 | MINOR | Purchased sessions follow execution flags; purchases are excluded from provider requests. | Continue driving actions from the returned flags. |
| 1.0.1 | 2026-10-26 | Docs | Explains the delay alert that now follows a missed update (PROV-019 delay bullet): the customer is told, nothing else changes. | None |
| 1.0.0 | 2026-10-26 | Initial | One endpoint `POST /orders/{order}/status` taking the target stage, `can_*` action flags on every order, 3 new typed errors. | Drive the order screen from the `can_*` flags and send the matching `status`. |

## Changelog

### 1.4.0 — 2026-11-09

- **Added:** `POST /orders/{order}/location` (`provider.orders.location`) so a provider travelling herself shares her position with the customer's live view (R08, TD-183).
- **App action required:** send `{lat, lng}` every few seconds in the foreground while she travels herself and the order is `on_the_way`; stop at arrival.

### 1.3.0 — 2026-11-01

- **Added:** `POST /orders/{order}/confirm-arrival` (`provider.orders.confirm-arrival`, DRIV-015, TD-149 — resolves DEC-08). On a Joshen-driver order whose trip is `arrived`, she confirms the driver brought her; the trip becomes `completed` and the driver's amount due is recorded. The driver or the customer may confirm first; then her call is a 200 no-op.
- **Added:** `transport.trip.arrival_confirmed_by` (`null` or `{value: driver|provider|customer, label}`) and `transport.trip.can_confirm_arrival` (bool) on the order details.
- **Changed:** `POST /orders/{order}/status` with `in_progress` on a driver order whose trip is still unconfirmed confirms the arrival as `provider` (starting the service proves the driver delivered her). The response is otherwise unchanged.
- **Breaking:** No. **App action required:** optionally show "Confirm driver arrival" while `transport.trip.can_confirm_arrival` is true; refetch on `trip.updated` with `reason: arrival_confirmed`.
- **BRD / decisions:** DRIV-015, DEC-08 → TD-149.

### 1.2.0 — 2026-10-28

- **Changed:** Driver trips now synchronize departure and arrival; provider arrival requires trip arrival (DEC-08, TD-139).
- **App action required:** Handle driver-managed travel and the arrival boundary.
- **BRD / decisions:** Cycle F, TD-132–140; DEC-08 remains open.

### 1.1.0 — 2026-10-27

- **Added / changed:** Purchased sessions follow execution flags; purchases are excluded from provider requests.
- **Fixed / removed:** None.
- **Breaking:** No.
- **Endpoints affected:** Existing order or package reads and actions described below.
- **App action required:** Continue driving actions from the returned flags.
- **BRD / decisions:** D1, TD-123–128.


### 1.0.1 — 2026-10-26
- **Added / Changed / Fixed / Removed:** None. **Breaking:** No. **Endpoints affected:** none.
- **Documented:** if she does not update the status in time, the **customer** gets a delay alert (BR-21, TD-109…111): the clock is 15 minutes (awaiting client confirmation) and starts at confirmation (instant) or 60 minutes before the service time (scheduled); an alert fires once per stage. It changes nothing for her: the order stays hers, no reassignment, and her flags and endpoints behave as before. She is not alerted herself (out of scope). Each stage is expected as in [admin/orders](../admin/orders.md) §9.
- **App action required:** None.
- **BRD / decisions:** PROV-019 (delay bullet), CUST-019, BR-21.

### 1.0.0 — 2026-10-26
- **Added:** `POST /orders/{order}/status` with body `{"status": "preparing|on_the_way|arrived|in_progress|completed"}`. It returns the full order details (same shape as `GET /orders/{order}`). The order resource carries `can_prepare`, `can_start_travel`, `can_mark_arrived`, `can_start_service`, `can_complete` and `can_rate` (see [provider/orders](orders.md) 2.1.0). New error codes `ORDER_STAGE_MANAGED_BY_DRIVER`, `ORDER_EXECUTION_TOO_EARLY`, `ORDER_NOT_EXECUTABLE`. The "completed" tab of `GET /appointments` now lists her completed orders.
- **Changed / Fixed / Removed:** None. **Breaking:** No.
- **Endpoints affected:** `POST /orders/{order}/status`; `GET /appointments?tab=completed`.
- **App action required:** send the matching `status` for each true flag; after any response (success or error) replace the order with the returned/refetched details; do not show "Rate" yet (see §11).
- **BRD / decisions:** PROV-019, PROV-020, CUST-019, BR-03/19/22/23/28, TD-101…104.

## 1. Business summary

Package sessions use the same execution flags as other visits; purchases are excluded from provider requests and have no visit. See [my packages](../client/my-packages.md).

After an order is confirmed, the provider moves it forward herself: optionally **preparing**, then **on the way**, **arrived**, **in progress** and finally **completed** (she confirms completion from the order details, PROV-020). The customer sees only the one resulting status (BR-22), live over the socket and in her inbox. Leaving for the customer ends the customer's free cancellation (BR-19), so a scheduled order may only be started inside a window before its time (TD-102). When a Joshen driver brings her to the customer, the driver trip records departure and arrival; she starts the service and completes it. Completion moves the order to her completed list and opens rating eligibility; **no earning is written yet** (Sprint 3).

## 2. User flow and screens

```
confirmed ─► (preparing) ─► on_the_way ─► arrived ─► in_progress ─► completed
              optional     "Heading out"  "Arrived"   "Start"        "Complete"
```

| Step | Screen / widget | Endpoint | Notes |
|------|-----------------|----------|-------|
| 1 | Order details (confirmed) | `POST /orders/{order}/status` with `preparing` (optional) or `on_the_way` | Buttons shown from `can_prepare` and `can_start_travel`. |
| 2 | Heading out | `POST /orders/{order}/status` with `arrived` | `can_mark_arrived`. |
| 3 | Arrived | `POST /orders/{order}/status` with `in_progress` | `can_start_service`. |
| 4 | In progress | `POST /orders/{order}/status` with `completed` | `can_complete`. |
| 5 | Completed tab | `GET /appointments?tab=completed` | Newest first; unpaginated lean tiles. |

## 3. Endpoints

### `POST /orders/{order}/status`

| | |
|---|---|
| Auth | **Approved provider** bearer token (BR-03). |
| Route name | `provider.orders.status` |
| Extra headers | None. |
| Rate limit | None. |

A foreign, unknown or integer order id is a 404; a non-approved account is a 403; no token is a 401. The response is the order **details** resource ([provider/orders](orders.md)): `status` is the new stage and the `can_*` flags are already recalculated.

**Path:** `order` (uuid, required) — one of her own orders.

**Request body**

| Field | Type | Required | Validation (mirror on the client) | Description |
|-------|------|----------|-----------------------------------|-------------|
| `status` | string | yes | one of `preparing`, `on_the_way`, `arrived`, `in_progress`, `completed` | The stage to move to. Send the one behind the `can_*` flag the provider tapped. |

```json
{ "status": "on_the_way" }
```

| `status` sent | Flag that allows it | From |
|---------------|---------------------|------|
| `preparing` | `can_prepare` | `confirmed` |
| `on_the_way` | `can_start_travel` | `confirmed`, `preparing` |
| `arrived` | `can_mark_arrived` | `on_the_way` |
| `in_progress` | `can_start_service` | `arrived` |
| `completed` | `can_complete` | `in_progress` |

Postman: `provider.orders.status` › "200 Order moved to preparing", "200 Order moved to on_the_way", "200 Order moved to arrived", "200 Order moved to in_progress", "200 Order moved to completed".

**Repeats are safe.** Sending the stage the order is already in returns `200` with the order unchanged (no second notification or timestamp). This is checked before every other rule, so a double tap or a retry after a lost response never errors. Postman: `provider.orders.status` › "200 Repeat of the current stage changes nothing".

**Stage timestamps** (`departed_at`, `arrived_at`, `started_at`, `completed_at`) are stamped by the server; the app does not send them.

**Success — `200`** (body `{"status": "on_the_way"}`; Postman: `provider.orders.status` › "200 Order moved to on_the_way"), abridged:

```json
{
  "status": "success",
  "message": "",
  "data": {
    "id": "01a17730-f339-73ad-aa68-b00101d1ae32",
    "code": "JSH-suzm00755931",
    "status": { "value": "on_the_way", "label": "On the way" },
    "booking_type": { "value": "instant", "label": "Instant" },
    "can_respond": false,
    "can_prepare": false,
    "can_start_travel": false,
    "can_mark_arrived": true,
    "can_start_service": false,
    "can_complete": false,
    "can_rate": false
  }
}
```

**Completion — `200`** (body `{"status": "completed"}`; Postman: `provider.orders.status` › "200 Service completion confirmed"): `status.value` is `completed`, every action flag is `false` and `can_rate` is `true`.

### Errors

Every refusal is a `422` with a stable `data.error_code`. Rules are checked in this order and the first failing one is returned.

| HTTP | `error_code` | When | What the app should do | Postman |
|------|--------------|------|------------------------|---------|
| 422 | — (validation) | `status` is missing or not one of the five stages (`confirmed`, `cancelled`, a typo…). The errors are under `data.errors.status`. | Fix the request; never show this to the provider. | `provider.orders.status` › "422 Status missing", "422 Status is not an execution stage" |
| 422 | `ORDER_NOT_EXECUTABLE` | The order is a package purchase: it has no visit to carry out. | Do not offer stage buttons for it (all flags are `false`). | `provider.orders.status` › "422 Package purchase order" |
| 422 | `INVALID_STATE_TRANSITION` | Wrong or skipped stage (arrival before departure, completion before the service started, anything before `confirmed` or after `completed`). | Refetch the order and show the buttons its flags allow. | `provider.orders.status` › "422 Arrival before departure", "422 Completion before the service started", "422 Preparing after departure", "422 Departure before the order is confirmed", "422 Start after completion" |
| 422 | `ORDER_STAGE_MANAGED_BY_DRIVER` | `status: on_the_way` on an order that uses a Joshen driver: the departure comes from the driver trip. | Hide "Heading out" (`can_start_travel` is `false`); show the status the driver trip sets. | `provider.orders.status` › "422 Departure of an order that uses a Joshen driver" |
| 422 | `ORDER_EXECUTION_TOO_EARLY` | A scheduled order is started (`preparing` or `on_the_way`) earlier than `execution_early_start_minutes` (default 120) before `scheduled_at`. | Show `message`; the flags are `false` until the window opens, so refetch near the time. | `provider.orders.status` › "422 Scheduled order started before its window" |
| 404 | — | Another provider's, unknown or integer order id. | Return to the list. | `provider.orders.status` › "404 Another provider order" |
| 403 | — | Provider account is not approved (BR-03). | Show review status only. | `provider.orders.status` › "403 Provider account not approved" |
| 401 | — | Missing or expired token. | Refresh the token or sign in. | shared per surface |

> The status code for `INVALID_STATE_TRANSITION` is `422`, the same as the booking endpoints (`POST /orders/{order}/accept`).

### `POST /orders/{order}/location`

| | |
|---|---|
| Auth | **Approved provider** bearer token (BR-03). |
| Route name | `provider.orders.location` |
| Body | `lat` (numeric, -90..90), `lng` (numeric, -180..180), both required. |
| Rate limit | `throttle:dispatch-availability` (the driver location budget, `dispatch_availability_per_minute`, default 60 a minute). |

Self travel only (R08, TD-183): her own order with `transport_mode = self` and status `on_the_way`. The position is stored on the order (latest only, no trail) and published to the customer's tracking room as `order.tracking.location`; the customer's `transport.tracking` then carries her pin, `freshness`, `distance_km` and `eta_minutes` ([client/orders §3.4](../client/orders.md)). Send updates while the app is in the foreground and she is travelling herself; stop at arrival. Success returns `{"recorded_at": "..."}`.

| HTTP | `error_code` | When | Postman |
|------|--------------|------|---------|
| 200 | — | Position recorded | "200 Position shared while travelling" |
| 404 | `ENTITY_NOT_FOUND` | Not her order | "404 Order not found" |
| 422 | `VALIDATION_FAILED` | Coordinates out of range | "422 Invalid coordinates" |
| 422 | `INVALID_STATE_TRANSITION` | Not self travel, or the order is not `on_the_way` | "422 Not travelling herself on the way" |
| 429 | `RATE_LIMITED` | Over the budget; honor `Retry-After` | "429 Too many location updates" |

### `POST /orders/{order}/confirm-arrival`

| | |
|---|---|
| Auth | **Approved provider** bearer token (BR-03). |
| Route name | `provider.orders.confirm-arrival` |
| Body | None. |
| Rate limit | `throttle:order-arrival`, per user (`ORDER_ARRIVAL_PER_MINUTE`, default 10). |

Her own order with a Joshen driver whose current trip is `arrived`. Returns the order details. The first confirmation by any party completes the trip; a later one returns the order unchanged. Postman: `provider.orders.confirm-arrival` › "200 Provider confirms the driver arrival" (abridged):

```json
{
  "status": "success",
  "message": "Arrival confirmed. The trip is complete.",
  "data": {
    "id": "01a197a9-678d-70fd-9548-22c2619cca4b",
    "code": "JSH-ppfp59903139",
    "status": {
      "value": "arrived",
      "label": "Arrived"
    },
    "can_start_service": true,
    "transport": {
      "mode": "joshen_driver",
      "trip": {
        "id": "01a197a9-6798-73bb-9c11-3e1968e0d900",
        "code": "TRP-gomy39653712",
        "status": {
          "value": "completed",
          "label": "Completed"
        },
        "search_expires_at": "2026-11-01T15:13:24+00:00",
        "delivery_fee": "25.00",
        "driver": {
          "first_name": "Kaela",
          "vehicle": {
            "type": "car",
            "make_model": "Toyota Yaris",
            "plate_number": "CAR-epkq52509495"
          }
        },
        "arrival_confirmed_by": {
          "value": "provider",
          "label": "Provider"
        },
        "can_confirm_arrival": false
      }
    }
  }
}
```

| HTTP | `error_code` | When | Postman |
|------|--------------|------|---------|
| 422 | `INVALID_STATE_TRANSITION` | No driver trip (she travels herself) or the driver has not reported arrival | "422 Provider order has no driver trip to confirm" |
| 404 | — | Another provider's, unknown or integer order id | "404 Another provider order is hidden" |
| 429 | `RATE_LIMITED` | More than `ORDER_ARRIVAL_PER_MINUTE` (default 10) calls a minute; honor `Retry-After` | "429 Too many arrival confirmations" |
| 403 / 401 | — | Not approved / no token | shared per surface |

### `GET /appointments?tab=completed`

Her `completed` orders only, newest `completed_at` first, in the lean tile (unpaginated, like the other tabs). Postman: `provider.appointments.index` › "200 Completed tab" and "200 Completed tab with no completed orders". The `today` and `upcoming` tabs keep a scheduled order through `preparing … in_progress`, so an order under way does not disappear from the schedule until it is completed.

## 4. Statuses and enums

| `status.value` | Meaning | Flag that is `true` |
|----------------|---------|---------------------|
| `confirmed` | Paid and accepted. | `can_prepare`, `can_start_travel` (a scheduled order only inside its window; a Joshen-driver order only `can_prepare`) |
| `preparing` | She is getting ready (optional). | `can_start_travel` (not on a Joshen-driver order) |
| `on_the_way` | Heading to the customer. | `can_mark_arrived` |
| `arrived` | At the address. | `can_start_service` |
| `in_progress` | Service under way. | `can_complete` |
| `completed` | She confirmed completion. | `can_rate` (until she has rated the customer) |

## Data model

| Table | Read / write | Notes |
|-------|--------------|-------|
| `orders` | write | `status` and `departed_at` / `arrived_at` / `started_at` / `completed_at`, under a row lock; `transport_mode` is read. |
| `order_status_history` | trigger write | One row per stage with `actor_type = provider`. |
| `reviews` | read | `can_rate` looks for a `provider_to_customer` review. |
| `notifications` | write | One `order_status_changed` inbox row for the customer per stage. |

## 5. Dart models

```dart
class ExecutionFlags {
  final bool canPrepare, canStartTravel, canMarkArrived, canStartService, canComplete, canRate;
  ExecutionFlags.fromJson(Map<String, dynamic> j)
      : canPrepare = j['can_prepare'] as bool,
        canStartTravel = j['can_start_travel'] as bool,
        canMarkArrived = j['can_mark_arrived'] as bool,
        canStartService = j['can_start_service'] as bool,
        canComplete = j['can_complete'] as bool,
        canRate = j['can_rate'] as bool;
}
```

The flags are always present booleans (also on list tiles). Never infer a button from `status` alone.

## 6. Business rules enforced by the backend

| Rule | Backend | App response |
|------|---------|--------------|
| BR-03 | Approved account and her own order only. | 403 / 404 as above. |
| BR-19 | Free cancellation ends when she leaves (`on_the_way`); a scheduled order cannot leave before its window (TD-102). | `ORDER_EXECUTION_TOO_EARLY`. |
| BR-22 | Every stage goes through the one shared action, so the customer sees one status whoever contributes it. | Nothing to do. |
| BR-23 | Trip completion (driver) and service completion (provider) are separate events; `complete` here is the service only. | Nothing to do. |
| BR-28 | `can_rate` is per direction (§11). | Do not show "Rate" yet. |
| PROV-019 | With a Joshen driver the departure derives from the driver trip; she keeps arrival, start and completion. | `ORDER_STAGE_MANAGED_BY_DRIVER` when `status` is `on_the_way`. |
| PROV-020 | Completion applies to self travel and to a driver. | — |

## 7. Notifications and real-time events

Each new stage sends the **customer** one inbox row (`order_status_changed`, `data.order_id` is the uuid) and one live `order.updated` event with `reason: status_changed` ([API conventions §9](../API_CONVENTIONS.md), TD-96, TD-100). Nothing is sent to the provider for her own action. A repeat sends nothing.

## 8. Localization and UI notes

`message` and the status `label` follow `Accept-Language` (Arabic and English are translated for all three new errors). Timestamps are ISO 8601 with offset.

## 9. Integration checklist

- [ ] Render the primary action from the `can_*` flags; at most one stage button is true at a time (plus `can_prepare` next to `can_start_travel`).
- [ ] Disable the button while the request is in flight; a repeat is harmless but pointless.
- [ ] After every response, success or `422`, replace the order with the response/refetch.
- [ ] Handle the four 422 `error_code`s above with their own messages.
- [ ] Keep completed orders in the schedule's completed tab (`GET /appointments?tab=completed`).
- [ ] Do not show a "Rate" action even when `can_rate` is `true` (§11).

## 10. Acceptance scenarios

| AC ID | Scenario | Expected | Test |
|-------|----------|----------|------|
| AC-PROV-019 | Update departure, arrival, start | Each stage returns the new status; history has one row per stage; customer gets one inbox row each. | `test_ac_prov_019_provider_walks_her_order_through_every_stage_and_the_customer_sees_each` |
| AC-PROV-019 | Preparing optional, repeats safe | `confirmed → on_the_way` directly; a repeat is `200` unchanged. | `…_preparing_is_optional`, `…_repeating_the_current_stage_is_a_no_op` |
| AC-PROV-019 | Wrong or skipped stage | `INVALID_STATE_TRANSITION`. | `…_a_skipped_reversed_or_unconfirmed_stage_is_invalid_state_transition` |
| AC-PROV-019 | Scheduled window | `ORDER_EXECUTION_TOO_EARLY` before the window; setting is read from `app_configs`. | `…_a_scheduled_order_cannot_leave_before_its_window_opens`, `…_the_window_is_read_from_app_configs` |
| AC-PROV-019 | Joshen driver | `ORDER_STAGE_MANAGED_BY_DRIVER` on departure only; arrival, start, completion allowed. | `…_a_joshen_driver_order_leaves_departure_to_the_driver_trip` |
| AC-PROV-019 | Bad `status` value | Missing or non-stage `status` is a validation 422. | `…_the_status_must_be_an_execution_stage` |
| AC-PROV-019 | Not executable / not hers | `ORDER_NOT_EXECUTABLE`; 404; 403; 401. | `…_a_package_purchase_has_no_visit_to_execute`, `…_only_her_own_order_and_only_when_approved` |
| AC-PROV-020 | Confirm completion | `completed`, customer notified, order in the completed tab, rating eligibility opens, works for a driver order. | `test_ac_prov_020_*` |

## 11. Open decisions and known limitations

- **TD-102 (awaiting client confirmation):** the scheduled start window is 120 minutes (`execution_early_start_minutes`, `app_configs`). On an existing database run `php artisan db:seed --class=AppConfigSeeder` to add the key.
- **DEC-08 resolved by TD-149 (2026-11-01):** driver trip arrival gates provider arrival; any one of driver, provider or customer then confirms it, and starting the service confirms implicitly. Driver payouts remain for the settlement feature (DEC-11).
- **Rating (`can_rate`):** it means the order is completed and she has not rated the customer yet (BR-28). The rating endpoints (CUST-021, PROV-024) are separate Phase 4 features, so **the app must not show the Rate action yet**. DEC-19's customer submission limits are still open; only the schema's one-review-per-direction rule applies.
- **Earnings:** completion writes no earning, commission or payout row (PROV-020 amounts due, Sprint 3).
- **Out of scope here:** cancellation (cycle B), delay alerts to the provider herself (the customer alert is built, see 1.0.1), chat (TD-16, `can_chat` stays `false`).
- **TD-103:** a suspended provider cannot advance an order that was already confirmed; handling of those orders is deferred with cancellation.
- **Instant orders in the schedule tabs:** `today` / `upcoming` only list orders that have a `scheduled_at`, so a confirmed *instant* order is reached from its realtime event / `GET /orders/{order}`, not from those tabs. This predates this feature and is not changed here.
