feat(company): add tenant_id support for hub_admin in company creation and updates

This commit is contained in:
2026-05-15 10:28:47 -05:00
parent 125405458c
commit 64d81c83f9
5 changed files with 38 additions and 15 deletions

View File

@@ -12,6 +12,9 @@ from pydantic import BaseModel, ConfigDict, Field, field_validator
class CompanyCreateDTO(BaseModel):
"""DTO para crear una empresa"""
# Solo requerido cuando hub_admin crea una empresa (no tiene tenant_id en token)
tenant_id: Optional[int] = Field(None, description="Tenant ID (requerido para hub_admin)")
name: Optional[str] = Field(None, max_length=255, description="Company name")
rfc: Optional[str] = Field(None, max_length=30, description="Company RFC")
main_activity: Optional[str] = Field(

View File

@@ -10,7 +10,7 @@ from typing import List, Optional
from pathlib import Path
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, status, File, UploadFile
from fastapi import APIRouter, Depends, HTTPException, Request, status, File, UploadFile
from fastapi.responses import FileResponse, Response
from sqlalchemy.orm import Session
@@ -116,6 +116,7 @@ router = APIRouter(prefix="/company")
summary="Create a new company",
)
async def create_company(
request: Request,
data: CompanyCreateDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
@@ -124,10 +125,18 @@ async def create_company(
tenant_id = resolve_tenant_id_required(current_user, db=db)
if tenant_id is None:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Hub admin: debe especificar tenant_id para crear una empresa",
)
# Prioridad: X-Tenant-Override del header (tenant seleccionado en el sidebar)
override = request.headers.get("X-Tenant-Override")
if override and str(override).strip().isdigit():
tenant_id = int(override)
# Fallback: campo tenant_id en el body (enviado por el frontend como respaldo)
elif data.tenant_id:
tenant_id = data.tenant_id
else:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Hub admin: selecciona un tenant en el sidebar antes de crear una empresa",
)
service = CompanyService(db)
new_company = service.create_company_manually(data, tenant_id=tenant_id)
@@ -140,6 +149,7 @@ async def create_company(
summary="Get companies with pagination",
)
async def list_companies(
request: Request,
page: int = 1,
page_size: int = 50,
name: Optional[str] = None,
@@ -151,6 +161,11 @@ async def list_companies(
_assert_permission_any_company(db, current_user, "cat_company.view")
tenant_id = resolve_tenant_id_required(current_user, db=db)
# Hub admin: si hay X-Tenant-Override y aún no se resolvió tenant, filtra por ese tenant
if tenant_id is None and is_hub_admin(current_user):
override = request.headers.get("X-Tenant-Override")
if override and str(override).strip().isdigit():
tenant_id = int(override)
skip = (page - 1) * page_size
filters = {}
@@ -189,28 +204,27 @@ async def list_companies(
summary="Get all companies for current tenant",
)
async def get_my_companies(
request: Request,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
"""
Compañías accesibles: tenant del token/Hub (si existe) unión con membership en BD
(``user_company_roles``, ``user_company_permissions``, ``user_tenants``).
Un usuario solo con roles de app y sin ``tenant_id`` en /auth/me sigue pudiendo
listar sus compañías asignadas.
Hub admin: si hay X-Tenant-Override muestra solo ese tenant; si no, muestra todos.
"""
from core.security import collect_user_role_names
user_roles = collect_user_role_names(current_user)
# Hub admin: visibilidad global sobre todas las compañías sin restricciones
# de tenant ni licencia. El Hub ya validó el rol en /auth/me.
# Hub admin: respeta X-Tenant-Override para filtrar por tenant seleccionado;
# sin override devuelve todo (visibilidad global).
if "hub_admin" in user_roles:
service = CompanyService(db)
all_companies = (
db.query(Company)
.filter(Company.deleted_at.is_(None))
.order_by(Company.name)
.all()
)
override = request.headers.get("X-Tenant-Override")
query = db.query(Company).filter(Company.deleted_at.is_(None))
if override and str(override).strip().isdigit():
query = query.filter(Company.tenant_id == int(override))
all_companies = query.order_by(Company.name).all()
return [
CompanyResponseDTO.model_validate(service.flatten_company_dto(c))
for c in all_companies