# Customers Scan Specification

## Purpose

The `customers-scan` capability exposes a read-only JSON autocomplete endpoint (`GET /customers/scan`) that allows POS terminal clients to search for existing customers by phone number or name fragment and receive their full profile — including pricing tier — in a single response.

## Requirements

### Requirement: Authenticated Admin Session Required

The system SHALL reject any request that does not carry a valid authenticated admin session by redirecting to `auth/login` with HTTP `302` before the action executes. (Implemented in `routes/web.php:23-28,53`)

#### Scenario: Unauthenticated request is redirected

- **GIVEN** a client that is not authenticated as an admin
- **WHEN** the client sends `GET /customers/scan`
- **THEN** the system responds with HTTP `302` redirecting to `auth/login` and the action body does not execute

---

### Requirement: Substring Search Over Phone and Full Name

The system SHALL search for customers whose `phone` column OR `fullname` column contains the query token `q` as a substring (`LIKE %q%`) when `q` is provided in the query string. (Implemented in `app/Http/Controllers/CustomerController.php:285-287`)

#### Scenario: Match by phone substring

- **GIVEN** an authenticated admin session and at least one live customer whose `phone` contains the string `"0900"`
- **WHEN** the client sends `GET /customers/scan?q=0900`
- **THEN** the response includes that customer in the JSON array

#### Scenario: Match by fullname substring

- **GIVEN** an authenticated admin session and at least one live customer whose `fullname` contains the string `"Nguyen"`
- **WHEN** the client sends `GET /customers/scan?q=Nguyen`
- **THEN** the response includes that customer in the JSON array

#### Scenario: No match returns empty array

- **GIVEN** an authenticated admin session and no live customer whose `phone` or `fullname` contains the string `"ZZZNOMATCH"`
- **WHEN** the client sends `GET /customers/scan?q=ZZZNOMATCH`
- **THEN** the system responds HTTP `200` with body `[]`

---

### Requirement: Result Set Capped at Ten Records

The system SHALL return at most 10 customer records per response regardless of how many rows match the query. No pagination token or total count is included. (Implemented in `app/Http/Controllers/CustomerController.php:287`)

#### Scenario: More than ten matches are capped

- **GIVEN** an authenticated admin session and 11 or more live customers all matching the provided `q`
- **WHEN** the client sends `GET /customers/scan?q=<fragment>`
- **THEN** the response contains exactly 10 records

---

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

The system SHALL respond with HTTP `200` and a JSON array body for every request that reaches the action, including when the result set is empty. The system SHALL NOT respond with `204` or a non-array JSON value. (Implemented in `app/Http/Controllers/CustomerController.php:289-291`)

#### Scenario: Successful search returns 200 array

- **GIVEN** an authenticated admin session and at least one customer matching `q`
- **WHEN** the client sends `GET /customers/scan?q=<fragment>`
- **THEN** the system responds HTTP `200` with a JSON array containing the matching customer objects

#### Scenario: Empty search result still returns 200 array

- **GIVEN** an authenticated admin session and no customers matching `q`
- **WHEN** the client sends `GET /customers/scan?q=<nonexistent>`
- **THEN** the system responds HTTP `200` with body `[]` and not HTTP `204`

---

### Requirement: Full Customer Record Serialized Including Type Attributes

The system SHALL serialize each matching customer as a full `Customer` JSON object that includes the `type` field (defaulting to `khach_le` when not set) and the computed `type_label` field, so the POS client can bind the pricing tier without an additional request. (Implemented in `app/Http/Controllers/CustomerController.php:289-291`, `app/Models/Customer.php:15,25,49-55`)

#### Scenario: Result objects expose type and type_label

- **GIVEN** an authenticated admin session and a customer matching `q`
- **WHEN** the client sends `GET /customers/scan?q=<fragment>`
- **THEN** each object in the response array contains `type` (a non-null string) and `type_label` (the human-readable label for that type)

#### Scenario: Customer with no explicit type defaults to khach_le

- **GIVEN** an authenticated admin session and a customer with no `type` set, matching `q`
- **WHEN** the client sends `GET /customers/scan?q=<fragment>`
- **THEN** the matching customer object in the response has `"type": "khach_le"`

---

### Requirement: Soft-Deleted Customers Excluded From Results

The system SHALL exclude soft-deleted customers (those with a non-null `deleted_at`) from all search results regardless of whether the match would be on `phone` or `fullname`. (Implemented in `app/Models/Customer.php:9-12`, `app/Http/Controllers/CustomerController.php:287`)

#### Scenario: Soft-deleted customer absent when matched by phone

- **GIVEN** an authenticated admin session and a customer that is soft-deleted whose `phone` matches `q`
- **WHEN** the client sends `GET /customers/scan?q=<phone_fragment>`
- **THEN** that customer does not appear in the response array

#### Scenario: Soft-deleted customer absent when matched by fullname

- **GIVEN** an authenticated admin session and a customer that is soft-deleted whose `fullname` matches `q`
- **WHEN** the client sends `GET /customers/scan?q=<name_fragment>`
- **THEN** that customer does not appear in the response array

---

### Requirement: Missing or Empty q Returns First Ten Live Rows

The system SHALL treat a missing or empty `q` parameter as the pattern `LIKE '%%'`, which matches every live customer row, and return the first 10 live customers in default storage order. (Implemented in `app/Http/Controllers/CustomerController.php:285-287`)

#### Scenario: Request with no q parameter returns rows

- **GIVEN** an authenticated admin session and at least one live customer in the database
- **WHEN** the client sends `GET /customers/scan` with no `q` parameter
- **THEN** the system responds HTTP `200` with a JSON array of up to 10 live customer records

#### Scenario: Request with empty q returns rows

- **GIVEN** an authenticated admin session and at least one live customer in the database
- **WHEN** the client sends `GET /customers/scan?q=`
- **THEN** the system responds HTTP `200` with a JSON array of up to 10 live customer records
