diff --git a/backend/api/v1/modules/a76/general_catalogs/company/dto.py b/backend/api/v1/modules/a76/general_catalogs/company/dto.py index 97421872..7ff5f030 100644 --- a/backend/api/v1/modules/a76/general_catalogs/company/dto.py +++ b/backend/api/v1/modules/a76/general_catalogs/company/dto.py @@ -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( diff --git a/backend/api/v1/modules/a76/general_catalogs/company/routes.py b/backend/api/v1/modules/a76/general_catalogs/company/routes.py index 395befcb..012e7306 100644 --- a/backend/api/v1/modules/a76/general_catalogs/company/routes.py +++ b/backend/api/v1/modules/a76/general_catalogs/company/routes.py @@ -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 diff --git a/frontend/src/lib/api/dashboard/a76/general_catalogs/company.ts b/frontend/src/lib/api/dashboard/a76/general_catalogs/company.ts index 7934463e..4367ac53 100644 --- a/frontend/src/lib/api/dashboard/a76/general_catalogs/company.ts +++ b/frontend/src/lib/api/dashboard/a76/general_catalogs/company.ts @@ -256,6 +256,8 @@ export interface Company { } export interface CompanyCreate { + /** Solo requerido cuando hub_admin crea una empresa (no tiene tenant_id en token) */ + tenant_id?: number | null; name?: string | null; rfc?: string | null; curp?: string | null; diff --git a/frontend/src/lib/components/dashboard/general_catalogs/company/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/general_catalogs/company/create-edit-dialog.svelte index 933f4680..a347593d 100644 --- a/frontend/src/lib/components/dashboard/general_catalogs/company/create-edit-dialog.svelte +++ b/frontend/src/lib/components/dashboard/general_catalogs/company/create-edit-dialog.svelte @@ -104,6 +104,8 @@ // Construir payload limpiando strings vacíos a null const dataToSend = { + // Hub admin no tiene tenant_id en token; lo tomamos de la empresa activa + tenant_id: companyStore.activeCompany?.tenant_id ?? undefined, name: formData.name.trim(), rfc: formData.rfc.trim(), curp: formData.curp.trim() || null, diff --git a/frontend/src/routes/dashboard/general_catalogs/company_information/edit/[[id]]/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/company_information/edit/[[id]]/+page.svelte index e45c88af..dec8eb52 100644 --- a/frontend/src/routes/dashboard/general_catalogs/company_information/edit/[[id]]/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/company_information/edit/[[id]]/+page.svelte @@ -588,6 +588,8 @@ const payload = { ...formData, + // Hub admin no tiene tenant_id en token; se infiere de la empresa activa en contexto + tenant_id: companyStore.activeCompany?.tenant_id ?? undefined, name: formData.name.trim(), rfc: formData.rfc.trim(), responsible: