## Context

`LunchOrderController::ordersReport()` builds one query: active `lunch_orders` filtered by company/department/search/date, joined to a sub-query that counts menus per account, grouped by `account_id`, selecting `number_orders`, `total` and `total_amount` (`coalesce(sum(price), 0)`). With `limit` it returns `paginate()`, otherwise `get()`. The paginator has no `meta`/`links` envelope, so `APIResponseFormat` leaves the body unchanged.

`LunchItem` has `price` (decimal, nullable, cast to float) in `$fillable` and not in `$hidden`. `LunchMenuController::show()` loads `items` through a `belongsToMany` with no column restriction, and `LunchItemController::getList()` returns full models, so both endpoints already return `price`.

## Goals / Non-Goals

**Goals:**
- Return correct whole-result totals with the paginated report.
- Let the Statistics page sort employees by total amount, with stable paging.
- Lock the item `price` contract (null vs 0) for the two admin endpoints.

**Non-Goals:**
- Changing the non-paginated report shape (its default row order does change, see Decisions).
- Changing per-row fields of the report, or the export.
- Filling null prices with `default_item_price` or migrating legacy data.

## Decisions

**Summary only on the paginated response.** The paginated response is an object, so adding `summary` is additive. The plain-array response has nowhere to put it without becoming an object, which would break existing callers. The Statistics page uses pagination. Alternative (always return an object `{ data, summary }`) was rejected as breaking.

**Compute the summary from the grouped query, before paginating.** Clone the fully built, grouped query and wrap it as a derived table: `SELECT count(*) AS total_people, coalesce(sum(number_orders),0) AS total_orders, coalesce(sum(total_amount),0) AS total_amount FROM (<grouped query>) t`, with the clone's ORDER BY removed. This reuses every filter and the same join to the menu sub-query, so totals always match the rows. Alternatives: summing over the loaded page (wrong across pages), or a separate ungrouped query (risks drifting from the row filters, e.g. the inner join to the menu sub-query drops some orders).

**Response assembly.** Return `array_merge($paginator->toArray(), ['summary' => $summary])` so all existing pagination keys stay as they are. Cast `total_people`/`total_orders` to int and `total_amount` to float.

**Drop ORDER BY on the summary clone by hand.** The project runs Laravel 6.20, which has no `Query\Builder::reorder()` (added in 7.x). The clone used for the summary clears `orders` and the `order` bindings directly, which is what `reorder()` does internally.

**Sort by total amount, whitelisted.** `sort` accepts only `total_amount` and `order` only `asc`/`desc`, following the `sort`/`order` convention in `LunchItemController::getList()`; anything else falls back to `total_amount desc`. Ties are broken by `number_orders desc`, then `account_id asc`, so rows never repeat or disappear between pages. The sort is applied to the page query only, so `summary` does not depend on it. The default moves from `number_orders desc` to `total_amount desc` for both response shapes; the row fields stay the same.

**Item price: no code change.** Both endpoints already serialize `price` as stored; null stays null because the float cast keeps nulls. Work is limited to verification and OpenAPI docs.

## Risks / Trade-offs

- [One extra aggregate query per report request] → It runs on the same indexed filters as the page query; acceptable for an admin page.
- [Environments without the price migration have no `price` column] → Verify the migration `2026_08_27_000001_add_price_to_lunch_items_table` has run where the UI is tested.
- [Legacy items show `price: null`] → Intended; UI must render it as "no price" rather than 0.
