feature/catalogos-generales-permisos

This commit is contained in:
2026-05-06 11:13:09 -06:00
parent 12dd328eb5
commit e3ad7e7a7e
118 changed files with 1318 additions and 324 deletions

View File

@@ -19,6 +19,8 @@ from core.database import get_core_db
from core.s3_keys import company_certificate_key, company_logo_key
from core.storage_s3 import delete_object_if_exists, get_object_bytes, put_object_bytes
from core.security import (
collect_company_ids_from_app_membership,
collect_user_role_names,
get_current_user,
get_tenant_from_token,
resolve_effective_tenant_id_from_user,
@@ -37,6 +39,48 @@ MAX_FILE_SIZE = 5 * 1024 * 1024 # 5MB
logger = logging.getLogger(__name__)
def _user_is_admin(current_user: dict) -> bool:
return "admin" in collect_user_role_names(current_user)
def _assert_permission_any_company(
db: Session,
current_user: dict,
permission_code: str,
) -> None:
"""
Enforces a permission when endpoint has no explicit company_id param.
"""
if _user_is_admin(current_user):
return
user_id = current_user.get("sub") or current_user.get("id")
if not user_id:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Permission denied")
company_ids = collect_company_ids_from_app_membership(db, str(user_id))
if not company_ids:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Permission denied")
from api.v1.modules.core.permissions.service import PermissionService
permission_service = PermissionService(db)
for company_id in company_ids:
if permission_service.has_any_permission(str(user_id), int(company_id), [permission_code]):
return
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Permission denied")
def _assert_permission_for_company(
db: Session,
company_id: int,
current_user: dict,
permission_code: str,
) -> int:
return validate_access_to_resource(db, company_id, current_user, [permission_code])
def _resolve_tenant_id_int(current_user: dict) -> int:
"""Misma lógica que validate_access_to_resource: entero estable para BD y claves S3."""
tid = get_tenant_from_token(current_user)
@@ -87,6 +131,7 @@ async def create_company(
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
_assert_permission_any_company(db, current_user, "cat_company.create")
tenant_id = current_user.get("tenant_id")
if not tenant_id:
@@ -114,6 +159,8 @@ async def list_companies(
current_user: dict = Depends(get_current_user),
):
"""Get paginated list of companies for current tenant with optional filters"""
_assert_permission_any_company(db, current_user, "cat_company.view")
tenant_id = current_user.get("tenant_id")
if not tenant_id:
raise HTTPException(
@@ -142,7 +189,7 @@ async def list_companies(
return {
"items": [
CompanyResponseDTO.model_validate(service.flatten_company_dto(item))
CompanyResponseDTO.model_validate(service.flatten_company_dto(item))
for item in items
],
"total": total,
@@ -167,6 +214,8 @@ async def get_my_companies(
Un usuario solo con roles de app y sin ``tenant_id`` en /auth/me sigue pudiendo
listar sus compañías asignadas.
"""
_assert_permission_any_company(db, current_user, "cat_company.view")
user_id = current_user.get("sub") or current_user.get("id")
tenant_id = resolve_effective_tenant_id_from_user(current_user)
@@ -194,6 +243,8 @@ async def get_company(
current_user: dict = Depends(get_current_user),
):
"""Get a specific company by ID"""
_assert_permission_for_company(db, company_id, current_user, "cat_company.view")
tenant_id = current_user.get("tenant_id")
if not tenant_id:
raise HTTPException(
@@ -224,6 +275,8 @@ async def update_company(
current_user: dict = Depends(get_current_user),
):
"""Update a company"""
_assert_permission_for_company(db, company_id, current_user, "cat_company.edit")
tenant_id = current_user.get("tenant_id")
if not tenant_id:
raise HTTPException(
@@ -299,6 +352,8 @@ async def delete_company(
current_user: dict = Depends(get_current_user),
):
"""Delete a company"""
_assert_permission_for_company(db, company_id, current_user, "cat_company.delete")
tenant_id = current_user.get("tenant_id")
if not tenant_id:
raise HTTPException(
@@ -328,6 +383,8 @@ async def upload_company_logo(
current_user: dict = Depends(get_current_user),
):
"""Upload a logo for a company"""
_assert_permission_for_company(db, company_id, current_user, "cat_company.edit")
tenant_id = _resolve_tenant_id_int(current_user)
# Validar que la empresa existe
@@ -409,6 +466,8 @@ async def upload_company_certificate(
Upload a certificate for a company
certificate_type: fiel_cer, fiel_key, cfdi_cert_cer, cfdi_cert_key, cancel_cer, cancel_key
"""
_assert_permission_for_company(db, company_id, current_user, "cat_company.edit")
tenant_id = _resolve_tenant_id_int(current_user)
# Validar que la empresa existe

View File

@@ -30,6 +30,10 @@ base_router = TenantCRUDRoutes(
enable_filters=False,
default_page_size=100,
max_page_size=1000,
get_permissions=["cat_depreciation_catalog.view"],
create_permissions=["cat_depreciation_catalog.create"],
update_permissions=["cat_depreciation_catalog.edit"],
delete_permissions=["cat_depreciation_catalog.delete"],
)
router = APIRouter(prefix="/depreciation-catalog", tags=["a76 / general catalogs / depreciation catalog"])
@@ -49,7 +53,9 @@ async def list_depreciation_catalog(
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
tenant_id = validate_access_to_resource(db, company_id, current_user)
tenant_id = validate_access_to_resource(
db, company_id, current_user, ["cat_depreciation_catalog.view"]
)
items, total = DepreciationCatalogService.get_all(
db, tenant_id, company_id, page, page_size, search

View File

@@ -254,7 +254,9 @@ async def print_doda_pdf(
Genera o reutiliza el PDF almacenado en S3 cuando el contenido no ha cambiado
(huella SHA-256 de DODA + hijos).
"""
tenant_id = int(validate_access_to_resource(db, company_id, current_user))
tenant_id = int(
validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"])
)
doda = DodaService.get_by_id(db, doda_id, tenant_id, company_id)
if not doda:
raise HTTPException(
@@ -327,9 +329,12 @@ async def print_doda_pdf(
)
async def get_doda_containers(
doda_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
"""Get all containers for a specific DODA"""
validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"])
containers = DodaService.get_containers(db, doda_id)
return [DodaContainerResponseDTO.model_validate(c) for c in containers]
@@ -343,9 +348,12 @@ async def get_doda_containers(
async def add_container(
doda_id: int,
container_data: DodaContainerCreateDTO,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
"""Add a new container to a DODA"""
validate_access_to_resource(db, company_id, current_user, ["cat_doda.edit"])
container = DodaService.add_container(db, doda_id, container_data)
if not container:
raise HTTPException(
@@ -364,9 +372,12 @@ async def update_container(
doda_id: int,
container_line: int,
container_data: DodaContainerUpdateDTO,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
"""Update a container"""
validate_access_to_resource(db, company_id, current_user, ["cat_doda.edit"])
container = DodaService.update_container(
db, doda_id, container_line, container_data
)
@@ -386,12 +397,15 @@ async def update_container(
async def delete_container(
doda_id: int,
container_line: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
"""
Elimina un contenedor del DODA.
Devuelve 409 si el contenedor tiene precintos (candados) asignados.
"""
validate_access_to_resource(db, company_id, current_user, ["cat_doda.delete"])
DodaService.delete_container(db, doda_id, container_line)
return None
@@ -415,8 +429,11 @@ def _seal_to_response(seal) -> DodaContainerSealResponseDTO:
async def get_container_seals(
doda_id: int,
container_line: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"])
seals = DodaService.get_seals_for_container(db, doda_id, container_line)
return [_seal_to_response(s) for s in seals]
@@ -431,8 +448,11 @@ async def add_container_seal(
doda_id: int,
container_line: int,
seal_data: DodaContainerSealCreateDTO,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
validate_access_to_resource(db, company_id, current_user, ["cat_doda.edit"])
seal = DodaService.add_seal(db, doda_id, container_line, seal_data)
return _seal_to_response(seal)
@@ -446,8 +466,11 @@ async def delete_container_seal(
doda_id: int,
container_line: int,
seal_line: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
validate_access_to_resource(db, company_id, current_user, ["cat_doda.delete"])
DodaService.delete_seal(db, doda_id, container_line, seal_line)
return None
@@ -460,9 +483,12 @@ async def delete_container_seal(
)
async def get_american_pedimentos(
doda_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
"""Get all American pedimentos for a specific DODA"""
validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"])
pedimentos = DodaService.get_american_pedimentos(db, doda_id)
return [DodaAmericanPedimentoResponseDTO.model_validate(p) for p in pedimentos]
@@ -476,9 +502,12 @@ async def get_american_pedimentos(
async def add_american_pedimento(
doda_id: int,
pedimento_data: DodaAmericanPedimentoCreateDTO,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
"""Add a new American pedimento to a DODA"""
validate_access_to_resource(db, company_id, current_user, ["cat_doda.edit"])
pedimento = DodaService.add_american_pedimento(db, doda_id, pedimento_data)
if not pedimento:
raise HTTPException(
@@ -497,9 +526,12 @@ async def add_american_pedimento(
async def delete_american_pedimento(
doda_id: int,
pedimento_line: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
"""Elimina un pedimento americano del DODA."""
validate_access_to_resource(db, company_id, current_user, ["cat_doda.delete"])
DodaService.delete_american_pedimento(db, doda_id, pedimento_line)
return None
@@ -512,9 +544,12 @@ async def delete_american_pedimento(
)
async def get_doda_pedimentos(
doda_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
"""Get all pedimentos for a specific DODA"""
validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"])
pedimentos = DodaService.get_pedimentos(db, doda_id)
return [DodaPedimentoResponseDTO.model_validate(p) for p in pedimentos]
@@ -528,9 +563,12 @@ async def get_doda_pedimentos(
async def add_pedimento(
doda_id: int,
pedimento_data: DodaPedimentoCreateDTO,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
"""Add a new pedimento to a DODA"""
validate_access_to_resource(db, company_id, current_user, ["cat_doda.edit"])
pedimento = DodaService.add_pedimento(db, doda_id, pedimento_data)
if not pedimento:
raise HTTPException(
@@ -548,9 +586,12 @@ async def add_pedimento(
async def delete_pedimento(
doda_id: int,
pedimento_line: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
"""Elimina un pedimento del DODA."""
validate_access_to_resource(db, company_id, current_user, ["cat_doda.delete"])
DodaService.delete_pedimento(db, doda_id, pedimento_line)
return None
@@ -575,7 +616,7 @@ async def get_doda_elegibilidad(
Porta las validaciones del sistema legacy (campos requeridos, max 4 contenedores,
gafete si DODA, patente vs agente, certificados DODA en VU).
"""
tenant_id = validate_access_to_resource(db, company_id, current_user)
tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"])
user_email = (
current_user.get("email")
or current_user.get("preferred_username")
@@ -614,7 +655,7 @@ async def post_doda_alta(
Verifica elegibilidad, construye el payload desde los datos del DODA y su VU,
y envía el alta al servicio externo. Retorna {task_id, status, message} para polling.
"""
tenant_id = validate_access_to_resource(db, company_id, current_user)
tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_doda.edit"])
user_email = (
current_user.get("email")
or current_user.get("preferred_username")
@@ -694,11 +735,14 @@ async def post_doda_alta(
)
async def get_doda_alta_status(
task_id: str,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
) -> Any:
"""
Proxy transparente al servicio externo para consultar el estado de una tarea de alta DODA.
"""
validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"])
try:
ext = DodaExternalService()
return ext.get_status(task_id)
@@ -727,7 +771,7 @@ async def post_doda_consulta(
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
) -> Dict[str, Any]:
tenant_id = validate_access_to_resource(db, company_id, current_user)
tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_doda.edit"])
user_email = (
current_user.get("email")
or current_user.get("preferred_username")
@@ -788,8 +832,11 @@ async def post_doda_consulta(
)
async def get_doda_consulta_status(
task_id: str,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
) -> Any:
validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"])
try:
ext = DodaExternalService()
return ext.get_consulta_status(task_id)
@@ -818,7 +865,9 @@ async def post_doda_consulta_apply(
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
) -> Dict[str, Any]:
tenant_id = int(validate_access_to_resource(db, company_id, current_user))
tenant_id = int(
validate_access_to_resource(db, company_id, current_user, ["cat_doda.edit"])
)
doda_record = DodaService.get_by_id(db, doda_id, tenant_id, company_id)
if not doda_record:
raise HTTPException(status_code=404, detail="DODA no encontrado.")
@@ -880,7 +929,7 @@ async def post_doda_eliminar(
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
) -> Dict[str, Any]:
tenant_id = validate_access_to_resource(db, company_id, current_user)
tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_doda.delete"])
user_email = (
current_user.get("email")
or current_user.get("preferred_username")
@@ -960,8 +1009,11 @@ async def post_doda_eliminar(
)
async def get_doda_eliminar_status(
task_id: str,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
) -> Any:
validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"])
try:
ext = DodaExternalService()
return ext.get_eliminar_status(task_id)
@@ -996,7 +1048,7 @@ async def list_doda_alta_logs(
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
tenant_id = validate_access_to_resource(db, company_id, current_user)
tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"])
return DodaAltaLogService.list(
db, company_id, int(tenant_id), page, page_size, doda_id, search
)
@@ -1014,7 +1066,7 @@ async def get_doda_alta_log(
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
tenant_id = validate_access_to_resource(db, company_id, current_user)
tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"])
record = DodaAltaLogService.get(db, log_id, company_id, int(tenant_id))
if not record:
raise HTTPException(status_code=404, detail="Registro de alta DODA no encontrado.")
@@ -1034,7 +1086,7 @@ async def create_doda_alta_log(
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
tenant_id = validate_access_to_resource(db, company_id, current_user)
tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_doda.create"])
record = DodaAltaLogService.create(db, dto, company_id, int(tenant_id))
return DodaAltaLogResponseDTO.model_validate(record)
@@ -1052,7 +1104,7 @@ async def update_doda_alta_log(
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
tenant_id = validate_access_to_resource(db, company_id, current_user)
tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_doda.edit"])
record = DodaAltaLogService.get(db, log_id, company_id, int(tenant_id))
if not record:
raise HTTPException(status_code=404, detail="Registro de alta DODA no encontrado.")
@@ -1072,7 +1124,7 @@ async def delete_doda_alta_log(
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
tenant_id = validate_access_to_resource(db, company_id, current_user)
tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_doda.delete"])
record = DodaAltaLogService.get(db, log_id, company_id, int(tenant_id))
if not record:
raise HTTPException(status_code=404, detail="Registro de alta DODA no encontrado.")

View File

@@ -4,12 +4,12 @@ Rutas para gestión de catálogos de errores
from typing import List
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import get_current_user
from api.v1.common.tenant_crud_routes import TenantCRUDRoutes
from api.v1.common.tenant_crud_routes import TenantCRUDRoutes, validate_access_to_resource
from .dto import (
ErrorClassificationCreateDTO,
ErrorClassificationResponseDTO,
@@ -35,6 +35,11 @@ classification_crud = TenantCRUDRoutes(
tags=["error-classifications"],
resource_name="ErrorClassification",
enable_list=True,
list_permissions=["cat_errors.view"],
get_permissions=["cat_errors.view"],
create_permissions=["cat_errors.create"],
update_permissions=["cat_errors.edit"],
delete_permissions=["cat_errors.delete"],
)
# Add custom endpoints for classifications
@@ -47,15 +52,17 @@ classification_crud = TenantCRUDRoutes(
)
async def get_classification_by_code(
code: str,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
"""Get an error classification by its code with all related errors"""
tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_errors.view"])
classification = ErrorClassificationService.get_by_code(
db,
code,
tenant_id=current_user["tenant_id"],
company_id=current_user["company_id"]
tenant_id=tenant_id,
company_id=company_id,
)
if not classification:
raise HTTPException(
@@ -74,15 +81,17 @@ async def get_classification_by_code(
)
async def get_classification(
id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
"""Get an error classification by its ID with all related errors"""
tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_errors.view"])
classification = ErrorClassificationService.get_by_id(
db,
id,
tenant_id=current_user["tenant_id"],
company_id=current_user["company_id"]
tenant_id=tenant_id,
company_id=company_id,
)
if not classification:
raise HTTPException(
@@ -104,6 +113,11 @@ catalog_crud = TenantCRUDRoutes(
tags=["error-catalogs"],
resource_name="ErrorCatalog",
enable_list=True,
list_permissions=["cat_errors.view"],
get_permissions=["cat_errors.view"],
create_permissions=["cat_errors.create"],
update_permissions=["cat_errors.edit"],
delete_permissions=["cat_errors.delete"],
)
# Add custom endpoints for catalogs
@@ -116,15 +130,17 @@ catalog_crud = TenantCRUDRoutes(
)
async def get_error_by_code(
code: str,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
"""Get an error by its code with classification details"""
tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_errors.view"])
error = ErrorCatalogService.get_by_code(
db,
code,
tenant_id=current_user["tenant_id"],
company_id=current_user["company_id"]
tenant_id=tenant_id,
company_id=company_id,
)
if not error:
raise HTTPException(
@@ -141,15 +157,17 @@ async def get_error_by_code(
)
async def get_errors_by_classification(
classification_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
"""Get all errors for a specific classification"""
tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_errors.view"])
errors = ErrorCatalogService.get_by_classification(
db,
classification_id,
tenant_id=current_user["tenant_id"],
company_id=current_user["company_id"]
tenant_id=tenant_id,
company_id=company_id,
)
return [ErrorCatalogResponseDTO.model_validate(error) for error in errors]
@@ -163,15 +181,17 @@ async def get_errors_by_classification(
)
async def get_error(
id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
"""Get an error by its ID with classification details"""
tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_errors.view"])
error = ErrorCatalogService.get_by_id(
db,
id,
tenant_id=current_user["tenant_id"],
company_id=current_user["company_id"]
tenant_id=tenant_id,
company_id=company_id,
)
if not error:
raise HTTPException(

View File

@@ -25,9 +25,9 @@ def list_canadian_fractions(
db: Session = Depends(get_core_db),
current_user = Depends(get_current_user)
):
from core.security import validate_access_to_resource as validate_perm
validate_perm(db, company_id, current_user, ["frac_canadian.view"])
tenant_id = validate_access_to_resource(db, company_id, current_user)
tenant_id = validate_access_to_resource(
db, company_id, current_user, ["frac_canadian.view"]
)
skip = (page - 1) * page_size
service = CanadianTariffFractionService(db)
items, total = service.get_multi(
@@ -52,9 +52,9 @@ def get_canadian_fraction(
db: Session = Depends(get_core_db),
current_user = Depends(get_current_user)
):
from core.security import validate_access_to_resource as validate_perm
validate_perm(db, company_id, current_user, ["frac_canadian.view"])
tenant_id = validate_access_to_resource(db, company_id, current_user)
tenant_id = validate_access_to_resource(
db, company_id, current_user, ["frac_canadian.view"]
)
service = CanadianTariffFractionService(db)
item = service.get(id, tenant_id, company_id)
if not item:
@@ -68,9 +68,9 @@ def create_canadian_fraction(
db: Session = Depends(get_core_db),
current_user = Depends(get_current_user)
):
from core.security import validate_access_to_resource as validate_perm
validate_perm(db, company_id, current_user, ["frac_canadian.create"])
tenant_id = validate_access_to_resource(db, company_id, current_user)
tenant_id = validate_access_to_resource(
db, company_id, current_user, ["frac_canadian.create"]
)
service = CanadianTariffFractionService(db)
return service.create(item_in, tenant_id, company_id)
@@ -82,9 +82,9 @@ def update_canadian_fraction(
db: Session = Depends(get_core_db),
current_user = Depends(get_current_user)
):
from core.security import validate_access_to_resource as validate_perm
validate_perm(db, company_id, current_user, ["frac_canadian.edit"])
tenant_id = validate_access_to_resource(db, company_id, current_user)
tenant_id = validate_access_to_resource(
db, company_id, current_user, ["frac_canadian.edit"]
)
service = CanadianTariffFractionService(db)
item = service.get(id, tenant_id, company_id)
if not item:
@@ -98,9 +98,9 @@ def delete_canadian_fraction(
db: Session = Depends(get_core_db),
current_user = Depends(get_current_user)
):
from core.security import validate_access_to_resource as validate_perm
validate_perm(db, company_id, current_user, ["frac_canadian.delete"])
tenant_id = validate_access_to_resource(db, company_id, current_user)
tenant_id = validate_access_to_resource(
db, company_id, current_user, ["frac_canadian.delete"]
)
service = CanadianTariffFractionService(db)
item = service.get(id, tenant_id, company_id)
if not item:

View File

@@ -20,9 +20,9 @@ def get_historical_fractions(
db: Session = Depends(get_core_db),
current_user = Depends(get_current_user)
):
from core.security import validate_access_to_resource as validate_perm
validate_perm(db, company_id, current_user, ["frac_historical.view"])
tenant_id = validate_access_to_resource(db, company_id, current_user)
tenant_id = validate_access_to_resource(
db, company_id, current_user, ["frac_historical.view"]
)
skip = (page - 1) * page_size
service = HistoricalTariffFractionService(db)
items, total = service.get_multi(tenant_id, company_id, skip=skip, limit=page_size, historical_fraction=historical_fraction)
@@ -46,9 +46,9 @@ async def get_rate(
db: Session = Depends(get_core_db),
current_user = Depends(get_current_user)
):
from core.security import validate_access_to_resource as validate_perm
validate_perm(db, company_id, current_user, ["frac_historical.view"])
tenant_id = validate_access_to_resource(db, company_id, current_user)
tenant_id = validate_access_to_resource(
db, company_id, current_user, ["frac_historical.view"]
)
# Parse date
try:
@@ -80,9 +80,9 @@ def get_historical_fraction(
db: Session = Depends(get_core_db),
current_user = Depends(get_current_user)
):
from core.security import validate_access_to_resource as validate_perm
validate_perm(db, company_id, current_user, ["frac_historical.view"])
tenant_id = validate_access_to_resource(db, company_id, current_user)
tenant_id = validate_access_to_resource(
db, company_id, current_user, ["frac_historical.view"]
)
service = HistoricalTariffFractionService(db)
fraction = service.get(id, tenant_id, company_id)
if not fraction:
@@ -96,9 +96,9 @@ def create_historical_fraction(
db: Session = Depends(get_core_db),
current_user = Depends(get_current_user)
):
from core.security import validate_access_to_resource as validate_perm
validate_perm(db, company_id, current_user, ["frac_historical.create"])
tenant_id = validate_access_to_resource(db, company_id, current_user)
tenant_id = validate_access_to_resource(
db, company_id, current_user, ["frac_historical.create"]
)
service = HistoricalTariffFractionService(db)
return service.create(fraction_in, tenant_id, company_id)
@@ -110,9 +110,9 @@ def update_historical_fraction(
db: Session = Depends(get_core_db),
current_user = Depends(get_current_user)
):
from core.security import validate_access_to_resource as validate_perm
validate_perm(db, company_id, current_user, ["frac_historical.edit"])
tenant_id = validate_access_to_resource(db, company_id, current_user)
tenant_id = validate_access_to_resource(
db, company_id, current_user, ["frac_historical.edit"]
)
service = HistoricalTariffFractionService(db)
fraction = service.get(id, tenant_id, company_id)
if not fraction:
@@ -126,9 +126,9 @@ def delete_historical_fraction(
db: Session = Depends(get_core_db),
current_user = Depends(get_current_user)
):
from core.security import validate_access_to_resource as validate_perm
validate_perm(db, company_id, current_user, ["frac_historical.delete"])
tenant_id = validate_access_to_resource(db, company_id, current_user)
tenant_id = validate_access_to_resource(
db, company_id, current_user, ["frac_historical.delete"]
)
service = HistoricalTariffFractionService(db)
fraction = service.get(id, tenant_id, company_id)
if not fraction:

View File

@@ -40,8 +40,10 @@ async def list_tariff_fractions(
current_user: Dict[str, Any] = Depends(get_current_user),
):
# Validar permisos según el catálogo solicitado
if catalog in ["mex", "usa"]:
if catalog == "mex":
validate_access_to_resource(db, company_id, current_user, ["frac_sitar.view"])
elif catalog == "usa":
validate_access_to_resource(db, company_id, current_user, ["frac_sitar_us.view"])
elif catalog == "american":
validate_access_to_resource(db, company_id, current_user, ["frac_american.view"])
@@ -83,12 +85,19 @@ async def list_tariff_fractions(
)
async def get_tariff_fraction(
tariff_fraction_id: int,
catalog: str = Query("mex", description="Catalog source: 'mex', 'usa', or 'american'"),
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
# Por defecto asumimos vista de SITAR para este endpoint de consulta por ID general
validate_access_to_resource(db, company_id, current_user, ["frac_sitar.view"])
if catalog == "mex":
validate_access_to_resource(db, company_id, current_user, ["frac_sitar.view"])
elif catalog == "usa":
validate_access_to_resource(db, company_id, current_user, ["frac_sitar_us.view"])
elif catalog == "american":
validate_access_to_resource(db, company_id, current_user, ["frac_american.view"])
else:
raise HTTPException(status_code=400, detail=f"Unsupported catalog '{catalog}'")
item = TariffFractionService.get_by_id(db, tariff_fraction_id)
if not item:
from fastapi import HTTPException

View File

@@ -77,11 +77,9 @@ async def list_us_tariff_fractions(
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
from core.security import validate_access_to_resource as validate_perm
validate_perm(db, company_id, current_user, ["frac_american.view"])
tenant_id = validate_access_to_resource(db, company_id, current_user)
validate_access_to_resource(db, company_id, current_user)
tenant_id = validate_access_to_resource(
db, company_id, current_user, ["frac_sitar_us.view"]
)
skip = (page - 1) * page_size
try:

View File

@@ -17,4 +17,9 @@ router = TenantCRUDRoutes(
enable_list=True,
enable_filters=True,
max_page_size=1000,
list_permissions=["cat_locations.view"],
get_permissions=["cat_locations.view"],
create_permissions=["cat_locations.create"],
update_permissions=["cat_locations.edit"],
delete_permissions=["cat_locations.delete"],
).router

View File

@@ -4,12 +4,12 @@ Rutas para gestión de prevalidadores
from typing import List
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import get_current_user
from api.v1.common.tenant_crud_routes import TenantCRUDRoutes
from api.v1.common.tenant_crud_routes import TenantCRUDRoutes, validate_access_to_resource
from .dto import (
PrevalidatorCreateDTO,
PrevalidatorResponseDTO,
@@ -29,6 +29,11 @@ prevalidator_crud = TenantCRUDRoutes(
tags=["Prevalidators"],
resource_name="Prevalidator",
enable_list=True,
list_permissions=["cat_prevalidators.view"],
get_permissions=["cat_prevalidators.view"],
create_permissions=["cat_prevalidators.create"],
update_permissions=["cat_prevalidators.edit"],
delete_permissions=["cat_prevalidators.delete"],
)
# Add custom endpoints
@@ -41,15 +46,19 @@ prevalidator_crud = TenantCRUDRoutes(
)
async def get_prevalidator_by_code(
code: str,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
"""Get a prevalidator by its code"""
tenant_id = validate_access_to_resource(
db, company_id, current_user, ["cat_prevalidators.view"]
)
prevalidator = PrevalidatorService.get_by_code(
db,
code,
tenant_id=current_user["tenant_id"],
company_id=current_user["company_id"]
tenant_id=tenant_id,
company_id=company_id,
)
if not prevalidator:
raise HTTPException(
@@ -78,9 +87,12 @@ async def create_prevalidator(
async def update_prevalidator(
prevalidator_id: int,
prevalidator_data: PrevalidatorUpdateDTO,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
"""Update a prevalidator"""
validate_access_to_resource(db, company_id, current_user, ["cat_prevalidators.edit"])
prevalidator = PrevalidatorService.update(
db, prevalidator_id, prevalidator_data)
if not prevalidator:
@@ -98,9 +110,12 @@ async def update_prevalidator(
)
async def delete_prevalidator(
prevalidator_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
"""Delete a prevalidator"""
validate_access_to_resource(db, company_id, current_user, ["cat_prevalidators.delete"])
success = PrevalidatorService.delete(db, prevalidator_id)
if not success:
raise HTTPException(
@@ -117,10 +132,15 @@ async def delete_prevalidator(
)
async def get_prevalidators_by_customs(
customs: str,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
"""Get all prevalidators for a specific customs"""
prevalidators = PrevalidatorService.get_by_customs(db, customs)
tenant_id = validate_access_to_resource(
db, company_id, current_user, ["cat_prevalidators.view"]
)
prevalidators = PrevalidatorService.get_by_customs(db, customs, tenant_id, company_id)
return [
PrevalidatorResponseDTO.model_validate(prevalidator)
for prevalidator in prevalidators

View File

@@ -27,6 +27,11 @@ signature_crud = TenantCRUDRoutes(
enable_filters=True, # Enable filtering by code
default_page_size=50,
max_page_size=100,
list_permissions=["cat_signatures.view"],
get_permissions=["cat_signatures.view"],
create_permissions=["cat_signatures.create"],
update_permissions=["cat_signatures.edit"],
delete_permissions=["cat_signatures.delete"],
)
router = signature_crud.router
@@ -44,7 +49,9 @@ async def get_signature_by_code(
current_user: Dict[str, Any] = Depends(signature_crud.auth_dependency),
):
"""Get a signature by its code"""
tenant_id = validate_access_to_resource(db, company_id, current_user)
tenant_id = validate_access_to_resource(
db, company_id, current_user, ["cat_signatures.view"]
)
signature = SignatureService.get_by_code(db, code, tenant_id, company_id)
if not signature:
raise HTTPException(
@@ -61,9 +68,12 @@ async def get_signature_by_code(
)
async def delete_signature(
signature_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(signature_crud.auth_dependency),
):
"""Delete a signature"""
validate_access_to_resource(db, company_id, current_user, ["cat_signatures.delete"])
success = SignatureService.delete(db, signature_id)
if not success:
raise HTTPException(