"""Logic module for new applications and roles.""" from typing import Callable from owsresponse import response from sqlalchemy.orm import Session from account.connectors.mysql import db_read_only_session_wrap from account.constants.features import FEATURES from account.models import ( application as app_model, feature as feature_model, subaccount as subaccount_model, types, ) from account.models.types import TenantApplication from account.utils.dataloader_util import format_for_dataloader SubaccountApplicationChecker = Callable[ [types.SubaccountAttributes], types.TenantApplication | None ] ENABLED_APPLICATIONS = [ app_model.CUSTOMER_ACCOUNTING_APP, app_model.INSIGHTS_APP, app_model.WORKSTATION_ANALYTICS_APP, app_model.WORKSTATION_CATALOG_APP, app_model.SETTINGS_APP, ] def get_insights_app_for_subaccount( subaccount_attributes: types.SubaccountAttributes, ) -> types.TenantApplication | None: """ Get insights application for a subaccount based on the company brand the features of the parent vendor. Args: subaccount_attributes (TenantApplication): Subaccount attributes. Return: TenantApplication | None: Insights application with a base role. """ if subaccount_attributes['vendor_attributes']['company_brand']['name'] == 'knr': return None return TenantApplication( application_id=app_model.INSIGHTS_APP, roles=[app_model.INSIGHTS_BASE_ROLE], url=app_model.get_application_url( app_model.INSIGHTS_APP, subaccount_attributes['vendor_attributes']['company_brand']['name'], ), ) def get_ws_analytics_app_for_subaccount( subaccount_attributes: types.SubaccountAttributes, ) -> types.TenantApplication | None: """ Get Workstation analytics application for a subaccount based on the company brand the features of the parent vendor. Args: subaccount_attributes (TenantApplication): Subaccount attributes. Return: TenantApplication | None: Workstation analytics application with a base role. """ if subaccount_attributes['vendor_attributes']['company_brand']['name'] != 'theorchard': return None required_features = [ FEATURES.WORKSTATION_PHYSICAL_REPORTING, FEATURES.WORKSTATION_ANALYTICS_OVERVIEW, ] if not any( feature in subaccount_attributes['vendor_attributes']['features'] for feature in required_features ): return None return TenantApplication( application_id=app_model.WORKSTATION_ANALYTICS_APP, roles=[app_model.WORKSTATION_ANALYTICS_BASE_ROLE], url=app_model.get_application_url( app_model.WORKSTATION_ANALYTICS_APP, subaccount_attributes['vendor_attributes']['company_brand']['name'], ), ) def get_customer_accounting_app_for_subaccount( subaccount_attributes: types.SubaccountAttributes, ) -> types.TenantApplication | None: """ Get Customer accounting application for a subaccount based on the company brand the features of the parent vendor. Args: subaccount_attributes (TenantApplication): Subaccount attributes. Return: TenantApplication | None: Customer accounting application with a base role. """ # TODO: refactor the FEATURES.ACCOUNTING check # for all accounts after backfilling FC for AWAL / KNR company_brand_name = subaccount_attributes['vendor_attributes']['company_brand']['name'] has_feature = ( company_brand_name in ['awal', 'knr'] or FEATURES.ACCOUNTING in subaccount_attributes['vendor_attributes']['features'] ) if not has_feature: return None if not subaccount_attributes['vendor_attributes']['migrated_to_abacus']: return None return TenantApplication( application_id=app_model.CUSTOMER_ACCOUNTING_APP, roles=[app_model.CUSTOMER_ACCOUNTING_BASE_ROLE], url=app_model.get_application_url( app_model.CUSTOMER_ACCOUNTING_APP, subaccount_attributes['vendor_attributes']['company_brand']['name'], ), ) def get_ws_catalog_app_for_subaccount( subaccount_attributes: types.SubaccountAttributes, ) -> types.TenantApplication | None: """ Get Workstation catalog accounting application for a subaccount based on the company brand the features of the parent vendor. Args: subaccount_attributes (TenantApplication): Subaccount attributes. Return: TenantApplication | None: Workstation catalog application with a base role. """ if subaccount_attributes['vendor_attributes']['company_brand']['name'] == 'knr': return None required_features = [ FEATURES.AUDIO_RELEASE_BUILDER, FEATURES.MUSIC_VIDEO_RELEASE_BUILDER, FEATURES.PHYSICAL_PRODUCT_BUILDER, ] if not any( feature in subaccount_attributes['vendor_attributes']['features'] for feature in required_features ): return None return TenantApplication( application_id=app_model.WORKSTATION_CATALOG_APP, roles=[app_model.WORKSTATION_CATALOG_ROLE], url=app_model.get_application_url( app_model.WORKSTATION_CATALOG_APP, subaccount_attributes['vendor_attributes']['company_brand']['name'], ), ) def get_settings_app_for_subaccount( subaccount_attributes: types.SubaccountAttributes, ) -> types.TenantApplication | None: """ Get settings application for a subaccount based on the company brand the features of the parent vendor. Args: subaccount_attributes (TenantApplication): Subaccount attributes. Return: TenantApplication | None: Settings application with a base role. """ return TenantApplication( application_id=app_model.SETTINGS_APP, roles=[app_model.SETTINGS_BASE_ROLE], url=app_model.get_application_url( app_model.SETTINGS_APP, subaccount_attributes['vendor_attributes']['company_brand']['name'], ), ) def generate_subaccount_app_check_map( subaccount_attributes: types.SubaccountAttributes, ) -> dict[str, SubaccountApplicationChecker]: """ Generates a dictionary of functions that can be used to check if a particular application is enabled for a given subaccount. The list of applications to check can become dynamic in the future. Args: subaccount_attributes (SubaccountAttributes): The subaccount object used to check enabled applications. Returns: dict[str, Callable[[], TenantApplication | None]]: A dictionary where keys are application IDs and values are functions that check the enabled status of those applications. """ app_checkers: dict[str, SubaccountApplicationChecker] = { app_model.CUSTOMER_ACCOUNTING_APP: get_customer_accounting_app_for_subaccount, app_model.INSIGHTS_APP: get_insights_app_for_subaccount, app_model.WORKSTATION_ANALYTICS_APP: get_ws_analytics_app_for_subaccount, app_model.WORKSTATION_CATALOG_APP: get_ws_catalog_app_for_subaccount, app_model.SETTINGS_APP: get_settings_app_for_subaccount, } return { app: (lambda app_id=app: app_checkers[app_id](subaccount_attributes)) for app in ENABLED_APPLICATIONS } def _has_workstation_apps(applications: list[types.TenantApplication]) -> bool: """ Check if any workstation apps are present in the applications list. Args: applications: List of enabled applications to check. Returns: bool: True if any workstation apps are present, False otherwise. """ workstation_app_ids = { app_model.WORKSTATION_ANALYTICS_APP, app_model.WORKSTATION_CATALOG_APP, } return any(app['application_id'] in workstation_app_ids for app in applications) def get_enabled_subaccount_applications( subaccount_attributes: types.SubaccountAttributes, ) -> list[types.TenantApplication]: """ Get all applications for a subaccount. Args: subaccount_attributes (SubaccountAttributes): The subaccount object used to check enabled applications. Return: list[TenantApplication]: list of subaccount applications. """ applications_map = generate_subaccount_app_check_map(subaccount_attributes) applications = [func() for app_id, func in applications_map.items()] enabled_applications = [app for app in applications if app is not None] # Check if any workstation apps are enabled and add WORKSTATION_ADMIN_ROLE to Settings app settings_app = next( (app for app in enabled_applications if app['application_id'] == app_model.SETTINGS_APP), None, ) if settings_app and _has_workstation_apps(enabled_applications): settings_app['roles'].append(app_model.WORKSTATION_ADMIN_ROLE) return enabled_applications @db_read_only_session_wrap def get_subaccount_applications(subaccount_uuid: str, session: Session) -> response.Response: """ Get all applications for a subaccount. Args: subaccount_uuid (str): Subaccount identifier. session (Session): SQLAlchemy session. Return: Response: containing dict of applications and roles """ result = subaccount_model.lookup_subaccount_and_vendor_and_company_brand_by_uuid( subaccount_uuid, session=session ) if result is None: return response.create_not_found_response(message='Subaccount not found') subaccount, vendor, company_brand = result fc = feature_model.get_enabled_features_for_vendor_with_session( vendor['vendor_id'], session=session ) vendor_features = feature_model.convert_features_to_list(fc) subaccount_attributes = types.SubaccountAttributes( subaccount_id=subaccount['subaccount_id'], uuid=subaccount['uuid'], vendor_attributes=types.VendorAttributes( vendor_id=vendor['vendor_id'], uuid=vendor['uuid'], migrated_to_abacus=vendor['migrated_to_abacus'], company_brand=company_brand, features=vendor_features, ), ) applications = get_enabled_subaccount_applications(subaccount_attributes) return response.Response({'applications': applications}) @db_read_only_session_wrap def get_bulk_subaccount_applications( subaccount_uuids: list[str], session: Session ) -> dict[str, list[dict]]: """ Get all applications for multiple subaccounts. Args: subaccount_uuids (list[str]): List of subaccount identifiers. session (Session): SQLAlchemy session. Return: Response: containing dict mapping vendor UUIDs to their applications and roles """ # Bulk lookup subaccounts, vendors and company brands subaccounts_lookup = ( subaccount_model.lookup_subaccounts_and_vendors_and_company_brands_by_uuids( subaccount_uuids=subaccount_uuids, session=session ) ) # Extract vendor IDs for bulk features lookup vendor_ids = [ subaccount_data[1]['vendor_id'] for subaccount_data in subaccounts_lookup.values() ] # Bulk lookup features for all valid vendor IDs features_lookup = feature_model.get_enabled_features_for_vendors_with_session( vendor_ids=vendor_ids, session=session ) # Combine results into SubaccountAttributes list (intersection of vendors with features) subaccount_attributes_list = [] for uuid, subaccount_data in subaccounts_lookup.items(): subaccount, vendor, company_brand = subaccount_data vendor_id = vendor['vendor_id'] # Only include vendors that have features features = features_lookup.get(vendor_id) if features is not None: vendor_features = feature_model.convert_features_to_list(features) subaccount_attributes = types.SubaccountAttributes( subaccount_id=subaccount['subaccount_id'], uuid=subaccount['uuid'], vendor_attributes=types.VendorAttributes( vendor_id=vendor['vendor_id'], uuid=vendor['uuid'], migrated_to_abacus=vendor['migrated_to_abacus'], company_brand=company_brand, features=vendor_features, ), ) applications = get_enabled_subaccount_applications(subaccount_attributes) subaccount_attributes_list.append({'uuid': uuid, 'applications': applications}) # format_for_dataloader to handle ordering and None entries return { 'subaccounts': format_for_dataloader( items=subaccount_attributes_list, keys=subaccount_uuids, lookup_key='uuid' ) }