Spaces:
Sleeping
Sleeping
| """ | |
| Client Management Endpoints | |
| """ | |
| from fastapi import APIRouter, Depends, HTTPException, status, Query | |
| from sqlalchemy.orm import Session | |
| from typing import List | |
| from uuid import UUID | |
| import logging | |
| from app.api.deps import get_db, get_current_active_user | |
| from app.models.client import Client | |
| from app.models.user import User | |
| from app.schemas.client import ClientCreate, ClientUpdate, ClientResponse | |
| logger = logging.getLogger(__name__) | |
| router = APIRouter(prefix="/clients", tags=["Clients"]) | |
| async def create_client( | |
| client_data: ClientCreate, | |
| db: Session = Depends(get_db), | |
| current_user: User = Depends(get_current_active_user) | |
| ): | |
| """ | |
| Create a new client organization | |
| **Requires:** platform_admin, client_admin, or contractor_admin role | |
| **Business Logic:** | |
| - Platform admins can create any client | |
| - Client admins can create clients (to track their own organization or partners) | |
| - Contractor admins can create clients they work with | |
| This enables self-service onboarding: when a contractor needs to work with a | |
| new client not yet in the system, they can create the client record themselves. | |
| - **name**: Client name (must be unique) | |
| - **industry**: Industry type (e.g., 'Telecommunications', 'Utilities') | |
| - **main_email**: Primary contact email | |
| - **main_phone**: Primary contact phone | |
| - **default_sla_days**: Default SLA window in days | |
| """ | |
| # Check authorization - platform_admin, client_admin, contractor_admin, project_manager, sales_manager, dispatcher | |
| if current_user.role not in ['platform_admin', 'client_admin', 'contractor_admin', 'project_manager', 'sales_manager', 'dispatcher']: | |
| raise HTTPException( | |
| status_code=status.HTTP_403_FORBIDDEN, | |
| detail="Only platform administrators, organization admins, project managers, sales managers, and dispatchers can create clients" | |
| ) | |
| # Check if client name already exists | |
| existing_client = db.query(Client).filter( | |
| Client.name == client_data.name, | |
| Client.deleted_at == None | |
| ).first() | |
| if existing_client: | |
| # Return existing client with flag | |
| logger.info(f"Client '{client_data.name}' already exists, returning existing record") | |
| return existing_client | |
| # Check if email already exists | |
| if client_data.main_email: | |
| existing_email = db.query(Client).filter( | |
| Client.main_email == client_data.main_email, | |
| Client.deleted_at == None | |
| ).first() | |
| if existing_email: | |
| logger.info(f"Client with email '{client_data.main_email}' already exists") | |
| return existing_email | |
| # Create new client | |
| new_client = Client(**client_data.model_dump()) | |
| # Generate SwiftOps code if not provided | |
| if not new_client.swiftops_code: | |
| from app.utils.org_code_generator import generate_org_code | |
| new_client.swiftops_code = generate_org_code(new_client.name, db) | |
| db.add(new_client) | |
| db.commit() | |
| db.refresh(new_client) | |
| # Audit log | |
| from app.services.audit_service import AuditService | |
| from fastapi import Request | |
| AuditService.log_action( | |
| db=db, | |
| action='create', | |
| entity_type='client', | |
| entity_id=str(new_client.id), | |
| description=f"Client organization created: {new_client.name} by {current_user.role}", | |
| user=current_user, | |
| request=None, | |
| changes={'new': {'name': new_client.name, 'industry': new_client.industry}} | |
| ) | |
| logger.info(f"New client created: {new_client.name} by {current_user.email} ({current_user.role})") | |
| return new_client | |
| async def list_clients( | |
| skip: int = Query(0, ge=0), | |
| limit: int = Query(100, ge=1, le=100), | |
| is_active: bool = Query(None), | |
| db: Session = Depends(get_db), | |
| current_user: User = Depends(get_current_active_user) | |
| ): | |
| """ | |
| List all clients with pagination | |
| **Authorization:** | |
| - Platform admins see all clients | |
| - Client admins see only their own client organization | |
| - Contractor admins see all clients (their business partners) | |
| - **skip**: Number of records to skip (default: 0) | |
| - **limit**: Maximum number of records to return (default: 100) | |
| - **is_active**: Filter by active status (optional) | |
| """ | |
| query = db.query(Client).filter(Client.deleted_at == None) | |
| # Org scoping for client_admin | |
| if current_user.role == 'client_admin' and current_user.client_id: | |
| query = query.filter(Client.id == current_user.client_id) | |
| if is_active is not None: | |
| query = query.filter(Client.is_active == is_active) | |
| clients = query.order_by(Client.created_at.desc()).offset(skip).limit(limit).all() | |
| return clients | |
| async def get_client( | |
| client_id: UUID, | |
| db: Session = Depends(get_db), | |
| current_user: User = Depends(get_current_active_user) | |
| ): | |
| """ | |
| Get a specific client by ID | |
| """ | |
| client = db.query(Client).filter( | |
| Client.id == client_id, | |
| Client.deleted_at == None | |
| ).first() | |
| if not client: | |
| raise HTTPException( | |
| status_code=status.HTTP_404_NOT_FOUND, | |
| detail="Client not found" | |
| ) | |
| return client | |
| async def update_client( | |
| client_id: UUID, | |
| client_data: ClientUpdate, | |
| db: Session = Depends(get_db), | |
| current_user: User = Depends(get_current_active_user) | |
| ): | |
| """ | |
| Update a client | |
| **Requires:** platform_admin or client_admin role | |
| """ | |
| # Get client | |
| client = db.query(Client).filter( | |
| Client.id == client_id, | |
| Client.deleted_at == None | |
| ).first() | |
| if not client: | |
| raise HTTPException( | |
| status_code=status.HTTP_404_NOT_FOUND, | |
| detail="Client not found" | |
| ) | |
| # Check authorization | |
| if current_user.role not in ['platform_admin', 'client_admin', 'project_manager', 'sales_manager', 'dispatcher']: | |
| raise HTTPException( | |
| status_code=status.HTTP_403_FORBIDDEN, | |
| detail="Insufficient permissions to update client" | |
| ) | |
| # If client_admin or manager/dispatcher, ensure they belong to this client | |
| if current_user.role in ['client_admin', 'project_manager', 'sales_manager', 'dispatcher'] and current_user.client_id and current_user.client_id != client_id: | |
| raise HTTPException( | |
| status_code=status.HTTP_403_FORBIDDEN, | |
| detail="You can only update your own client organization" | |
| ) | |
| # Update fields | |
| update_data = client_data.model_dump(exclude_unset=True) | |
| # Check for name uniqueness if updating name | |
| if 'name' in update_data and update_data['name'] != client.name: | |
| existing = db.query(Client).filter(Client.name == update_data['name']).first() | |
| if existing: | |
| raise HTTPException( | |
| status_code=status.HTTP_400_BAD_REQUEST, | |
| detail=f"Client with name '{update_data['name']}' already exists" | |
| ) | |
| # Check for email uniqueness if updating email | |
| if 'main_email' in update_data and update_data['main_email'] != client.main_email: | |
| existing = db.query(Client).filter(Client.main_email == update_data['main_email']).first() | |
| if existing: | |
| raise HTTPException( | |
| status_code=status.HTTP_400_BAD_REQUEST, | |
| detail=f"Client with email '{update_data['main_email']}' already exists" | |
| ) | |
| for field, value in update_data.items(): | |
| setattr(client, field, value) | |
| db.commit() | |
| db.refresh(client) | |
| return client | |
| async def delete_client( | |
| client_id: UUID, | |
| db: Session = Depends(get_db), | |
| current_user: User = Depends(get_current_active_user) | |
| ): | |
| """ | |
| Soft delete a client | |
| **Requires:** platform_admin role | |
| """ | |
| # Check authorization | |
| if current_user.role != 'platform_admin': | |
| raise HTTPException( | |
| status_code=status.HTTP_403_FORBIDDEN, | |
| detail="Only platform administrators can delete clients" | |
| ) | |
| # Get client | |
| client = db.query(Client).filter( | |
| Client.id == client_id, | |
| Client.deleted_at == None | |
| ).first() | |
| if not client: | |
| raise HTTPException( | |
| status_code=status.HTTP_404_NOT_FOUND, | |
| detail="Client not found" | |
| ) | |
| # Soft delete | |
| from datetime import datetime, timezone | |
| client.deleted_at = datetime.now(timezone.utc) | |
| db.commit() | |
| return None | |