- Fix bidirectional R1 resolution in all report query builders (Temporary, Definitive, Repair, Export, ExportRepair): use JOIN on pedimento_rectification_origin in both directions so the rectified pedimento number is resolved correctly and not duplicated. - Restore original CSV export format (ValorComercialMN, ValorMPTemp, ValorAgre as separate columns); compute them from item_line_financials SUM instead of invoice-level header totals which were always 0. - Rename CSV column "PEDIMENTO RECTIFICACION" to "PEDIMENTO R1" in both backend csv_utils.py and frontend manual download. - Harden temporary invoice update validator to safely handle null compliance_mx / logistics objects without crashing. - Add R1 rectification fields (es_rectificacion, pedimento_original, etc.) to the pedimento other-data form and initialize their default state. - Remove default companyId parameter from pedimentosApi methods to avoid hardcoded company ID 1. - Minor: whitespace cleanup, error handler adjustments, keyboard manager fix.
183 lines
5.3 KiB
Python
183 lines
5.3 KiB
Python
"""
|
|
Manejadores globales de excepciones para FastAPI
|
|
"""
|
|
|
|
import logging
|
|
from typing import Any, Dict
|
|
|
|
from fastapi import Request, status
|
|
from fastapi.responses import JSONResponse
|
|
from fastapi.encoders import jsonable_encoder
|
|
from fastapi.exceptions import RequestValidationError
|
|
from sqlalchemy.exc import IntegrityError, SQLAlchemyError
|
|
|
|
from .exceptions import BaseAPIException
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
async def base_exception_handler(
|
|
request: Request,
|
|
exc: BaseAPIException,
|
|
) -> JSONResponse:
|
|
"""
|
|
Manejador para todas las excepciones personalizadas de la API
|
|
"""
|
|
logger.warning(
|
|
f"API Exception: {exc.error_code} - {exc.message}",
|
|
extra={
|
|
"path": request.url.path,
|
|
"method": request.method,
|
|
"status_code": exc.status_code,
|
|
},
|
|
)
|
|
|
|
# Log detailed errors if they exist
|
|
if hasattr(exc, "errors") and exc.errors:
|
|
logger.warning(f"Validation errors details: {exc.errors}")
|
|
|
|
return JSONResponse(
|
|
status_code=exc.status_code,
|
|
content=jsonable_encoder(exc.to_dict()),
|
|
)
|
|
|
|
|
|
async def validation_exception_handler(
|
|
request: Request,
|
|
exc: RequestValidationError,
|
|
) -> JSONResponse:
|
|
"""
|
|
Manejador para errores de validación de Pydantic/FastAPI
|
|
"""
|
|
errors = []
|
|
for error in exc.errors():
|
|
field = ".".join(str(loc) for loc in error["loc"] if loc != "body")
|
|
errors.append(
|
|
{
|
|
"field": field,
|
|
"message": error["msg"],
|
|
"type": error["type"],
|
|
}
|
|
)
|
|
|
|
logger.warning(
|
|
f"Validation Error en {request.url.path}",
|
|
extra={"errors": errors},
|
|
)
|
|
|
|
return JSONResponse(
|
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
|
content={
|
|
"error": "VALIDATION_ERROR",
|
|
"message": "Error de validación en los datos recibidos",
|
|
"status_code": status.HTTP_422_UNPROCESSABLE_ENTITY,
|
|
"errors": errors,
|
|
},
|
|
)
|
|
|
|
|
|
async def integrity_error_handler(
|
|
request: Request,
|
|
exc: IntegrityError,
|
|
) -> JSONResponse:
|
|
"""
|
|
Manejador para errores de integridad de la base de datos
|
|
"""
|
|
logger.error(
|
|
f"Database Integrity Error: {str(exc.orig)}",
|
|
extra={
|
|
"path": request.url.path,
|
|
"method": request.method,
|
|
},
|
|
)
|
|
|
|
orig_msg = str(exc.orig).lower()
|
|
|
|
# Check for unique/duplicate key violations (English and Spanish)
|
|
if any(kw in orig_msg for kw in ["unique constraint", "duplicate key", "duplicada", "unicidad", "ya existe"]):
|
|
error_message = "El registro ya existe. Verifica los campos únicos (Año, Aduana, Patente, Número, etc.)."
|
|
# Check for foreign key violations (English and Spanish)
|
|
elif any(kw in orig_msg for kw in ["foreign key", "foránea", "referencia"]):
|
|
error_message = "Referencia inválida a otro registro. Verifica las categorías y catálogos seleccionados."
|
|
# Check for not null violations (English and Spanish)
|
|
elif any(kw in orig_msg for kw in ["not null", "no nulo", "valor nulo"]):
|
|
error_message = "Falta un campo requerido. Asegúrate de llenar todos los datos obligatorios."
|
|
else:
|
|
error_message = "Error de integridad en la base de datos"
|
|
|
|
return JSONResponse(
|
|
status_code=status.HTTP_409_CONFLICT,
|
|
content={
|
|
"error": "DATABASE_INTEGRITY_ERROR",
|
|
"message": error_message,
|
|
"status_code": status.HTTP_409_CONFLICT,
|
|
},
|
|
)
|
|
|
|
|
|
async def sqlalchemy_error_handler(
|
|
request: Request,
|
|
exc: SQLAlchemyError,
|
|
) -> JSONResponse:
|
|
"""
|
|
Manejador para errores generales de SQLAlchemy
|
|
"""
|
|
logger.error(
|
|
f"Database Error: {str(exc)}",
|
|
extra={
|
|
"path": request.url.path,
|
|
"method": request.method,
|
|
},
|
|
exc_info=True,
|
|
)
|
|
|
|
return JSONResponse(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
content={
|
|
"error": "DATABASE_ERROR",
|
|
"message": "Error en la operación de base de datos",
|
|
"status_code": status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
},
|
|
)
|
|
|
|
|
|
async def general_exception_handler(
|
|
request: Request,
|
|
exc: Exception,
|
|
) -> JSONResponse:
|
|
"""
|
|
Manejador para excepciones no capturadas
|
|
"""
|
|
logger.error(
|
|
f"Unhandled Exception: {str(exc)}",
|
|
extra={
|
|
"path": request.url.path,
|
|
"method": request.method,
|
|
},
|
|
exc_info=True,
|
|
)
|
|
|
|
return JSONResponse(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
content={
|
|
"error": "INTERNAL_SERVER_ERROR",
|
|
"message": "Error interno del servidor",
|
|
"status_code": status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
},
|
|
)
|
|
|
|
|
|
def register_exception_handlers(app) -> None:
|
|
"""
|
|
Registra todos los manejadores de excepciones en la aplicación FastAPI
|
|
|
|
Args:
|
|
app: Instancia de FastAPI
|
|
"""
|
|
app.add_exception_handler(BaseAPIException, base_exception_handler)
|
|
app.add_exception_handler(RequestValidationError, validation_exception_handler)
|
|
app.add_exception_handler(IntegrityError, integrity_error_handler)
|
|
app.add_exception_handler(SQLAlchemyError, sqlalchemy_error_handler)
|
|
app.add_exception_handler(Exception, general_exception_handler)
|
|
|