# User Stories — Order Management (Back-office)

> Produced by the Reversa **Writer** (phase: generation) · doc_level: `complete`
> Generated on 2026-09-21

**Confidence scale:** 🟢 CONFIRMED · 🟡 INFERRED · 🔴 GAP

**Actor:** Store Administrator.
**Owning units:** `orders-crud` (index/show/update/destroy), `orders-print`, `orders-note`, `orders-scan`.

Order creation itself happens at the terminal ([point-of-sale.md](point-of-sale.md)); this journey covers the back-office order list and the actions taken on an existing order. Orders have **no soft-delete** — deletion is permanent but reverses side effects first. The `create` and `edit` verbs are empty stubs. 🟢

---

### US-ORD-1 — Browse and search orders

**As a** Store Administrator, **I want** to list and search orders, **so that** I can find a specific sale.

- **Given** the order list
- **When** I open `GET /orders`
- **Then** orders load `with('customer')` ordered by `updated_at` desc, paginated 30/page 🟢
- **When** I enter a `q`
- **Then** it matches order id, order code (`#QT78-{id}`), or customer phone; an optional `status` filter narrows draft/done 🟢

Notes / gaps:
- The `q` filter is a grouped closure, so `status` stays ANDed with the id/code/phone match. ✅ Fixed 2026-09-21 (was ungrouped, letting an id/code match escape the `status` filter). 🟢 (`orders-crud`)

Traces to: `orders-crud/` (`index`)

---

### US-ORD-2 — View an order's detail

**As a** Store Administrator, **I want** to open a single order, **so that** I can inspect its lines and totals.

- **Given** a valid order id
- **When** I open `GET /orders/{id}`
- **Then** `findOrFail` loads it into `pages.orders-detail` (unknown id ⇒ 404) 🟢

Traces to: `orders-crud/` (`show`)

---

### US-ORD-3 — Edit / finalise within the 24h window

**As a** Store Administrator, **I want** to edit a recent order, **so that** I can correct a mistake shortly after the sale.

- **Given** an order that is `draft`, or `done` and within 24h of `updated_at`
- **When** I submit `PUT /orders/{id}`
- **Then** the totalling engine re-runs (points still award only once via `points_awarded_at`; debt stays frozen if `debt_locked`) 🟢
- **Given** a `done` order older than 24h
- **Then** the update is refused with "Không thể cập nhật đơn hàng hoàn thành quá 24h" 🟢
- **Given** a non-draft order
- **Then** the customer cannot be changed (immutable after draft) 🟢

Traces to: `orders-crud/` (`update`, `Order::is_editable`)

---

### US-ORD-4 — Annotate an order with a note

**As a** Store Administrator, **I want** to add/edit a free-text note on an order, **so that** it appears in the customer's annotated-orders panel and I can record context.

- **Given** any order
- **When** I submit the inline note form (`PUT /orders/{order}/note`, `notes` nullable|string)
- **Then** only `orders.notes` is written (bounded by `$fillable`), a toastr "Cập nhật thành công" shows, and I'm redirected back 🟢
- **And** the order now surfaces in the customer-statistics "annotated orders" panel (`whereNotNull('notes')`) 🟢

Notes / gaps:
- The note write has **no editability/status guard** — a note is editable indefinitely, unlike order edits. 🟡 (confirm intended; notes = free metadata)
- The failure branch is effectively unreachable (no halting save event). 🟡
- Any admin can edit any order's note (no per-record authorization, ADR-0009). 🟡

Traces to: `orders-note/` (`OrderController::updateNote`)

---

### US-ORD-5 — Re-print an order's receipt

**As a** Store Administrator, **I want** to re-print a past order, **so that** I can hand a shopper a duplicate receipt.

- **Given** an order id
- **When** I open `GET /orders/{order}/print` (e.g. via the `?ref=orders` link on the list)
- **Then** the 80mm receipt renders 🟢

Notes / gaps:
- Unknown id → clean `404` (`Order::findOrFail`). ✅ Fixed 2026-09-21 (was `Order::find`, a fatal 500). 🟢 (`orders-print`)

Traces to: `orders-print/` (`OrderController::printOrder`)

---

### US-ORD-6 — Delete an order and reverse its effects

**As a** Store Administrator, **I want** to delete an order, **so that** an erroneous or cancelled sale is removed and its financial effects undone.

- **Given** an order (possibly with points and/or `pos_debt`)
- **When** I request `DELETE /orders/{id}`
- **Then** in a single transaction the earned points are reversed (clamped ≥ 0), any `pos_debt` is voided (`debt_void`, clamped by current balance), and the order is **hard-deleted**; `order_product` cascades and `customer_debts.order_id` is set null 🟢
- **And** the response is `{ status, message }` JSON; an unknown id returns `{ status:false }` (uses `find`, not `findOrFail`) rather than 404 🟢

Notes / gaps:
- The retained `debt_void` trail after a hard delete is confirmed as designed (GAP-O1, team-confirmed). 🟢
- Concurrency and reversal are correct here (transactional, `lockForUpdate`). 🟢 (not a gap)

Traces to: `orders-crud/` (`destroy`), `customers-debt-actions/` (`CustomerDebt::voidForOrder`), `customers-loyalty/` (`reversePointsForOrder`)
