Add new changes to client profile and related files

This commit is contained in:
2026-02-05 14:03:56 -07:00
parent 896c99d586
commit ea682d8cd9
11 changed files with 2761 additions and 243 deletions

View File

@@ -0,0 +1,299 @@
"""
Client Profile Endpoints - ServiceManagerWeb
Endpoints para gestión del perfil empresarial de clientes
"""
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, or_
from typing import Optional
from app.core.database import get_db
from app.api.deps import get_current_user, get_current_tenant
from app.api.schemas.client_profile import (
ClientProfileCreate,
ClientProfileUpdate,
ClientProfileResponse,
ClientProfileSummary
)
from app.models.user import User
from app.models.tenant import Tenant
from app.models.client_profile import ClientProfile
import uuid
router = APIRouter()
@router.get("/", response_model=ClientProfileResponse)
async def get_current_client_profile(
current_user: User = Depends(get_current_user),
current_tenant: Tenant = Depends(get_current_tenant),
db: AsyncSession = Depends(get_db)
):
"""
Obtener el perfil empresarial del tenant actual.
**Permisos**: CLIENT_ADMIN, CLIENT_USER
"""
# Solo clientes pueden acceder
if current_user.role not in ['CLIENT_ADMIN', 'CLIENT_USER']:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Solo los clientes pueden acceder al perfil empresarial"
)
# Buscar perfil existente
result = await db.execute(
select(ClientProfile).where(ClientProfile.tenant_id == current_tenant.id)
)
profile = result.scalar_one_or_none()
if not profile:
# Si no existe, crear uno vacío
profile = ClientProfile(tenant_id=current_tenant.id)
db.add(profile)
await db.commit()
await db.refresh(profile)
return profile
@router.post("/", response_model=ClientProfileResponse)
async def create_or_update_client_profile(
profile_data: ClientProfileCreate,
current_user: User = Depends(get_current_user),
current_tenant: Tenant = Depends(get_current_tenant),
db: AsyncSession = Depends(get_db)
):
"""
Crear o actualizar el perfil empresarial del tenant actual.
**Permisos**: CLIENT_ADMIN
"""
# Solo CLIENT_ADMIN puede modificar el perfil
if current_user.role != 'CLIENT_ADMIN':
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Solo los administradores de cliente pueden modificar el perfil empresarial"
)
# Buscar perfil existente
result = await db.execute(
select(ClientProfile).where(ClientProfile.tenant_id == current_tenant.id)
)
existing_profile = result.scalar_one_or_none()
if existing_profile:
# Actualizar perfil existente
update_data = profile_data.dict(exclude_unset=True)
for field, value in update_data.items():
setattr(existing_profile, field, value)
profile = existing_profile
else:
# Crear nuevo perfil
profile = ClientProfile(
tenant_id=current_tenant.id,
**profile_data.dict()
)
db.add(profile)
await db.commit()
await db.refresh(profile)
return profile
@router.patch("/", response_model=ClientProfileResponse)
async def update_client_profile(
profile_data: ClientProfileUpdate,
current_user: User = Depends(get_current_user),
current_tenant: Tenant = Depends(get_current_tenant),
db: AsyncSession = Depends(get_db)
):
"""
Actualizar parcialmente el perfil empresarial del tenant actual.
**Permisos**: CLIENT_ADMIN
"""
# Solo CLIENT_ADMIN puede modificar el perfil
if current_user.role != 'CLIENT_ADMIN':
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Solo los administradores de cliente pueden modificar el perfil empresarial"
)
# Buscar perfil existente
result = await db.execute(
select(ClientProfile).where(ClientProfile.tenant_id == current_tenant.id)
)
profile = result.scalar_one_or_none()
if not profile:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Perfil empresarial no encontrado"
)
# Actualizar solo campos proporcionados
update_data = profile_data.dict(exclude_unset=True)
for field, value in update_data.items():
setattr(profile, field, value)
await db.commit()
await db.refresh(profile)
return profile
@router.delete("/")
async def delete_client_profile(
current_user: User = Depends(get_current_user),
current_tenant: Tenant = Depends(get_current_tenant),
db: AsyncSession = Depends(get_db)
):
"""
Eliminar el perfil empresarial del tenant actual.
**Permisos**: CLIENT_ADMIN
"""
# Solo CLIENT_ADMIN puede eliminar el perfil
if current_user.role != 'CLIENT_ADMIN':
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Solo los administradores de cliente pueden eliminar el perfil empresarial"
)
# Buscar perfil existente
result = await db.execute(
select(ClientProfile).where(ClientProfile.tenant_id == current_tenant.id)
)
profile = result.scalar_one_or_none()
if not profile:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Perfil empresarial no encontrado"
)
await db.delete(profile)
await db.commit()
return {"message": "Perfil empresarial eliminado exitosamente"}
# === ENDPOINTS ADMINISTRATIVOS (Solo para ADMIN y SUPPORT_MANAGER) ===
@router.get("/admin/list", response_model=list[ClientProfileSummary])
async def list_all_client_profiles(
skip: int = 0,
limit: int = 100,
search: Optional[str] = None,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
"""
Listar todos los perfiles empresariales (solo para administradores).
**Permisos**: ADMIN, SUPPORT_MANAGER
"""
# Solo personal interno puede ver todos los perfiles
if current_user.role not in ['ADMIN', 'SUPPORT_MANAGER']:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Acceso denegado"
)
query = select(ClientProfile)
# Filtro de búsqueda
if search:
search_filter = or_(
ClientProfile.business_name.ilike(f"%{search}%"),
ClientProfile.commercial_name.ilike(f"%{search}%"),
ClientProfile.rfc.ilike(f"%{search}%"),
ClientProfile.client_code.ilike(f"%{search}%")
)
query = query.where(search_filter)
# Paginación
query = query.offset(skip).limit(limit)
query = query.order_by(ClientProfile.created_at.desc())
result = await db.execute(query)
profiles = result.scalars().all()
return profiles
@router.get("/admin/{tenant_id}", response_model=ClientProfileResponse)
async def get_client_profile_by_tenant(
tenant_id: uuid.UUID,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
"""
Obtener perfil empresarial de un tenant específico (solo para administradores).
**Permisos**: ADMIN, SUPPORT_MANAGER
"""
# Solo personal interno puede ver perfiles de otros tenants
if current_user.role not in ['ADMIN', 'SUPPORT_MANAGER']:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Acceso denegado"
)
result = await db.execute(
select(ClientProfile).where(ClientProfile.tenant_id == tenant_id)
)
profile = result.scalar_one_or_none()
if not profile:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Perfil empresarial no encontrado"
)
return profile
@router.patch("/admin/{tenant_id}", response_model=ClientProfileResponse)
async def update_client_profile_by_admin(
tenant_id: uuid.UUID,
profile_data: ClientProfileUpdate,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
"""
Actualizar perfil empresarial de un tenant específico (solo para administradores).
**Permisos**: ADMIN, SUPPORT_MANAGER
"""
# Solo personal interno puede modificar perfiles de otros tenants
if current_user.role not in ['ADMIN', 'SUPPORT_MANAGER']:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Acceso denegado"
)
result = await db.execute(
select(ClientProfile).where(ClientProfile.tenant_id == tenant_id)
)
profile = result.scalar_one_or_none()
if not profile:
# Crear perfil si no existe
profile = ClientProfile(tenant_id=tenant_id, **profile_data.dict(exclude_unset=True))
db.add(profile)
else:
# Actualizar perfil existente
update_data = profile_data.dict(exclude_unset=True)
for field, value in update_data.items():
setattr(profile, field, value)
await db.commit()
await db.refresh(profile)
return profile

View File

@@ -6,7 +6,7 @@ Router principal para la API v1
from fastapi import APIRouter
from app.api.v1.endpoints import auth, health, tenants, users, systems, categories, tickets
from app.api.v1.endpoints import auth, health, tenants, users, systems, categories, tickets, client_profile
api_router = APIRouter()
@@ -54,6 +54,9 @@ api_router.include_router(
tags=["tickets"]
)
# Ensure FastAPI is installed in the environment
# If not, install it using:
# pip install fastapi
# Client Profile routes
api_router.include_router(
client_profile.router,
prefix="/client-profile",
tags=["client-profile"]
)