# app/modules/tenancy/services/tenancy_features.py """ Tenancy feature provider for the billing feature system. Declares tenancy-related billable features (team member limits, role-based access) and provides usage tracking queries for feature gating. The team_members feature tracks how many active users a merchant has across their stores on a platform. """ from __future__ import annotations import logging from typing import TYPE_CHECKING from sqlalchemy import func from app.modules.contracts.features import ( FeatureDeclaration, FeatureScope, FeatureType, FeatureUsage, ) if TYPE_CHECKING: from sqlalchemy.orm import Session logger = logging.getLogger(__name__) class TenancyFeatureProvider: """Feature provider for the tenancy module. Declares: - team_members: quantitative merchant-level limit on active team members - single_user: binary merchant-level feature for single-user mode - team_basic: binary merchant-level feature for basic team support - team_roles: binary merchant-level feature for role-based access - audit_log: binary merchant-level feature for audit logging """ @property def feature_category(self) -> str: return "tenancy" def get_feature_declarations(self) -> list[FeatureDeclaration]: return [ FeatureDeclaration( code="team_members", name_key="tenancy.features.team_members.name", description_key="tenancy.features.team_members.description", category="tenancy", feature_type=FeatureType.QUANTITATIVE, scope=FeatureScope.MERCHANT, default_limit=1, unit_key="tenancy.features.team_members.unit", ui_icon="users", display_order=10, ), FeatureDeclaration( code="single_user", name_key="tenancy.features.single_user.name", description_key="tenancy.features.single_user.description", category="tenancy", feature_type=FeatureType.BINARY, scope=FeatureScope.MERCHANT, ui_icon="user", display_order=20, ), FeatureDeclaration( code="team_basic", name_key="tenancy.features.team_basic.name", description_key="tenancy.features.team_basic.description", category="tenancy", feature_type=FeatureType.BINARY, scope=FeatureScope.MERCHANT, ui_icon="user-plus", display_order=30, ), FeatureDeclaration( code="team_roles", name_key="tenancy.features.team_roles.name", description_key="tenancy.features.team_roles.description", category="tenancy", feature_type=FeatureType.BINARY, scope=FeatureScope.MERCHANT, ui_icon="shield", display_order=40, ), FeatureDeclaration( code="audit_log", name_key="tenancy.features.audit_log.name", description_key="tenancy.features.audit_log.description", category="tenancy", feature_type=FeatureType.BINARY, scope=FeatureScope.MERCHANT, ui_icon="clipboard-list", display_order=50, ), ] def get_store_usage( self, db: Session, store_id: int, ) -> list[FeatureUsage]: # team_members is MERCHANT-scoped, not applicable at store level return [] def get_merchant_usage( self, db: Session, merchant_id: int, platform_id: int, ) -> list[FeatureUsage]: from app.modules.tenancy.models.store import Store, StoreUser from app.modules.tenancy.models.store_platform import StorePlatform from app.modules.tenancy.models.user import User # Count active users associated with stores owned by this merchant count = ( db.query(func.count(func.distinct(User.id))) .join(StoreUser, User.id == StoreUser.user_id) .join(Store, StoreUser.store_id == Store.id) .join(StorePlatform, Store.id == StorePlatform.store_id) .filter( Store.merchant_id == merchant_id, StorePlatform.platform_id == platform_id, User.is_active == True, # noqa: E712 ) .scalar() or 0 ) return [ FeatureUsage( feature_code="team_members", current_count=count, label="Team members", ), ] # Singleton instance for module registration tenancy_feature_provider = TenancyFeatureProvider() __all__ = [ "TenancyFeatureProvider", "tenancy_feature_provider", ]