feat: implement modular platform architecture (Phase 1)

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>
This commit is contained in:
2026-01-25 21:42:44 +01:00
parent 899935ab13
commit 5be42c5907
8 changed files with 1985 additions and 0 deletions

112
app/modules/base.py Normal file
View File

@@ -0,0 +1,112 @@
# app/modules/base.py
"""
Base module definition class.
A Module is a self-contained unit of functionality that can be enabled/disabled
per platform. Each module contains:
- Features: Granular capabilities for tier-based access control
- Menu items: Sidebar entries per frontend type
- Routes: API and page routes (future: dynamically registered)
"""
from dataclasses import dataclass, field
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from fastapi import APIRouter
from models.database.admin_menu_config import FrontendType
@dataclass
class ModuleDefinition:
"""
Definition of a platform module.
A module groups related functionality that can be enabled/disabled per platform.
Core modules cannot be disabled and are always available.
Attributes:
code: Unique identifier (e.g., "billing", "marketplace")
name: Display name (e.g., "Billing & Subscriptions")
description: Description of what this module provides
requires: List of module codes this module depends on
features: List of feature codes this module provides
menu_items: Dict mapping FrontendType to list of menu item IDs
is_core: Core modules cannot be disabled
admin_router: FastAPI router for admin routes (future)
vendor_router: FastAPI router for vendor routes (future)
Example:
billing_module = ModuleDefinition(
code="billing",
name="Billing & Subscriptions",
description="Subscription tiers, billing history, and payment processing",
features=["subscription_management", "billing_history", "stripe_integration"],
menu_items={
FrontendType.ADMIN: ["subscription-tiers", "subscriptions", "billing-history"],
FrontendType.VENDOR: ["billing"],
},
)
"""
# Identity
code: str
name: str
description: str = ""
# Dependencies
requires: list[str] = field(default_factory=list)
# Components
features: list[str] = field(default_factory=list)
menu_items: dict[FrontendType, list[str]] = field(default_factory=dict)
# Status
is_core: bool = False
# Routes (registered dynamically) - Future implementation
admin_router: "APIRouter | None" = None
vendor_router: "APIRouter | None" = None
def get_menu_items(self, frontend_type: FrontendType) -> list[str]:
"""Get menu item IDs for a specific frontend type."""
return self.menu_items.get(frontend_type, [])
def get_all_menu_items(self) -> set[str]:
"""Get all menu item IDs across all frontend types."""
all_items = set()
for items in self.menu_items.values():
all_items.update(items)
return all_items
def has_feature(self, feature_code: str) -> bool:
"""Check if this module provides a specific feature."""
return feature_code in self.features
def has_menu_item(self, menu_item_id: str) -> bool:
"""Check if this module provides a specific menu item."""
return menu_item_id in self.get_all_menu_items()
def check_dependencies(self, enabled_modules: set[str]) -> list[str]:
"""
Check if all required modules are enabled.
Args:
enabled_modules: Set of enabled module codes
Returns:
List of missing required module codes
"""
return [req for req in self.requires if req not in enabled_modules]
def __hash__(self) -> int:
return hash(self.code)
def __eq__(self, other: object) -> bool:
if isinstance(other, ModuleDefinition):
return self.code == other.code
return False
def __repr__(self) -> str:
return f"<Module({self.code}, core={self.is_core})>"