# app/modules/tenancy/services/platform_service.py """ Platform Service Business logic for platform management in the Multi-Platform CMS. Platforms represent different business offerings (OMS, Loyalty, Site Builder, Main Marketing). Each platform has its own: - Marketing pages (homepage, pricing, features) - Store defaults (about, terms, privacy) - Configuration and branding """ import logging from dataclasses import dataclass from sqlalchemy import func from sqlalchemy.orm import Session from app.modules.cms.models import ContentPage from app.modules.tenancy.exceptions import ( PlatformNotFoundException, ) from app.modules.tenancy.models import Platform, StorePlatform logger = logging.getLogger(__name__) @dataclass class PlatformStats: """Platform statistics.""" platform_id: int platform_code: str platform_name: str store_count: int platform_pages_count: int store_defaults_count: int store_overrides_count: int = 0 published_pages_count: int = 0 draft_pages_count: int = 0 class PlatformService: """Service for platform operations.""" @staticmethod def get_platform_by_code(db: Session, code: str) -> Platform: """ Get platform by code. Args: db: Database session code: Platform code (oms, loyalty, main, etc.) Returns: Platform object Raises: PlatformNotFoundException: If platform not found """ platform = db.query(Platform).filter(Platform.code == code).first() if not platform: raise PlatformNotFoundException(code) return platform @staticmethod def get_platform_by_code_optional(db: Session, code: str) -> Platform | None: """ Get platform by code, returns None if not found. Args: db: Database session code: Platform code Returns: Platform object or None """ return db.query(Platform).filter(Platform.code == code).first() @staticmethod def get_platform_by_id(db: Session, platform_id: int) -> Platform: """ Get platform by ID. Args: db: Database session platform_id: Platform ID Returns: Platform object Raises: PlatformNotFoundException: If platform not found """ platform = db.query(Platform).filter(Platform.id == platform_id).first() if not platform: raise PlatformNotFoundException(str(platform_id)) return platform @staticmethod def list_platforms( db: Session, include_inactive: bool = False ) -> list[Platform]: """ List all platforms. Args: db: Database session include_inactive: Include inactive platforms Returns: List of Platform objects """ query = db.query(Platform) if not include_inactive: query = query.filter(Platform.is_active == True) return query.order_by(Platform.id).all() @staticmethod def get_store_count(db: Session, platform_id: int) -> int: """ Get count of stores on a platform. Args: db: Database session platform_id: Platform ID Returns: Store count """ return ( db.query(func.count(StorePlatform.store_id)) .filter(StorePlatform.platform_id == platform_id) .scalar() or 0 ) @staticmethod def get_active_store_count(db: Session, platform_id: int) -> int: """ Get count of active stores on a platform. Args: db: Database session platform_id: Platform ID Returns: Active store count """ from app.modules.tenancy.models import Store return ( db.query(func.count(StorePlatform.store_id)) .join(Store, Store.id == StorePlatform.store_id) .filter( StorePlatform.platform_id == platform_id, Store.is_active == True, # noqa: E712 ) .scalar() or 0 ) @staticmethod def get_platform_pages_count(db: Session, platform_id: int) -> int: """ Get count of platform marketing pages. Args: db: Database session platform_id: Platform ID Returns: Platform pages count """ return ( db.query(func.count(ContentPage.id)) .filter( ContentPage.platform_id == platform_id, ContentPage.store_id is None, ContentPage.is_platform_page == True, ) .scalar() or 0 ) @staticmethod def get_store_defaults_count(db: Session, platform_id: int) -> int: """ Get count of store default pages. Args: db: Database session platform_id: Platform ID Returns: Store defaults count """ return ( db.query(func.count(ContentPage.id)) .filter( ContentPage.platform_id == platform_id, ContentPage.store_id is None, ContentPage.is_platform_page == False, ) .scalar() or 0 ) @staticmethod def get_store_overrides_count(db: Session, platform_id: int) -> int: """ Get count of store override pages. Args: db: Database session platform_id: Platform ID Returns: Store overrides count """ return ( db.query(func.count(ContentPage.id)) .filter( ContentPage.platform_id == platform_id, ContentPage.store_id is not None, ) .scalar() or 0 ) @staticmethod def get_published_pages_count(db: Session, platform_id: int) -> int: """ Get count of published pages on a platform. Args: db: Database session platform_id: Platform ID Returns: Published pages count """ return ( db.query(func.count(ContentPage.id)) .filter( ContentPage.platform_id == platform_id, ContentPage.is_published == True, ) .scalar() or 0 ) @staticmethod def get_draft_pages_count(db: Session, platform_id: int) -> int: """ Get count of draft pages on a platform. Args: db: Database session platform_id: Platform ID Returns: Draft pages count """ return ( db.query(func.count(ContentPage.id)) .filter( ContentPage.platform_id == platform_id, ContentPage.is_published == False, ) .scalar() or 0 ) @classmethod def get_platform_stats(cls, db: Session, platform: Platform) -> PlatformStats: """ Get comprehensive statistics for a platform. Args: db: Database session platform: Platform object Returns: PlatformStats dataclass """ return PlatformStats( platform_id=platform.id, platform_code=platform.code, platform_name=platform.name, store_count=cls.get_store_count(db, platform.id), platform_pages_count=cls.get_platform_pages_count(db, platform.id), store_defaults_count=cls.get_store_defaults_count(db, platform.id), store_overrides_count=cls.get_store_overrides_count(db, platform.id), published_pages_count=cls.get_published_pages_count(db, platform.id), draft_pages_count=cls.get_draft_pages_count(db, platform.id), ) @staticmethod def update_platform( db: Session, platform: Platform, update_data: dict ) -> Platform: """ Update platform fields. Note: This method does NOT commit the transaction. The caller (API endpoint) is responsible for committing. Args: db: Database session platform: Platform to update update_data: Dictionary of fields to update Returns: Updated Platform object (with pending changes) """ for field, value in update_data.items(): if hasattr(platform, field): setattr(platform, field, value) logger.info(f"[PLATFORMS] Updated platform: {platform.code}") return platform # Singleton instance for convenience platform_service = PlatformService()