# Phase 2: Menu System (Filament + API)

## Objective
Deliver a complete menu management system accessible from **both** Filament admin and the mobile API. Restaurant managers can create categories, menu items (with allergens, dietary labels, images, pricing), and compose daily menus with sections — from either interface.

---

## Filament Admin

### 2.0 Filament Navigation Structure

The Restaurant module uses the same navigation pattern as Shop:

**Sidebar Navigation Group:**
```php
// RestaurantResource.php
protected static ?string $navigationGroup = 'Restaurant';
protected static ?int $navigationSort = 10;
protected static ?string $navigationIcon = 'heroicon-o-cake';
```

**PlaceResource Integration (same pattern as Shop):**

1. **Management Page** — `ManageRestaurants`
   - **File:** `app/Filament/Resources/PlaceResource/Pages/Management/ManageRestaurants.php`
   - Route: `/{record}/restaurants`
   - Replaces the deleted `ManageRestaurantRevenues` page
   - Shows restaurants linked to this place with quick access

2. **Dashboard Widget** — `RestaurantSalesWidget`
   - **File:** `app/Filament/Resources/PlaceResource/Widgets/RestaurantSalesWidget.php`
   - Displays: Restaurant Revenue (30-day), Active Restaurants count, Restaurant Orders (30-day)
   - Same pattern as existing `ShopSalesWidget`

3. **Register in PlaceResource:**
   - Add `ManageRestaurants` to `getPages()` array
   - Add `RestaurantSalesWidget` to `getWidgets()` array

### 2.1 RestaurantResource (Filament)

**File:** `app/Filament/Resources/RestaurantResource.php`

Full CRUD for restaurants. Navigation group = "Restaurant" (like Shop).

**Form:**
- `name` (TextInput, translatable)
- `description` (Textarea, translatable)
- `slug` (TextInput, auto-generated from name)
- `opening_hours` (Repeater per day with open/close times)
- `service_periods` (CheckboxList from RestaurantServicePeriod enum with time range inputs per period)
- `max_covers` (TextInput, numeric)
- `reservation_duration` (TextInput, numeric, minutes, default 90)
- `reservation_delay` (TextInput, numeric, hours, default 0)
- `auto_confirm_reservations` (Toggle)
- `accepts_preorders` (Toggle)
- `is_active` (Toggle)
- Media: logo/cover image

**Table Columns:**
- Name, Place, max_covers, is_active (icon), created_at

**Filters:**
- Place, is_active

**RelationManagers:**
- `TablesRelationManager` (Phase 3)
- `MenuCategoriesRelationManager`
- `MenuItemsRelationManager`
- `MenusRelationManager`
- `ReservationsRelationManager` (Phase 4)

### 2.2 RestaurantMenuCategoryResource (Filament RelationManager)

**File:** `app/Filament/Resources/RestaurantResource/RelationManagers/MenuCategoriesRelationManager.php`

**Form:**
- `name` (TextInput, translatable)
- `description` (Textarea, translatable)
- `parent_id` (Select, self-referencing, nullable)
- `type` (Select from RestaurantMenuCategoryType: FOOD, DRINK)
- `sort_order` (TextInput, numeric)
- `is_active` (Toggle)
- Media: icon image

**Table Columns:**
- Name, type (badge), parent, items count, sort_order, is_active

**Actions:** Create, Edit, Delete, Reorder

### 2.3 RestaurantMenuItemResource (Filament RelationManager)

**File:** `app/Filament/Resources/RestaurantResource/RelationManagers/MenuItemsRelationManager.php`

**Form:**
- `name` (TextInput, translatable)
- `description` (Textarea, translatable)
- `category_id` (Select, filtered by restaurant)
- **Pricing Section:**
  - `money_price` (TextInput, numeric — standard price)
  - `member_money_price` (TextInput, numeric, nullable — member price)
  - `resort_money_price` (TextInput, numeric, nullable — resort credits price)
- **Allergens Section:**
  - `allergens` (CheckboxList from Allergen enum — EU 14)
- **Dietary Section:**
  - `dietary_labels` (CheckboxList from DietaryLabel enum)
- **Details:**
  - `preparation_time` (TextInput, numeric, minutes)
  - `calories` (TextInput, numeric)
  - `sort_order` (TextInput, numeric)
  - `is_active` (Toggle)
- **Media:**
  - `image` (FileUpload, single image)

**Table Columns:**
- Image (thumbnail), Name, Category, Price, Allergen icons, is_active

**Filters:**
- Category, type (food/drink via category), is_active, allergen presence

### 2.4 RestaurantMenuResource (Filament RelationManager)

**File:** `app/Filament/Resources/RestaurantResource/RelationManagers/MenusRelationManager.php`

**Form:**
- `name` (TextInput, translatable — e.g., "Menu du Jour", "Brunch Spécial")
- `description` (Textarea, translatable)
- `date` (DatePicker)
- `service_periods` (CheckboxList from RestaurantServicePeriod enum)
- `is_active` (Toggle)
- **Sections Repeater:**
  - `sections` (Repeater):
    - `name` (TextInput — e.g., "Entrée", "Plat", "Dessert")
    - `items` (Select multiple, from restaurant's menu items)
    - `sort_order` (auto-incremented)

**Table Columns:**
- Name, Date, Service periods (badges), Sections count, is_active

**Actions:** Create, Edit, Delete, Duplicate (to quickly create tomorrow's menu from today)

---

## API Endpoints (Restaurant Manager)

### 2.5 List Menu Categories

**Controller:** `app/Http/Controllers/Api/Employee/RestaurantManager/ListMenuCategoriesController.php`

- `GET /v1/places/{place}/restaurants/{restaurant}/menu-categories`
- Query params: `type` (food|drink), `parent_id`, `include_inactive`
- Returns paginated categories with children count and item count

### 2.6 Create Menu Category

**Controller:** `app/Http/Controllers/Api/Employee/RestaurantManager/CreateMenuCategoryController.php`

- `POST /v1/places/{place}/restaurants/{restaurant}/menu-categories`
- Body: `{ name, description?, parent_id?, type, sort_order? }`
- Returns created category

### 2.7 Update Menu Category

**Controller:** `app/Http/Controllers/Api/Employee/RestaurantManager/UpdateMenuCategoryController.php`

- `PUT /v1/places/{place}/restaurants/{restaurant}/menu-categories/{category}`
- Body: same as create
- Returns updated category

### 2.8 Delete Menu Category

**Controller:** `app/Http/Controllers/Api/Employee/RestaurantManager/DeleteMenuCategoryController.php`

- `DELETE /v1/places/{place}/restaurants/{restaurant}/menu-categories/{category}`
- Soft deletes. Fails if category has active items.

### 2.9 List Menu Items

**Controller:** `app/Http/Controllers/Api/Employee/RestaurantManager/ListMenuItemsController.php`

- `GET /v1/places/{place}/restaurants/{restaurant}/menu-items`
- Query params: `search`, `category_id`, `type` (food|drink), `allergen`, `dietary_label`, `include_inactive`, `sort` (name|price|sort_order), `page`, `size`
- Returns paginated items with category, allergens, dietary labels, image URL

### 2.10 Show Menu Item

**Controller:** `app/Http/Controllers/Api/Employee/RestaurantManager/ShowMenuItemController.php`

- `GET /v1/places/{place}/restaurants/{restaurant}/menu-items/{item}`
- Returns full item detail with image, allergens, dietary labels, all prices

### 2.11 Create Menu Item

**Controller:** `app/Http/Controllers/Api/Employee/RestaurantManager/CreateMenuItemController.php`

- `POST /v1/places/{place}/restaurants/{restaurant}/menu-items`
- Body: `{ name, description?, category_id, money_price, member_money_price?, resort_money_price?, allergens[]?, dietary_labels[]?, preparation_time?, calories?, image? }`
- Multipart for image upload

### 2.12 Update Menu Item

**Controller:** `app/Http/Controllers/Api/Employee/RestaurantManager/UpdateMenuItemController.php`

- `PUT /v1/places/{place}/restaurants/{restaurant}/menu-items/{item}`
- Body: same as create
- Returns updated item

### 2.13 Delete Menu Item

**Controller:** `app/Http/Controllers/Api/Employee/RestaurantManager/DeleteMenuItemController.php`

- `DELETE /v1/places/{place}/restaurants/{restaurant}/menu-items/{item}`
- Soft deletes

### 2.14 Toggle Menu Item Active

**Controller:** `app/Http/Controllers/Api/Employee/RestaurantManager/ToggleMenuItemController.php`

- `PATCH /v1/places/{place}/restaurants/{restaurant}/menu-items/{item}/toggle`
- Toggles `is_active`

### 2.15 List Menus

**Controller:** `app/Http/Controllers/Api/Employee/RestaurantManager/ListMenusController.php`

- `GET /v1/places/{place}/restaurants/{restaurant}/menus`
- Query params: `date`, `service_period`, `include_inactive`
- Returns menus with sections and item counts

### 2.16 Get Today's Menu

**Controller:** `app/Http/Controllers/Api/Employee/RestaurantManager/TodayMenuController.php`

- `GET /v1/places/{place}/restaurants/{restaurant}/today-menu`
- Query params: `service_period` (optional, defaults to current)
- Returns today's active menu with full sections and items

### 2.17 Create Menu

**Controller:** `app/Http/Controllers/Api/Employee/RestaurantManager/CreateMenuController.php`

- `POST /v1/places/{place}/restaurants/{restaurant}/menus`
- Body:
```json
{
  "name": "Menu du Jour",
  "description": "...",
  "date": "2026-03-14",
  "service_periods": ["lunch", "dinner"],
  "sections": [
    {
      "name": "Entrée",
      "items": ["menu_item_public_id_1", "menu_item_public_id_2"]
    },
    {
      "name": "Plat",
      "items": ["menu_item_public_id_3"]
    }
  ]
}
```

### 2.18 Update Menu

**Controller:** `app/Http/Controllers/Api/Employee/RestaurantManager/UpdateMenuController.php`

- `PUT /v1/places/{place}/restaurants/{restaurant}/menus/{menu}`
- Body: same as create
- Replaces sections entirely (delete old, create new)

### 2.19 Delete Menu

**Controller:** `app/Http/Controllers/Api/Employee/RestaurantManager/DeleteMenuController.php`

- `DELETE /v1/places/{place}/restaurants/{restaurant}/menus/{menu}`
- Soft deletes

### 2.20 Duplicate Menu

**Controller:** `app/Http/Controllers/Api/Employee/RestaurantManager/DuplicateMenuController.php`

- `POST /v1/places/{place}/restaurants/{restaurant}/menus/{menu}/duplicate`
- Body: `{ date, service_periods? }`
- Creates a copy of the menu with all sections and items for a new date

---

## API Endpoints (Customer)

### 2.21 List Restaurants for Place

**Controller:** `app/Http/Controllers/Api/Restaurant/ListRestaurantsController.php`

- `GET /v1/places/{place}/restaurants`
- Returns active restaurants with name, description, hours, image

### 2.22 Show Restaurant Detail

**Controller:** `app/Http/Controllers/Api/Restaurant/ShowRestaurantController.php`

- `GET /v1/places/{place}/restaurants/{restaurant}`
- Returns full detail: hours, service periods, zones, capacity

### 2.23 Get Restaurant Menu (Customer)

**Controller:** `app/Http/Controllers/Api/Restaurant/RestaurantMenuController.php`

- `GET /v1/places/{place}/restaurants/{restaurant}/menu`
- Query params: `date` (defaults today), `service_period` (defaults to current)
- Returns active menu with sections, items, prices, allergens, dietary labels, images
- Member pricing shown if user is member

---

## Actions

### 2.24 DuplicateMenuAction

**File:** `app/Actions/RestaurantManager/DuplicateMenuAction.php`

Takes a menu, a target date, and optional service periods. Creates a deep copy (menu + sections + section items).

---

## Resources (API Serialization)

**Files in:** `app/Http/Resources/RestaurantManager/`

- `RestaurantResource` — restaurant summary
- `RestaurantDetailResource` — full restaurant info
- `MenuCategoryResource` — category with children count
- `MenuItemResource` — item with category, allergens, labels, image
- `MenuResource` — menu with sections
- `MenuSectionResource` — section with items

**Files in:** `app/Http/Resources/Restaurant/` (customer-facing)

- `CustomerRestaurantResource`
- `CustomerMenuResource`
- `CustomerMenuItemResource` — includes allergen icons, dietary badges

---

## Form Requests

- `CreateMenuCategoryRequest`
- `UpdateMenuCategoryRequest`
- `CreateMenuItemRequest`
- `UpdateMenuItemRequest`
- `CreateMenuRequest`
- `UpdateMenuRequest`
- `DuplicateMenuRequest`

---

## Tests

**File:** `tests/Feature/Controllers/RestaurantManager/MenuSystemTest.php`

- [ ] List categories returns correct structure
- [ ] Create category with parent works
- [ ] Create category with invalid type returns 422
- [ ] List items with search filter works
- [ ] List items filtered by category works
- [ ] List items filtered by allergen works
- [ ] Create item with all fields and image works
- [ ] Create item without required fields returns 422
- [ ] Update item changes values correctly
- [ ] Toggle item active/inactive works
- [ ] Delete item soft deletes
- [ ] Create menu with sections and items works
- [ ] Today's menu returns current service period menu
- [ ] Duplicate menu creates deep copy with new date
- [ ] Customer can view active menu with allergens
- [ ] Customer sees member pricing if member
- [ ] Customer cannot see inactive items
- [ ] Filament CRUD operations match API results
- [ ] Allergen enum covers all 14 EU allergens
- [ ] All endpoints return public_id, never id
- [ ] Non-restaurant-manager gets 403

---

## Files Created/Modified

| Action | File |
|---|---|
| Create | `app/Filament/Resources/RestaurantResource.php` (navigationGroup = 'Restaurant') |
| Create | `app/Filament/Resources/RestaurantResource/Pages/ListRestaurants.php` |
| Create | `app/Filament/Resources/RestaurantResource/Pages/CreateRestaurant.php` |
| Create | `app/Filament/Resources/RestaurantResource/Pages/EditRestaurant.php` |
| Create | `app/Filament/Resources/PlaceResource/Pages/Management/ManageRestaurants.php` |
| Create | `app/Filament/Resources/PlaceResource/Widgets/RestaurantSalesWidget.php` |
| Modify | `app/Filament/Resources/PlaceResource.php` — add ManageRestaurants page + RestaurantSalesWidget |
| Create | `app/Filament/Resources/RestaurantResource/RelationManagers/MenuCategoriesRelationManager.php` |
| Create | `app/Filament/Resources/RestaurantResource/RelationManagers/MenuItemsRelationManager.php` |
| Create | `app/Filament/Resources/RestaurantResource/RelationManagers/MenusRelationManager.php` |
| Create | Controllers: 19 API controllers (see above) |
| Create | Resources: 9 API resources (see above) |
| Create | Requests: 7 form requests (see above) |
| Create | `app/Actions/RestaurantManager/DuplicateMenuAction.php` |
| Create | `tests/Feature/Controllers/RestaurantManager/MenuSystemTest.php` |
| Modify | `routes/api.php` — add menu routes |

---

## Acceptance Criteria

- [ ] Filament: Full CRUD for categories, items, menus from RestaurantResource
- [ ] API: All 19 endpoints functional and returning correct data
- [ ] Customer API: 3 endpoints for browsing restaurants and menus
- [ ] Allergens: All 14 EU allergens selectable and displayed
- [ ] Dietary labels: All 10 labels selectable and displayed
- [ ] Menu items have images uploadable via both Filament and API
- [ ] Daily menus with sections correctly compose items
- [ ] Menu duplication creates accurate deep copy
- [ ] Translations work on name/description fields
- [ ] Member pricing shown to members, standard to non-members
- [ ] Filament: "Restaurant" navigation group visible in sidebar
- [ ] Filament: ManageRestaurants page accessible from PlaceResource
- [ ] Filament: RestaurantSalesWidget displayed on Place dashboard
- [ ] Swagger annotations on all controllers
- [ ] All tests pass
- [ ] `vendor/bin/pint` passes on all new/modified files
- [ ] `vendor/bin/phpstan --level=7` passes on all new/modified files
