# Orders Scan Specification

## Purpose

The Orders Scan capability exposes a single read-only JSON endpoint (`GET /orders/scan`) that allows the POS terminal to list and search recent orders. Its primary use is to let an operator resume a saved draft by returning up to 10 fully-hydrated orders — including customer and product lines with pivot data — in a single response.

## Requirements

### Requirement: Admin Authentication Required

The system SHALL reject any unauthenticated request to `GET /orders/scan` with an HTTP `302` redirect to `auth/login` before the action executes. Only requests that pass the admin route group middleware (`['web','admin']`) SHALL reach the handler. (Implemented in `routes/web.php:24-28,72`)

#### Scenario: Unauthenticated request is redirected

- **GIVEN** a client that has no authenticated admin session
- **WHEN** the client sends `GET /orders/scan`
- **THEN** the system returns `302` redirecting to `auth/login` and does not return any order data

---

### Requirement: Result Set Capped at Ten, Ordered Newest First

The system SHALL return at most 10 orders per request, ordered by `id` descending, with no pagination cursor. (Implemented in `app/Http/Controllers/OrderController.php:430`)

#### Scenario: More than ten orders exist

- **GIVEN** an authenticated admin and 12 existing orders
- **WHEN** the client sends `GET /orders/scan` with no filters
- **THEN** the response body is a JSON array of exactly 10 orders, where the first element has the highest `id` and the last element has a lower `id`

---

### Requirement: Response Is Always a 200 JSON Array

The system SHALL respond with HTTP `200` and a JSON array for every successful request. When no orders match the applied filters, the response SHALL be an empty array (`[]`). The system SHALL NOT return `204`. (Implemented in `app/Http/Controllers/OrderController.php:432-436`)

#### Scenario: No orders match the filter

- **GIVEN** an authenticated admin and a `status` filter that matches no existing orders
- **WHEN** the client sends `GET /orders/scan?status=draft`
- **THEN** the system returns `200` with the JSON body `[]`

#### Scenario: Matching orders exist

- **GIVEN** an authenticated admin and at least one order
- **WHEN** the client sends `GET /orders/scan`
- **THEN** the system returns `200` with a non-empty JSON array of order objects

---

### Requirement: Full Order Payload Eager-Loaded

The system SHALL include, on each order object in the response array, the full `customer` relation (or `null` for walk-in orders) and a `products` array where each product carries a `pivot` object with `qty`, `price`, `unit_id`, and `conversion_qty`. (Implemented in `app/Http/Controllers/OrderController.php:430`, `app/Models/Order.php:39-41`)

#### Scenario: Order with a customer and product lines is returned

- **GIVEN** an authenticated admin and an order linked to a customer with two line items
- **WHEN** the client sends `GET /orders/scan`
- **THEN** the matching order object embeds a non-null `customer` object and a `products` array where each element has a `pivot` containing `qty`, `price`, `unit_id`, and `conversion_qty`

#### Scenario: Walk-in order (no customer) is returned without a filter

- **GIVEN** an authenticated admin and an order with no linked customer
- **WHEN** the client sends `GET /orders/scan` with no `q` parameter
- **THEN** the walk-in order appears in the array with `customer` equal to `null`

---

### Requirement: Appended Computed Attributes Included Per Order

The system SHALL include the appended attributes `code`, `is_editable`, and `debt_locked` on each order object in the response. The `code` value SHALL follow the pattern `#QT78-{id}`. (Implemented in `app/Models/Order.php:14,49-67`)

#### Scenario: Appended attributes appear on each returned order

- **GIVEN** an authenticated admin and an order with `id` 42
- **WHEN** the client sends `GET /orders/scan`
- **THEN** the order object contains `"code": "#QT78-42"`, a boolean `is_editable`, and a boolean `debt_locked`

---

### Requirement: Optional Customer Search via `q` Parameter

When the query parameter `q` is present and non-empty, the system SHALL restrict results to orders whose related customer has a `phone` or `fullname` that contains `q` (case-insensitive `LIKE %q%` match). Orders with no linked customer SHALL NOT appear in the result when `q` is supplied. The `%` and `_` characters in `q` act as SQL `LIKE` wildcards. (Implemented in `app/Http/Controllers/OrderController.php:422-426`)

#### Scenario: Search returns only orders matching the customer

- **GIVEN** an authenticated admin, an order linked to a customer with phone `"0901234"`, and a walk-in order with no customer
- **WHEN** the client sends `GET /orders/scan?q=0901`
- **THEN** the response array contains the order linked to the matching customer and does NOT contain the walk-in order

#### Scenario: No `q` parameter — customerless orders are eligible

- **GIVEN** an authenticated admin and a walk-in order with no customer
- **WHEN** the client sends `GET /orders/scan` without a `q` parameter
- **THEN** the walk-in order may appear in the response array

#### Scenario: `q` combined with `status` does not leak the OR

- **GIVEN** an authenticated admin, a draft order whose customer phone matches `q`, and a done order whose customer also matches `q`
- **WHEN** the client sends `GET /orders/scan?q=090&status=draft`
- **THEN** the response array contains only draft orders matching the customer search, and the done order does not appear

---

### Requirement: Optional Status Filter via `status` Parameter

When the query parameter `status` is present, the system SHALL restrict results to orders whose `status` column equals the supplied value exactly. The expected values are `draft` and `done`. When `status` is absent, the system SHALL apply no status restriction. (Implemented in `app/Http/Controllers/OrderController.php:427-429`)

#### Scenario: Draft filter returns only draft orders

- **GIVEN** an authenticated admin with both draft and done orders
- **WHEN** the client sends `GET /orders/scan?status=draft`
- **THEN** every order in the response array has `status` equal to `"draft"` and no done orders are included

#### Scenario: No status parameter — all statuses eligible

- **GIVEN** an authenticated admin with draft and done orders
- **WHEN** the client sends `GET /orders/scan` with no `status` parameter
- **THEN** the response array may contain orders of any status
