## 1. Validation

- [x] 1.1 In `LunchMenuController::store()`, after `validate()`, group `custom_items` by `mb_strtolower(trim(name))` and throw `ValidationException::withMessages(['custom_items.<index>.price' => ...])` when entries in one group have different prices (omitted counts as null; compare as float)

## 2. Price update

- [x] 2.1 Replace the `pluck('id', 'name')` lookup with a keyed collection of `LunchItem` models (`id`, `name`, `price`) keyed by `lower(trim(name))`
- [x] 2.2 In the matched-item branch, when `price` is present and non-null and differs from the item's current price, update the item's `price`; otherwise leave it unchanged
- [x] 2.3 Keep the new-item branch as is (`price ?? default_item_price`) and add the created model to the lookup so later duplicates reuse it
- [x] 2.4 Wrap item creation/updates, menu creation, `handleDisplayName`, items/accounts sync and `updateStatus` in `DB::transaction()`
- [x] 2.5 Update the OpenAPI description of `custom_items.price` to state that it also updates an existing item with the same name, and that null/omitted keeps the existing price

## 3. Verification

- [x] 3.1 Lint `LunchMenuController.php` (`php -l`, PHP 7.4 in the app container)
- [x] 3.2 On the local DB, create a menu with `custom_items` containing an existing item name and a new price → item price updated, menu shows new price
- [x] 3.3 Same with price omitted → existing price unchanged
- [x] 3.4 Duplicate names with different prices (including one omitted) → 422 and no item/menu changes; same price → success, item attached once
- [x] 3.5 Place an employee order on the new menu → `lunch_orders.price` equals the new price; an older order keeps its old price
