""" Enterprise Upgrade API Endpoints for upgrading tenants to enterprise tier and managing child outlets """ from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel, Field from typing import Dict, Any, Optional import uuid from datetime import datetime, date from sqlalchemy.ext.asyncio import AsyncSession from app.models.tenants import Tenant from app.models.tenant_location import TenantLocation from app.services.tenant_service import EnhancedTenantService from app.core.config import settings from shared.auth.tenant_access import verify_tenant_permission_dep from shared.auth.decorators import get_current_user_dep from shared.clients.subscription_client import SubscriptionServiceClient, get_subscription_service_client from shared.subscription.plans import SubscriptionTier, QuotaLimits from shared.database.base import create_database_manager import structlog logger = structlog.get_logger() router = APIRouter() # 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") # Pydantic models for request bodies class EnterpriseUpgradeRequest(BaseModel): location_name: Optional[str] = Field(default="Central Production Facility") address: Optional[str] = None city: Optional[str] = None postal_code: Optional[str] = None latitude: Optional[float] = None longitude: Optional[float] = None production_capacity_kg: Optional[int] = Field(default=1000) class ChildOutletRequest(BaseModel): name: str subdomain: str address: str city: Optional[str] = None postal_code: str latitude: Optional[float] = None longitude: Optional[float] = None phone: Optional[str] = None email: Optional[str] = None delivery_days: Optional[list] = None @router.post("/tenants/{tenant_id}/upgrade-to-enterprise") async def upgrade_to_enterprise( tenant_id: str, upgrade_data: EnterpriseUpgradeRequest, subscription_client: SubscriptionServiceClient = Depends(get_subscription_service_client), current_user: Dict[str, Any] = Depends(get_current_user_dep) ): """ Upgrade a tenant to enterprise tier with central production facility """ try: from app.core.database import database_manager from app.repositories.tenant_repository import TenantRepository # Get the current tenant async with database_manager.get_session() as session: tenant_repo = TenantRepository(Tenant, session) tenant = await tenant_repo.get_by_id(tenant_id) if not tenant: raise HTTPException(status_code=404, detail="Tenant not found") # Verify current subscription allows upgrade to enterprise current_subscription = await subscription_client.get_subscription(tenant_id) if current_subscription['plan'] not in [SubscriptionTier.STARTER.value, SubscriptionTier.PROFESSIONAL.value]: raise HTTPException(status_code=400, detail="Only starter and professional tier tenants can be upgraded to enterprise") # Verify user has admin/owner role # This is handled by current_user check # Update tenant to parent type async with database_manager.get_session() as session: tenant_repo = TenantRepository(Tenant, session) updated_tenant = await tenant_repo.update( tenant_id, { 'tenant_type': 'parent', 'hierarchy_path': f"{tenant_id}" # Root path } ) await session.commit() # Create central production location location_data = { 'tenant_id': tenant_id, 'name': upgrade_data.location_name, 'location_type': 'central_production', 'address': upgrade_data.address or tenant.address, 'city': upgrade_data.city or tenant.city, 'postal_code': upgrade_data.postal_code or tenant.postal_code, 'latitude': upgrade_data.latitude or tenant.latitude, 'longitude': upgrade_data.longitude or tenant.longitude, 'capacity': upgrade_data.production_capacity_kg, 'is_active': True } from app.repositories.tenant_location_repository import TenantLocationRepository from app.core.database import database_manager # Create async session async with database_manager.get_session() as session: location_repo = TenantLocationRepository(session) created_location = await location_repo.create_location(location_data) await session.commit() # Update subscription to enterprise tier await subscription_client.update_subscription_plan( tenant_id=tenant_id, new_plan=SubscriptionTier.ENTERPRISE.value ) return { 'success': True, 'tenant': updated_tenant, 'production_location': created_location, 'message': 'Tenant successfully upgraded to enterprise tier' } except Exception as e: raise HTTPException(status_code=500, detail=f"Failed to upgrade tenant: {str(e)}") @router.post("/tenants/{parent_id}/add-child-outlet") async def add_child_outlet( parent_id: str, child_data: ChildOutletRequest, subscription_client: SubscriptionServiceClient = Depends(get_subscription_service_client), current_user: Dict[str, Any] = Depends(get_current_user_dep) ): """ Add a new child outlet to a parent tenant """ try: from app.core.database import database_manager from app.repositories.tenant_repository import TenantRepository # Get parent tenant and verify it's a parent async with database_manager.get_session() as session: tenant_repo = TenantRepository(Tenant, session) parent_tenant = await tenant_repo.get_by_id(parent_id) if not parent_tenant: raise HTTPException(status_code=400, detail="Parent tenant not found") parent_dict = { 'id': str(parent_tenant.id), 'name': parent_tenant.name, 'tenant_type': parent_tenant.tenant_type, 'subscription_tier': parent_tenant.subscription_tier, 'business_type': parent_tenant.business_type, 'business_model': parent_tenant.business_model, 'city': parent_tenant.city, 'phone': parent_tenant.phone, 'email': parent_tenant.email, 'owner_id': parent_tenant.owner_id } if parent_dict.get('tenant_type') != 'parent': raise HTTPException(status_code=400, detail="Tenant is not a parent type") # Validate subscription tier from shared.clients import get_tenant_client from shared.subscription.plans import PlanFeatures tenant_client = get_tenant_client(config=settings, service_name="tenant-service") subscription = await tenant_client.get_tenant_subscription(parent_id) if not subscription: raise HTTPException( status_code=403, detail="No active subscription found for parent tenant" ) tier = subscription.get("plan", "starter") if not PlanFeatures.validate_tenant_access(tier, "child"): raise HTTPException( status_code=403, detail=f"Creating child outlets requires Enterprise subscription. Current plan: {tier}" ) # Check if parent has reached child quota async with database_manager.get_session() as session: tenant_repo = TenantRepository(Tenant, session) current_child_count = await tenant_repo.get_child_tenant_count(parent_id) # Get max children from subscription plan max_children = QuotaLimits.get_limit("MAX_CHILD_TENANTS", tier) if max_children is not None and current_child_count >= max_children: raise HTTPException( status_code=403, detail=f"Child tenant limit reached. Current: {current_child_count}, Maximum: {max_children}" ) # Create new child tenant child_id = str(uuid.uuid4()) child_tenant_data = { 'id': child_id, 'name': child_data.name, 'subdomain': child_data.subdomain, 'business_type': parent_dict.get('business_type', 'bakery'), 'business_model': parent_dict.get('business_model', 'retail_bakery'), 'address': child_data.address, 'city': child_data.city or parent_dict.get('city'), 'postal_code': child_data.postal_code, 'latitude': child_data.latitude, 'longitude': child_data.longitude, 'phone': child_data.phone or parent_dict.get('phone'), 'email': child_data.email or parent_dict.get('email'), 'parent_tenant_id': parent_id, 'tenant_type': 'child', 'hierarchy_path': f"{parent_id}.{child_id}", 'owner_id': parent_dict.get('owner_id'), # Same owner as parent 'is_active': True } # Use database managed session async with database_manager.get_session() as session: tenant_repo = TenantRepository(Tenant, session) created_child = await tenant_repo.create(child_tenant_data) await session.commit() created_child_dict = { 'id': str(created_child.id), 'name': created_child.name, 'subdomain': created_child.subdomain } # Create retail outlet location for the child location_data = { 'tenant_id': uuid.UUID(child_id), 'name': f"Outlet - {child_data.name}", 'location_type': 'retail_outlet', 'address': child_data.address, 'city': child_data.city or parent_dict.get('city'), 'postal_code': child_data.postal_code, 'latitude': child_data.latitude, 'longitude': child_data.longitude, 'delivery_windows': child_data.delivery_days, 'is_active': True } from app.repositories.tenant_location_repository import TenantLocationRepository # Create async session async with database_manager.get_session() as session: location_repo = TenantLocationRepository(session) created_location = await location_repo.create_location(location_data) await session.commit() location_dict = { 'id': str(created_location.id) if created_location else None, 'name': created_location.name if created_location else None } # Copy relevant settings from parent (with child-specific overrides) # This would typically involve copying settings via tenant settings service # Create child subscription inheriting from parent await subscription_client.create_child_subscription( child_tenant_id=child_id, parent_tenant_id=parent_id ) return { 'success': True, 'child_tenant': created_child_dict, 'location': location_dict, 'message': 'Child outlet successfully added' } except Exception as e: raise HTTPException(status_code=500, detail=f"Failed to add child outlet: {str(e)}") @router.get("/tenants/{tenant_id}/hierarchy") async def get_tenant_hierarchy( tenant_id: str, current_user: Dict[str, Any] = Depends(get_current_user_dep) ): """ Get tenant hierarchy information """ try: from app.core.database import database_manager from app.repositories.tenant_repository import TenantRepository async with database_manager.get_session() as session: tenant_repo = TenantRepository(Tenant, session) tenant = await tenant_repo.get_by_id(tenant_id) if not tenant: raise HTTPException(status_code=404, detail="Tenant not found") result = { 'tenant_id': tenant_id, 'name': tenant.name, 'tenant_type': tenant.tenant_type, 'parent_tenant_id': tenant.parent_tenant_id, 'hierarchy_path': tenant.hierarchy_path, 'is_parent': tenant.tenant_type == 'parent', 'is_child': tenant.tenant_type == 'child' } # If this is a parent, include child count if tenant.tenant_type == 'parent': child_count = await tenant_repo.get_child_tenant_count(tenant_id) result['child_tenant_count'] = child_count return result except Exception as e: raise HTTPException(status_code=500, detail=f"Failed to get hierarchy: {str(e)}") @router.get("/users/{user_id}/tenant-hierarchy") async def get_user_accessible_tenant_hierarchy( user_id: str, current_user: Dict[str, Any] = Depends(get_current_user_dep) ): """ Get all tenants a user has access to, organized in hierarchy """ try: from app.core.database import database_manager from app.repositories.tenant_repository import TenantRepository # Fetch all tenants where user has access, organized hierarchically async with database_manager.get_session() as session: tenant_repo = TenantRepository(Tenant, session) user_tenants = await tenant_repo.get_user_tenants_with_hierarchy(user_id) return { 'user_id': user_id, 'tenants': user_tenants, 'total_count': len(user_tenants) } except Exception as e: raise HTTPException(status_code=500, detail=f"Failed to get user hierarchy: {str(e)}")