# Phase 1: Foundation & Schema

## Objective
Set up all models, migrations, enums, morph maps, role, middleware, and route skeleton for the entire Restaurant Manager module. This phase creates the data foundation that all subsequent phases build upon.

---

## Tasks

### 1.1 Add RESTAURANT_MANAGER to AdminRole Enum

**File:** `app/Enums/AdminRole.php`

```php
case RESTAURANT_MANAGER = 'restaurant_manager';
```

Update `label()` method with translation key.

### 1.2 Add `is_restaurant_manager` Accessor to Employee & User

**File:** `app/Models/Employee.php`
```php
public function getIsRestaurantManagerAttribute(): bool
{
    return $this->hasRole(AdminRole::RESTAURANT_MANAGER->value);
}
```

**File:** `app/Models/User.php`
```php
public function getIsRestaurantManagerAttribute(): bool
{
    return $this->employee?->is_restaurant_manager ?? false;
}
```

Update `getEmployeeType()` and `getEmployeeTypes()` in Employee model.

### 1.3 Create New Enums

**File:** `app/Enums/RestaurantServicePeriod.php`
```php
enum RestaurantServicePeriod: string
{
    case BREAKFAST = 'breakfast';
    case BRUNCH = 'brunch';
    case LUNCH = 'lunch';
    case AFTERNOON_TEA = 'afternoon_tea';
    case DINNER = 'dinner';
    case LATE_NIGHT = 'late_night';
}
```

**File:** `app/Enums/RestaurantReservationStatus.php`
```php
enum RestaurantReservationStatus: string
{
    case PENDING = 'pending';
    case CONFIRMED = 'confirmed';
    case SEATED = 'seated';
    case COMPLETED = 'completed';
    case CANCELLED = 'cancelled';
    case NO_SHOW = 'no_show';
}
```

**File:** `app/Enums/RestaurantTableZone.php`
```php
enum RestaurantTableZone: string
{
    case INDOOR = 'indoor';
    case OUTDOOR = 'outdoor';
    case TERRACE = 'terrace';
    case VIP = 'vip';
    case BAR = 'bar';
    case PRIVATE = 'private';
}
```

**File:** `app/Enums/RestaurantTableStatus.php`
```php
enum RestaurantTableStatus: string
{
    case AVAILABLE = 'available';
    case OCCUPIED = 'occupied';
    case RESERVED = 'reserved';
    case MAINTENANCE = 'maintenance';
}
```

**File:** `app/Enums/Allergen.php`
```php
enum Allergen: string
{
    case GLUTEN = 'gluten';
    case CRUSTACEANS = 'crustaceans';
    case EGGS = 'eggs';
    case FISH = 'fish';
    case PEANUTS = 'peanuts';
    case SOYBEANS = 'soybeans';
    case DAIRY = 'dairy';
    case NUTS = 'nuts';
    case CELERY = 'celery';
    case MUSTARD = 'mustard';
    case SESAME = 'sesame';
    case SULPHITES = 'sulphites';
    case LUPIN = 'lupin';
    case MOLLUSCS = 'molluscs';
}
```

**File:** `app/Enums/DietaryLabel.php`
```php
enum DietaryLabel: string
{
    case VEGETARIAN = 'vegetarian';
    case VEGAN = 'vegan';
    case GLUTEN_FREE = 'gluten_free';
    case HALAL = 'halal';
    case KOSHER = 'kosher';
    case DAIRY_FREE = 'dairy_free';
    case NUT_FREE = 'nut_free';
    case ORGANIC = 'organic';
    case RAW = 'raw';
    case SUGAR_FREE = 'sugar_free';
}
```

**File:** `app/Enums/RestaurantMenuCategoryType.php`
```php
enum RestaurantMenuCategoryType: string
{
    case FOOD = 'food';
    case DRINK = 'drink';
}
```

All enums must have `label()` method with translation keys, use `HasOptions` and `HasEnumStaticMethods` traits.

### 1.4 Update OrderableType Enum

**File:** `app/Enums/OrderableType.php`

Add:
```php
case RESTAURANT_MENU_ITEM = 'restaurant_menu_item';
case RESTAURANT_RESERVATION = 'restaurant_reservation';
```

Update `label()`, `labelForOrderable()`, and `code()` methods.

### 1.5 Create All Models

#### Restaurant Model
**File:** `app/Models/Restaurant.php`

```php
final class Restaurant extends BaseModel implements HasMedia
{
    use HasPublicId, HasFactory, InteractsWithMedia, SoftDeletes,
        LogsActivity, HasDatabaseTranslations, HasPlaceScope;

    protected $fillable = [
        'place_id', 'name', 'description', 'slug',
        'opening_hours', 'service_periods', 'max_covers',
        'reservation_duration', 'reservation_delay',
        'auto_confirm_reservations', 'accepts_preorders', 'is_active',
    ];

    protected $casts = [
        'opening_hours' => 'array',
        'service_periods' => 'array',
        'max_covers' => 'integer',
        'reservation_duration' => 'integer',
        'reservation_delay' => 'integer',
        'auto_confirm_reservations' => 'boolean',
        'accepts_preorders' => 'boolean',
        'is_active' => 'boolean',
    ];

    protected array $translatable = ['name', 'description'];
}
```

**Relations:**
- `place()` BelongsTo Place
- `tables()` HasMany RestaurantTable
- `menuCategories()` HasMany RestaurantMenuCategory
- `menuItems()` HasMany RestaurantMenuItem
- `menus()` HasMany RestaurantMenu
- `reservations()` HasMany RestaurantReservation
- `orders()` HasMany Order (via restaurant_id)

**Methods:**
- `getOpeningHours(): OpeningHours` — same pattern as Service model
- `getCurrentServicePeriod(): ?RestaurantServicePeriod`
- `getAvailableCovers(Carbon $dateTime): int`
- `scopeActive($query)` — filter is_active = true

#### RestaurantTable Model
**File:** `app/Models/RestaurantTable.php`

```php
final class RestaurantTable extends BaseModel
{
    use HasPublicId, HasFactory, SoftDeletes, LogsActivity;

    protected $fillable = [
        'restaurant_id', 'number', 'label', 'seats',
        'zone', 'status', 'is_active',
    ];

    protected $casts = [
        'number' => 'integer',
        'seats' => 'integer',
        'zone' => RestaurantTableZone::class,
        'status' => RestaurantTableStatus::class,
        'is_active' => 'boolean',
    ];
}
```

**Relations:**
- `restaurant()` BelongsTo Restaurant
- `reservations()` BelongsToMany RestaurantReservation (via pivot)

#### RestaurantMenuCategory Model
**File:** `app/Models/RestaurantMenuCategory.php`

```php
final class RestaurantMenuCategory extends BaseModel implements HasMedia
{
    use HasPublicId, HasFactory, SoftDeletes, LogsActivity,
        HasDatabaseTranslations, InteractsWithMedia;

    protected $fillable = [
        'restaurant_id', 'parent_id', 'name', 'description',
        'type', 'sort_order', 'is_active',
    ];

    protected $casts = [
        'type' => RestaurantMenuCategoryType::class,
        'sort_order' => 'integer',
        'is_active' => 'boolean',
    ];

    protected array $translatable = ['name', 'description'];
}
```

**Relations:**
- `restaurant()` BelongsTo Restaurant
- `parent()` BelongsTo RestaurantMenuCategory (nullable)
- `children()` HasMany RestaurantMenuCategory
- `menuItems()` HasMany RestaurantMenuItem

#### RestaurantMenuItem Model
**File:** `app/Models/RestaurantMenuItem.php`

Implements `Orderable` interface (same pattern as Product/Service).

```php
final class RestaurantMenuItem extends BaseModel implements HasMedia, Orderable
{
    use HasPublicId, HasFactory, SoftDeletes, LogsActivity,
        HasDatabaseTranslations, InteractsWithMedia;

    protected $fillable = [
        'restaurant_id', 'category_id', 'name', 'description',
        'money_price_amount', 'money_price_currency',
        'member_money_price_amount', 'member_money_price_currency',
        'resort_money_price_amount', 'resort_money_price_currency',
        'allergens', 'dietary_labels', 'preparation_time',
        'calories', 'is_active', 'sort_order',
    ];

    protected $casts = [
        'money_price' => AsMoney::class,
        'member_money_price' => AsMoney::class,
        'resort_money_price' => AsMoney::class,
        'allergens' => 'array',
        'dietary_labels' => 'array',
        'preparation_time' => 'integer',
        'calories' => 'integer',
        'is_active' => 'boolean',
        'sort_order' => 'integer',
    ];

    protected array $translatable = ['name', 'description'];
}
```

**Relations:**
- `restaurant()` BelongsTo Restaurant
- `category()` BelongsTo RestaurantMenuCategory
- `menuSections()` BelongsToMany RestaurantMenuSection (via pivot)

**Orderable interface methods:**
- `orderableMoneyPrice(...)` — returns money_price or member_money_price
- `orderableCreditsPrice(...)` — returns credits equivalent
- `orderableCreditsTaxes(...)` — returns tax amount
- `orderableConstraints()` — returns `[new RequiresMetadata(['quantity'])]`

**Media:** `image` collection (single image per item)

#### RestaurantMenu Model
**File:** `app/Models/RestaurantMenu.php`

```php
final class RestaurantMenu extends BaseModel
{
    use HasPublicId, HasFactory, SoftDeletes, LogsActivity,
        HasDatabaseTranslations;

    protected $fillable = [
        'restaurant_id', 'name', 'description', 'date',
        'service_periods', 'is_active',
    ];

    protected $casts = [
        'date' => 'date',
        'service_periods' => 'array',
        'is_active' => 'boolean',
    ];

    protected array $translatable = ['name', 'description'];
}
```

**Relations:**
- `restaurant()` BelongsTo Restaurant
- `sections()` HasMany RestaurantMenuSection

**Scopes:**
- `scopeForDate($query, Carbon $date)`
- `scopeForServicePeriod($query, RestaurantServicePeriod $period)`
- `scopeActive($query)`

#### RestaurantMenuSection Model
**File:** `app/Models/RestaurantMenuSection.php`

```php
final class RestaurantMenuSection extends BaseModel
{
    use HasPublicId, HasFactory, HasDatabaseTranslations;

    protected $fillable = ['menu_id', 'name', 'sort_order'];
    protected $casts = ['sort_order' => 'integer'];
    protected array $translatable = ['name'];
}
```

**Relations:**
- `menu()` BelongsTo RestaurantMenu
- `items()` BelongsToMany RestaurantMenuItem (via `restaurant_menu_section_items` pivot with `sort_order`, `override_price_amount`, `override_price_currency`)

#### RestaurantReservation Model
**File:** `app/Models/RestaurantReservation.php`

Implements `Orderable` interface for optional paid reservations.

```php
final class RestaurantReservation extends BaseModel implements Orderable
{
    use HasPublicId, HasFactory, SoftDeletes, LogsActivity;

    protected $fillable = [
        'restaurant_id', 'user_id', 'date', 'start_at', 'end_at',
        'party_size', 'special_requests', 'status',
        'order_item_id', 'preorder_order_id',
        'confirmed_at', 'seated_at', 'completed_at',
        'cancelled_at', 'cancellation_reason',
    ];

    protected $casts = [
        'date' => 'date',
        'start_at' => 'immutable_datetime',
        'end_at' => 'immutable_datetime',
        'party_size' => 'integer',
        'status' => RestaurantReservationStatus::class,
        'confirmed_at' => 'immutable_datetime',
        'seated_at' => 'immutable_datetime',
        'completed_at' => 'immutable_datetime',
        'cancelled_at' => 'immutable_datetime',
    ];
}
```

**Relations:**
- `restaurant()` BelongsTo Restaurant
- `user()` BelongsTo User (the customer)
- `tables()` BelongsToMany RestaurantTable (via `restaurant_reservation_tables` pivot)
- `orderItem()` BelongsTo OrderItem (nullable, for paid reservation)
- `preorderOrder()` BelongsTo Order (nullable, for linked pre-order)

**Methods:**
- `confirm()`, `seat()`, `complete()`, `cancel(string $reason)`, `markNoShow()`
- `totalSeats(): int` — sum of assigned tables' seats
- `canBeCancelled(): bool`

**Orderable interface methods:**
- `orderableMoneyPrice(...)` — reservation fee if configured
- `orderableConstraints()` — `[RequiresMetadata(['date', 'start_at', 'party_size']), MustBeTodayOrFuture(), RestaurantMustBeAvailable()]`

### 1.6 Create All Migrations

See `00-MASTER-PLAN.md` § Schema Migrations for all 11 migration definitions.

All migrations must have `down()` method for rollback.

### 1.7 Update Morph Maps

**File:** `app/Providers/AppServiceProvider.php`

Add to both `enforceMorphMap()` and `MorphMap()`:
```php
'restaurant' => Restaurant::class,
'restaurant_table' => RestaurantTable::class,
'restaurant_menu_category' => RestaurantMenuCategory::class,
'restaurant_menu_item' => RestaurantMenuItem::class,
'restaurant_menu' => RestaurantMenu::class,
'restaurant_reservation' => RestaurantReservation::class,
```

### 1.8 Create EnsureRestaurantManager Middleware

**File:** `app/Http/Middleware/EnsureRestaurantManagerMiddleware.php`

Logic:
- Verify authenticated user is an active employee
- Verify employee has `restaurant_manager` role
- Verify employee is attached to the requested place
- Verify `{restaurant}` route parameter belongs to the place
- Return 403 if any check fails
- Bind resolved Restaurant to request for downstream controllers

### 1.9 Register Route Groups

**File:** `routes/api.php`

```php
// Restaurant Manager routes (employee)
Route::prefix('v1/places/{place}/restaurants/{restaurant}')
    ->middleware([
        'auth:sanctum',
        sprintf('ability:%s', User::ABILITY_FULL),
        EnsureRestaurantManagerMiddleware::class,
    ])
    ->group(function () {
        // Phase 2: Menu items
        // Phase 3: Tables
        // Phase 4: Reservations (manager)
        // Phase 5: Carts & Payments
        // Phase 6: Dashboards
    });

// Restaurant Customer routes
Route::prefix('v1/places/{place}/restaurants')
    ->middleware(['auth:sanctum', sprintf('ability:%s', User::ABILITY_FULL)])
    ->group(function () {
        // Phase 4: Reservations (customer)
        // Phase 5: Pre-order
        // Phase 2: Menu browsing (customer)
    });
```

### 1.10 Create Model Factories

Create factories for all 7 main models:
- `RestaurantFactory`
- `RestaurantTableFactory`
- `RestaurantMenuCategoryFactory`
- `RestaurantMenuItemFactory`
- `RestaurantMenuFactory`
- `RestaurantMenuSectionFactory`
- `RestaurantReservationFactory`

### 1.11 Create Role Seeding Migration

Seed `restaurant_manager` role via Spatie Permission with guard `'filament'`.

### 1.12 Add Restaurant Relation to Place

**File:** `app/Models/Place.php`

```php
/** @return HasMany<Restaurant> */
public function restaurants(): HasMany
{
    return $this->hasMany(Restaurant::class);
}
```

### 1.13 Deprecate RestaurantRevenue Model

**File:** `app/Models/RestaurantRevenue.php`

Add `@deprecated` PHPDoc annotation to the class:
```php
/**
 * @deprecated Use the new Restaurant module instead. This model is legacy and will be removed in a future release.
 */
```

**Remove from Filament PlaceResource:**
- Remove `RestaurantRevenuesRelationManager` from PlaceResource's `getRelations()` array
- Remove `ManageRestaurantRevenues` page from PlaceResource's `getPages()` array
- Delete file: `app/Filament/Resources/PlaceResource/RelationManagers/RestaurantRevenuesRelationManager.php`
- Delete file: `app/Filament/Resources/PlaceResource/Pages/Management/ManageRestaurantRevenues.php`

**Do NOT delete** the model or migration — existing data must be preserved. Just deprecate and remove from UI.

### 1.14 Add Restaurant & Tip Fields to Order

**File:** `app/Models/Order.php`

Add to $fillable: `restaurant_id`, `restaurant_table_ids`, `tip_amount`, `tip_currency`
Add casts: `restaurant_table_ids => array`, `tip => AsMoney`
Add relation: `restaurant()` BelongsTo Restaurant (nullable)

### 1.15 Tests

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

Test cases:
- [ ] Restaurant manager can access `/v1/places/{place}/restaurants/{restaurant}/*` routes
- [ ] Regular employee without restaurant_manager role gets 403
- [ ] Customer user gets 403
- [ ] Coach user gets 403
- [ ] Restaurant manager at Place A cannot access Place B's restaurant routes
- [ ] Restaurant manager cannot access restaurant belonging to different place
- [ ] All model factories create valid records
- [ ] Morph map resolves correctly for new types

---

## Files Created/Modified

| Action | File |
|---|---|
| Modify | `app/Enums/AdminRole.php` |
| Modify | `app/Enums/OrderableType.php` |
| Create | `app/Enums/RestaurantServicePeriod.php` |
| Create | `app/Enums/RestaurantReservationStatus.php` |
| Create | `app/Enums/RestaurantTableZone.php` |
| Create | `app/Enums/RestaurantTableStatus.php` |
| Create | `app/Enums/Allergen.php` |
| Create | `app/Enums/DietaryLabel.php` |
| Create | `app/Enums/RestaurantMenuCategoryType.php` |
| Create | `app/Models/Restaurant.php` |
| Create | `app/Models/RestaurantTable.php` |
| Create | `app/Models/RestaurantMenuCategory.php` |
| Create | `app/Models/RestaurantMenuItem.php` |
| Create | `app/Models/RestaurantMenu.php` |
| Create | `app/Models/RestaurantMenuSection.php` |
| Create | `app/Models/RestaurantReservation.php` |
| Create | `app/Http/Middleware/EnsureRestaurantManagerMiddleware.php` |
| Deprecate | `app/Models/RestaurantRevenue.php` — add @deprecated annotation |
| Delete | `app/Filament/Resources/PlaceResource/RelationManagers/RestaurantRevenuesRelationManager.php` |
| Delete | `app/Filament/Resources/PlaceResource/Pages/Management/ManageRestaurantRevenues.php` |
| Modify | `app/Filament/Resources/PlaceResource.php` — remove RestaurantRevenues references |
| Modify | `app/Models/Place.php` |
| Modify | `app/Models/Order.php` |
| Modify | `app/Models/Employee.php` |
| Modify | `app/Models/User.php` |
| Modify | `app/Providers/AppServiceProvider.php` |
| Modify | `routes/api.php` |
| Create | `database/migrations/..._create_restaurants_table.php` |
| Create | `database/migrations/..._create_restaurant_tables_table.php` |
| Create | `database/migrations/..._create_restaurant_menu_categories_table.php` |
| Create | `database/migrations/..._create_restaurant_menu_items_table.php` |
| Create | `database/migrations/..._create_restaurant_menus_table.php` |
| Create | `database/migrations/..._create_restaurant_menu_sections_table.php` |
| Create | `database/migrations/..._create_restaurant_menu_section_items_table.php` |
| Create | `database/migrations/..._create_restaurant_reservations_table.php` |
| Create | `database/migrations/..._create_restaurant_reservation_tables_table.php` |
| Create | `database/migrations/..._add_restaurant_fields_to_orders_table.php` |
| Create | `database/migrations/..._add_restaurant_manager_role.php` |
| Create | `database/factories/RestaurantFactory.php` |
| Create | `database/factories/RestaurantTableFactory.php` |
| Create | `database/factories/RestaurantMenuCategoryFactory.php` |
| Create | `database/factories/RestaurantMenuItemFactory.php` |
| Create | `database/factories/RestaurantMenuFactory.php` |
| Create | `database/factories/RestaurantMenuSectionFactory.php` |
| Create | `database/factories/RestaurantReservationFactory.php` |
| Create | `tests/Feature/Controllers/RestaurantManager/RestaurantManagerAuthorizationTest.php` |

---

## Quality Checks

**Do NOT run `./gitCheck.sh`.** Instead, run lint and PHPStan only on new/modified files:
```bash
vendor/bin/pint app/Models/Restaurant.php app/Models/RestaurantTable.php ... (all new/modified files)
vendor/bin/phpstan analyse app/Models/Restaurant.php app/Models/RestaurantTable.php ... --level=7
```

Do NOT commit. The user will commit manually.

---

## Acceptance Criteria

- [ ] All 7 enums created with `label()` and `HasOptions`
- [ ] All 7 models created with proper relations, casts, fillable
- [ ] All 11 migrations run without errors, `down()` works
- [ ] RestaurantMenuItem implements Orderable correctly
- [ ] RestaurantReservation implements Orderable correctly
- [ ] Morph maps resolve for all new types
- [ ] EnsureRestaurantManagerMiddleware blocks unauthorized access
- [ ] Route groups registered and accessible
- [ ] All factories create valid records
- [ ] `AdminRole::RESTAURANT_MANAGER` recognized by Spatie
- [ ] Place→restaurants() relation works
- [ ] Order→restaurant(), tip fields work
- [ ] RestaurantRevenue model marked @deprecated
- [ ] RestaurantRevenuesRelationManager and ManageRestaurantRevenues removed from PlaceResource
- [ ] All tests pass
- [ ] `vendor/bin/pint` passes on all new/modified files
- [ ] `vendor/bin/phpstan --level=7` passes on all new/modified files
