# PRP-004 — Orders engine

- **Status:** Done
- **Plan:** Lite
- **Depends on:** PRP-001
- **Estimated effort:** ~1 day
- **Pro KB ref:** §9 (Orders), §10 (Statuses), §11 (Types)

> **Audience:** junior dev. The order API (create with manual fee, status choke point, list, summary,
> usage, transitions) is already implemented (scaffold + PRP-001). This PRP **hardens** it and builds
> the **order detail view** and the **locked Pro toolbar** (bulk, import, assign driver, order types,
> returns). No DB changes.

---

## 1. Goal / Why
A small operator runs their whole day on the Orders screen. It must be fast, guided (only legal status
moves), and clearly show what Pro unlocks. The status choke point stays the single seam where Pro
effects (SMS, invoicing, stock) will attach later.

## 2. Scope
**In:** order list (search + status filter + pagination), create (manual fee + COD), guided status
update via transitions, order detail with timeline, daily-quota indicator (from PRP-001), locked Pro
toolbar buttons.
**Out (Pro teaser):** zone auto-pricing, order types, returns, partial delivery, bulk ops, Excel
import/export, driver assignment, SMS.

## 3. Prerequisites
- PRP-001 merged (manual fee + cap + `/orders/usage/today`).
- Be able to log in as a manager and create orders in the UI.

## 4. Step-by-step implementation

### Step 1 — Verify the backend contract (read, don't rewrite)
Open **`server/src/modules/orders/orders.service.js`** and confirm these functions exist and behave:
- `create(companyId, userId, data)` — uses `data.fees` (manual), enforces daily cap, writes a
  `created` audit action.
- `updateStatus(companyId, userId, role, id, nextStatus, description)` — **the choke point**:
  blocks changing a terminal `collected` order; enforces `STATUS_TRANSITIONS`; ROOT may override;
  writes an `order_actions` row.
- `list`, `getOne`, `summary`, `allowedTransitions`, `getTodayUsage`.

If all present, **no backend change** is needed. (If you must add a field later, do it via a new Knex
migration — never hand-edit the DB.)

### Step 2 — Order detail page
Create **`client/src/pages/OrderDetail.tsx`** that loads `GET /orders/:id` (`{ order, actions }`) and
shows: reference, receiver block, city, COD, fee, status badge, and the **timeline** (actions newest
-first). Add a guided **status updater**:
```tsx
// fetch allowed transitions, render only legal buttons
const tr = await api(`/orders/${id}/transitions`); // { current, allowed }
// for each `allowed` status, render a button that POSTs /orders/:id/status
```
Wire a route in `App.tsx`: `<Route path="/orders/:id" element={<Protected><OrderDetail /></Protected>} />`
and make each row's "Update" in `Orders.tsx` link to the detail page (replace the `prompt()` hack with
proper buttons on the detail page; keep list lightweight).

### Step 3 — Locked Pro toolbar on the Orders list
In **`client/src/pages/Orders.tsx`**, add a toolbar row above the table with disabled Pro buttons
(consistent with PRP-002 style; PRP-010 will refactor into a `<ProFeature>` component):
```tsx
<div style={{ display: 'flex', gap: 8, marginBottom: 12 }}>
  <button disabled title="Pro feature">Bulk update <span className="lock">PRO</span></button>
  <button disabled title="Pro feature">Import Excel <span className="lock">PRO</span></button>
  <button disabled title="Pro feature">Assign driver <span className="lock">PRO</span></button>
  <button disabled title="Pro feature">Order type <span className="lock">PRO</span></button>
  <button disabled title="Pro feature">Create return <span className="lock">PRO</span></button>
</div>
```
Each should link to the upgrade URL on click later (PRP-010); for now `disabled` + tooltip is fine.

### Step 4 — Search + status filter UI
Ensure the list has:
- a search box bound to `?q=` (debounced or on Enter),
- a status `<select>` bound to `?status=` (options from a small constant list mirroring server statuses),
- pagination using `meta.total` (Prev/Next with `limit`/`offset`).
Re-fetch `GET /orders?status=&q=&limit=&offset=` on change.

### Step 5 — Quota + fee note (already from PRP-001 — verify present)
Confirm the "X / 30 today" badge, the cap-reached upgrade banner, and the "zone-based pricing is Pro"
note under the Fee field are all present. If a previous dev removed them, re-add per PRP-001 §6.

### Step 6 — Build & manual test
```bash
npm run build:client
npm run dev:server && npm run dev:client   # in two terminals
```
Create an order, open its detail, advance status only via the allowed buttons, confirm the timeline
grows.

---

## 5. API endpoints & Postman documentation
**No new endpoints.** All already in the Postman **Orders** folder. Documented contract for the dev:

### POST `/api/orders` (auth, manager-scoped)
- **Body** (note `fees` is **required**, manual):
  ```json
  { "receiver_name": "Mona", "receiver_phone": "+201111", "receiver_address": "12 Tahrir St",
    "city": "Cairo", "fees": 75, "cod": 250, "notes": "leave at door" }
  ```
- **201** → the created order. **422** if `fees` missing/negative. **429** `DAILY_CAP` when over the
  Lite limit:
  ```json
  { "success": false, "code": "DAILY_CAP", "message": "Daily order limit reached (30/day...)",
    "meta": { "cap": 30, "used": 30, "resets_at": "2026-07-01T00:00:00.000Z" } }
  ```

### GET `/api/orders?status=&q=&limit=&offset=` (auth)
- **200** → `{ success, data: [orders], meta: { total } }`. `q` matches reference/name/phone/city.

### GET `/api/orders/summary` (auth)
- **200** → `{ total, active, closed, byStatus: { processing: n, ... } }`.

### GET `/api/orders/usage/today` (auth)
- **200** → `{ used, cap, remaining, resets_at, plan }` (`cap`/`remaining` null for Pro).

### GET `/api/orders/:id` (auth)
- **200** → `{ order, actions: [ { name, description, created_at } ] }`. **404** if not in company.

### GET `/api/orders/:id/transitions` (auth)
- **200** → `{ current: "processing", allowed: ["picked_up","canceled"] }`.

### POST `/api/orders/:id/status` (auth)
- **Body** `{ "status": "picked_up", "description": "optional" }`.
- **200** → updated order. **422** illegal transition (manager). **409** if already `collected`.

> Postman already contains all of these. No collection change required for this PRP.

---

## 6. Manual test / acceptance verification
```bash
B=http://localhost:3000/api
T=$(curl -s -X POST $B/auth/login -H "Content-Type: application/json" -d '{"email":"<manager>","password":"<pw>"}' | node -pe "JSON.parse(require('fs').readFileSync(0)).data.token")
# create
OID=$(curl -s -X POST $B/orders -H "Authorization: Bearer $T" -H "Content-Type: application/json" -d '{"receiver_name":"A","receiver_phone":"+201","receiver_address":"x","city":"Cairo","fees":40,"cod":100}' | node -pe "JSON.parse(require('fs').readFileSync(0)).data.id")
# legal path
for s in picked_up on_route delivered collected; do
  curl -s -X POST $B/orders/$OID/status -H "Authorization: Bearer $T" -H "Content-Type: application/json" -d "{\"status\":\"$s\"}" >/dev/null
done
# illegal after collected → expect 409
curl -s -X POST $B/orders/$OID/status -H "Authorization: Bearer $T" -H "Content-Type: application/json" -d '{"status":"on_route"}'
```

**Acceptance criteria**
- [x] Order stores entered fee/COD verbatim; reference unique per company.
- [x] Illegal transition → 422 (manager); already-collected change → 409.
- [x] Search + status filter return correctly scoped results with `meta.total`.
- [x] Order detail shows timeline; status updates only offer legal next states.
- [x] Locked Pro toolbar visible (bulk/import/assign/type/return).

---

## 7. Git commits (follow in order)
**Commit 1 — order detail page + route**
```bash
git add client/src/pages/OrderDetail.tsx client/src/App.tsx client/src/pages/Orders.tsx
git commit -m "feat(web): PRP-004 order detail page with guided status timeline

Adds /orders/:id detail view (receiver, money, status, audit timeline) and
legal-only status update buttons via the transitions endpoint; list rows link
to detail.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>"
```

**Commit 2 — list search/filter/pagination + locked Pro toolbar**
```bash
git add client/src/pages/Orders.tsx
git commit -m "feat(web): PRP-004 orders search/filter/pagination + locked Pro toolbar

Adds status filter, search (?q), pagination via meta.total, and disabled Pro
buttons (bulk, import, assign driver, order type, return).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>"
```

**Commit 3 — PRP status**
```bash
git add PRPs/PRP-004-orders.md PRPs/README.md
git commit -m "docs(prp): mark PRP-004 done"
```

---

## 8. Done checklist
- [x] Backend contract verified (no rewrite).
- [x] Order detail + timeline + guided status done.
- [x] Search/filter/pagination + locked toolbar done.
- [x] `npm run build:client` passes; manual tests pass.
- [ ] Commits made; PRP marked Done.
