# Customers CRUD Specification

## Purpose

The Customers CRUD capability manages the customer master-data records that the POS terminal, pricing, orders, debt, and loyalty flows read. It exposes a paginated and searchable customer listing, create and edit forms, persistence with soft-delete, and a JSON quick-add path consumed by the POS terminal.

## Requirements

### Requirement: Customer Listing Pagination

The system SHALL return an HTML page containing a paginator of at most 30 customers per page, ordered by record identifier descending by default, when an authenticated admin requests `GET /customers`. (Implemented in `app/Http/Controllers/CustomerController.php:27-49`)

#### Scenario: First page returns newest customers

- **GIVEN** 45 live customers exist
- **WHEN** an authenticated admin requests `GET /customers`
- **THEN** the response is `200` HTML rendering `pages.customers` with 30 customers ordered by `id` descending

#### Scenario: Second page returns remaining customers

- **GIVEN** 45 live customers exist
- **WHEN** an authenticated admin requests `GET /customers?page=2`
- **THEN** the response is `200` HTML rendering `pages.customers` with the remaining 15 customers

### Requirement: Customer Search

The system SHALL filter the customer listing to rows where `phone` equals the query parameter `q` exactly OR `fullname` contains `q` as a substring, without allowing the match to escape any concurrently active filter. (Implemented in `app/Http/Controllers/CustomerController.php:38-42`)

#### Scenario: Search by exact phone returns matching customer

- **GIVEN** a customer exists with `phone` = `0912345678`
- **WHEN** an authenticated admin requests `GET /customers?q=0912345678`
- **THEN** that customer appears in the listing

#### Scenario: Search by name substring returns all matches

- **GIVEN** customers named "Anna" and "Hanna" exist
- **WHEN** an authenticated admin requests `GET /customers?q=anna`
- **THEN** both customers appear in the listing

#### Scenario: Search combined with month filter does not leak results across months

- **GIVEN** a customer with `phone` = `0900000001` whose `birthday2` is in month 1 exists, and no customer whose `birthday2` is in month 9 has that phone
- **WHEN** an authenticated admin requests `GET /customers?q=0900000001&month=9`
- **THEN** the listing is empty

### Requirement: Birthday Month Filter

The system SHALL, when the query parameter `month` (integer 1–12) is present, restrict the listing to customers whose `birthday2` falls in that month and order results by day-of-month ascending, replacing the default identifier-descending order. (Implemented in `app/Http/Controllers/CustomerController.php:41-45`)

#### Scenario: Month filter returns matching customers in day-of-month order

- **GIVEN** three customers with `birthday2` values `1990-09-05`, `1985-09-15`, and `1992-09-01` exist
- **WHEN** an authenticated admin requests `GET /customers?month=9`
- **THEN** all three customers are returned ordered by day: 1st, 5th, 15th

### Requirement: Create Form Display

The system SHALL serve an HTML creation form when an authenticated admin requests `GET /customers/create`, and the form SHALL NOT include a customer-tier selector. (Implemented in `app/Http/Controllers/CustomerController.php:56-65`)

#### Scenario: Create form renders without tier selector

- **GIVEN** an authenticated admin session
- **WHEN** the admin requests `GET /customers/create`
- **THEN** the response is `200` HTML rendering `pages.customer-add` with no `type` field

### Requirement: Customer Creation via Web Form

The system SHALL validate a new customer submission, persist the customer, and redirect to the customer listing with a success toastr on success; on validation failure it SHALL redirect back with `422` validation errors; on a falsy persistence result it SHALL redirect back with the error `'Thêm thất bại'`. (Implemented in `app/Http/Controllers/CustomerController.php:73-105`)

#### Scenario: Valid submission creates customer and redirects

- **GIVEN** an authenticated admin and a `phone` not used by any live customer
- **WHEN** the admin submits `POST /customers` with `fullname` and a valid `phone` using a non-JSON-expecting request
- **THEN** a customer row is created and the response is a `302` redirect to `customers.index` with a success toastr

#### Scenario: Duplicate live phone fails validation

- **GIVEN** a live customer with `phone` = `0912345678`
- **WHEN** an admin submits `POST /customers` with `phone` = `0912345678`
- **THEN** the response is `422` with a validation error on `phone` and no customer is created

#### Scenario: Invalid gender value fails validation

- **GIVEN** an authenticated admin
- **WHEN** the admin submits `POST /customers` with `gender` = `unknown`
- **THEN** the response is `422` with a validation error on `gender`

### Requirement: POS Quick-Add JSON Path

The system SHALL, when `POST /customers` is received with a JSON-expecting request and a valid payload, return the created customer as a `200` JSON response; if persistence fails it SHALL return `400` with a `null` body. (Implemented in `app/Http/Controllers/CustomerController.php:93-98`)

#### Scenario: Successful POS quick-add returns customer JSON

- **GIVEN** an authenticated admin session and a valid `phone` not in use
- **WHEN** a client submits `POST /customers` with `Accept: application/json`, `fullname`, and `phone`
- **THEN** the response is `200` with the created customer as JSON

#### Scenario: Failed persistence on quick-add returns 400 with null body

- **GIVEN** an authenticated admin session and a valid payload
- **WHEN** a JSON-expecting client submits `POST /customers` and persistence returns falsy
- **THEN** the response is `400` with a `null` body

### Requirement: New Customer Tier Default

The system SHALL NOT accept a `type` field on create; every newly created customer SHALL have tier `khach_le` regardless of any submitted `type` value. (Implemented in `app/Http/Controllers/CustomerController.php:76-84`, `app/Models/Customer.php:49-51`)

#### Scenario: Created customer defaults to retail tier

- **GIVEN** a valid create submission with no `type` field
- **WHEN** the customer is created
- **THEN** the customer's `type` is `khach_le`

#### Scenario: Submitted type is ignored on create

- **GIVEN** a valid create submission that includes `type` = `si_1`
- **WHEN** the customer is created
- **THEN** the customer's `type` is `khach_le`, not `si_1`

### Requirement: Birthday Format Parsing on Create

The system SHALL, when a valid `birthday` in `d/m/yyyy` format is submitted on create, store the parsed date in `birthday2` as `Y-m-d`; if parsing fails, the system SHALL null `birthday` and omit `birthday2`, still completing the creation. (Implemented in `app/Http/Controllers/CustomerController.php:86-90`)

#### Scenario: Valid birthday is parsed to birthday2

- **GIVEN** a valid create submission with `birthday` = `5/9/1990`
- **WHEN** the customer is created
- **THEN** the stored `birthday2` is `1990-09-05`

#### Scenario: Unparseable birthday does not block creation

- **GIVEN** a create submission where `birthday` passes the regex but cannot be parsed to a date
- **WHEN** the system creates the customer
- **THEN** the customer is created with `birthday` = `null` and no `birthday2` is stored

### Requirement: Live Phone Uniqueness

The system SHALL reject a `phone` value that is already used by a live customer on both create and update, and SHALL allow reuse of a `phone` that belongs only to soft-deleted customers. (Implemented in `app/Http/Controllers/CustomerController.php:78`, `app/Http/Controllers/CustomerController.php:150`)

#### Scenario: Live phone is rejected on create

- **GIVEN** a live customer with `phone` = `0912345678`
- **WHEN** a create submission uses `phone` = `0912345678`
- **THEN** validation fails on `phone` and no customer is created

#### Scenario: Soft-deleted phone can be reused on create

- **GIVEN** a soft-deleted customer with `phone` = `0912345678` and no live customer with that phone
- **WHEN** a create submission uses `phone` = `0912345678`
- **THEN** the new customer is created successfully

### Requirement: Edit Form Display

The system SHALL serve an HTML edit form containing the customer's current data and the available customer-tier options when an authenticated admin requests `GET /customers/{id}/edit`, and SHALL return `404` if the customer does not exist or is soft-deleted. (Implemented in `app/Http/Controllers/CustomerController.php:124-137`)

#### Scenario: Edit form renders with customer data and tier options

- **GIVEN** a live customer with identifier `42`
- **WHEN** an authenticated admin requests `GET /customers/42/edit`
- **THEN** the response is `200` HTML rendering `pages.customer-edit` with the customer's data and the `Customer::$types` tier list

#### Scenario: Edit form for missing customer returns 404

- **GIVEN** no customer with identifier `999` exists or it is soft-deleted
- **WHEN** an authenticated admin requests `GET /customers/999/edit`
- **THEN** the response is `404`

### Requirement: Customer Update

The system SHALL validate and persist changes to an existing customer including the `type` tier, redirect back with a success toastr on success, redirect back with input and errors on falsy persistence, and return `404` if the customer does not exist. (Implemented in `app/Http/Controllers/CustomerController.php:146-167`)

#### Scenario: Valid update persists tier change and redirects

- **GIVEN** a live customer with `type` = `khach_le`
- **WHEN** an authenticated admin submits `PUT /customers/{id}` with `type` = `si_1` and all required fields valid
- **THEN** the customer's `type` is updated to `si_1` and the response is a `302` redirect back with a success toastr

#### Scenario: Update of non-existent customer returns 404

- **GIVEN** no customer with the given identifier exists
- **WHEN** an authenticated admin submits `PUT /customers/{id}` with a valid payload
- **THEN** the response is `404`

#### Scenario: Invalid type value fails validation

- **GIVEN** a live customer
- **WHEN** an authenticated admin submits `PUT /customers/{id}` with `type` = `invalid_tier`
- **THEN** the response is `422` with a validation error and no changes are persisted

### Requirement: Customer Soft-Delete

The system SHALL soft-delete a customer and return a JSON object with a boolean `status` field and a translated `message` field when `DELETE /customers/{id}` is received; the deleted customer SHALL vanish from all default-scoped queries and its `phone` SHALL become reusable. (Implemented in `app/Http/Controllers/CustomerController.php:175-188`)

#### Scenario: Successful soft-delete returns JSON success status

- **GIVEN** a live customer with identifier `42`
- **WHEN** an authenticated admin sends `DELETE /customers/42`
- **THEN** the response is JSON `{"status": true, "message": "<admin.delete_succeeded>"}` and the customer row has `deleted_at` set

#### Scenario: Soft-deleted customer's phone is reusable

- **GIVEN** a soft-deleted customer with `phone` = `0912345678`
- **WHEN** a new customer is created with `phone` = `0912345678`
- **THEN** the creation succeeds

#### Scenario: Failed soft-delete returns JSON failure status

- **GIVEN** a live customer and a falsy result from the destroy operation
- **WHEN** an authenticated admin sends `DELETE /customers/{id}`
- **THEN** the response is JSON `{"status": false, "message": "<admin.delete_failed>"}`

### Requirement: Admin Authentication

The system SHALL redirect any unauthenticated request to any `/customers` route to `auth/login` with a `302` status. (Implemented in `routes/web.php:24-28`)

#### Scenario: Unauthenticated request is redirected to login

- **GIVEN** a client with no authenticated admin session
- **WHEN** any `/customers` route is requested
- **THEN** the response is `302` redirecting to `auth/login`
