Add module system for enabling/disabling feature bundles per platform. Module System: - ModuleDefinition dataclass for defining modules - 12 modules: core, platform-admin, billing, inventory, orders, marketplace, customers, cms, analytics, messaging, dev-tools, monitoring - Core modules (core, platform-admin) cannot be disabled - Module dependencies (e.g., marketplace requires inventory) MenuService Integration: - Menu items filtered by module enablement - MenuItemConfig includes is_module_enabled and module_code fields - Module-disabled items hidden from sidebar Platform Configuration: - BasePlatformConfig.enabled_modules property - OMS: all modules enabled (full commerce) - Loyalty: focused subset (no billing/inventory/orders/marketplace) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
812 lines
28 KiB
Python
812 lines
28 KiB
Python
# app/services/menu_service.py
|
|
"""
|
|
Menu service for platform-specific menu configuration.
|
|
|
|
Provides:
|
|
- Menu visibility checking based on platform/user configuration
|
|
- Module-based filtering (menu items only shown if module is enabled)
|
|
- Filtered menu rendering for frontends
|
|
- Menu configuration management (super admin only)
|
|
- Mandatory item enforcement
|
|
|
|
Menu Resolution Order:
|
|
1. Module enablement: Is the module providing this item enabled?
|
|
2. Visibility config: Is this item explicitly shown/hidden?
|
|
3. Mandatory status: Is this item mandatory (always visible)?
|
|
|
|
Usage:
|
|
from app.services.menu_service import menu_service
|
|
|
|
# Check if menu item is accessible
|
|
if menu_service.can_access_menu_item(db, FrontendType.ADMIN, "inventory", platform_id=1):
|
|
...
|
|
|
|
# Get filtered menu for rendering
|
|
menu = menu_service.get_menu_for_rendering(db, FrontendType.ADMIN, platform_id=1)
|
|
|
|
# Update menu visibility (super admin)
|
|
menu_service.update_menu_visibility(db, FrontendType.ADMIN, "inventory", False, platform_id=1)
|
|
"""
|
|
|
|
import logging
|
|
from copy import deepcopy
|
|
from dataclasses import dataclass
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.config.menu_registry import (
|
|
ADMIN_MENU_REGISTRY,
|
|
VENDOR_MENU_REGISTRY,
|
|
get_all_menu_item_ids,
|
|
get_menu_item,
|
|
is_super_admin_only_item,
|
|
)
|
|
from app.modules.service import module_service
|
|
from models.database.admin_menu_config import (
|
|
AdminMenuConfig,
|
|
FrontendType,
|
|
MANDATORY_MENU_ITEMS,
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@dataclass
|
|
class MenuItemConfig:
|
|
"""Menu item configuration for admin UI."""
|
|
|
|
id: str
|
|
label: str
|
|
icon: str
|
|
url: str
|
|
section_id: str
|
|
section_label: str | None
|
|
is_visible: bool
|
|
is_mandatory: bool
|
|
is_super_admin_only: bool
|
|
is_module_enabled: bool = True # Whether the module providing this item is enabled
|
|
module_code: str | None = None # Module that provides this item
|
|
|
|
|
|
class MenuService:
|
|
"""
|
|
Service for menu visibility configuration and rendering.
|
|
|
|
Menu visibility is an opt-in model:
|
|
- All items are hidden by default (except mandatory)
|
|
- Database stores explicitly shown items (is_visible=True)
|
|
- Mandatory items are always visible and cannot be hidden
|
|
"""
|
|
|
|
# =========================================================================
|
|
# Menu Access Checking
|
|
# =========================================================================
|
|
|
|
def can_access_menu_item(
|
|
self,
|
|
db: Session,
|
|
frontend_type: FrontendType,
|
|
menu_item_id: str,
|
|
platform_id: int | None = None,
|
|
user_id: int | None = None,
|
|
) -> bool:
|
|
"""
|
|
Check if a menu item is accessible for a given scope.
|
|
|
|
Checks in order:
|
|
1. Menu item exists in registry
|
|
2. Module providing this item is enabled (if platform_id given)
|
|
3. Mandatory status
|
|
4. Visibility configuration
|
|
|
|
Args:
|
|
db: Database session
|
|
frontend_type: Which frontend (admin or vendor)
|
|
menu_item_id: Menu item identifier
|
|
platform_id: Platform ID (for platform admins and vendors)
|
|
user_id: User ID (for super admins only)
|
|
|
|
Returns:
|
|
True if menu item is visible/accessible
|
|
"""
|
|
# Validate menu item exists in registry
|
|
all_items = get_all_menu_item_ids(frontend_type)
|
|
if menu_item_id not in all_items:
|
|
logger.warning(f"Unknown menu item: {menu_item_id} for {frontend_type.value}")
|
|
return False
|
|
|
|
# Check module enablement if platform is specified
|
|
if platform_id:
|
|
if not module_service.is_menu_item_module_enabled(
|
|
db, platform_id, menu_item_id, frontend_type
|
|
):
|
|
return False
|
|
|
|
# Mandatory items are always accessible (if module is enabled)
|
|
if menu_item_id in MANDATORY_MENU_ITEMS.get(frontend_type, set()):
|
|
return True
|
|
|
|
# No scope specified - show all by default (fallback for unconfigured)
|
|
if not platform_id and not user_id:
|
|
return True
|
|
|
|
# Get visibility from database (opt-in: must be explicitly shown)
|
|
shown_items = self._get_shown_items(db, frontend_type, platform_id, user_id)
|
|
|
|
# If no configuration exists, show all items (first-time setup)
|
|
if shown_items is None:
|
|
return True
|
|
|
|
return menu_item_id in shown_items
|
|
|
|
def get_visible_menu_items(
|
|
self,
|
|
db: Session,
|
|
frontend_type: FrontendType,
|
|
platform_id: int | None = None,
|
|
user_id: int | None = None,
|
|
) -> set[str]:
|
|
"""
|
|
Get set of visible menu item IDs for a scope.
|
|
|
|
Filters by:
|
|
1. Module enablement (if platform_id given)
|
|
2. Visibility configuration
|
|
3. Mandatory status
|
|
|
|
Args:
|
|
db: Database session
|
|
frontend_type: Which frontend (admin or vendor)
|
|
platform_id: Platform ID (for platform admins and vendors)
|
|
user_id: User ID (for super admins only)
|
|
|
|
Returns:
|
|
Set of visible menu item IDs
|
|
"""
|
|
all_items = get_all_menu_item_ids(frontend_type)
|
|
mandatory_items = MANDATORY_MENU_ITEMS.get(frontend_type, set())
|
|
|
|
# Filter by module enablement if platform is specified
|
|
if platform_id:
|
|
module_available_items = module_service.get_module_menu_items(
|
|
db, platform_id, frontend_type
|
|
)
|
|
# Only keep items from enabled modules (or items not associated with any module)
|
|
all_items = module_service.filter_menu_items_by_modules(
|
|
db, platform_id, all_items, frontend_type
|
|
)
|
|
# Mandatory items from enabled modules only
|
|
mandatory_items = mandatory_items & all_items
|
|
|
|
# No scope specified - return all items (fallback)
|
|
if not platform_id and not user_id:
|
|
return all_items
|
|
|
|
shown_items = self._get_shown_items(db, frontend_type, platform_id, user_id)
|
|
|
|
# If no configuration exists yet, show all items (first-time setup)
|
|
if shown_items is None:
|
|
return all_items
|
|
|
|
# Shown items plus mandatory (mandatory are always visible)
|
|
# But only if module is enabled
|
|
visible = (shown_items | mandatory_items) & all_items
|
|
return visible
|
|
|
|
def _get_shown_items(
|
|
self,
|
|
db: Session,
|
|
frontend_type: FrontendType,
|
|
platform_id: int | None = None,
|
|
user_id: int | None = None,
|
|
) -> set[str] | None:
|
|
"""
|
|
Get set of shown menu item IDs from database.
|
|
|
|
Returns:
|
|
Set of shown item IDs, or None if no configuration exists.
|
|
"""
|
|
query = db.query(AdminMenuConfig).filter(
|
|
AdminMenuConfig.frontend_type == frontend_type,
|
|
)
|
|
|
|
if platform_id:
|
|
query = query.filter(AdminMenuConfig.platform_id == platform_id)
|
|
elif user_id:
|
|
query = query.filter(AdminMenuConfig.user_id == user_id)
|
|
else:
|
|
return None
|
|
|
|
# Check if any config exists for this scope
|
|
configs = query.all()
|
|
if not configs:
|
|
return None # No config = use defaults (all visible)
|
|
|
|
# Return only items marked as visible
|
|
return {c.menu_item_id for c in configs if c.is_visible}
|
|
|
|
# =========================================================================
|
|
# Menu Rendering
|
|
# =========================================================================
|
|
|
|
def get_menu_for_rendering(
|
|
self,
|
|
db: Session,
|
|
frontend_type: FrontendType,
|
|
platform_id: int | None = None,
|
|
user_id: int | None = None,
|
|
is_super_admin: bool = False,
|
|
) -> dict:
|
|
"""
|
|
Get filtered menu structure for frontend rendering.
|
|
|
|
Filters by:
|
|
1. Module enablement (items from disabled modules are removed)
|
|
2. Visibility configuration
|
|
3. Super admin status
|
|
|
|
Args:
|
|
db: Database session
|
|
frontend_type: Which frontend (admin or vendor)
|
|
platform_id: Platform ID (for platform admins and vendors)
|
|
user_id: User ID (for super admins only)
|
|
is_super_admin: Whether user is super admin (affects admin-only sections)
|
|
|
|
Returns:
|
|
Filtered menu structure ready for rendering
|
|
"""
|
|
registry = (
|
|
ADMIN_MENU_REGISTRY if frontend_type == FrontendType.ADMIN else VENDOR_MENU_REGISTRY
|
|
)
|
|
|
|
visible_items = self.get_visible_menu_items(db, frontend_type, platform_id, user_id)
|
|
|
|
# Deep copy to avoid modifying the registry
|
|
filtered_menu = deepcopy(registry)
|
|
filtered_sections = []
|
|
|
|
for section in filtered_menu["sections"]:
|
|
# Skip super_admin_only sections if user is not super admin
|
|
if section.get("super_admin_only") and not is_super_admin:
|
|
continue
|
|
|
|
# Filter items to only visible ones
|
|
# Also skip super_admin_only items if user is not super admin
|
|
filtered_items = [
|
|
item for item in section["items"]
|
|
if item["id"] in visible_items
|
|
and (not item.get("super_admin_only") or is_super_admin)
|
|
]
|
|
|
|
# Only include section if it has visible items
|
|
if filtered_items:
|
|
section["items"] = filtered_items
|
|
filtered_sections.append(section)
|
|
|
|
filtered_menu["sections"] = filtered_sections
|
|
return filtered_menu
|
|
|
|
# =========================================================================
|
|
# Menu Configuration (Super Admin)
|
|
# =========================================================================
|
|
|
|
def get_platform_menu_config(
|
|
self,
|
|
db: Session,
|
|
frontend_type: FrontendType,
|
|
platform_id: int,
|
|
) -> list[MenuItemConfig]:
|
|
"""
|
|
Get full menu configuration for a platform (for admin UI).
|
|
|
|
Returns all menu items with their visibility status and module info.
|
|
Items from disabled modules are marked with is_module_enabled=False.
|
|
|
|
Args:
|
|
db: Database session
|
|
frontend_type: Which frontend (admin or vendor)
|
|
platform_id: Platform ID
|
|
|
|
Returns:
|
|
List of MenuItemConfig with current visibility state and module info
|
|
"""
|
|
from app.modules.registry import get_menu_item_module
|
|
|
|
registry = (
|
|
ADMIN_MENU_REGISTRY if frontend_type == FrontendType.ADMIN else VENDOR_MENU_REGISTRY
|
|
)
|
|
|
|
shown_items = self._get_shown_items(db, frontend_type, platform_id=platform_id)
|
|
mandatory_items = MANDATORY_MENU_ITEMS.get(frontend_type, set())
|
|
|
|
# Get module-available items
|
|
module_available_items = module_service.filter_menu_items_by_modules(
|
|
db, platform_id, get_all_menu_item_ids(frontend_type), frontend_type
|
|
)
|
|
|
|
result = []
|
|
for section in registry["sections"]:
|
|
section_id = section["id"]
|
|
section_label = section.get("label")
|
|
is_super_admin_section = section.get("super_admin_only", False)
|
|
|
|
for item in section["items"]:
|
|
item_id = item["id"]
|
|
|
|
# Check if module is enabled for this item
|
|
is_module_enabled = item_id in module_available_items
|
|
module_code = get_menu_item_module(item_id, frontend_type)
|
|
|
|
# If no config exists (shown_items is None), show all by default
|
|
# Otherwise, item is visible if in shown_items or mandatory
|
|
# Note: visibility config is independent of module enablement
|
|
is_visible = (
|
|
shown_items is None
|
|
or item_id in shown_items
|
|
or item_id in mandatory_items
|
|
)
|
|
|
|
# Item is super admin only if section or item is marked as such
|
|
is_item_super_admin_only = is_super_admin_section or item.get("super_admin_only", False)
|
|
|
|
result.append(
|
|
MenuItemConfig(
|
|
id=item_id,
|
|
label=item["label"],
|
|
icon=item["icon"],
|
|
url=item["url"],
|
|
section_id=section_id,
|
|
section_label=section_label,
|
|
is_visible=is_visible,
|
|
is_mandatory=item_id in mandatory_items,
|
|
is_super_admin_only=is_item_super_admin_only,
|
|
is_module_enabled=is_module_enabled,
|
|
module_code=module_code,
|
|
)
|
|
)
|
|
|
|
return result
|
|
|
|
def get_user_menu_config(
|
|
self,
|
|
db: Session,
|
|
user_id: int,
|
|
) -> list[MenuItemConfig]:
|
|
"""
|
|
Get admin menu configuration for a super admin user.
|
|
|
|
Super admins don't have platform context, so all modules are shown.
|
|
Module enablement is always True for super admin menu config.
|
|
|
|
Args:
|
|
db: Database session
|
|
user_id: Super admin user ID
|
|
|
|
Returns:
|
|
List of MenuItemConfig with current visibility state
|
|
"""
|
|
from app.modules.registry import get_menu_item_module
|
|
|
|
shown_items = self._get_shown_items(
|
|
db, FrontendType.ADMIN, user_id=user_id
|
|
)
|
|
mandatory_items = MANDATORY_MENU_ITEMS.get(FrontendType.ADMIN, set())
|
|
|
|
result = []
|
|
for section in ADMIN_MENU_REGISTRY["sections"]:
|
|
section_id = section["id"]
|
|
section_label = section.get("label")
|
|
is_super_admin_section = section.get("super_admin_only", False)
|
|
|
|
for item in section["items"]:
|
|
item_id = item["id"]
|
|
module_code = get_menu_item_module(item_id, FrontendType.ADMIN)
|
|
|
|
# If no config exists (shown_items is None), show all by default
|
|
# Otherwise, item is visible if in shown_items or mandatory
|
|
is_visible = (
|
|
shown_items is None
|
|
or item_id in shown_items
|
|
or item_id in mandatory_items
|
|
)
|
|
# Item is super admin only if section or item is marked as such
|
|
is_item_super_admin_only = is_super_admin_section or item.get("super_admin_only", False)
|
|
result.append(
|
|
MenuItemConfig(
|
|
id=item_id,
|
|
label=item["label"],
|
|
icon=item["icon"],
|
|
url=item["url"],
|
|
section_id=section_id,
|
|
section_label=section_label,
|
|
is_visible=is_visible,
|
|
is_mandatory=item_id in mandatory_items,
|
|
is_super_admin_only=is_item_super_admin_only,
|
|
is_module_enabled=True, # Super admins see all modules
|
|
module_code=module_code,
|
|
)
|
|
)
|
|
|
|
return result
|
|
|
|
def update_menu_visibility(
|
|
self,
|
|
db: Session,
|
|
frontend_type: FrontendType,
|
|
menu_item_id: str,
|
|
is_visible: bool,
|
|
platform_id: int | None = None,
|
|
user_id: int | None = None,
|
|
) -> None:
|
|
"""
|
|
Update visibility for a menu item (opt-in model).
|
|
|
|
In the opt-in model:
|
|
- is_visible=True: Create/update record to show item
|
|
- is_visible=False: Remove record (item hidden by default)
|
|
|
|
Args:
|
|
db: Database session
|
|
frontend_type: Which frontend (admin or vendor)
|
|
menu_item_id: Menu item identifier
|
|
is_visible: Whether the item should be visible
|
|
platform_id: Platform ID (for platform-scoped config)
|
|
user_id: User ID (for user-scoped config, admin frontend only)
|
|
|
|
Raises:
|
|
ValueError: If menu item is mandatory or doesn't exist
|
|
ValueError: If neither platform_id nor user_id is provided
|
|
ValueError: If user_id is provided for vendor frontend
|
|
"""
|
|
# Validate menu item exists
|
|
all_items = get_all_menu_item_ids(frontend_type)
|
|
if menu_item_id not in all_items:
|
|
raise ValueError(f"Unknown menu item: {menu_item_id}")
|
|
|
|
# Check if mandatory - mandatory items are always visible, no need to store
|
|
mandatory = MANDATORY_MENU_ITEMS.get(frontend_type, set())
|
|
if menu_item_id in mandatory:
|
|
if not is_visible:
|
|
raise ValueError(f"Cannot hide mandatory menu item: {menu_item_id}")
|
|
# Mandatory items don't need explicit config, they're always visible
|
|
return
|
|
|
|
# Validate scope
|
|
if not platform_id and not user_id:
|
|
raise ValueError("Either platform_id or user_id must be provided")
|
|
|
|
if user_id and frontend_type == FrontendType.VENDOR:
|
|
raise ValueError("User-scoped config not supported for vendor frontend")
|
|
|
|
# Find existing config
|
|
query = db.query(AdminMenuConfig).filter(
|
|
AdminMenuConfig.frontend_type == frontend_type,
|
|
AdminMenuConfig.menu_item_id == menu_item_id,
|
|
)
|
|
|
|
if platform_id:
|
|
query = query.filter(AdminMenuConfig.platform_id == platform_id)
|
|
else:
|
|
query = query.filter(AdminMenuConfig.user_id == user_id)
|
|
|
|
config = query.first()
|
|
|
|
if is_visible:
|
|
# Opt-in: Create or update config to visible (explicitly show)
|
|
if config:
|
|
config.is_visible = True
|
|
else:
|
|
config = AdminMenuConfig(
|
|
frontend_type=frontend_type,
|
|
platform_id=platform_id,
|
|
user_id=user_id,
|
|
menu_item_id=menu_item_id,
|
|
is_visible=True,
|
|
)
|
|
db.add(config)
|
|
logger.info(
|
|
f"Set menu config visible: {frontend_type.value}/{menu_item_id} "
|
|
f"(platform_id={platform_id}, user_id={user_id})"
|
|
)
|
|
else:
|
|
# Opt-in: Remove config to hide (hidden is default)
|
|
if config:
|
|
db.delete(config)
|
|
logger.info(
|
|
f"Removed menu config (hidden): {frontend_type.value}/{menu_item_id} "
|
|
f"(platform_id={platform_id}, user_id={user_id})"
|
|
)
|
|
|
|
def bulk_update_menu_visibility(
|
|
self,
|
|
db: Session,
|
|
frontend_type: FrontendType,
|
|
visibility_map: dict[str, bool],
|
|
platform_id: int | None = None,
|
|
user_id: int | None = None,
|
|
) -> None:
|
|
"""
|
|
Update visibility for multiple menu items at once.
|
|
|
|
Args:
|
|
db: Database session
|
|
frontend_type: Which frontend (admin or vendor)
|
|
visibility_map: Dict of menu_item_id -> is_visible
|
|
platform_id: Platform ID (for platform-scoped config)
|
|
user_id: User ID (for user-scoped config, admin frontend only)
|
|
"""
|
|
for menu_item_id, is_visible in visibility_map.items():
|
|
try:
|
|
self.update_menu_visibility(
|
|
db, frontend_type, menu_item_id, is_visible, platform_id, user_id
|
|
)
|
|
except ValueError as e:
|
|
logger.warning(f"Skipping {menu_item_id}: {e}")
|
|
|
|
def reset_platform_menu_config(
|
|
self,
|
|
db: Session,
|
|
frontend_type: FrontendType,
|
|
platform_id: int,
|
|
) -> None:
|
|
"""
|
|
Reset menu configuration for a platform to defaults (all hidden except mandatory).
|
|
|
|
In opt-in model, reset means hide everything so user can opt-in to what they want.
|
|
|
|
Args:
|
|
db: Database session
|
|
frontend_type: Which frontend (admin or vendor)
|
|
platform_id: Platform ID
|
|
"""
|
|
# Delete all existing records
|
|
deleted = (
|
|
db.query(AdminMenuConfig)
|
|
.filter(
|
|
AdminMenuConfig.frontend_type == frontend_type,
|
|
AdminMenuConfig.platform_id == platform_id,
|
|
)
|
|
.delete()
|
|
)
|
|
logger.info(
|
|
f"Reset menu config for platform {platform_id} ({frontend_type.value}): "
|
|
f"deleted {deleted} rows"
|
|
)
|
|
|
|
# Create records with is_visible=False for all non-mandatory items
|
|
# This makes "reset" mean "hide everything except mandatory"
|
|
all_items = get_all_menu_item_ids(frontend_type)
|
|
mandatory_items = MANDATORY_MENU_ITEMS.get(frontend_type, set())
|
|
|
|
for item_id in all_items:
|
|
if item_id not in mandatory_items:
|
|
config = AdminMenuConfig(
|
|
frontend_type=frontend_type,
|
|
platform_id=platform_id,
|
|
user_id=None,
|
|
menu_item_id=item_id,
|
|
is_visible=False,
|
|
)
|
|
db.add(config)
|
|
|
|
logger.info(
|
|
f"Created {len(all_items) - len(mandatory_items)} hidden records for platform {platform_id}"
|
|
)
|
|
|
|
def reset_user_menu_config(
|
|
self,
|
|
db: Session,
|
|
user_id: int,
|
|
) -> None:
|
|
"""
|
|
Reset menu configuration for a super admin user to defaults (all hidden except mandatory).
|
|
|
|
In opt-in model, reset means hide everything so user can opt-in to what they want.
|
|
|
|
Args:
|
|
db: Database session
|
|
user_id: Super admin user ID
|
|
"""
|
|
# Delete all existing records
|
|
deleted = (
|
|
db.query(AdminMenuConfig)
|
|
.filter(
|
|
AdminMenuConfig.frontend_type == FrontendType.ADMIN,
|
|
AdminMenuConfig.user_id == user_id,
|
|
)
|
|
.delete()
|
|
)
|
|
logger.info(f"Reset menu config for user {user_id}: deleted {deleted} rows")
|
|
|
|
# Create records with is_visible=False for all non-mandatory items
|
|
all_items = get_all_menu_item_ids(FrontendType.ADMIN)
|
|
mandatory_items = MANDATORY_MENU_ITEMS.get(FrontendType.ADMIN, set())
|
|
|
|
for item_id in all_items:
|
|
if item_id not in mandatory_items:
|
|
config = AdminMenuConfig(
|
|
frontend_type=FrontendType.ADMIN,
|
|
platform_id=None,
|
|
user_id=user_id,
|
|
menu_item_id=item_id,
|
|
is_visible=False,
|
|
)
|
|
db.add(config)
|
|
|
|
logger.info(
|
|
f"Created {len(all_items) - len(mandatory_items)} hidden records for user {user_id}"
|
|
)
|
|
|
|
def show_all_platform_menu_config(
|
|
self,
|
|
db: Session,
|
|
frontend_type: FrontendType,
|
|
platform_id: int,
|
|
) -> None:
|
|
"""
|
|
Show all menu items for a platform.
|
|
|
|
Args:
|
|
db: Database session
|
|
frontend_type: Which frontend (admin or vendor)
|
|
platform_id: Platform ID
|
|
"""
|
|
# Delete all existing records
|
|
deleted = (
|
|
db.query(AdminMenuConfig)
|
|
.filter(
|
|
AdminMenuConfig.frontend_type == frontend_type,
|
|
AdminMenuConfig.platform_id == platform_id,
|
|
)
|
|
.delete()
|
|
)
|
|
logger.info(
|
|
f"Show all menu config for platform {platform_id} ({frontend_type.value}): "
|
|
f"deleted {deleted} rows"
|
|
)
|
|
|
|
# Create records with is_visible=True for all non-mandatory items
|
|
all_items = get_all_menu_item_ids(frontend_type)
|
|
mandatory_items = MANDATORY_MENU_ITEMS.get(frontend_type, set())
|
|
|
|
for item_id in all_items:
|
|
if item_id not in mandatory_items:
|
|
config = AdminMenuConfig(
|
|
frontend_type=frontend_type,
|
|
platform_id=platform_id,
|
|
user_id=None,
|
|
menu_item_id=item_id,
|
|
is_visible=True,
|
|
)
|
|
db.add(config)
|
|
|
|
logger.info(
|
|
f"Created {len(all_items) - len(mandatory_items)} visible records for platform {platform_id}"
|
|
)
|
|
|
|
def show_all_user_menu_config(
|
|
self,
|
|
db: Session,
|
|
user_id: int,
|
|
) -> None:
|
|
"""
|
|
Show all menu items for a super admin user.
|
|
|
|
Args:
|
|
db: Database session
|
|
user_id: Super admin user ID
|
|
"""
|
|
# Delete all existing records
|
|
deleted = (
|
|
db.query(AdminMenuConfig)
|
|
.filter(
|
|
AdminMenuConfig.frontend_type == FrontendType.ADMIN,
|
|
AdminMenuConfig.user_id == user_id,
|
|
)
|
|
.delete()
|
|
)
|
|
logger.info(f"Show all menu config for user {user_id}: deleted {deleted} rows")
|
|
|
|
# Create records with is_visible=True for all non-mandatory items
|
|
all_items = get_all_menu_item_ids(FrontendType.ADMIN)
|
|
mandatory_items = MANDATORY_MENU_ITEMS.get(FrontendType.ADMIN, set())
|
|
|
|
for item_id in all_items:
|
|
if item_id not in mandatory_items:
|
|
config = AdminMenuConfig(
|
|
frontend_type=FrontendType.ADMIN,
|
|
platform_id=None,
|
|
user_id=user_id,
|
|
menu_item_id=item_id,
|
|
is_visible=True,
|
|
)
|
|
db.add(config)
|
|
|
|
logger.info(
|
|
f"Created {len(all_items) - len(mandatory_items)} visible records for user {user_id}"
|
|
)
|
|
|
|
def initialize_menu_config(
|
|
self,
|
|
db: Session,
|
|
frontend_type: FrontendType,
|
|
platform_id: int | None = None,
|
|
user_id: int | None = None,
|
|
) -> bool:
|
|
"""
|
|
Initialize menu configuration with all items visible.
|
|
|
|
Called when first customizing a menu. Creates records for all items
|
|
so the user can then toggle individual items off.
|
|
|
|
Args:
|
|
db: Database session
|
|
frontend_type: Which frontend (admin or vendor)
|
|
platform_id: Platform ID (for platform-scoped config)
|
|
user_id: User ID (for user-scoped config)
|
|
|
|
Returns:
|
|
True if initialized, False if config already exists with visible items
|
|
"""
|
|
if not platform_id and not user_id:
|
|
return False # No scope specified
|
|
|
|
# Helper to build a fresh query for this scope
|
|
def scope_query():
|
|
q = db.query(AdminMenuConfig).filter(
|
|
AdminMenuConfig.frontend_type == frontend_type,
|
|
)
|
|
if platform_id:
|
|
return q.filter(AdminMenuConfig.platform_id == platform_id)
|
|
else:
|
|
return q.filter(AdminMenuConfig.user_id == user_id)
|
|
|
|
# Check if any visible records exist (valid opt-in config)
|
|
visible_count = scope_query().filter(
|
|
AdminMenuConfig.is_visible == True # noqa: E712
|
|
).count()
|
|
if visible_count > 0:
|
|
logger.debug(f"Config already exists with {visible_count} visible items, skipping init")
|
|
return False # Already initialized
|
|
|
|
# Check if ANY records exist (even is_visible=False from old opt-out model)
|
|
total_count = scope_query().count()
|
|
if total_count > 0:
|
|
# Clean up old records first
|
|
deleted = scope_query().delete(synchronize_session='fetch')
|
|
db.flush() # Ensure deletes are applied before inserts
|
|
logger.info(f"Cleaned up {deleted} old menu config records before initialization")
|
|
|
|
# Get all menu items for this frontend
|
|
all_items = get_all_menu_item_ids(frontend_type)
|
|
mandatory_items = MANDATORY_MENU_ITEMS.get(frontend_type, set())
|
|
|
|
# Create visible records for all non-mandatory items
|
|
for item_id in all_items:
|
|
if item_id not in mandatory_items:
|
|
config = AdminMenuConfig(
|
|
frontend_type=frontend_type,
|
|
platform_id=platform_id,
|
|
user_id=user_id,
|
|
menu_item_id=item_id,
|
|
is_visible=True,
|
|
)
|
|
db.add(config)
|
|
|
|
logger.info(
|
|
f"Initialized menu config with {len(all_items) - len(mandatory_items)} items "
|
|
f"(platform_id={platform_id}, user_id={user_id})"
|
|
)
|
|
return True
|
|
|
|
|
|
# Singleton instance
|
|
menu_service = MenuService()
|
|
|
|
|
|
__all__ = [
|
|
"menu_service",
|
|
"MenuService",
|
|
"MenuItemConfig",
|
|
]
|