# Orders-Note — Requirements

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

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

## Overview

`orders-note` is the single write endpoint that updates the free-text annotation (`orders.notes`) of an existing order (`PUT /orders/{order}/note`, route name `orders.update_note`, `OrderController::updateNote`). It is the only surface that edits an order **without** going through the full order totalling/finalisation path — it touches nothing but the `notes` column. 🟢 (`routes/web.php:74`, `OrderController.php:342-356`)

The note is edited inline from two back-office reader screens: the customer "quick statistics" page (`customers-statistics`) and the customer purchase-history page (`customers-purchase-history`), each of which renders a small per-order `PUT` form pointed at this route with the order id substituted for the `#id#` placeholder. 🟢 (`customer-statis.blade.php:183`, `customer-orders.blade.php:121`)

The route is declared **before** `resource('/orders')`, so `/orders/{order}/note` is not captured by the resource `update` route (which is `PUT /orders/{order}` — a different path), and it carries the explicit name `orders.update_note`. 🟢 (`routes/web.php:74-75`)

## Responsibilities

- Validate the incoming payload: `notes` is `nullable|string`. 🟢 (`:344-346`)
- Load the target order by id with `Order::findOrFail($id)` (404 on unknown id). 🟢 (`:348`)
- Fill and persist **only** the validated `notes` value onto the order. 🟢 (`:349-351`)
- On a successful save, flash an `admin_toastr` success message (`Cập nhật thành công`, "Updated successfully") and redirect back. 🟢 (`:351-353`)
- On a failed save, redirect back with the old input and an error message (`Cập nhật thất bại`, "Update failed"). 🟢 (`:355`)

## Business Rules

- **Notes-only mutation.** `updateNote` writes exactly one column (`notes`); it never recomputes totals, points, debt, pivots, or status. Because `Order::$fillable = ['notes','debt_amount']`, the `fill($valided)` with only `notes` present cannot touch any other column. 🟢 (`:349`; `Order.php:11`)
- **Note is optional.** `notes` is `nullable|string`, so submitting an empty value clears the annotation to `null`/empty. 🟢 (`:345`)
- **No status guard.** There is no `is_editable` / `draft` / `done` / 24h check (unlike `orders-crud` `update`); a note can be edited on any order regardless of status or age. **Confirmed intentional** (2026-09-25, `questions.md#question-14`) — notes are free metadata, not financial state. 🟢 (`:342-356`)
- **Unknown id is a hard 404.** `findOrFail` raises `ModelNotFoundException` for a missing order. 🟢 (`:348`)
- **Redirect-back contract.** Both success and failure return a redirect back to the referring page (the statistics or purchase-history screen), never JSON. 🟢 (`:353,355`)
- **`notes` is the annotation that makes an order appear in the statistics "annotated orders" panel.** `customers-statistics` lists only orders with `whereNotNull('notes')`; adding a note here is what surfaces an order there. 🟢 (`customers-statistics` unit; `CustomerController::statis`)

## Functional Requirements

| ID | Requirement | Priority | Acceptance criterion |
|----|-------------|----------|----------------------|
| RF-01 | Update an order's `notes` at `PUT /orders/{id}/note` | Must | For a valid order id, the order's `notes` column is set to the submitted value and the response redirects back with a success toastr. 🟢 |
| RF-02 | Accept an empty/absent note (clears it) | Should | Submitting no `notes` value persists `null`/empty without a validation error. 🟢 |
| RF-03 | Reject a non-string note | Should | A non-string `notes` value fails validation (`nullable|string`) and redirects back with errors. 🟢 |
| RF-04 | Return `404` for an unknown order id | Must | `PUT /orders/{unknown}/note` raises `ModelNotFoundException` → `404`. 🟢 |
| RF-05 | Surface a save failure to the user | Should | If `save()` returns false, the response redirects back with old input and the `Cập nhật thất bại` error. 🟢 |
| RF-06 | Require an authenticated admin session | Must | Anonymous request → `302` to `auth/login`. 🟢 (`routes/web.php:24-28`) |

## Non-Functional Requirements

| Type | Inferred requirement | Evidence in code | Confidence |
|------|----------------------|------------------|------------|
| Security | Admin authentication required (admin route group middleware `['web','admin']`) | `routes/web.php:24-28,74` | 🟢 |
| Security | CSRF token required (Laravel `web` middleware; the view uses `Form::open` which emits the token + `_method=PUT`) | `customer-statis.blade.php:183`, `customer-orders.blade.php:121` | 🟢 |
| Security | Mass-assignment is bounded by `Order::$fillable`, so `fill()` cannot write columns other than `notes`/`debt_amount` | `app/Models/Order.php:11` | 🟢 |
| Observability | None — no log/metric/trace on the note-write path | `OrderController.php:342-356` (absence) | 🔴 |

> Inferred from code. Validate with the operations team.

## Acceptance Criteria

```gherkin
Given an authenticated administrator and an existing order
When he submits PUT /orders/{id}/note with notes="Cliente pediu embalagem separada"
Then the orders.notes field is updated, a success toastr "Cập nhật thành công" is shown, and the response redirects back to the previous page

Given an existing order with a filled note
When PUT /orders/{id}/note is called with notes empty
Then the note is cleared (null/empty) with no validation error

Given a notes value that is not a string (e.g. an array)
When PUT /orders/{id}/note is called
Then the nullable|string validation fails and the response redirects back with errors

Given a non-existent order id
When PUT /orders/{id}/note is called
Then it receives HTTP 404 (findOrFail)

Given a request with no authenticated admin session
When PUT /orders/{id}/note is called
Then it receives HTTP 302 redirecting to auth/login
```

## Priority (MoSCoW)

| Requirement | MoSCoW | Justification |
|-------------|--------|---------------|
| Update `notes` (RF-01) | Must | The endpoint's whole purpose |
| 404 on unknown id (RF-04) | Must | `findOrFail` is the load contract |
| Admin authentication (RF-06) | Must | Enforced by the route group |
| Clear an empty note (RF-02) | Should | Natural consequence of `nullable`; expected editing behaviour |
| Reject non-string (RF-03) | Should | Input hardening via the one validation rule present |
| Surface save failure (RF-05) | Could | Rare (`save()` rarely returns false without throwing) |

> Priority inferred from the endpoint's single-column, redirect-back annotation role, called from the two customer reader screens.

## Code Traceability

| File | Function / Class | Coverage |
|------|------------------|----------|
| `app/Http/Controllers/OrderController.php:342-356` | `OrderController::updateNote` | 🟢 |
| `routes/web.php:74` | `PUT /orders/{order}/note` (name `orders.update_note`, declared before the resource) | 🟢 |
| `app/Models/Order.php:11` | `Order::$fillable = ['notes','debt_amount']` (mass-assignment guard) | 🟢 |
| `resources/views/pages/customer-statis.blade.php:183` | Inline note form (statistics screen) | 🟢 |
| `resources/views/pages/customer-orders.blade.php:121` | Inline note form (purchase-history screen) | 🟢 |
