# shared/clients/tenant_client.py """ Tenant Service Client for Inter-Service Communication Provides access to tenant settings and configuration from other services """ import structlog from typing import Dict, Any, Optional from uuid import UUID from shared.clients.base_service_client import BaseServiceClient from shared.config.base import BaseServiceSettings logger = structlog.get_logger() class TenantServiceClient(BaseServiceClient): """Client for communicating with the Tenant Service""" def __init__(self, config: BaseServiceSettings): super().__init__("tenant", config) def get_service_base_path(self) -> str: return "/api/v1" # ================================================================ # TENANT SETTINGS ENDPOINTS # ================================================================ async def get_settings(self, tenant_id: str) -> Optional[Dict[str, Any]]: """ Get all settings for a tenant Args: tenant_id: Tenant ID (UUID as string) Returns: Dictionary with all settings categories """ try: result = await self.get("settings", tenant_id=tenant_id) if result: logger.info("Retrieved all settings from tenant service", tenant_id=tenant_id) return result except Exception as e: logger.error("Error getting all settings", error=str(e), tenant_id=tenant_id) return None async def get_category_settings(self, tenant_id: str, category: str) -> Optional[Dict[str, Any]]: """ Get settings for a specific category Args: tenant_id: Tenant ID (UUID as string) category: Category name (procurement, inventory, production, supplier, pos, order) Returns: Dictionary with category settings """ try: result = await self.get(f"settings/{category}", tenant_id=tenant_id) if result: logger.info("Retrieved category settings from tenant service", tenant_id=tenant_id, category=category) return result except Exception as e: logger.error("Error getting category settings", error=str(e), tenant_id=tenant_id, category=category) return None async def get_procurement_settings(self, tenant_id: str) -> Optional[Dict[str, Any]]: """Get procurement settings for a tenant""" result = await self.get_category_settings(tenant_id, "procurement") return result.get('settings', {}) if result else {} async def get_inventory_settings(self, tenant_id: str) -> Optional[Dict[str, Any]]: """Get inventory settings for a tenant""" result = await self.get_category_settings(tenant_id, "inventory") return result.get('settings', {}) if result else {} async def get_production_settings(self, tenant_id: str) -> Optional[Dict[str, Any]]: """Get production settings for a tenant""" result = await self.get_category_settings(tenant_id, "production") return result.get('settings', {}) if result else {} async def get_supplier_settings(self, tenant_id: str) -> Optional[Dict[str, Any]]: """Get supplier settings for a tenant""" result = await self.get_category_settings(tenant_id, "supplier") return result.get('settings', {}) if result else {} async def get_pos_settings(self, tenant_id: str) -> Optional[Dict[str, Any]]: """Get POS settings for a tenant""" result = await self.get_category_settings(tenant_id, "pos") return result.get('settings', {}) if result else {} async def get_order_settings(self, tenant_id: str) -> Optional[Dict[str, Any]]: """Get order settings for a tenant""" result = await self.get_category_settings(tenant_id, "order") return result.get('settings', {}) if result else {} async def get_notification_settings(self, tenant_id: str) -> Optional[Dict[str, Any]]: """Get notification settings for a tenant""" result = await self.get_category_settings(tenant_id, "notification") return result.get('settings', {}) if result else {} async def update_settings(self, tenant_id: str, settings_data: Dict[str, Any]) -> Optional[Dict[str, Any]]: """ Update settings for a tenant Args: tenant_id: Tenant ID (UUID as string) settings_data: Settings data to update Returns: Updated settings dictionary """ try: result = await self.put("settings", data=settings_data, tenant_id=tenant_id) if result: logger.info("Updated tenant settings", tenant_id=tenant_id) return result except Exception as e: logger.error("Error updating tenant settings", error=str(e), tenant_id=tenant_id) return None async def update_category_settings( self, tenant_id: str, category: str, settings_data: Dict[str, Any] ) -> Optional[Dict[str, Any]]: """ Update settings for a specific category Args: tenant_id: Tenant ID (UUID as string) category: Category name settings_data: Settings data to update Returns: Updated settings dictionary """ try: result = await self.put(f"settings/{category}", data=settings_data, tenant_id=tenant_id) if result: logger.info("Updated category settings", tenant_id=tenant_id, category=category) return result except Exception as e: logger.error("Error updating category settings", error=str(e), tenant_id=tenant_id, category=category) return None async def reset_category_settings(self, tenant_id: str, category: str) -> Optional[Dict[str, Any]]: """ Reset category settings to default values Args: tenant_id: Tenant ID (UUID as string) category: Category name Returns: Reset settings dictionary """ try: result = await self.post(f"settings/{category}/reset", data={}, tenant_id=tenant_id) if result: logger.info("Reset category settings to defaults", tenant_id=tenant_id, category=category) return result except Exception as e: logger.error("Error resetting category settings", error=str(e), tenant_id=tenant_id, category=category) return None # ================================================================ # TENANT MANAGEMENT # ================================================================ async def get_tenant(self, tenant_id: str) -> Optional[Dict[str, Any]]: """ Get tenant details Args: tenant_id: Tenant ID (UUID as string) Returns: Tenant data dictionary """ try: # The tenant endpoint is not tenant-scoped, it's a direct path result = await self._make_request("GET", f"tenants/{tenant_id}") if result: logger.info("Retrieved tenant details", tenant_id=tenant_id) return result except Exception as e: logger.error("Error getting tenant details", error=str(e), tenant_id=tenant_id) return None async def get_active_tenants(self, skip: int = 0, limit: int = 100) -> Optional[list]: """ Get all active tenants Args: skip: Number of records to skip (pagination) limit: Maximum number of records to return Returns: List of active tenant dictionaries """ try: # Call tenants endpoint (not tenant-scoped) result = await self._make_request( "GET", f"tenants?skip={skip}&limit={limit}" ) if result: logger.info("Retrieved active tenants from tenant service", count=len(result) if isinstance(result, list) else 0) return result if result else [] except Exception as e: logger.error("Error getting active tenants", error=str(e)) return [] # ================================================================ # UTILITY METHODS # ================================================================ async def health_check(self) -> bool: """Check if tenant service is healthy""" try: result = await self.get("../health") # Health endpoint is not tenant-scoped return result is not None except Exception as e: logger.error("Tenant service health check failed", error=str(e)) return False # Factory function for dependency injection def create_tenant_client(config: BaseServiceSettings) -> TenantServiceClient: """Create tenant service client instance""" return TenantServiceClient(config)