feature/catalog-importation

This commit is contained in:
2026-03-18 08:28:20 -06:00
parent 3e6cc07003
commit d37d6ef699
8 changed files with 271 additions and 22 deletions

View File

@@ -10,6 +10,7 @@ from api.v1.modules.public.reference_data.customs_sections.models import Customs
from api.v1.modules.public.reference_data.pedimento_codes.models import PedimentoCode
from api.v1.modules.public.reference_data.code_pedimento_regimens.models import CodePedimentoRegimen
from api.v1.modules.public.reference_data.incoterms.models import Incoterm
from api.v1.modules.public.reference_data.valuation_methods.models import ValuationMethod
from api.v1.modules.public.reference_data.transport_modes.models import TransportMode
# Import A76 Services
@@ -31,6 +32,7 @@ from api.v1.modules.public.reference_data.transport_types.dto import TransportTy
from api.v1.modules.public.reference_data.customs_sections.dto import CustomsSectionDTO
from api.v1.modules.public.reference_data.code_pedimento_regimens.dto import CodePedimentoRegimenDTO
from api.v1.modules.public.reference_data.incoterms.dto import IncotermDTO
from api.v1.modules.public.reference_data.valuation_methods.dto import ValuationMethodDTO
from api.v1.modules.public.reference_data.transport_modes.dto import TransportModeDTO
# Additional DTOs
@@ -72,6 +74,9 @@ class InvoiceCatalogService:
response.incoterms = [
IncotermDTO.model_validate(obj) for obj in db.query(Incoterm).all()
]
response.valuation_methods = [
ValuationMethodDTO.model_validate(obj) for obj in db.query(ValuationMethod).all()
]
response.transport_modes = [
TransportModeDTO.model_validate(obj) for obj in db.query(TransportMode).all()
]

View File

@@ -3,9 +3,10 @@ from api.v1.common.tenant_crud_routes import TenantCRUDRoutes
from core.database import get_core_db
from core.security import get_current_user, validate_access_to_resource
from fastapi import APIRouter, Depends, HTTPException, Query, Path
from sqlalchemy import func
from sqlalchemy.orm import Session
from . import schemas, services
from . import schemas, services, models
from .catalog_service import InvoiceCatalogService
# Create main router
@@ -36,6 +37,27 @@ def get_edition_data(
return data
@router.get("/invoices/remesa-suggestion", response_model=Dict[str, int])
def get_remesa_suggestion(
pedimento_id: int = Query(..., description="Pedimento ID"),
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Suggest next remesa for selected pedimento (max+1)."""
tenant_id = validate_access_to_resource(db, company_id, current_user)
max_rem = (
db.query(func.max(models.InvoiceComplianceMx.remesa))
.filter(
models.InvoiceComplianceMx.pedimento_id == pedimento_id,
models.InvoiceComplianceMx.tenant_id == tenant_id,
models.InvoiceComplianceMx.company_id == company_id,
)
.scalar()
)
return {"next_remesa": int((max_rem or 0) + 1)}
# Create CRUD routes for Invoice Header using TenantCRUDRoutes
invoice_crud = TenantCRUDRoutes(
service=services.InvoiceService,

View File

@@ -605,6 +605,7 @@ from api.v1.modules.public.reference_data.currency_types.dto import CurrencyType
from api.v1.modules.public.reference_data.transport_types.dto import TransportTypeDTO
from api.v1.modules.public.reference_data.customs_sections.dto import CustomsSectionDTO
from api.v1.modules.public.reference_data.incoterms.dto import IncotermDTO
from api.v1.modules.public.reference_data.valuation_methods.dto import ValuationMethodDTO
from api.v1.modules.public.reference_data.transport_modes.dto import TransportModeDTO
from api.v1.modules.public.reference_data.code_pedimento_regimens.dto import CodePedimentoRegimenDTO
@@ -634,6 +635,7 @@ class InvoiceCatalogsResponse(BaseModel):
code_pedimento_regimens: List[CodePedimentoRegimenDTO] = []
seals: List[dict] = [] # Placeholder, refine with actual DTO
incoterms: List[IncotermDTO] = []
valuation_methods: List[ValuationMethodDTO] = []
pedimentos: List[dict] = [] # Placeholder, refine with actual DTO
transport_modes: List[TransportModeDTO] = []
default_settings: Optional[dict] = None

View File

@@ -34,6 +34,60 @@ def _get_current_username() -> str:
return "System"
def _autofill_remesa_if_needed(db: Session, invoice_data, tenant_id: int, company_id: int) -> None:
"""
Autocalcula remesa cuando hay pedimento consolidado y remesa viene vacía.
Se hace ANTES de validar para que cumpla reglas de required en validators.
"""
try:
compliance = getattr(invoice_data, "compliance_mx", None)
if not compliance:
return
pedimento_id = getattr(compliance, "pedimento_id", None)
remesa = getattr(compliance, "remesa", None)
if not pedimento_id or remesa:
return
# No aplicar a MEX (por consistencia con CSV import donde remesa es None para MEX)
invoice_type = getattr(invoice_data, "invoice_type", None)
if invoice_type == "MEX":
return
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos, PedimentoType
ped = (
db.query(Pedimentos)
.filter(
Pedimentos.id == pedimento_id,
Pedimentos.tenant_id == tenant_id,
Pedimentos.company_id == company_id,
)
.first()
)
if not ped:
return
if getattr(ped, "pedimento_type", None) != PedimentoType.CONSOLIDATED:
return
max_rem = (
db.query(func.max(models.InvoiceComplianceMx.remesa))
.filter(
models.InvoiceComplianceMx.pedimento_id == pedimento_id,
models.InvoiceComplianceMx.tenant_id == tenant_id,
models.InvoiceComplianceMx.company_id == company_id,
)
.scalar()
)
next_rem = (max_rem or 0) + 1
compliance.remesa = next_rem
except Exception:
# No bloquear guardado por fallo de autocalculo; validación normal aplicará.
return
class InvoiceService:
"""Service for Invoice Header operations"""
@@ -126,6 +180,9 @@ class InvoiceService:
# Validaciones con ErrorCollector
errors = ErrorCollector()
# Autocalculo remesa (si aplica) ANTES de validar
_autofill_remesa_if_needed(db, invoice_data, tenant_id, company_id)
# Validar si la factura ya existe
invoice_exists(db, invoice_data.invoice_number, tenant_id, company_id, errors)
if invoice_data.operation_type == "exp":
@@ -284,6 +341,8 @@ class InvoiceService:
if invoice_data.operation_type == "exp":
validate_update_export(db, invoice_data, invoice, tenant_id, company_id, errors)
else:
# Autocalculo remesa (si aplica) ANTES de validar
_autofill_remesa_if_needed(db, invoice_data, tenant_id, company_id)
validate_update_import(db, invoice_data, invoice, tenant_id, company_id, errors)
# Si hay errores, lanzar excepción ANTES de actualizar

View File

@@ -78,7 +78,7 @@ async def delete_incoterm(
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin")),
):
obj = db.query(Incoterm).filter(Incoterm.key == key).first()
obj = db.query(Incoterm).filter(Incoterm.code == key).first()
if not obj:
raise HTTPException(status_code=404, detail="Not found")
db.delete(obj)