# models/database/admin_menu_config.py """ Menu visibility configuration for admin and vendor frontends. Supports two frontend types: - 'admin': Admin panel menus (for super admins and platform admins) - 'vendor': Vendor dashboard menus (configured per platform) Supports two scopes: - Platform-level: Menu config for a platform (platform_id is set) → For admin frontend: applies to platform admins → For vendor frontend: applies to all vendors on that platform - User-level: Menu config for a specific super admin (user_id is set) → Only for admin frontend (super admins configuring their own menu) Design: - Opt-out model: All items visible by default, store hidden items - Mandatory items: Some items cannot be hidden (defined per frontend type) - Only stores non-default state (is_visible=False) to keep table small """ from sqlalchemy import ( Boolean, CheckConstraint, Column, Enum, ForeignKey, Index, Integer, String, UniqueConstraint, ) from sqlalchemy.orm import relationship from app.core.database import Base from models.database.base import TimestampMixin # Import FrontendType and MANDATORY_MENU_ITEMS from the central location # and re-export for backward compatibility with existing imports. # These were moved to app.modules.enums to break a circular import: # app.modules.base -> models.database -> model discovery -> module definitions -> app.modules.base from app.modules.enums import FrontendType, MANDATORY_MENU_ITEMS class AdminMenuConfig(Base, TimestampMixin): """ Menu visibility configuration for admin and vendor frontends. Supports two frontend types: - 'admin': Admin panel menus - 'vendor': Vendor dashboard menus Supports two scopes: - Platform scope: platform_id is set → Admin: applies to platform admins of that platform → Vendor: applies to all vendors on that platform - User scope: user_id is set (admin frontend only) → Applies to a specific super admin user Resolution order for admin frontend: - Platform admins: Check platform config → fall back to default - Super admins: Check user config → fall back to default Resolution order for vendor frontend: - Check platform config → fall back to default Examples: - Platform "OMS" wants to hide "inventory" from admin panel → frontend_type='admin', platform_id=1, menu_item_id="inventory", is_visible=False - Platform "OMS" wants to hide "letzshop" from vendor dashboard → frontend_type='vendor', platform_id=1, menu_item_id="letzshop", is_visible=False - Super admin "john" wants to hide "code-quality" from their admin panel → frontend_type='admin', user_id=5, menu_item_id="code-quality", is_visible=False """ __tablename__ = "admin_menu_configs" id = Column(Integer, primary_key=True, index=True) # ======================================================================== # Frontend Type # ======================================================================== frontend_type = Column( Enum(FrontendType, values_callable=lambda obj: [e.value for e in obj]), nullable=False, default=FrontendType.ADMIN, index=True, comment="Which frontend this config applies to (admin or vendor)", ) # ======================================================================== # Scope: Platform scope OR User scope (for admin frontend only) # ======================================================================== platform_id = Column( Integer, ForeignKey("platforms.id", ondelete="CASCADE"), nullable=True, index=True, comment="Platform scope - applies to users/vendors of this platform", ) user_id = Column( Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=True, index=True, comment="User scope - applies to this specific super admin (admin frontend only)", ) # ======================================================================== # Menu Item Configuration # ======================================================================== menu_item_id = Column( String(50), nullable=False, index=True, comment="Menu item identifier from registry (e.g., 'products', 'inventory')", ) is_visible = Column( Boolean, default=True, nullable=False, comment="Whether this menu item is visible (False = hidden)", ) # ======================================================================== # Relationships # ======================================================================== platform = relationship( "Platform", back_populates="menu_configs", ) user = relationship( "User", back_populates="menu_configs", ) # ======================================================================== # Constraints # ======================================================================== __table_args__ = ( # Unique constraint: one config per frontend+platform+menu_item UniqueConstraint( "frontend_type", "platform_id", "menu_item_id", name="uq_frontend_platform_menu_config", ), # Unique constraint: one config per frontend+user+menu_item UniqueConstraint( "frontend_type", "user_id", "menu_item_id", name="uq_frontend_user_menu_config", ), # Check: exactly one scope must be set (platform_id XOR user_id) CheckConstraint( "(platform_id IS NOT NULL AND user_id IS NULL) OR " "(platform_id IS NULL AND user_id IS NOT NULL)", name="ck_admin_menu_config_scope", ), # Check: user_id scope only allowed for admin frontend CheckConstraint( "(user_id IS NULL) OR (frontend_type = 'admin')", name="ck_user_scope_admin_only", ), # Performance indexes Index( "idx_admin_menu_config_frontend_platform", "frontend_type", "platform_id", ), Index( "idx_admin_menu_config_frontend_user", "frontend_type", "user_id", ), Index( "idx_admin_menu_config_platform_visible", "platform_id", "is_visible", ), Index( "idx_admin_menu_config_user_visible", "user_id", "is_visible", ), ) # ======================================================================== # Properties # ======================================================================== @property def scope_type(self) -> str: """Get the scope type for this config.""" if self.platform_id: return "platform" return "user" @property def scope_id(self) -> int: """Get the scope ID (platform_id or user_id).""" return self.platform_id or self.user_id def __repr__(self) -> str: scope = f"platform_id={self.platform_id}" if self.platform_id else f"user_id={self.user_id}" return ( f"" )