## Context

See proposal.md - Why. Relevant current state (`packages/w3suga/lunch-ordering`):
- `LunchItem` (table `lunch_items`) has no price column.
- `LunchOrder` (table `lunch_orders`) links an `account_id` to an `item_id` on a `booking_menu_id`; it has no price column either.
- `LunchMenuController::store()`/`update()` accepts `custom_items` as a plain array of name strings and auto-creates a `LunchItem` for any name not already found (case-insensitive match on trimmed name).
- `LunchOrderController::ordersReport()` already aggregates, per employee, `number_orders` (count) and `total` (distinct menus in range) over a date range (default: current month) - it has no monetary total today.
- `LunchItemResource`/`LunchMenuResource` are empty pass-through resources; controllers return Eloquent models directly, so any new fillable/cast attribute is automatically exposed without resource changes.

## Goals / Non-Goals

**Goals:**
- Make price a first-class, optional attribute of a lunch item.
- Make the price recorded on an order immutable once set, except when the order's item is explicitly changed (reorder).
- Make the existing monthly order report money-aware without changing its existing count-based fields.

**Non-Goals:**
- Multi-currency or per-company pricing - a single price value per item is sufficient (matches current uniform pricing).
- Retroactively repricing orders when an item's price changes - explicitly out of scope per the spec (orders keep the price they were created with).
- Building any employee-facing "amount due" UI/export beyond what the report/export endpoints already return - this change only adds the data.
- Applying the "fine_amount" (unused-order penalty) into the total amount - the report's new total covers ordered dishes only; fines remain a separate, already-existing concept (`LunchSetting.fine_amount`) that this change does not fold in.

## Decisions

**1. Snapshot price on `LunchOrder` rather than resolving it at read-time from `LunchItem`.**
Read-time resolution would make historical totals drift whenever an item's price changes later, silently corrupting any period whose billing hasn't been closed out yet (and any period already reported on, if reported again). Storing the price at order-creation/change time keeps a completed month's totals stable regardless of future price edits. Alternative considered: a price-history table keyed by item+effective-date, joined at report time - rejected as unnecessary complexity for a feature whose only requirement is "don't let past orders drift."

**2. `custom_items` request shape changes from `string[]` to `{name, price}[]`.**
The only way to add a price to a custom item is to accept it in the same payload that creates the item. Since `custom_items` is currently an array of bare name strings, the shape must change to an array of objects. This is a breaking change to that one field (noted as **BREAKING** for the API consumer(s) of `POST/PUT /admin/lunch-menus`); `items` (existing-item references) is unaffected.

**3. Backfill both `lunch_items.price` and `lunch_orders.price` in the migrations themselves.**
`lunch_items.price` is backfilled to 32,000 (the current uniform price, per product decision) so every pre-existing item has a real price going forward. `lunch_orders.price` is then backfilled from each order's item price (post-item-backfill) so that orders placed earlier in the current billing period - before this change deploys - still contribute correctly to `ordersReport()`'s new total. Alternative considered: leave historical orders at price `NULL` and treat `NULL` as 0 in the sum - rejected because it would silently undercount the very month this feature is meant to close out.

**4a. A `custom_items` entry matching an existing item backfills its price only if that item currently has none.**
`custom_items` is meant for the common case of adding a brand-new dish, but its name-matching lookup can also hit an existing item (e.g. it was created earlier without a price via the plain item endpoint). Silently dropping the supplied price in that case would contradict the point of sending it. Silently overwriting an already-priced item's price would turn a "quick add item" shortcut into an unintended repricing side channel. Backfilling only when the existing item's price is currently `null` satisfies the former without risking the latter.

**4. New `price` columns are nullable with no application-level default.**
Per product requirement, price is optional at the item level (some dishes may legitimately have no price, e.g. not yet priced). Snapshot logic on `LunchOrder` simply copies whatever the item's price is (including `null`) - it does not invent a default. The report sums whatever is present; `NULL` prices behave as 0 in `SUM()`.

## Risks / Trade-offs

- **[Breaking payload shape]** `custom_items: string[]` → `{name, price}[]` breaks any existing API consumer sending the old shape → Mitigation: this is a backend-only repo; the corresponding frontend must be updated in lockstep with this deploy. Flag clearly in the MR/release notes.
- **[Backfill accuracy]** The `lunch_orders.price` backfill assumes every existing order's item price should equal today's uniform 32,000 price → Mitigation: acceptable because all existing items are currently priced identically (per product confirmation); if that assumption is ever wrong for a specific historical order, it must be corrected manually after migration.
- **[Report semantics]** Summing `price` on `active()` orders only (mirrors existing `ordersReport()` filtering) means cancelled/inactive orders never contribute to the total, and orders marked `is_unused` still count (an employee who didn't show up still ordered and is still billed) → Mitigation: this matches current billing intuition (you pay for what you ordered, not for whether you ate it); revisit only if the business decides "unused" orders should be excluded or handled via `fine_amount` instead.

## Migration Plan

1. Add `lunch_items.price` (nullable decimal), backfill existing rows to 32000.
2. Add `lunch_orders.price` (nullable decimal), backfill from each order's `lunch_items.price` via the now-populated column.
3. Deploy backend changes (validation, snapshot-on-write, report aggregation) together with the migrations - order-writing code must not run against a schema that lacks the `price` column.
4. No rollback data concern: `down()` on both migrations simply drops the columns; no other schema depends on them.
