# Customers Statistics Specification

## Purpose

The `customers-statistics` capability exposes a read-only admin HTML page that presents a single customer's lifetime purchasing summary — headline totals, per-category consumption breakdowns, and a list of annotated orders. Headline totals and category data are served from a precomputed nightly read model; debt and reward-point balance are read live from the customer record.

## Requirements

### Requirement: Authenticated Admin Access

The system SHALL restrict `GET /customers/{customer}/statistic` to requests authenticated under the `['web','admin']` middleware group. An unauthenticated request SHALL be redirected with `302` to `auth/login` before the controller action runs. No per-record authorization is applied; any authenticated admin MAY access any customer's statistics page. (Implemented in `routes/web.php:57`, ADR-0009)

#### Scenario: Unauthenticated request redirected

- **GIVEN** a request with no valid admin session
- **WHEN** `GET /customers/{customer}/statistic` is called
- **THEN** the response is `302` with a `Location` header pointing to `auth/login`

#### Scenario: Authenticated admin accesses any customer

- **GIVEN** an authenticated admin session
- **WHEN** `GET /customers/{customer}/statistic` is called for a customer the admin does not own
- **THEN** the response is `200 OK` with the statistics page rendered

### Requirement: Customer Resolution

The system SHALL resolve the `{customer}` path parameter via a lookup that returns `404` when the id is unknown or soft-deleted. (Implemented in `app/Http/Controllers/CustomerController.php:223`)

#### Scenario: Known customer id returns page

- **GIVEN** an authenticated admin and a customer id that exists and is not soft-deleted
- **WHEN** `GET /customers/{id}/statistic` is called
- **THEN** the response is `200 OK`

#### Scenario: Unknown customer id returns 404

- **GIVEN** an authenticated admin and a customer id that does not exist or is soft-deleted
- **WHEN** `GET /customers/{id}/statistic` is called
- **THEN** the response is `404`

### Requirement: Precomputed Headline Totals

The system SHALL render three headline totals — lifetime orders count, lifetime amount spent, and lifetime points earned — sourced from the customer's row in the `customer_order_summary` table. When no summary row exists, each of these three fields SHALL default to `0`. (Implemented in `app/Http/Controllers/CustomerController.php:226-229`, ADR-0005)

#### Scenario: Summary row present — totals reflect summary

- **GIVEN** a customer with a row in `customer_order_summary` containing `orders_count`, `amount_total`, and `points_total`
- **WHEN** an authenticated admin loads the statistics page
- **THEN** the page displays those exact values for orders count, amount spent, and points earned

#### Scenario: No summary row — totals default to zero

- **GIVEN** a customer with no row in `customer_order_summary`
- **WHEN** an authenticated admin loads the statistics page
- **THEN** the page returns `200 OK` and displays `0` for orders count, amount spent, and points earned

### Requirement: Live Debt and Reward-Point Balance

The system SHALL read the customer's current debt balance and current spendable reward-point balance directly from the customer record on every request, independently of the precomputed summary. These two fields SHALL always reflect the current state of the customer, even when the summary data is stale. (Implemented in `app/Http/Controllers/CustomerController.php:228`, `resources/views/pages/customer-statis.blade.php:50-51`)

#### Scenario: Debt reflects live value after change

- **GIVEN** a customer whose `debt_total` changed after the last nightly summary rebuild
- **WHEN** an authenticated admin loads the statistics page
- **THEN** the debt balance displayed matches the current `customer.debt_total`, not the summary value

#### Scenario: Current points balance is live

- **GIVEN** a customer who redeemed points after the last summary rebuild
- **WHEN** an authenticated admin loads the statistics page
- **THEN** the "current points" cell reflects the post-redemption `customer.points` balance, while "points earned" (from summary) is unchanged

### Requirement: Category Breakdown Split

The system SHALL decode the `categories_statistic` field from the summary row and split it into three distinct views: milk (category id `1`) items displayed in a dedicated table, medicine (category id `8`) items displayed in a dedicated table, and all remaining tracked categories aggregated into a goods table sorted by category name. Each goods-table row SHALL contain the category name, the sum of `qty_total` across that category's items, and the sum of `amount_total` across that category's items. (Implemented in `app/Http/Controllers/CustomerController.php:233-251`, `app/Models/Order.php:23-25`, ADR-0006)

#### Scenario: Milk items appear only in the milk table

- **GIVEN** a summary row with category id `1` entries containing per-weight items
- **WHEN** the statistics page is rendered
- **THEN** those items appear in the milk breakdown table and not in the medicine or goods tables

#### Scenario: Medicine items appear only in the medicine table

- **GIVEN** a summary row with category id `8` entries containing per-weight items
- **WHEN** the statistics page is rendered
- **THEN** those items appear in the medicine breakdown table and not in the milk or goods tables

#### Scenario: Remaining categories aggregated and name-sorted in goods table

- **GIVEN** a summary row with two non-milk, non-medicine tracked categories named "Alpha" and "Zeta"
- **WHEN** the statistics page is rendered
- **THEN** the goods table shows one row per category with summed qty and amount, ordered "Alpha" before "Zeta"

### Requirement: Graceful Degradation on Missing or Malformed Categories

The system SHALL degrade gracefully when `categories_statistic` is absent or cannot be decoded. In either case the response SHALL be `200 OK`, the three category tables SHALL be empty, and the headline totals SHALL still be rendered correctly. (Implemented in `app/Http/Controllers/CustomerController.php:233-256`)

#### Scenario: Absent categories_statistic produces empty tables

- **GIVEN** a customer whose summary row has no `categories_statistic` value
- **WHEN** the statistics page is rendered
- **THEN** the milk, medicine, and goods tables are empty; the headline totals remain correct; the response is `200 OK`

#### Scenario: Corrupt categories_statistic produces empty tables without 500

- **GIVEN** a customer whose `categories_statistic` is malformed and cannot be decoded
- **WHEN** the statistics page is rendered
- **THEN** the milk, medicine, and goods tables are empty; the headline totals remain correct; the response is `200 OK` (not `500`)

### Requirement: Freshness Timestamp

The system SHALL display the timestamp of the most recent summary rebuild (`summary.updated_at`) when the summary row exists. When no summary row exists, the system SHALL render a "not yet updated" placeholder in place of a timestamp. (Implemented in `app/Http/Controllers/CustomerController.php:231`, `resources/views/pages/customer-statis.blade.php:107-111`)

#### Scenario: Summary present — timestamp shown

- **GIVEN** a customer with a summary row whose `updated_at` is set
- **WHEN** the statistics page is rendered
- **THEN** the page displays the formatted rebuild timestamp

#### Scenario: No summary — placeholder shown

- **GIVEN** a customer with no `customer_order_summary` row
- **WHEN** the statistics page is rendered
- **THEN** the page renders a "not yet updated" placeholder instead of a date

### Requirement: Annotated Orders Panel

The system SHALL display a paginated list of the customer's orders that have a non-null `notes` value, ordered by id descending (newest first), with a page size of 10. Orders without notes SHALL be excluded from this panel. (Implemented in `app/Http/Controllers/CustomerController.php:258`)

#### Scenario: Only annotated orders appear

- **GIVEN** a customer with three orders, two of which have non-null notes
- **WHEN** an authenticated admin loads the statistics page
- **THEN** the annotated orders panel shows exactly the two orders that have notes, in descending order by id

#### Scenario: Orders without notes are excluded

- **GIVEN** a customer whose orders all have null notes
- **WHEN** the statistics page is rendered
- **THEN** the annotated orders panel is empty

#### Scenario: Panel is paginated at 10 per page

- **GIVEN** a customer with 15 annotated orders
- **WHEN** `GET /customers/{id}/statistic` is called without a `page` parameter
- **THEN** the panel shows 10 orders and pagination controls are rendered; requesting `?page=2` returns the remaining 5 orders

### Requirement: Read-Only Operation

The system SHALL produce no side effects when handling `GET /customers/{customer}/statistic`. The request SHALL not trigger a summary rebuild, modify any record, or emit any writes to the `customer_order_summary` table. (Implemented in `app/Http/Controllers/CustomerController.php:215-261`)

#### Scenario: Repeated requests do not alter state

- **GIVEN** an authenticated admin loading the statistics page multiple times for the same customer
- **WHEN** each request completes
- **THEN** the `customer_order_summary` row and all customer records remain unchanged between requests
