# Order Tracking

Lets a buyer follow an order from placement to delivery.

```
Order Placed ──► Confirmed ──► Shipped ──► Out for Delivery ──► Delivered
```

## Setup

```bash
node src/scripts/create-order-tracking-stages.js
```

Adds `out_for_delivery` to the order lifecycle and backfills a first
"Order Placed" row for any order that has no timeline yet. The timeline itself
is read from `order_status_history`, which `create-order-tracking.js` already
created.

## Stages

Labels, descriptions and the transition rules live in one place —
[orderTracking.js](src/models/orderTracking.js) — so the app never hardcodes
them and a stage can be renamed without touching a controller.

| status (database) | label (buyer sees) |
|---|---|
| `pending` | Order Placed |
| `accepted` | Confirmed |
| `shipped` | Shipped |
| `out_for_delivery` | Out for Delivery |
| `delivered` | Delivered |
| `cancelled` | Cancelled — appended as a final step |
| `returned` | Returned — appended as a final step |

Every list endpoint (`my-orders`, vendor orders) now returns `status_label`
alongside `status`, so the wording matches the tracking screen everywhere.

---

## Track an order

`GET /api/vendors/store/orders/:order_id/track?user_id=12`

Readable by the buyer, the seller shipping it, or an admin — anyone else gets
`403`.

```json
{
  "success": true,
  "data": {
    "order_id": 104,
    "status": "out_for_delivery",
    "status_label": "Out for Delivery",
    "is_delivered": false,
    "is_cancelled": false,
    "progress": { "reached": 4, "total": 5 },
    "placed_at": "2026-08-27T10:12:00.000Z",
    "delivered_at": null,
    "expected_delivery_date": "2026-09-05",
    "courier": { "name": "Delhivery", "tracking_number": "DL999" },
    "stages": [
      { "key": "pending",          "label": "Order Placed",     "done": true,  "current": false,
        "at": "2026-08-27T10:12:00.000Z", "note": "Order placed" },
      { "key": "accepted",         "label": "Confirmed",        "done": true,  "current": false,
        "at": "2026-08-27T11:40:00.000Z", "note": null },
      { "key": "shipped",          "label": "Shipped",          "done": true,  "current": false,
        "at": "2026-08-28T09:05:00.000Z", "note": null },
      { "key": "out_for_delivery", "label": "Out for Delivery", "done": true,  "current": true,
        "at": "2026-08-29T07:30:00.000Z", "note": null },
      { "key": "delivered",        "label": "Delivered",        "done": false, "current": false,
        "at": null, "note": null }
    ],
    "order": {
      "id": 104, "product_name": "English Willow Bat", "quantity": 1,
      "total_amount": 5395.00, "payment_status": "paid",
      "shop_name": "Cric Store", "address": "…"
    }
  }
}
```

Draw the stage list straight from `stages` — `done` fills the line, `current`
marks the dot, `at` is the timestamp under each label, and `progress` drives a
bar. A `null` `at` on a done stage just means it was never recorded separately.

**Cancelled and returned orders** get a sixth step appended after whatever they
had reached, and the stages they never got to stay `done: false`:

```
Order Placed ✓  Confirmed ✓  Shipped ✗  Out for Delivery ✗  Delivered ✗  Cancelled ✓
```

## Move an order along

`POST /api/vendors/orders/:order_id/status`

```json
{ "status": "out_for_delivery", "user_id": 7, "note": "Rider assigned" }
```

**Forward only.** Skipping ahead is allowed — a seller who ships the same day
shouldn't have to click through four steps — but going backwards is refused:

- `delivered → shipped` → *"Cannot move an order back from Delivered to Shipped"*
- `delivered → cancelled` → *"…already delivered. Please raise a return request instead."*
- anything on a `cancelled` order → refused

This isn't only tidiness: reversing out of `delivered` would reopen the buyer's
return window and re-run the vendor's earning release, so the wallet would pay
out twice.

Each call writes an `order_status_history` row, which is what the timeline is
built from. `note` (or `reason`) is stored against that step and shown to the
buyer.

## Courier details

`PUT /api/vendors/orders/:order_id/shipment`

```json
{ "user_id": 7, "courier_name": "Delhivery", "tracking_number": "DL999",
  "expected_delivery_date": "2026-09-05" }
```

Seller or admin only. Deliberately separate from the status change, so a
tracking number can be added or corrected at any time without moving the order
along. Send any one of the three fields; the others are left as they are.

## Notes

- An order writes its first timeline row **when it is placed** (both
  `/store/buy` and `/cart/checkout`), so the journey is complete from step one.
- Orders that predate tracking still show "Order Placed" — the timeline falls
  back to the order's `created_at` when no row exists, so no order ever renders
  with an empty first step.
- Push notifications on each status change are **not** wired up. The app has the
  infrastructure (`NotificationModel.createNotification`), so it is a small
  follow-up if you want it.
