""" Tenant Operations API - BUSINESS operations Handles complex tenant operations, registration, search, subscriptions, and analytics """ import structlog from datetime import datetime from fastapi import APIRouter, Depends, HTTPException, status, Path, Query from typing import List, Dict, Any, Optional from uuid import UUID from app.schemas.tenants import ( BakeryRegistration, TenantResponse, TenantAccessResponse, TenantSearchRequest ) from app.services.tenant_service import EnhancedTenantService from app.services.subscription_limit_service import SubscriptionLimitService from app.services.payment_service import PaymentService from shared.auth.decorators import ( get_current_user_dep, require_admin_role_dep ) from shared.routing.route_builder import RouteBuilder from shared.database.base import create_database_manager from shared.monitoring.metrics import track_endpoint_metrics logger = structlog.get_logger() router = APIRouter() route_builder = RouteBuilder("tenants") # Dependency injection for enhanced tenant service def get_enhanced_tenant_service(): try: from app.core.config import settings database_manager = create_database_manager(settings.DATABASE_URL, "tenant-service") return EnhancedTenantService(database_manager) except Exception as e: logger.error("Failed to create enhanced tenant service", error=str(e)) raise HTTPException(status_code=500, detail="Service initialization failed") def get_subscription_limit_service(): try: from app.core.config import settings database_manager = create_database_manager(settings.DATABASE_URL, "tenant-service") return SubscriptionLimitService(database_manager) except Exception as e: logger.error("Failed to create subscription limit service", error=str(e)) raise HTTPException(status_code=500, detail="Service initialization failed") def get_payment_service(): try: return PaymentService() except Exception as e: logger.error("Failed to create payment service", error=str(e)) raise HTTPException(status_code=500, detail="Payment service initialization failed") # ============================================================================ # TENANT REGISTRATION & ACCESS OPERATIONS # ============================================================================ @router.post(route_builder.build_base_route("register", include_tenant_prefix=False), response_model=TenantResponse) async def register_bakery( bakery_data: BakeryRegistration, current_user: Dict[str, Any] = Depends(get_current_user_dep), tenant_service: EnhancedTenantService = Depends(get_enhanced_tenant_service) ): """Register a new bakery/tenant with enhanced validation and features""" try: result = await tenant_service.create_bakery( bakery_data, current_user["user_id"] ) logger.info("Bakery registered successfully", name=bakery_data.name, owner_email=current_user.get('email'), tenant_id=result.id) return result except HTTPException: raise except Exception as e: logger.error("Bakery registration failed", name=bakery_data.name, owner_id=current_user["user_id"], error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Bakery registration failed" ) @router.get(route_builder.build_base_route("{tenant_id}/my-access", include_tenant_prefix=False), response_model=TenantAccessResponse) async def get_current_user_tenant_access( tenant_id: UUID = Path(..., description="Tenant ID"), current_user: Dict[str, Any] = Depends(get_current_user_dep) ): """Get current user's access to tenant with role and permissions""" try: # Create tenant service directly from app.core.config import settings database_manager = create_database_manager(settings.DATABASE_URL, "tenant-service") tenant_service = EnhancedTenantService(database_manager) access_info = await tenant_service.verify_user_access(current_user["user_id"], str(tenant_id)) return access_info except Exception as e: logger.error("Current user access verification failed", user_id=current_user["user_id"], tenant_id=str(tenant_id), error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Access verification failed" ) @router.get(route_builder.build_base_route("{tenant_id}/access/{user_id}", include_tenant_prefix=False), response_model=TenantAccessResponse) async def verify_tenant_access( tenant_id: UUID = Path(..., description="Tenant ID"), user_id: str = Path(..., description="User ID") ): """Verify if user has access to tenant - Enhanced version with detailed permissions""" # Check if this is a service request if user_id in ["training-service", "data-service", "forecasting-service", "auth-service"]: # Services have access to all tenants for their operations return TenantAccessResponse( has_access=True, role="service", permissions=["read", "write"] ) try: # Create tenant service directly from app.core.config import settings database_manager = create_database_manager(settings.DATABASE_URL, "tenant-service") tenant_service = EnhancedTenantService(database_manager) access_info = await tenant_service.verify_user_access(user_id, str(tenant_id)) return access_info except Exception as e: logger.error("Access verification failed", user_id=user_id, tenant_id=str(tenant_id), error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Access verification failed" ) # ============================================================================ # TENANT SEARCH & DISCOVERY OPERATIONS # ============================================================================ @router.get(route_builder.build_base_route("subdomain/{subdomain}", include_tenant_prefix=False), response_model=TenantResponse) @track_endpoint_metrics("tenant_get_by_subdomain") async def get_tenant_by_subdomain( subdomain: str = Path(..., description="Tenant subdomain"), current_user: Dict[str, Any] = Depends(get_current_user_dep), tenant_service: EnhancedTenantService = Depends(get_enhanced_tenant_service) ): """Get tenant by subdomain with enhanced validation""" tenant = await tenant_service.get_tenant_by_subdomain(subdomain) if not tenant: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Tenant not found" ) # Verify user has access to this tenant access = await tenant_service.verify_user_access(current_user["user_id"], tenant.id) if not access.has_access: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Access denied to tenant" ) return tenant @router.get(route_builder.build_base_route("user/{user_id}/owned", include_tenant_prefix=False), response_model=List[TenantResponse]) async def get_user_owned_tenants( user_id: str = Path(..., description="User ID"), current_user: Dict[str, Any] = Depends(get_current_user_dep), tenant_service: EnhancedTenantService = Depends(get_enhanced_tenant_service) ): """Get all tenants owned by a user with enhanced data""" # Users can only get their own tenants unless they're admin user_role = current_user.get('role', '').lower() if user_id != current_user["user_id"] and user_role != 'admin': raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Can only access your own tenants" ) tenants = await tenant_service.get_user_tenants(user_id) return tenants @router.get(route_builder.build_base_route("search", include_tenant_prefix=False), response_model=List[TenantResponse]) @track_endpoint_metrics("tenant_search") async def search_tenants( search_term: str = Query(..., description="Search term"), business_type: Optional[str] = Query(None, description="Business type filter"), city: Optional[str] = Query(None, description="City filter"), skip: int = Query(0, ge=0, description="Number of records to skip"), limit: int = Query(50, ge=1, le=100, description="Maximum number of records to return"), current_user: Dict[str, Any] = Depends(get_current_user_dep), tenant_service: EnhancedTenantService = Depends(get_enhanced_tenant_service) ): """Search tenants with advanced filters and pagination""" tenants = await tenant_service.search_tenants( search_term=search_term, business_type=business_type, city=city, skip=skip, limit=limit ) return tenants @router.get(route_builder.build_base_route("nearby", include_tenant_prefix=False), response_model=List[TenantResponse]) @track_endpoint_metrics("tenant_get_nearby") async def get_nearby_tenants( latitude: float = Query(..., description="Latitude coordinate"), longitude: float = Query(..., description="Longitude coordinate"), radius_km: float = Query(10.0, ge=0.1, le=100.0, description="Search radius in kilometers"), limit: int = Query(50, ge=1, le=100, description="Maximum number of results"), current_user: Dict[str, Any] = Depends(get_current_user_dep), tenant_service: EnhancedTenantService = Depends(get_enhanced_tenant_service) ): """Get tenants near a geographic location with enhanced geospatial search""" tenants = await tenant_service.get_tenants_near_location( latitude=latitude, longitude=longitude, radius_km=radius_km, limit=limit ) return tenants @router.get(route_builder.build_base_route("users/{user_id}", include_tenant_prefix=False), response_model=List[TenantResponse]) @track_endpoint_metrics("tenant_get_user_tenants") async def get_user_tenants( user_id: str = Path(..., description="User ID"), tenant_service: EnhancedTenantService = Depends(get_enhanced_tenant_service) ): """Get all tenants owned by a user - Fixed endpoint for frontend""" try: tenants = await tenant_service.get_user_tenants(user_id) logger.info("Retrieved user tenants", user_id=user_id, tenant_count=len(tenants)) return tenants except Exception as e: logger.error("Get user tenants failed", user_id=user_id, error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to get user tenants" ) @router.get(route_builder.build_base_route("members/user/{user_id}", include_tenant_prefix=False)) @track_endpoint_metrics("tenant_get_user_memberships") async def get_user_memberships( user_id: str = Path(..., description="User ID"), tenant_service: EnhancedTenantService = Depends(get_enhanced_tenant_service) ): """Get all tenant memberships for a user (for authentication service)""" try: memberships = await tenant_service.get_user_memberships(user_id) logger.info("Retrieved user memberships", user_id=user_id, membership_count=len(memberships)) return memberships except Exception as e: logger.error("Get user memberships failed", user_id=user_id, error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to get user memberships" ) # ============================================================================ # TENANT MODEL STATUS OPERATIONS # ============================================================================ @router.put(route_builder.build_base_route("{tenant_id}/model-status", include_tenant_prefix=False)) @track_endpoint_metrics("tenant_update_model_status") async def update_tenant_model_status( tenant_id: UUID = Path(..., description="Tenant ID"), ml_model_trained: bool = Query(..., description="Whether model is trained"), last_training_date: Optional[datetime] = Query(None, description="Last training date"), current_user: Dict[str, Any] = Depends(get_current_user_dep), tenant_service: EnhancedTenantService = Depends(get_enhanced_tenant_service) ): """Update tenant model training status with enhanced tracking""" try: result = await tenant_service.update_model_status( str(tenant_id), ml_model_trained, current_user["user_id"], last_training_date ) return result except HTTPException: raise except Exception as e: logger.error("Model status update failed", tenant_id=str(tenant_id), error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to update model status" ) # ============================================================================ # TENANT ACTIVATION/DEACTIVATION OPERATIONS # ============================================================================ @router.post(route_builder.build_base_route("{tenant_id}/deactivate", include_tenant_prefix=False)) @track_endpoint_metrics("tenant_deactivate") async def deactivate_tenant( tenant_id: UUID = Path(..., description="Tenant ID"), current_user: Dict[str, Any] = Depends(get_current_user_dep), tenant_service: EnhancedTenantService = Depends(get_enhanced_tenant_service) ): """Deactivate a tenant (owner only) with enhanced validation""" try: success = await tenant_service.deactivate_tenant( str(tenant_id), current_user["user_id"] ) if success: return {"success": True, "message": "Tenant deactivated successfully"} else: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to deactivate tenant" ) except HTTPException: raise except Exception as e: logger.error("Tenant deactivation failed", tenant_id=str(tenant_id), error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to deactivate tenant" ) @router.post(route_builder.build_base_route("{tenant_id}/activate", include_tenant_prefix=False)) @track_endpoint_metrics("tenant_activate") async def activate_tenant( tenant_id: UUID = Path(..., description="Tenant ID"), current_user: Dict[str, Any] = Depends(get_current_user_dep), tenant_service: EnhancedTenantService = Depends(get_enhanced_tenant_service) ): """Activate a previously deactivated tenant (owner only) with enhanced validation""" try: success = await tenant_service.activate_tenant( str(tenant_id), current_user["user_id"] ) if success: return {"success": True, "message": "Tenant activated successfully"} else: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to activate tenant" ) except HTTPException: raise except Exception as e: logger.error("Tenant activation failed", tenant_id=str(tenant_id), error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to activate tenant" ) # ============================================================================ # TENANT STATISTICS & ANALYTICS # ============================================================================ @router.get(route_builder.build_base_route("statistics", include_tenant_prefix=False), dependencies=[Depends(require_admin_role_dep)]) @track_endpoint_metrics("tenant_get_statistics") async def get_tenant_statistics( current_user: Dict[str, Any] = Depends(get_current_user_dep), tenant_service: EnhancedTenantService = Depends(get_enhanced_tenant_service) ): """Get comprehensive tenant statistics (admin only) with enhanced analytics""" try: stats = await tenant_service.get_tenant_statistics() return stats except Exception as e: logger.error("Get tenant statistics failed", error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to get tenant statistics" ) # ============================================================================ # SUBSCRIPTION OPERATIONS # ============================================================================ @router.get(route_builder.build_base_route("subscriptions/{tenant_id}/limits", include_tenant_prefix=False)) async def get_subscription_limits( tenant_id: UUID = Path(..., description="Tenant ID"), current_user: Dict[str, Any] = Depends(get_current_user_dep), limit_service: SubscriptionLimitService = Depends(get_subscription_limit_service) ): """Get current subscription limits for a tenant""" try: limits = await limit_service.get_tenant_subscription_limits(str(tenant_id)) return limits except Exception as e: logger.error("Failed to get subscription limits", tenant_id=str(tenant_id), error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to get subscription limits" ) @router.get(route_builder.build_base_route("subscriptions/{tenant_id}/usage", include_tenant_prefix=False)) async def get_usage_summary( tenant_id: UUID = Path(..., description="Tenant ID"), current_user: Dict[str, Any] = Depends(get_current_user_dep), limit_service: SubscriptionLimitService = Depends(get_subscription_limit_service) ): """Get usage summary vs limits for a tenant""" try: usage = await limit_service.get_usage_summary(str(tenant_id)) return usage except Exception as e: logger.error("Failed to get usage summary", tenant_id=str(tenant_id), error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to get usage summary" ) @router.get(route_builder.build_base_route("subscriptions/{tenant_id}/can-add-location", include_tenant_prefix=False)) async def can_add_location( tenant_id: UUID = Path(..., description="Tenant ID"), current_user: Dict[str, Any] = Depends(get_current_user_dep), limit_service: SubscriptionLimitService = Depends(get_subscription_limit_service) ): """Check if tenant can add another location""" try: result = await limit_service.can_add_location(str(tenant_id)) return result except Exception as e: logger.error("Failed to check location limits", tenant_id=str(tenant_id), error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to check location limits" ) @router.get(route_builder.build_base_route("subscriptions/{tenant_id}/can-add-product", include_tenant_prefix=False)) async def can_add_product( tenant_id: UUID = Path(..., description="Tenant ID"), current_user: Dict[str, Any] = Depends(get_current_user_dep), limit_service: SubscriptionLimitService = Depends(get_subscription_limit_service) ): """Check if tenant can add another product""" try: result = await limit_service.can_add_product(str(tenant_id)) return result except Exception as e: logger.error("Failed to check product limits", tenant_id=str(tenant_id), error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to check product limits" ) @router.get(route_builder.build_base_route("subscriptions/{tenant_id}/can-add-user", include_tenant_prefix=False)) async def can_add_user( tenant_id: UUID = Path(..., description="Tenant ID"), current_user: Dict[str, Any] = Depends(get_current_user_dep), limit_service: SubscriptionLimitService = Depends(get_subscription_limit_service) ): """Check if tenant can add another user/member""" try: result = await limit_service.can_add_user(str(tenant_id)) return result except Exception as e: logger.error("Failed to check user limits", tenant_id=str(tenant_id), error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to check user limits" ) @router.get(route_builder.build_base_route("subscriptions/{tenant_id}/features/{feature}", include_tenant_prefix=False)) async def has_feature( tenant_id: UUID = Path(..., description="Tenant ID"), feature: str = Path(..., description="Feature name"), current_user: Dict[str, Any] = Depends(get_current_user_dep), limit_service: SubscriptionLimitService = Depends(get_subscription_limit_service) ): """Check if tenant has access to a specific feature""" try: result = await limit_service.has_feature(str(tenant_id), feature) return result except Exception as e: logger.error("Failed to check feature access", tenant_id=str(tenant_id), feature=feature, error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to check feature access" ) @router.get(route_builder.build_base_route("subscriptions/{tenant_id}/validate-upgrade/{new_plan}", include_tenant_prefix=False)) async def validate_plan_upgrade( tenant_id: UUID = Path(..., description="Tenant ID"), new_plan: str = Path(..., description="New plan name"), current_user: Dict[str, Any] = Depends(get_current_user_dep), limit_service: SubscriptionLimitService = Depends(get_subscription_limit_service) ): """Validate if tenant can upgrade to a new plan""" try: result = await limit_service.validate_plan_upgrade(str(tenant_id), new_plan) return result except Exception as e: logger.error("Failed to validate plan upgrade", tenant_id=str(tenant_id), new_plan=new_plan, error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to validate plan upgrade" ) @router.post(route_builder.build_base_route("subscriptions/{tenant_id}/upgrade", include_tenant_prefix=False)) async def upgrade_subscription_plan( tenant_id: UUID = Path(..., description="Tenant ID"), new_plan: str = Query(..., description="New plan name"), current_user: Dict[str, Any] = Depends(get_current_user_dep), limit_service: SubscriptionLimitService = Depends(get_subscription_limit_service) ): """Upgrade subscription plan for a tenant""" try: # First validate the upgrade validation = await limit_service.validate_plan_upgrade(str(tenant_id), new_plan) if not validation.get("can_upgrade", False): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=validation.get("reason", "Cannot upgrade to this plan") ) # Actually update the subscription plan in the database from app.core.config import settings from app.repositories.subscription_repository import SubscriptionRepository from app.models.tenants import Subscription from shared.database.base import create_database_manager database_manager = create_database_manager(settings.DATABASE_URL, "tenant-service") async with database_manager.get_session() as session: subscription_repo = SubscriptionRepository(Subscription, session) # Get the active subscription for this tenant active_subscription = await subscription_repo.get_active_subscription(str(tenant_id)) if not active_subscription: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="No active subscription found for this tenant" ) # Update the subscription plan updated_subscription = await subscription_repo.update_subscription_plan( str(active_subscription.id), new_plan ) # Commit the changes await session.commit() logger.info("Subscription plan upgraded successfully", tenant_id=str(tenant_id), subscription_id=str(active_subscription.id), old_plan=active_subscription.plan, new_plan=new_plan, user_id=current_user["user_id"]) return { "success": True, "message": f"Plan successfully upgraded to {new_plan}", "old_plan": active_subscription.plan, "new_plan": new_plan, "new_monthly_price": updated_subscription.monthly_price, "validation": validation } except HTTPException: raise except Exception as e: logger.error("Failed to upgrade subscription plan", tenant_id=str(tenant_id), new_plan=new_plan, error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to upgrade subscription plan" ) @router.get("/api/v1/plans") async def get_available_plans(): """Get all available subscription plans with features and pricing - Public endpoint""" try: # This could be moved to a config service or database plans = { "starter": { "name": "Starter", "description": "Ideal para panaderías pequeñas o nuevas", "monthly_price": 49.0, "max_users": 5, "max_locations": 1, "max_products": 50, "features": { "inventory_management": "basic", "demand_prediction": "basic", "production_reports": "basic", "analytics": "basic", "support": "email", "trial_days": 14, "locations": "1_location", "ai_model_configuration": "basic" }, "trial_available": True }, "professional": { "name": "Professional", "description": "Ideal para panaderías y cadenas en crecimiento", "monthly_price": 129.0, "max_users": 15, "max_locations": 2, "max_products": -1, # Unlimited "features": { "inventory_management": "advanced", "demand_prediction": "ai_92_percent", "production_management": "complete", "pos_integrated": True, "logistics": "basic", "analytics": "advanced", "support": "priority_24_7", "trial_days": 14, "locations": "1_2_locations", "ai_model_configuration": "advanced" }, "trial_available": True, "popular": True }, "enterprise": { "name": "Enterprise", "description": "Ideal para cadenas con obradores centrales", "monthly_price": 399.0, "max_users": -1, # Unlimited "max_locations": -1, # Unlimited "max_products": -1, # Unlimited "features": { "inventory_management": "multi_location", "demand_prediction": "ai_personalized", "production_optimization": "capacity", "erp_integration": True, "logistics": "advanced", "analytics": "predictive", "api_access": "personalized", "account_manager": True, "demo": "personalized", "locations": "unlimited_obradores", "ai_model_configuration": "enterprise" }, "trial_available": False, "contact_sales": True } } return {"plans": plans} except Exception as e: logger.error("Failed to get available plans", error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to get available plans" ) # ============================================================================ # PAYMENT OPERATIONS # ============================================================================ @router.post(route_builder.build_base_route("subscriptions/register-with-subscription", include_tenant_prefix=False)) async def register_with_subscription( user_data: Dict[str, Any], plan_id: str = Query(..., description="Plan ID to subscribe to"), payment_method_id: str = Query(..., description="Payment method ID from frontend"), use_trial: bool = Query(False, description="Whether to use trial period for pilot users"), payment_service: PaymentService = Depends(get_payment_service) ): """Process user registration with subscription creation""" try: result = await payment_service.process_registration_with_subscription( user_data, plan_id, payment_method_id, use_trial ) return { "success": True, "message": "Registration and subscription created successfully", "data": result } except Exception as e: logger.error("Failed to register with subscription", error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to register with subscription" ) @router.post(route_builder.build_base_route("subscriptions/{tenant_id}/cancel", include_tenant_prefix=False)) async def cancel_subscription( tenant_id: UUID = Path(..., description="Tenant ID"), current_user: Dict[str, Any] = Depends(get_current_user_dep), payment_service: PaymentService = Depends(get_payment_service) ): """Cancel subscription for a tenant""" try: # TODO: Add access control - verify user is owner/admin of tenant # In a real implementation, you would need to retrieve the subscription ID from the database # For now, this is a placeholder subscription_id = "sub_test" # This would come from the database result = await payment_service.cancel_subscription(subscription_id) return { "success": True, "message": "Subscription cancelled successfully", "data": { "subscription_id": result.id, "status": result.status } } except Exception as e: logger.error("Failed to cancel subscription", error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to cancel subscription" ) @router.get(route_builder.build_base_route("subscriptions/{tenant_id}/invoices", include_tenant_prefix=False)) async def get_invoices( tenant_id: UUID = Path(..., description="Tenant ID"), current_user: Dict[str, Any] = Depends(get_current_user_dep), payment_service: PaymentService = Depends(get_payment_service) ): """Get invoices for a tenant""" try: # TODO: Add access control - verify user has access to tenant # In a real implementation, you would need to retrieve the customer ID from the database # For now, this is a placeholder customer_id = "cus_test" # This would come from the database invoices = await payment_service.get_invoices(customer_id) return { "success": True, "data": invoices } except Exception as e: logger.error("Failed to get invoices", error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to get invoices" )