# Menu Management Architecture

The Orion platform provides a **module-driven menu system** where each module defines its own menu items. The `MenuDiscoveryService` aggregates menus from all enabled modules, applying visibility configuration and permission filtering.

## Overview

```
┌─────────────────────────────────────────────────────────────────────────┐
│                   MODULE DEFINITIONS (Source of Truth)                   │
│  app/modules/*/definition.py                                            │
│                                                                          │
│  Each module defines its menu items per FrontendType:                   │
│  ┌─────────────────────────────┐  ┌─────────────────────────────┐      │
│  │   catalog.definition.py     │  │   orders.definition.py      │      │
│  │   menus={ADMIN: [...],     │  │   menus={ADMIN: [...],      │      │
│  │          STORE: [...]}     │  │          STORE: [...]}     │      │
│  └─────────────────────────────┘  └─────────────────────────────┘      │
└─────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                    MENU DISCOVERY SERVICE                                │
│  app/modules/core/services/menu_discovery_service.py                    │
│                                                                          │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │  1. Collect menu items from all enabled modules                  │   │
│  │  2. Filter by user permissions (super_admin_only)               │   │
│  │  3. Apply visibility overrides (AdminMenuConfig)                │   │
│  │  4. Sort by section/item order                                  │   │
│  └─────────────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                    VISIBILITY CONFIGURATION                              │
│  app/modules/core/models/admin_menu_config.py                           │
│                                                                          │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                    AdminMenuConfig Table                         │   │
│  │  Stores visibility overrides (hidden items only)                 │   │
│  │  - Platform scope: applies to platform admins/stores            │   │
│  │  - User scope: applies to specific super admin                   │   │
│  └─────────────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                        FILTERED MENU OUTPUT                              │
│                                                                          │
│  Module Menus - Disabled Modules - Hidden Items = Visible Menu          │
└─────────────────────────────────────────────────────────────────────────┘
```

## Frontend Types

The system supports four distinct frontend types:

| Frontend | Description | Users |
|----------|-------------|-------|
| `PLATFORM` | Public marketing pages | Unauthenticated visitors |
| `ADMIN` | Admin panel | Super admins, platform admins |
| `STORE` | Store dashboard | Stores on a platform |
| `STOREFRONT` | Customer-facing shop | Shop customers |

```python
from app.modules.enums import FrontendType

# Use in code
FrontendType.PLATFORM    # "platform"
FrontendType.ADMIN       # "admin"
FrontendType.STORE      # "store"
FrontendType.STOREFRONT  # "storefront"
```

## Module-Driven Menus

Each module defines its menu items in `definition.py` using dataclasses:

### Menu Item Definition

```python
# app/modules/base.py

@dataclass
class MenuItemDefinition:
    """Single menu item definition."""
    id: str                      # Unique identifier (e.g., "catalog.products")
    label_key: str               # i18n key for label
    icon: str                    # Lucide icon name
    route: str                   # URL path
    order: int = 100             # Sort order within section
    is_mandatory: bool = False   # Cannot be hidden by user
    is_super_admin_only: bool = False  # Only visible to super admins

@dataclass
class MenuSectionDefinition:
    """Section containing menu items."""
    id: str                      # Section identifier
    label_key: str               # i18n key for section label
    icon: str                    # Section icon
    order: int = 100             # Sort order
    items: list[MenuItemDefinition] = field(default_factory=list)
    is_super_admin_only: bool = False
```

### Example Module Definition

```python
# app/modules/catalog/definition.py

from app.modules.base import ModuleDefinition, MenuSectionDefinition, MenuItemDefinition
from app.modules.enums import FrontendType

catalog_module = ModuleDefinition(
    code="catalog",
    name="Product Catalog",
    description="Product and category management",
    version="1.0.0",
    is_core=True,
    menus={
        FrontendType.ADMIN: [
            MenuSectionDefinition(
                id="catalog",
                label_key="menu.catalog",
                icon="package",
                order=30,
                items=[
                    MenuItemDefinition(
                        id="products",
                        label_key="menu.products",
                        icon="box",
                        route="/admin/products",
                        order=10,
                        is_mandatory=True
                    ),
                    MenuItemDefinition(
                        id="categories",
                        label_key="menu.categories",
                        icon="folder-tree",
                        route="/admin/categories",
                        order=20
                    ),
                ]
            )
        ],
        FrontendType.STORE: [
            MenuSectionDefinition(
                id="products",
                label_key="menu.my_products",
                icon="package",
                order=10,
                items=[
                    MenuItemDefinition(
                        id="products",
                        label_key="menu.products",
                        icon="box",
                        route="/store/{store_code}/products",
                        order=10,
                        is_mandatory=True
                    ),
                ]
            )
        ],
    }
)
```

## Menu Discovery Service

The `MenuDiscoveryService` aggregates menus from all enabled modules:

```python
from app.modules.core.services.menu_discovery_service import menu_discovery_service

# Get menu for a frontend type
sections = menu_discovery_service.get_menu_for_frontend(
    db=db,
    frontend_type=FrontendType.ADMIN,
    platform_id=platform.id,
    user_id=current_user.id,
    is_super_admin=current_user.is_super_admin,
)

# Get all menu items for configuration UI
all_items = menu_discovery_service.get_all_menu_items(
    db=db,
    frontend_type=FrontendType.ADMIN,
    platform_id=platform.id,
)
```

### Discovery Flow

1. **Collect**: Get menu definitions from all modules in `MODULES` registry
2. **Filter by Module**: Only include menus from enabled modules for the platform
3. **Filter by Permissions**: Remove `super_admin_only` items for non-super admins
4. **Apply Visibility**: Check `AdminMenuConfig` for hidden items
5. **Sort**: Order sections and items by their `order` field
6. **Return**: List of `MenuSectionDefinition` with filtered items

## Visibility Configuration

### Opt-Out Model

The system uses an **opt-out model**:
- All menu items are **visible by default**
- Only **hidden** items are stored in the database
- This keeps the database small and makes the default state explicit

### Mandatory Items

Certain menu items cannot be hidden. These are marked with `is_mandatory=True` in their definition:

```python
MenuItemDefinition(
    id="dashboard",
    label_key="menu.dashboard",
    icon="home",
    route="/admin/dashboard",
    is_mandatory=True,  # Cannot be hidden
)
```

### Scope Types

Menu configuration supports two scopes:

| Scope | Field | Description | Use Case |
|-------|-------|-------------|----------|
| Platform | `platform_id` | Applies to all users on platform | Hide features not used by platform |
| User | `user_id` | Applies to specific super admin | Personal preference customization |

**Important Rules:**
- Exactly one scope must be set (platform XOR user)
- User scope is only allowed for admin frontend (super admins only)
- Store frontend only supports platform scope

### Resolution Order

**Admin Frontend:**
```
Platform admin → Check platform config → Fall back to default (all visible)
Super admin   → Check user config → Fall back to default (all visible)
```

**Store Frontend:**
```
Store → Check platform config → Fall back to default (all visible)
```

## Database Model

### AdminMenuConfig Table

```sql
CREATE TABLE admin_menu_configs (
    id SERIAL PRIMARY KEY,
    frontend_type VARCHAR(10) NOT NULL,  -- 'admin' or 'store'
    platform_id INTEGER REFERENCES platforms(id),
    user_id INTEGER REFERENCES users(id),
    menu_item_id VARCHAR(50) NOT NULL,
    is_visible BOOLEAN NOT NULL DEFAULT TRUE,
    created_at TIMESTAMP,
    updated_at TIMESTAMP,

    -- Constraints
    CONSTRAINT uq_frontend_platform_menu_config
        UNIQUE (frontend_type, platform_id, menu_item_id),
    CONSTRAINT uq_frontend_user_menu_config
        UNIQUE (frontend_type, user_id, menu_item_id),
    CONSTRAINT ck_admin_menu_config_scope
        CHECK ((platform_id IS NOT NULL AND user_id IS NULL) OR
               (platform_id IS NULL AND user_id IS NOT NULL)),
    CONSTRAINT ck_user_scope_admin_only
        CHECK ((user_id IS NULL) OR (frontend_type = 'admin'))
);
```

### Examples

```python
from app.modules.core.models import AdminMenuConfig
from app.modules.enums import FrontendType

# Platform "OMS" hides inventory from admin panel
AdminMenuConfig(
    frontend_type=FrontendType.ADMIN,
    platform_id=oms_platform.id,  # OMS platform
    menu_item_id="inventory",
    is_visible=False
)

# Platform "OMS" hides letzshop from store dashboard
AdminMenuConfig(
    frontend_type=FrontendType.STORE,
    platform_id=oms_platform.id,  # OMS platform
    menu_item_id="letzshop",
    is_visible=False
)

# Super admin "john" hides code-quality from their admin panel
AdminMenuConfig(
    frontend_type=FrontendType.ADMIN,
    user_id=5,
    menu_item_id="code-quality",
    is_visible=False
)
```

## Module Integration

### Automatic Menu Discovery

When a module is enabled/disabled for a platform, the menu discovery service automatically includes/excludes its menu items:

```python
# Module enablement check happens automatically in MenuDiscoveryService
enabled_modules = module_service.get_enabled_module_codes(db, platform_id)

# Only modules in enabled_modules have their menus included
for module_code, module_def in MODULES.items():
    if module_code in enabled_modules:
        # Include this module's menus
        pass
```

### Three-Layer Filtering

The final visible menu is computed by three layers:

1. **Module Definitions**: All possible menu items from modules
2. **Module Enablement**: Items from disabled modules are hidden
3. **Visibility Config**: Explicitly hidden items are removed

```
Final Menu = Module Menu Items
           - Items from Disabled Modules
           - Items with is_visible=False in config
           - Items not matching user role (super_admin_only)
```

## Menu Service Integration

The `MenuService` provides the interface used by templates:

```python
from app.modules.core.services import menu_service

# Get menu for rendering in templates
menu_data = menu_service.get_menu_for_rendering(
    db=db,
    frontend_type=FrontendType.ADMIN,
    platform_id=platform_id,
    user_id=user_id,
    is_super_admin=is_super_admin,
    store_code=store_code,  # For store frontend
)

# Returns legacy format for template compatibility:
# {
#     "frontend_type": "admin",
#     "sections": [
#         {
#             "id": "main",
#             "label": None,
#             "items": [{"id": "dashboard", "label": "Dashboard", ...}]
#         },
#         ...
#     ]
# }
```

## Access Control

### Route Protection

```python
from app.api.deps import require_menu_access

@router.get("/admin/inventory")
async def inventory_page(
    _access: bool = Depends(require_menu_access("inventory", FrontendType.ADMIN))
):
    # Only accessible if menu item is visible for user's context
    pass
```

### Sidebar Rendering

The sidebar template filters items based on:
1. Module enablement
2. Visibility configuration
3. User role (super admin check)

```jinja2
{% for section in menu_sections %}
  {% if not section.super_admin_only or current_user.is_super_admin %}
    {% for item in section.items %}
      {% if item.id in visible_menu_items %}
        <a href="{{ item.url }}">{{ item.label }}</a>
      {% endif %}
    {% endfor %}
  {% endif %}
{% endfor %}
```

## UI for Menu Configuration

### Platform Admin Menu Config

Located at `/admin/platform-menu-config` (accessible by super admins):
- Configure which menu items are visible for platform admins
- Configure which menu items are visible for stores on this platform
- Mandatory items cannot be unchecked

### Personal Menu Config (Super Admins)

Located at `/admin/my-menu`:
- Super admins can customize their own admin panel menu
- Personal preferences that don't affect other users
- Useful for hiding rarely-used features

## Adding Menu Items to a Module

1. **Define menu sections and items** in your module's `definition.py`:

```python
# app/modules/mymodule/definition.py

from app.modules.base import ModuleDefinition, MenuSectionDefinition, MenuItemDefinition
from app.modules.enums import FrontendType

mymodule = ModuleDefinition(
    code="mymodule",
    name="My Module",
    description="Description of my module",
    version="1.0.0",
    menus={
        FrontendType.ADMIN: [
            MenuSectionDefinition(
                id="mymodule",
                label_key="mymodule.menu.section",
                icon="star",
                order=50,  # Position in sidebar
                items=[
                    MenuItemDefinition(
                        id="mymodule-main",
                        label_key="mymodule.menu.main",
                        icon="star",
                        route="/admin/mymodule",
                        order=10,
                    ),
                ]
            )
        ],
    }
)
```

2. **Add translation keys** in your module's locales:

```json
// app/modules/mymodule/locales/en.json
{
  "mymodule.menu.section": "My Module",
  "mymodule.menu.main": "Main Page"
}
```

3. **The menu is automatically discovered** - no registration needed.

## Best Practices

### Do

- Define menus in module `definition.py` using dataclasses
- Use translation keys (`label_key`) for labels
- Set appropriate `order` values for positioning
- Mark essential items as `is_mandatory=True`
- Use `is_super_admin_only=True` for admin-only features

### Don't

- Hardcode menu item labels (use i18n keys)
- Store `is_visible=True` in database (default state, wastes space)
- Allow hiding mandatory items via API
- Create menu items outside of module definitions

## Related Documentation

- [Module System](module-system.md) - Module architecture and definitions
- [Multi-Tenant System](multi-tenant.md) - Platform isolation
- [Feature Gating](../implementation/feature-gating-system.md) - Tier-based access
