Merge branch 'feature/invoices' into development
This commit is contained in:
@@ -5,6 +5,7 @@ from pydantic import BaseModel
|
||||
|
||||
class CustomsBrokerBaseDTO(BaseModel):
|
||||
"""Base fields for CustomsBroker"""
|
||||
|
||||
type: Optional[str] = None
|
||||
name: Optional[str] = None
|
||||
address: Optional[str] = None
|
||||
@@ -25,16 +26,20 @@ class CustomsBrokerBaseDTO(BaseModel):
|
||||
|
||||
class CustomsBrokerCreateDTO(CustomsBrokerBaseDTO):
|
||||
"""Schema for creating a new CustomsBroker"""
|
||||
|
||||
broker_key: str
|
||||
|
||||
|
||||
class CustomsBrokerUpdateDTO(CustomsBrokerBaseDTO):
|
||||
"""Schema for updating an existing CustomsBroker"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class CustomsBrokerResponseDTO(CustomsBrokerBaseDTO):
|
||||
"""Schema for CustomsBroker response"""
|
||||
|
||||
id: int
|
||||
broker_key: str
|
||||
tenant_id: int
|
||||
company_id: int
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
from api.v1.common.tenant_crud_routes import TenantCRUDRoutes
|
||||
from typing import Any, Dict, Optional
|
||||
from fastapi import Depends, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from api.v1.common.tenant_crud_routes import (
|
||||
TenantCRUDRoutes,
|
||||
validate_access_to_resource,
|
||||
get_core_db,
|
||||
get_current_user,
|
||||
)
|
||||
|
||||
from .dto import ExchangeRateCreateDTO, ExchangeRateResponseDTO, ExchangeRateUpdateDTO
|
||||
from .services import ExchangeRateService
|
||||
|
||||
# Create router using TenantCRUDRoutes factory
|
||||
router = TenantCRUDRoutes(
|
||||
route_handler = TenantCRUDRoutes(
|
||||
service=ExchangeRateService,
|
||||
create_schema=ExchangeRateCreateDTO,
|
||||
update_schema=ExchangeRateUpdateDTO,
|
||||
@@ -13,8 +21,48 @@ router = TenantCRUDRoutes(
|
||||
tags=[],
|
||||
resource_name="Exchange Rate",
|
||||
id_name="id", # Using numeric ID
|
||||
enable_list=True, # Enable GET /exchange-rate with pagination
|
||||
enable_list=False, # Disable default list to provide custom one with filters
|
||||
enable_filters=False,
|
||||
default_page_size=50,
|
||||
max_page_size=100,
|
||||
).router
|
||||
)
|
||||
|
||||
router = route_handler.router
|
||||
|
||||
|
||||
@router.get(
|
||||
"/",
|
||||
response_model=Dict[str, Any],
|
||||
summary="List Exchange Rates",
|
||||
description="Get paginated list of exchange rates with optional date filter",
|
||||
)
|
||||
async def list_exchange_rates(
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
date: Optional[str] = Query(None, description="Filter by date (YYYY-MM-DD)"),
|
||||
page: int = Query(1, ge=1, description="Page number"),
|
||||
page_size: int = Query(
|
||||
50,
|
||||
ge=1,
|
||||
le=100,
|
||||
description="Page size",
|
||||
),
|
||||
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)
|
||||
|
||||
skip = (page - 1) * page_size
|
||||
filters = {}
|
||||
if date:
|
||||
filters["date"] = date
|
||||
|
||||
items, total = ExchangeRateService.get_all(
|
||||
db, tenant_id, company_id, skip, page_size, filters
|
||||
)
|
||||
|
||||
return {
|
||||
"items": [ExchangeRateResponseDTO.model_validate(item) for item in items],
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from typing import Optional, Tuple, List, Dict, Any
|
||||
from datetime import datetime, time
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import cast, Date
|
||||
|
||||
from . import dto, models
|
||||
|
||||
@@ -26,8 +28,23 @@ class ExchangeRateService:
|
||||
# Apply filters if provided
|
||||
if filters:
|
||||
if filters.get("date"):
|
||||
query = query.filter(
|
||||
models.ExchangeRate.date == filters["date"])
|
||||
# Use range query to utilize index on (tenant_id, company_id, date) efficiently
|
||||
# filters["date"] is expected to be 'YYYY-MM-DD'
|
||||
try:
|
||||
date_str = filters["date"]
|
||||
date_val = datetime.strptime(date_str, "%Y-%m-%d").date()
|
||||
start_date = datetime.combine(date_val, time.min)
|
||||
end_date = datetime.combine(date_val, time.max)
|
||||
|
||||
query = query.filter(
|
||||
models.ExchangeRate.date >= start_date,
|
||||
models.ExchangeRate.date <= end_date,
|
||||
)
|
||||
except (ValueError, TypeError):
|
||||
# Fallback to cast if date format is invalid or logic fails, though validation should catch this
|
||||
query = query.filter(
|
||||
cast(models.ExchangeRate.date, Date) == filters["date"]
|
||||
)
|
||||
if filters.get("local_currency"):
|
||||
query = query.filter(
|
||||
models.ExchangeRate.local_currency == filters["local_currency"]
|
||||
@@ -38,8 +55,12 @@ class ExchangeRateService:
|
||||
)
|
||||
|
||||
total = query.count()
|
||||
exchange_rates = query.order_by(
|
||||
models.ExchangeRate.date.desc()).offset(skip).limit(limit).all()
|
||||
exchange_rates = (
|
||||
query.order_by(models.ExchangeRate.date.desc())
|
||||
.offset(skip)
|
||||
.limit(limit)
|
||||
.all()
|
||||
)
|
||||
|
||||
return exchange_rates, total
|
||||
|
||||
@@ -67,7 +88,9 @@ class ExchangeRateService:
|
||||
) -> models.ExchangeRate:
|
||||
"""Create a new exchange rate"""
|
||||
new_exchange_rate = models.ExchangeRate(
|
||||
**exchange_rate_data.model_dump(), tenant_id=tenant_id, company_id=company_id
|
||||
**exchange_rate_data.model_dump(),
|
||||
tenant_id=tenant_id,
|
||||
company_id=company_id
|
||||
)
|
||||
db.add(new_exchange_rate)
|
||||
db.commit()
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
from core.exceptions import ErrorCollector
|
||||
from .. import models
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
|
||||
def invoice_exists(
|
||||
db: Session,
|
||||
invoice_number: str,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
errors: ErrorCollector
|
||||
) -> bool:
|
||||
invoice_exists = (
|
||||
db.query(models.InvoiceHeader.id)
|
||||
.filter(
|
||||
models.InvoiceHeader.invoice_number == invoice_number,
|
||||
models.InvoiceHeader.tenant_id == tenant_id,
|
||||
models.InvoiceHeader.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
if invoice_exists:
|
||||
errors.add_duplicate_error(
|
||||
"invoice_number",
|
||||
invoice_number,
|
||||
f"Ya existe una factura con el número '{invoice_number}'",
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
15
backend/api/v1/modules/a76/invoices/common/mappers.py
Normal file
15
backend/api/v1/modules/a76/invoices/common/mappers.py
Normal file
@@ -0,0 +1,15 @@
|
||||
""" """
|
||||
|
||||
def clean_dict(data_dict: dict) -> dict:
|
||||
cleaned = {}
|
||||
for key, value in data_dict.items():
|
||||
|
||||
if isinstance(value, str) and not value.strip():
|
||||
cleaned[key] = None
|
||||
|
||||
elif value == 0 and (key.endswith('_id') or key == 'remesa'):
|
||||
cleaned[key] = None
|
||||
else:
|
||||
cleaned[key] = value
|
||||
return cleaned
|
||||
|
||||
@@ -0,0 +1,393 @@
|
||||
from sqlalchemy.orm import Session
|
||||
from .... import schemas
|
||||
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
|
||||
from api.v1.modules.a76.general_catalogs.exchange_rate.models import ExchangeRate
|
||||
from api.v1.modules.a76.clients_and_providers.models import ClientProvider
|
||||
from api.v1.modules.public.reference_data.incoterms.models import Incoterm
|
||||
from api.v1.modules.a76.items.models import Item
|
||||
from ....models import TransportType, Currency, WeightUnit
|
||||
from core.exceptions import ErrorCollector
|
||||
|
||||
|
||||
def validate_common(
|
||||
db: Session,
|
||||
invoice: schemas.InvoiceHeaderCreate,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
errors: ErrorCollector,
|
||||
):
|
||||
if invoice.compliance_mx.pedimento_id:
|
||||
pedimento = (
|
||||
db.query(Pedimentos)
|
||||
.filter(
|
||||
Pedimentos.id == invoice.compliance_mx.pedimento_id,
|
||||
Pedimentos.tenant_id == tenant_id,
|
||||
Pedimentos.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not pedimento:
|
||||
errors.add_error(
|
||||
field="compliance_mx.pedimento_id",
|
||||
message="El Pedimento no existe en el Catálogo de Pedimentos.",
|
||||
solution=["Verifica el ID", "Revisa el catálogo"],
|
||||
code="NOT_FOUND",
|
||||
value=invoice.compliance_mx.pedimento_id,
|
||||
)
|
||||
|
||||
if not invoice.compliance_mx.is_regime_change:
|
||||
if not pedimento.operation_type == 1:
|
||||
errors.add_error(
|
||||
field="compliance_mx.pedimento_id",
|
||||
message="El Pedimento seleccionado no corresponde a una Importación.",
|
||||
solution=["Selecciona un Pedimento de Importación"],
|
||||
code="INVALID_OPERATION_TYPE",
|
||||
value=pedimento.operation_type,
|
||||
)
|
||||
else:
|
||||
if pedimento.regime in ["EXD", "ETE", "ETR"]:
|
||||
errors.add_error(
|
||||
field="compliance_mx.pedimento_id",
|
||||
message="El Pedimento seleccionado corresponde a una Exportación, no a una Importación.",
|
||||
solution=["Selecciona un Pedimento de Importación"],
|
||||
code="INVALID_REGIME",
|
||||
value=pedimento.regime,
|
||||
)
|
||||
else:
|
||||
if invoice.document_type.upper().strip() != pedimento.regime:
|
||||
errors.add_error(
|
||||
field="document_type",
|
||||
message=f"El Tipo de Documento '{invoice.document_type}' no coincide con el Régimen '{pedimento.regime}' del Pedimento seleccionado.",
|
||||
solution=[
|
||||
"Ajusta el Tipo de Documento o selecciona otro Pedimento"
|
||||
],
|
||||
code="REGIME_MISMATCH",
|
||||
value=invoice.document_type,
|
||||
)
|
||||
else:
|
||||
if pedimento.operation_type != 2:
|
||||
errors.add_error(
|
||||
field="compliance_mx.pedimento_id",
|
||||
message="El Pedimento seleccionado no corresponde a una Importacion Definitiva.",
|
||||
solution=["Selecciona un Pedimento de Importacion Definitiva"],
|
||||
code="INVALID_OPERATION_TYPE",
|
||||
value=pedimento.operation_type,
|
||||
)
|
||||
else:
|
||||
if pedimento.regime != "IMD":
|
||||
errors.add_error(
|
||||
field="compliance_mx.pedimento_id",
|
||||
message=f"El Pedimento {pedimento.customs_office}-{pedimento.license}-{pedimento.pedimento_number} no corresponde a una Importacion Definitiva.",
|
||||
solution=["Selecciona un Pedimento de Importacion Definitiva"],
|
||||
code="INVALID_REGIME",
|
||||
value=pedimento.regime,
|
||||
)
|
||||
else:
|
||||
if invoice.document_type.upper().strip() != pedimento.regime:
|
||||
errors.add_error(
|
||||
field="document_type",
|
||||
message=f"El Tipo de Documento '{invoice.document_type}' no coincide con el Régimen '{pedimento.regime}' del Pedimento seleccionado.",
|
||||
solution=[
|
||||
"Ajusta el Tipo de Documento o selecciona otro Pedimento"
|
||||
],
|
||||
code="REGIME_MISMATCH",
|
||||
value=invoice.document_type,
|
||||
)
|
||||
else:
|
||||
if pedimento.pedimento_code not in ["A1", "A3"]:
|
||||
errors.add_error(
|
||||
field="compliance_mx.pedimento_id",
|
||||
message=f"El Pedimento seleccionado no es de tipo A1 o A3 requerido para Cambio de Régimen.",
|
||||
solution=["Selecciona un Pedimento de tipo A1 o A3"],
|
||||
code="INVALID_PEDEMENTO_CODE",
|
||||
value=pedimento.pedimento_code,
|
||||
)
|
||||
|
||||
if pedimento.pedimento_type == "consolidated":
|
||||
if (
|
||||
invoice.invoice_date < pedimento.pedimento_dates.entry_date
|
||||
or invoice.invoice_date > pedimento.pedimento_dates.end_date
|
||||
):
|
||||
errors.add_error(
|
||||
field="invoice_date",
|
||||
message=f"La Fecha de la Factura {invoice.invoice_date} no está dentro del rango de fechas del Pedimento {pedimento.customs_office}-{pedimento.license}-{pedimento.pedimento_number}.",
|
||||
solution=[
|
||||
f"Capturar una Fecha de Factura, entre la Fecha de Inicio: {pedimento.pedimento_dates.entry_date} y la Fecha Final: {pedimento.pedimento_dates.end_date} ."
|
||||
],
|
||||
code="DATE_OUT_OF_RANGE",
|
||||
value=invoice.invoice_date,
|
||||
)
|
||||
|
||||
if not invoice.compliance_mx.remesa:
|
||||
errors.add_error(
|
||||
field="compliance_mx.remesa",
|
||||
message="El campo Remesa es obligatorio cuando se asocia un Pedimento.",
|
||||
solution=["Proporciona un valor para Remesa"],
|
||||
code="REQUIRED_FIELD",
|
||||
value=invoice.compliance_mx.remesa,
|
||||
)
|
||||
elif invoice.compliance_mx.remesa == 0:
|
||||
errors.add_error(
|
||||
field="compliance_mx.remesa",
|
||||
message="El campo Remesa no puede ser cero cuando se asocia un Pedimento.",
|
||||
solution=["Proporciona un valor válido para Remesa"],
|
||||
code="INVALID_VALUE",
|
||||
value=invoice.compliance_mx.remesa,
|
||||
)
|
||||
|
||||
duplicated_remesa = (
|
||||
db.query(Pedimentos)
|
||||
.filter(
|
||||
Pedimentos.remesa == invoice.compliance_mx.remesa,
|
||||
Pedimentos.id != invoice.compliance_mx.pedimento_id,
|
||||
Pedimentos.tenant_id == tenant_id,
|
||||
Pedimentos.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if duplicated_remesa:
|
||||
errors.add_error(
|
||||
field="compliance_mx.remesa",
|
||||
message="El valor de Remesa ya está asociado a otro Pedimento.",
|
||||
solution=["Proporciona un valor único para Remesa"],
|
||||
code="DUPLICATE_VALUE",
|
||||
value=invoice.compliance_mx.remesa,
|
||||
)
|
||||
else:
|
||||
if invoice.compliance_mx.remesa and not invoice.compliance_mx.pedimento_id:
|
||||
errors.add_error(
|
||||
field="compliance_mx.pedimento_id",
|
||||
message="El campo Pedimento es obligatorio cuando se proporciona Remesa.",
|
||||
solution=["Proporciona un ID de Pedimento"],
|
||||
code="REQUIRED_FIELD",
|
||||
value=invoice.compliance_mx.pedimento_id,
|
||||
)
|
||||
|
||||
if len(invoice.invoice_number) > 100:
|
||||
errors.add_error(
|
||||
field="invoice_number",
|
||||
message="El número de factura excede la longitud máxima de 100 caracteres.",
|
||||
solution=["Acorta el número de factura a 100 caracteres o menos"],
|
||||
code="MAX_LENGTH_EXCEEDED",
|
||||
value=invoice.invoice_number,
|
||||
)
|
||||
|
||||
if not invoice.financials.exchange_rate or invoice.financials.exchange_rate <= 0:
|
||||
exchange_rate_exists = (
|
||||
db.query(ExchangeRate)
|
||||
.filter(
|
||||
ExchangeRate.date == invoice.invoice_date,
|
||||
ExchangeRate.tenant_id == tenant_id,
|
||||
ExchangeRate.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not exchange_rate_exists:
|
||||
errors.add_error(
|
||||
field="financials.exchange_rate",
|
||||
message=f"No existe un Tipo de Cambio registrado para la fecha {invoice.invoice_date.date()}.",
|
||||
solution=["Registra el Tipo de Cambio en el catálogo correspondiente"],
|
||||
code="EXCHANGE_RATE_NOT_FOUND",
|
||||
value=invoice.financials.exchange_rate,
|
||||
)
|
||||
|
||||
if invoice.compliance_mx.is_regime_change:
|
||||
if invoice.document_type in ["EXD", "ETE", "ETR"]:
|
||||
errors.add_error(
|
||||
field="document_type",
|
||||
message="El Tipo de Documento no puede ser de Exportación cuando se trata de un Cambio de Régimen.",
|
||||
solution=[
|
||||
"Selecciona un Tipo de Documento válido para Cambio de Régimen"
|
||||
],
|
||||
code="INVALID_DOCUMENT_TYPE",
|
||||
value=invoice.document_type,
|
||||
)
|
||||
else:
|
||||
if invoice.document_type == "IMD":
|
||||
errors.add_error(
|
||||
field="document_type",
|
||||
message="El Tipo de Documento no puede ser 'IMD' a menos que sea un Cambio de Régimen.",
|
||||
solution=["Selecciona un Tipo de Documento válido"],
|
||||
code="INVALID_DOCUMENT_TYPE",
|
||||
value=invoice.document_type,
|
||||
)
|
||||
|
||||
provider_exists = (
|
||||
db.query(ClientProvider)
|
||||
.filter(
|
||||
ClientProvider.id == invoice.provider_id,
|
||||
ClientProvider.tenant_id == tenant_id,
|
||||
ClientProvider.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not provider_exists:
|
||||
errors.add_error(
|
||||
field="provider_id",
|
||||
message="El Proveedor no existe en el Catálogo de Clientes y Proveedores.",
|
||||
solution=["Verifica el ID del Proveedor", "Revisa el catálogo"],
|
||||
code="NOT_FOUND",
|
||||
value=invoice.provider_id,
|
||||
)
|
||||
|
||||
selled_to_exists = (
|
||||
db.query(ClientProvider)
|
||||
.filter(
|
||||
ClientProvider.id == invoice.selled_to_id,
|
||||
ClientProvider.tenant_id == tenant_id,
|
||||
ClientProvider.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not selled_to_exists:
|
||||
errors.add_error(
|
||||
field="selled_to_id",
|
||||
message="El Cliente no existe en el Catálogo de Clientes y Proveedores.",
|
||||
solution=["Verifica el ID del Cliente", "Revisa el catálogo"],
|
||||
code="NOT_FOUND",
|
||||
value=invoice.selled_to_id,
|
||||
)
|
||||
|
||||
shipped_to_exists = (
|
||||
db.query(ClientProvider)
|
||||
.filter(
|
||||
ClientProvider.id == invoice.shipped_to_id,
|
||||
ClientProvider.tenant_id == tenant_id,
|
||||
ClientProvider.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not shipped_to_exists:
|
||||
errors.add_error(
|
||||
field="shipped_to_id",
|
||||
message="El Destinatario no existe en el Catálogo de Clientes y Proveedores.",
|
||||
solution=["Verifica el ID del Destinatario", "Revisa el catálogo"],
|
||||
code="NOT_FOUND",
|
||||
value=invoice.shipped_to_id,
|
||||
)
|
||||
|
||||
customs_broker_exists = (
|
||||
db.query(ClientProvider)
|
||||
.filter(
|
||||
ClientProvider.id == invoice.customs_broker_id,
|
||||
ClientProvider.tenant_id == tenant_id,
|
||||
ClientProvider.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not customs_broker_exists:
|
||||
errors.add_error(
|
||||
field="customs_broker_id",
|
||||
message="El Agente Aduanal no existe en el Catálogo de Clientes y Proveedores.",
|
||||
solution=["Verifica el ID del Agente Aduanal", "Revisa el catálogo"],
|
||||
code="NOT_FOUND",
|
||||
value=invoice.customs_broker_id,
|
||||
)
|
||||
|
||||
if invoice.logistics.carrier_id:
|
||||
carrier_exists = (
|
||||
db.query(ClientProvider)
|
||||
.filter(
|
||||
ClientProvider.id == invoice.logistics.carrier_id,
|
||||
ClientProvider.tenant_id == tenant_id,
|
||||
ClientProvider.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not carrier_exists:
|
||||
errors.add_error(
|
||||
field="logistics.carrier_id",
|
||||
message="El Transportista no existe en el Catálogo de Clientes y Proveedores.",
|
||||
solution=["Verifica el ID del Transportista", "Revisa el catálogo"],
|
||||
code="NOT_FOUND",
|
||||
value=invoice.logistics.carrier_id,
|
||||
)
|
||||
|
||||
if invoice.logistics.transport_type not in [t.value for t in TransportType]:
|
||||
errors.add_error(
|
||||
field="logistics.transport_type",
|
||||
message="El Tipo de Transporte proporcionado no es válido.",
|
||||
solution=[
|
||||
f"Selecciona un Tipo de Transporte válido: {[t.value for t in TransportType]}"
|
||||
],
|
||||
code="INVALID_TRANSPORT_TYPE",
|
||||
value=invoice.logistics.transport_type,
|
||||
)
|
||||
else:
|
||||
if invoice.logistics.transport_type == "none" and invoice.logistics.transport_num:
|
||||
errors.add_error(
|
||||
field="logistics.transport_num",
|
||||
message="El Número de Transporte no debe proporcionarse cuando el Tipo de Transporte es 'none'.",
|
||||
solution=["Elimina el Número de Transporte o selecciona un Tipo de Transporte válido"],
|
||||
code="INVALID_VALUE",
|
||||
value=invoice.logistics.transport_num,
|
||||
)
|
||||
else:
|
||||
if not invoice.logistics.transport_num and invoice.logistics.transport_type != "none":
|
||||
errors.add_error(
|
||||
field="logistics.transport_num",
|
||||
message="El Número de Transporte es obligatorio cuando se proporciona un Tipo de Transporte distinto de 'none'.",
|
||||
solution=["Proporciona un Número de Transporte válido"],
|
||||
code="REQUIRED_FIELD",
|
||||
value=invoice.logistics.transport_num,
|
||||
)
|
||||
|
||||
|
||||
invoice.financials.currency = (invoice.financials.currency or "foreign")
|
||||
|
||||
if invoice.financials.currency not in [c.value for c in Currency]:
|
||||
errors.add_error(
|
||||
field="financials.currency",
|
||||
message="La Moneda proporcionada no es válida.",
|
||||
solution=[
|
||||
f"Selecciona una Moneda válida: {[c.value for c in Currency]}"
|
||||
],
|
||||
code="INVALID_CURRENCY",
|
||||
value=invoice.financials.currency,
|
||||
)
|
||||
else:
|
||||
has_items = db.query(Item).filter(
|
||||
Item.invoice_id == invoice.id,
|
||||
Item.tenant_id == tenant_id,
|
||||
Item.company_id == company_id,
|
||||
).first()
|
||||
if has_items:
|
||||
errors.add_error(
|
||||
field="items",
|
||||
message=f"La opcion tipo de moneda {invoice.financials.currency} no puede ser modificada ya que la factura tiene items asociados.",
|
||||
solution=["Verifica la moneda de los items asociados a la factura."],
|
||||
code="CURRENCY_CANNOT_BE_CHANGED",
|
||||
value=invoice.financials.currency,
|
||||
)
|
||||
|
||||
if invoice.logistics.incoterms:
|
||||
incoterm_exists = (
|
||||
db.query(Incoterm)
|
||||
.filter(
|
||||
Incoterm.code == invoice.logistics.incoterms,
|
||||
Incoterm.tenant_id == tenant_id,
|
||||
Incoterm.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not incoterm_exists:
|
||||
errors.add_error(
|
||||
field="logistics.incoterms",
|
||||
message="El Incoterm no existe en el Catálogo de Incoterms.",
|
||||
solution=["Verifica el código del Incoterm", "Revisa el catálogo"],
|
||||
code="NOT_FOUND",
|
||||
value=invoice.logistics.incoterms,
|
||||
)
|
||||
|
||||
if invoice.logistics.weight_type not in [w.value for w in WeightUnit]:
|
||||
errors.add_error(
|
||||
field="logistics.weight_type",
|
||||
message="La Unidad de Peso proporcionada no es válida.",
|
||||
solution=[
|
||||
f"Selecciona una Unidad de Peso válida: {[w.value for w in WeightUnit]}"
|
||||
],
|
||||
code="INVALID_WEIGHT_UNIT",
|
||||
value=invoice.logistics.weight_type,
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from api.v1.modules.a76.general_catalogs.exchange_rate.models import ExchangeRate
|
||||
from core.exceptions import ErrorCollector
|
||||
from ....schemas import InvoiceHeaderCreate
|
||||
from .common import validate_common
|
||||
|
||||
def validate_create(db: Session, invoice: InvoiceHeaderCreate, tenant_id: int, company_id: int, errors: ErrorCollector) -> None:
|
||||
""" Valida la creación de una nueva factura de importe temporal """
|
||||
|
||||
if not invoice.operation_type:
|
||||
errors.add_required_error("operation_type")
|
||||
|
||||
if not invoice.invoice_type:
|
||||
errors.add_required_error("invoice_type")
|
||||
|
||||
if not invoice.document_type:
|
||||
errors.add_required_error("document_type")
|
||||
|
||||
if not invoice.invoice_number:
|
||||
errors.add_required_error("invoice_number")
|
||||
|
||||
if not invoice.invoice_date:
|
||||
errors.add_required_error("invoice_date")
|
||||
|
||||
if not invoice.compliance_mx.provider_id:
|
||||
errors.add_required_error("compliance_mx.provider_id")
|
||||
|
||||
if not invoice.compliance_mx.sold_to_id:
|
||||
errors.add_required_error("compliance_mx.sold_to_id")
|
||||
|
||||
if not invoice.compliance_mx.shipped_to_id:
|
||||
errors.add_required_error("compliance_mx.shipped_to_id")
|
||||
|
||||
if not invoice.compliance_mx.customs_broker_id:
|
||||
errors.add_required_error("compliance_mx.customs_broker_id")
|
||||
|
||||
if errors.has_errors():
|
||||
"""Se retorna por que hay campos obligatiorios para las validaciones que tienen que ser llenados"""
|
||||
return
|
||||
|
||||
validate_common(db, invoice, tenant_id, company_id, errors)
|
||||
|
||||
if errors.has_errors():
|
||||
"""Se retorna por que fallaron las validaciones generales"""
|
||||
return
|
||||
|
||||
if not invoice.compliance_mx.pedimento_id:
|
||||
invoice.compliance_mx.remesa = None
|
||||
|
||||
if not invoice.financials.exchange_rate:
|
||||
invoice.financials.exchange_rate = db.query(ExchangeRate.value).filter(ExchangeRate.date == invoice.invoice_date).scalar()
|
||||
|
||||
invoice.document_type = (invoice.document_type or "").upper()
|
||||
|
||||
if not invoice.logistics.transport_type:
|
||||
invoice.logistics.transport_type = "none"
|
||||
|
||||
if invoice.logistics.transport_type == "none" and invoice.logistics.transport_num:
|
||||
invoice.logistics.transport_num = None
|
||||
|
||||
if not invoice.financials.currency:
|
||||
invoice.financials.currency = "foreign"
|
||||
|
||||
if invoice.financials.currency == "local":
|
||||
invoice.financials.currency_type = "MXN"
|
||||
elif invoice.financials.currency_type == "foreign":
|
||||
invoice.financials.currency = "USD"
|
||||
elif invoice.financials.currency_type == "manual":
|
||||
invoice.financials.currency_type = invoice.financials.currency_type.upper()
|
||||
|
||||
invoice.logistics.incoterm = (invoice.logistics.incoterm or "").upper()
|
||||
|
||||
if not invoice.logistics.weight_type:
|
||||
invoice.logistics.weight_type = "kgs"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
def validate_update():
|
||||
pass
|
||||
@@ -6,6 +6,23 @@ from core.database import Base
|
||||
from datetime import datetime
|
||||
from ....common.base_models import TenantScopedMixin, TimestampMixin
|
||||
|
||||
class Currency(str, Enum):
|
||||
FOREIGN = "foreign"
|
||||
LOCAL = "local"
|
||||
MANUAL = "manual"
|
||||
|
||||
class WeightUnit(str, Enum):
|
||||
KGS = "kgs"
|
||||
LBS = "lbs"
|
||||
|
||||
class DestinationOriginCove(str, Enum):
|
||||
EDO_BC_PARC_SON = "edo_bc_parc_son"
|
||||
ESTADO_BCS = "estado_bcs"
|
||||
ESTADO_ROO = "estado_roo"
|
||||
MPIO_SALINA_CRUZ_OAX = "mpio_salina_cruz_oxa"
|
||||
FRANJA_FRONT_NORTE = "franja_front_norte"
|
||||
INTERIOR_PAIS = "interior_pais"
|
||||
MPIO_CABORCA_SON = "mpio_caborca_son"
|
||||
|
||||
class OperationType(str, Enum):
|
||||
IMP = "imp" # Importación
|
||||
@@ -38,10 +55,11 @@ class InvoiceHeader(Base, TenantScopedMixin, TimestampMixin):
|
||||
id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
|
||||
|
||||
# Identifiers
|
||||
system: Mapped[Optional[str]] = mapped_column(String(12)) # SISTEMA / Sistema de origen <-- no tiene campo en la antigua base de datos, sera para fixed_asset(scaf), inventory(scaii)
|
||||
operation_type: Mapped[OperationType] = mapped_column(String(10)) # TIPOMOVIMIENTO / Clasifica imp/exp/sm/ctm
|
||||
invoice_type: Mapped[Optional[str]] = mapped_column(ForeignKey("public.invoice_types.key")) # TIPOFACTURA / TIPODOC
|
||||
invoice_number: Mapped[Optional[str]] = mapped_column(String(20)) # FACTURAIMPO/FACTURAEXPO/FACTURAREMISION/FACTURAENVIO/FACTURASALIDA
|
||||
system: Mapped[str] = mapped_column(String(12)) # SISTEMA / Sistema de origen <-- no tiene campo en la antigua base de datos, sera para fixed_asset(scaf), inventory(scaii)
|
||||
operation_type: Mapped[OperationType] = mapped_column(String(11)) # TIPOMOVIMIENTO / Clasifica imp/exp/sm/ctm
|
||||
invoice_type: Mapped[str] = mapped_column(ForeignKey("public.invoice_types.key")) # TIPOFACTURA / TIPODOC
|
||||
document_type: Mapped[str] = mapped_column(ForeignKey("public.pedimento_regimens.code")) # CLAVEDOCUMENTO / Clave de documento
|
||||
invoice_number: Mapped[str] = mapped_column(String(100)) # FACTURAIMPO/FACTURAEXPO/FACTURAREMISION/FACTURAENVIO/FACTURASALIDA
|
||||
project_number: Mapped[Optional[str]] = mapped_column(String(14)) # NUMPROYECTO
|
||||
purchase_order: Mapped[Optional[str]] = mapped_column(String(50)) # ORDENCOMPRA
|
||||
related_doc_id: Mapped[Optional[int]] = mapped_column(Integer) # IDRELDOC / Para Rectificaciones
|
||||
@@ -50,20 +68,20 @@ class InvoiceHeader(Base, TenantScopedMixin, TimestampMixin):
|
||||
proforma_number: Mapped[Optional[str]] = mapped_column(String(20)) # NUMEROPROFORMA
|
||||
|
||||
# Dates
|
||||
invoice_date: Mapped[Optional[datetime]] = mapped_column(Date) # FECHAFACTURA
|
||||
invoice_date: Mapped[datetime] = mapped_column(Date) # FECHAFACTURA
|
||||
capture_date: Mapped[datetime] = mapped_column(TIMESTAMP(timezone=False), default=datetime.now) # FECHACAPTURA + HORAACTUAL
|
||||
emission_date: Mapped[Optional[datetime]] = mapped_column(Date) # FECHAEMISION
|
||||
|
||||
# Status & Control
|
||||
is_updated: Mapped[Optional[bool]] = mapped_column(Boolean) # ESTATUS
|
||||
is_updated: Mapped[bool] = mapped_column(Boolean) # ESTATUS
|
||||
is_updated_rec: Mapped[Optional[bool]] = mapped_column(Boolean) # ESTATUSREC / Estatus de recepción
|
||||
is_updated_rep: Mapped[Optional[bool]] = mapped_column(Boolean) # ESTATUSREP / Estatus de reporte
|
||||
updated_date: Mapped[Optional[datetime]] = mapped_column(TIMESTAMP(timezone=False)) # FECHAACTUALIZACION / FECHAACTUAL
|
||||
who_updated: Mapped[Optional[str]] = mapped_column(String(20)) # USUARIOACT / Quien actualizó
|
||||
capture_user: Mapped[Optional[str]] = mapped_column(String(20)) # USUARIOCAP / Usuario que capturó
|
||||
|
||||
traffic_light_status: Mapped[Optional[str]] = mapped_column(String(50)) # SEMAFORO / SEMAFOROEXPO/IMPO
|
||||
process_log: Mapped[Optional[str]] = mapped_column(String(300)) # COMOFUEPROCESADA
|
||||
status_rec: Mapped[Optional[int]] = mapped_column(Integer) # ESTATUSREC / Estatus de recepción
|
||||
status_rep: Mapped[Optional[str]] = mapped_column(String(2)) # ESTATUSREP / Estatus de reporte
|
||||
process_log: Mapped[Optional[str]] = mapped_column(String(300)) # COMOFUEPROCESADA
|
||||
|
||||
# Comments
|
||||
observation_es: Mapped[Optional[str]] = mapped_column(Text) # OBSERVACIONE / Observaciones en español
|
||||
@@ -81,9 +99,9 @@ class InvoiceHeader(Base, TenantScopedMixin, TimestampMixin):
|
||||
party_count: Mapped[Optional[int]] = mapped_column(Integer) # CANT_PARTIDAS / Cantidad de partidas
|
||||
|
||||
# Generation flags
|
||||
generate_id: Mapped[Optional[str]] = mapped_column(String(1)) # GENERAID
|
||||
generate_id: Mapped[Optional[bool]] = mapped_column(Boolean, default=False) # GENERAID
|
||||
generate_desc_parties: Mapped[Optional[str]] = mapped_column(String(12)) # GENDESCPARTIDAS / Generar descripción de partidas
|
||||
apply_manual_discount: Mapped[Optional[str]] = mapped_column(String(1)) # APLICADESCMANUAL
|
||||
apply_manual_discount: Mapped[Optional[bool]] = mapped_column(Boolean, default=False) # APLICADESCMANUAL
|
||||
|
||||
# Bulk & Downloads
|
||||
is_bulk: Mapped[Optional[bool]] = mapped_column(Boolean) # ESAGRANEL / Es a granel
|
||||
@@ -118,9 +136,9 @@ class InvoiceComplianceMx(Base, TenantScopedMixin, TimestampMixin):
|
||||
invoice_id: Mapped[int] = mapped_column(ForeignKey("a76.invoice_header.id"), primary_key=True)
|
||||
|
||||
# Core Customs Data
|
||||
pedimento: Mapped[Optional[str]] = mapped_column(String(19)) # PEDIMENTO/PEDIMENTOIMPO/EXPO
|
||||
pedimento_code: Mapped[Optional[str]] = mapped_column(String(5)) # PEDIMENTOR1
|
||||
pedimento_k1: Mapped[Optional[str]] = mapped_column(String(15)) # PEDIMENTOK1
|
||||
pedimento_id: Mapped[Optional[int]] = mapped_column(ForeignKey("a76.pedimentos.id")) # PEDIMENTO/PEDIMENTOIMPO/EXPO
|
||||
pedimento_r1: Mapped[Optional[int]] = mapped_column(ForeignKey("a76.pedimentos.id")) # PEDIMENTOR1
|
||||
pedimento_k1: Mapped[Optional[int]] = mapped_column(ForeignKey("a76.pedimentos.id")) # PEDIMENTOK1
|
||||
remesa: Mapped[Optional[int]] = mapped_column(Integer) # REMESA
|
||||
aduana: Mapped[Optional[str]] = mapped_column(ForeignKey("public.customs_sections.customs_code")) # ADUANA_CRUCE
|
||||
port_of_entry: Mapped[Optional[str]] = mapped_column(String(6)) # PUERTOENTRADA / Puerto de entrada
|
||||
@@ -129,15 +147,15 @@ class InvoiceComplianceMx(Base, TenantScopedMixin, TimestampMixin):
|
||||
|
||||
# Clients & Providers
|
||||
provider_header: Mapped[Optional[str]] = mapped_column(String(20)) # PROVEEDOREXPORTADOR
|
||||
provider_id: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.clients_and_providers.id")) # PROVEEDOR
|
||||
provider_id: Mapped[Optional[int]] = mapped_column(ForeignKey("a76.clients_and_providers.id")) # PROVEEDOR
|
||||
sold_to_header: Mapped[Optional[str]] = mapped_column(String(20)) # VENDIDOCONSIGNADO
|
||||
sold_to_id: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.clients_and_providers.id")) # VENDIDOA
|
||||
sold_to_id: Mapped[Optional[int]] = mapped_column(ForeignKey("a76.clients_and_providers.id")) # VENDIDOA
|
||||
shipped_to_header: Mapped[Optional[str]] = mapped_column(String(20)) # ENVIADOTRANSFERIDO
|
||||
shipped_to_id: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.clients_and_providers.id")) # ENVIADOA
|
||||
shipped_to_id: Mapped[Optional[int]] = mapped_column(ForeignKey("a76.clients_and_providers.id")) # ENVIADOA
|
||||
shipped_by_header: Mapped[Optional[str]] = mapped_column(String(20)) # ENVIADOPORVENDIDOPOR
|
||||
shipped_by_id: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.clients_and_providers.id")) # VENDIDOPOR/ENVIADOPOR
|
||||
customs_broker_id: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.customs_brokers.id")) # AADUANAL / Agente aduanal
|
||||
customs_broker_us_id: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.customs_brokers.id")) # AADUANALAME / Agente aduanal americano
|
||||
shipped_by_id: Mapped[Optional[int]] = mapped_column(ForeignKey("a76.clients_and_providers.id")) # VENDIDOPOR/ENVIADOPOR
|
||||
customs_broker_id: Mapped[Optional[int]] = mapped_column(ForeignKey("a76.customs_brokers.id")) # AADUANAL / Agente aduanal
|
||||
customs_broker_us_id: Mapped[Optional[int]] = mapped_column(ForeignKey("a76.customs_brokers.id")) # AADUANALAME / Agente aduanal americano
|
||||
|
||||
# Broker Invoice
|
||||
broker_invoice_num: Mapped[Optional[str]] = mapped_column(String(20)) # NUMFACTURABROKER / Número factura broker
|
||||
@@ -148,15 +166,15 @@ class InvoiceComplianceMx(Base, TenantScopedMixin, TimestampMixin):
|
||||
waste_type: Mapped[Optional[str]] = mapped_column(String(1)) # TIPODESPERDICIO / Tipo de desperdicio
|
||||
scrap_type: Mapped[Optional[str]] = mapped_column(String(1)) # TIPOSCRAP / Tipo de scrap
|
||||
appendix_17: Mapped[Optional[int]] = mapped_column(Integer) # APENDICE17 / Apéndice 17
|
||||
is_regime_change: Mapped[Optional[str]] = mapped_column(String(1)) # ESCAMBIOREGIMEN / Es cambio de régimen
|
||||
is_regime_change: Mapped[Optional[bool]] = mapped_column(Boolean, default=False) # ESCAMBIOREGIMEN / Es cambio de régimen
|
||||
which_exchange_rate: Mapped[Optional[str]] = mapped_column(String(5)) # CUALTIPOCAMBIO / Cuál tipo de cambio
|
||||
value_method: Mapped[Optional[str]] = mapped_column(String(2)) # METVALOR / Método de valoración
|
||||
act_value: Mapped[Optional[str]] = mapped_column(String(5)) # ACTVALOR / Actualizar valor
|
||||
is_pedimento_pending: Mapped[Optional[bool]] = mapped_column(Boolean, default=False) # PED_PENDIENTE_ASIGNAR (Mapear 1 -> True, 0 -> False)
|
||||
|
||||
# Ownership & Balances
|
||||
is_owner_of_goods: Mapped[Optional[str]] = mapped_column(String(2)) # ESDUENOMCIA / Es dueño de mercancía
|
||||
generate_balances: Mapped[Optional[str]] = mapped_column(String(2)) # GENERARSALDOS / Generar saldos
|
||||
is_owner_of_goods: Mapped[Optional[bool]] = mapped_column(Boolean, default=False) # ESDUENOMCIA / Es dueño de mercancía
|
||||
generate_balances: Mapped[Optional[bool]] = mapped_column(Boolean, default=False) # GENERARSALDOS / Generar saldos
|
||||
was_reviewed_by_company: Mapped[Optional[bool]] = mapped_column(Boolean) # FUEREVISADAMCIA / Fue revisada por la compañía
|
||||
|
||||
# VUCEM / Digital
|
||||
@@ -166,7 +184,7 @@ class InvoiceComplianceMx(Base, TenantScopedMixin, TimestampMixin):
|
||||
niu_number: Mapped[Optional[str]] = mapped_column(String(19)) # NUMERONIU / Número NIU
|
||||
bill_of_lading_count: Mapped[Optional[str]] = mapped_column(String(12)) # CANTGUIASEMBARQUE / Cantidad guías embarque
|
||||
addendum_vu: Mapped[Optional[str]] = mapped_column(String(204)) # ADENDAVU / Adenda VUCEM
|
||||
origin_destination_cove: Mapped[Optional[str]] = mapped_column(String(19)) # DESTINOORIGENCOVE / Destino/Origen COVE
|
||||
origin_destination_cove: Mapped[Optional[DestinationOriginCove]] = mapped_column(String(20)) # DESTINOORIGENCOVE / Destino/Origen COVE
|
||||
vucem_operation_num: Mapped[Optional[str]] = mapped_column(String(19)) # NUMOPERACIONVU / Número operación VUCEM
|
||||
customs_person_line: Mapped[Optional[int]] = mapped_column(Integer) # LINEAPERSONAAA / Línea persona agente aduanal
|
||||
|
||||
@@ -201,7 +219,7 @@ class InvoiceFinancials(Base, TenantScopedMixin, TimestampMixin):
|
||||
invoice_id: Mapped[int] = mapped_column(ForeignKey("a76.invoice_header.id"))
|
||||
|
||||
# Currency
|
||||
currency: Mapped[Optional[str]] = mapped_column(String(3)) # CLAVEMONEDA / Clave de moneda
|
||||
currency: Mapped[Currency] = mapped_column(String(7)) # CLAVEMONEDA / Clave de moneda
|
||||
currency_type: Mapped[Optional[str]] = mapped_column(ForeignKey("public.currency_types.code")) # TIPOMONEDA / TIPOCLAVEMONEDA
|
||||
exchange_rate: Mapped[Optional[float]] = mapped_column(Numeric(13, 6)) # TIPOCAMBIO / Tipo de cambio
|
||||
exchange_rate_mm: Mapped[Optional[float]] = mapped_column(Numeric(13, 6)) # TIPOCAMBIOMM / Tipo de cambio moneda a moneda
|
||||
@@ -278,7 +296,7 @@ class InvoiceLogistics(Base, TenantScopedMixin, TimestampMixin):
|
||||
transport_num: Mapped[Optional[str]] = mapped_column(String(20)) # NUMTRASPORTE / Número de transporte
|
||||
transport_mode: Mapped[Optional[str]] = mapped_column(String(15)) # MODTRANS / Modo de transporte
|
||||
driver_name: Mapped[Optional[str]] = mapped_column(String(80)) # CONDUCTOR / Nombre del conductor
|
||||
is_rail: Mapped[Optional[str]] = mapped_column(String(2)) # ESFERROCARRIL / Es ferrocarril
|
||||
is_rail: Mapped[Optional[bool]] = mapped_column(Boolean, default=False) # ESFERROCARRIL / Es ferrocarril
|
||||
rail_id: Mapped[Optional[str]] = mapped_column(String(31)) # IDFERRORCARRIL / ID ferrocarril
|
||||
|
||||
# Vehicle & Tracking
|
||||
@@ -302,7 +320,7 @@ class InvoiceLogistics(Base, TenantScopedMixin, TimestampMixin):
|
||||
complement_2: Mapped[Optional[str]] = mapped_column(String(30)) # COMPLEMENTO2 / Complemento 2
|
||||
|
||||
# Weight & Container Info
|
||||
weight_type: Mapped[Optional[str]] = mapped_column(String(6)) # TIPOPESO / Tipo de peso
|
||||
weight_type: Mapped[WeightUnit] = mapped_column(String(3)) # TIPOPESO / Tipo de peso
|
||||
container_types: Mapped[Optional[str]] = mapped_column(String(500)) # CONTENEDORESTIPO / Tipos de contenedores
|
||||
vehicle_data: Mapped[Optional[str]] = mapped_column(String(500)) # DATOSVEHICULO / Datos del vehículo
|
||||
|
||||
@@ -317,7 +335,7 @@ class InvoiceLogistics(Base, TenantScopedMixin, TimestampMixin):
|
||||
delivery_date: Mapped[Optional[datetime]] = mapped_column(Date) # FECHAENTREGA / Fecha de entrega
|
||||
|
||||
# Delivery Control
|
||||
delivered_status: Mapped[Optional[str]] = mapped_column(String(2)) # ENTREGADO / Estado de entrega
|
||||
delivered_status: Mapped[Optional[bool]] = mapped_column(Boolean, default=False) # ENTREGADO / Estado de entrega
|
||||
received_by: Mapped[Optional[str]] = mapped_column(String(50)) # RECIBIDOPOR / Recibido por
|
||||
|
||||
# Payment Info
|
||||
@@ -325,7 +343,7 @@ class InvoiceLogistics(Base, TenantScopedMixin, TimestampMixin):
|
||||
payment_receipt_num: Mapped[Optional[str]] = mapped_column(String(20)) # NUMRECIBOPAGO / Número de recibo de pago
|
||||
|
||||
# CTM Process
|
||||
is_ctm_process: Mapped[Optional[str]] = mapped_column(String(2)) # SETRATAPROCESOCTM / Se trata de proceso CTM
|
||||
is_ctm_process: Mapped[Optional[bool]] = mapped_column(Boolean, default=False) # SETRATAPROCESOCTM / Se trata de proceso CTM
|
||||
|
||||
# Relationship
|
||||
header: Mapped["InvoiceHeader"] = relationship(back_populates="logistics")
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from typing import Optional, List
|
||||
from typing import Literal, Optional, List
|
||||
from datetime import datetime, date
|
||||
from decimal import Decimal
|
||||
from pydantic import BaseModel, Field
|
||||
from .models import OperationType
|
||||
from .models import DestinationOriginCove, OperationType, Currency, TransportType, WeightUnit
|
||||
|
||||
|
||||
# --- Base Schemas ---
|
||||
@@ -11,11 +11,13 @@ class InvoiceHeaderBase(BaseModel):
|
||||
system: Optional[str] = Field(
|
||||
None, max_length=12, description="System of origin")
|
||||
operation_type: Optional[OperationType] = Field(
|
||||
None, max_length=10, description="Operation type: imp/exp/sm/ctm")
|
||||
..., description="Operation type: imp/exp/sm/ctm")
|
||||
invoice_type: Optional[str] = Field(
|
||||
None, max_length=5, description="Invoice type key")
|
||||
document_type: str = Field(
|
||||
..., max_length=3, description="Document type (Regimen Aduanero)")
|
||||
invoice_number: Optional[str] = Field(
|
||||
None, max_length=20, description="Invoice number")
|
||||
None, max_length=100, description="Invoice number")
|
||||
project_number: Optional[str] = Field(
|
||||
None, max_length=14, description="Project number")
|
||||
purchase_order: Optional[str] = Field(
|
||||
@@ -28,9 +30,9 @@ class InvoiceHeaderBase(BaseModel):
|
||||
None, max_length=19, description="Invoice reference")
|
||||
proforma_number: Optional[str] = Field(
|
||||
None, max_length=20, description="Proforma number")
|
||||
invoice_date: Optional[date] = Field(None, description="Invoice date")
|
||||
invoice_date: date = Field(..., description="Invoice date")
|
||||
emission_date: Optional[date] = Field(None, description="Emission date")
|
||||
is_updated: Optional[bool] = Field(None, description="Status")
|
||||
is_updated: bool = Field(False, description="Status")
|
||||
updated_date: Optional[datetime] = Field(None, description="Update date")
|
||||
who_updated: Optional[str] = Field(
|
||||
None, max_length=20, description="Who updated")
|
||||
@@ -40,8 +42,8 @@ class InvoiceHeaderBase(BaseModel):
|
||||
None, max_length=50, description="Traffic light status")
|
||||
process_log: Optional[str] = Field(
|
||||
None, max_length=300, description="Processing log")
|
||||
status_rec: Optional[int] = Field(None, description="Reception status")
|
||||
status_rep: Optional[str] = Field(
|
||||
is_updated_rec: Optional[int] = Field(None, description="Reception status")
|
||||
is_updated_rep: Optional[str] = Field(
|
||||
None, max_length=2, description="Report status")
|
||||
observation_es: Optional[str] = Field(
|
||||
None, description="Observations in Spanish")
|
||||
@@ -60,12 +62,10 @@ class InvoiceHeaderBase(BaseModel):
|
||||
subcompany: Optional[str] = Field(
|
||||
None, max_length=5, description="Subcompany")
|
||||
party_count: Optional[int] = Field(None, description="Quantity of parties")
|
||||
generate_id: Optional[str] = Field(
|
||||
None, max_length=1, description="Generate ID")
|
||||
generate_id: Optional[bool] = Field(False, description="Generate ID")
|
||||
generate_desc_parties: Optional[str] = Field(
|
||||
None, max_length=12, description="Generate description of parties")
|
||||
apply_manual_discount: Optional[str] = Field(
|
||||
None, max_length=1, description="Apply manual discount")
|
||||
apply_manual_discount: Optional[bool] = Field(False, description="Apply manual discount")
|
||||
is_bulk: Optional[bool] = Field(None, description="Is bulk")
|
||||
download_substance: Optional[bool] = Field(
|
||||
None, description="Download substance")
|
||||
@@ -84,66 +84,64 @@ class InvoiceHeaderBase(BaseModel):
|
||||
|
||||
class InvoiceComplianceMxBase(BaseModel):
|
||||
"""Base fields for Compliance MX"""
|
||||
pedimento: Optional[str] = Field(
|
||||
None, max_length=19, description="Pedimento number")
|
||||
pedimento_code: Optional[str] = Field(
|
||||
None, max_length=5, description="Pedimento code (R1)")
|
||||
pedimento_k1: Optional[str] = Field(
|
||||
None, max_length=15, description="Pedimento K1")
|
||||
pedimento_id: Optional[int] = Field(
|
||||
None, description="Pedimento id")
|
||||
pedimento_r1: Optional[int] = Field(
|
||||
None, description="Pedimento id (R1)")
|
||||
pedimento_k1: Optional[int] = Field(
|
||||
None, description="Pedimento id (K1)")
|
||||
remesa: Optional[int] = Field(None, description="Remesa")
|
||||
aduana: Optional[str] = Field(
|
||||
None, max_length=5, description="Customs office")
|
||||
aduana: Optional[str] = Field(None, max_length=5, description="Customs office")
|
||||
port_of_entry: Optional[str] = Field(
|
||||
None, max_length=6, description="Port of entry")
|
||||
destination: Optional[str] = Field(
|
||||
None, max_length=3, description="Destination code")
|
||||
manifest_number: Optional[str] = Field(
|
||||
None, max_length=15, description="Manifest number")
|
||||
provider_header: Optional[str] = Field(
|
||||
provider_header: str = Field(
|
||||
None, max_length=20, description="Provider header")
|
||||
provider_id: Optional[str] = Field(
|
||||
provider_id: int = Field(
|
||||
None, description="Provider ID")
|
||||
sold_to_header: Optional[str] = Field(
|
||||
sold_to_header: str = Field(
|
||||
None, max_length=20, description="Sold to header")
|
||||
sold_to_id: Optional[str] = Field(
|
||||
sold_to_id: int = Field(
|
||||
None, description="Sold to ID")
|
||||
shipped_to_header: Optional[str] = Field(
|
||||
shipped_to_header: str = Field(
|
||||
None, max_length=20, description="Shipped to header")
|
||||
shipped_to_id: Optional[str] = Field(
|
||||
shipped_to_id:int = Field(
|
||||
None, description="Shipped to ID")
|
||||
shipped_by_header: Optional[str] = Field(
|
||||
shipped_by_header: Optional[int] = Field(
|
||||
None, max_length=20, description="Shipped by header")
|
||||
shipped_by_id: Optional[str] = Field(
|
||||
shipped_by_id: Optional[int] = Field(
|
||||
None, description="Shipped by ID")
|
||||
customs_broker_id: Optional[str] = Field(
|
||||
customs_broker_id: int = Field(
|
||||
None, description="Customs broker ID")
|
||||
customs_broker_us_id: Optional[str] = Field(
|
||||
customs_broker_us_id: Optional[int] = Field(
|
||||
None, description="US customs broker ID")
|
||||
broker_invoice_num: Optional[str] = Field(
|
||||
None, max_length=20, description="Broker invoice number")
|
||||
broker_invoice_date: Optional[date] = Field(
|
||||
None, description="Broker invoice date")
|
||||
is_mixed: Optional[bool] = Field(
|
||||
None, description="Is mixed operation")
|
||||
False, description="Is mixed operation")
|
||||
waste_type: Optional[str] = Field(
|
||||
None, max_length=1, description="Waste type")
|
||||
scrap_type: Optional[str] = Field(
|
||||
None, max_length=1, description="Scrap type")
|
||||
appendix_17: Optional[int] = Field(None, description="Appendix 17")
|
||||
is_regime_change: Optional[str] = Field(
|
||||
None, max_length=1, description="Is regime change")
|
||||
is_regime_change: Optional[bool] = Field(
|
||||
False, description="Is regime change")
|
||||
which_exchange_rate: Optional[str] = Field(
|
||||
None, max_length=5, description="Which exchange rate")
|
||||
value_method: Optional[str] = Field(
|
||||
None, max_length=2, description="Value method")
|
||||
act_value: Optional[str] = Field(
|
||||
None, max_length=5, description="Act value")
|
||||
is_pedimento_pending: Optional[bool] = Field(
|
||||
None, description="Is pedimento pending")
|
||||
is_owner_of_goods: Optional[str] = Field(
|
||||
None, max_length=2, description="Is owner of goods")
|
||||
generate_balances: Optional[str] = Field(
|
||||
None, max_length=2, description="Generate balances")
|
||||
is_pedimento_pending: bool = Field(..., description="Is pedimento pending")
|
||||
is_owner_of_goods: Optional[bool] = Field(
|
||||
False, description="Is owner of goods")
|
||||
generate_balances: Optional[bool] = Field(
|
||||
False, description="Generate balances")
|
||||
was_reviewed_by_company: Optional[bool] = Field(
|
||||
None, description="Was reviewed by company")
|
||||
edocument: Optional[str] = Field(
|
||||
@@ -158,8 +156,7 @@ class InvoiceComplianceMxBase(BaseModel):
|
||||
None, max_length=12, description="Bill of lading count")
|
||||
addendum_vu: Optional[str] = Field(
|
||||
None, max_length=204, description="VUCEM addendum")
|
||||
origin_destination_cove: Optional[str] = Field(
|
||||
None, max_length=19, description="Origin/Destination COVE")
|
||||
origin_destination_cove: Optional[DestinationOriginCove] = Field('franja_front_norte', max_length=20, description="Origin/Destination COVE")
|
||||
vucem_operation_num: Optional[str] = Field(
|
||||
None, max_length=19, description="VUCEM operation number")
|
||||
customs_person_line: Optional[int] = Field(
|
||||
@@ -191,11 +188,11 @@ class InvoiceComplianceMxBase(BaseModel):
|
||||
|
||||
class InvoiceFinancialsBase(BaseModel):
|
||||
"""Base fields for Financials"""
|
||||
currency: Optional[str] = Field(
|
||||
None, max_length=3, description="Currency code")
|
||||
currency: Currency = Field(
|
||||
None, max_length=7, description="Currency code")
|
||||
currency_type: Optional[str] = Field(
|
||||
None, description="Currency type")
|
||||
exchange_rate: Optional[Decimal] = Field(None, description="Exchange rate")
|
||||
"USD", description="Currency type")
|
||||
exchange_rate: Decimal = Field(0.00, description="Exchange rate")
|
||||
exchange_rate_mm: Optional[Decimal] = Field(
|
||||
None, description="Exchange rate currency to currency")
|
||||
value_mn: Optional[Decimal] = Field(None, description="Value in MXN")
|
||||
@@ -245,8 +242,7 @@ class InvoiceFinancialsBase(BaseModel):
|
||||
None, description="IVA in foreign currency")
|
||||
iva_mc: Optional[Decimal] = Field(
|
||||
None, description="IVA in third currency")
|
||||
iva_factor: Optional[str] = Field(
|
||||
None, max_length=10, description="IVA factor")
|
||||
iva_factor: Optional[Decimal] = Field(None, description="IVA factor")
|
||||
tax_value_me: Optional[Decimal] = Field(
|
||||
None, description="Tax value in foreign currency")
|
||||
seal_value_2500: Optional[bool] = Field(
|
||||
@@ -267,16 +263,16 @@ class InvoiceLogisticsBase(BaseModel):
|
||||
None, max_length=10, description="Transport ID")
|
||||
transport_us_id: Optional[str] = Field(
|
||||
None, max_length=10, description="US transport ID")
|
||||
transport_type: Optional[str] = Field(
|
||||
None, max_length=15, description="Transport type")
|
||||
transport_type: TransportType = Field(
|
||||
'none', max_length=15, description="Transport type")
|
||||
transport_num: Optional[str] = Field(
|
||||
None, max_length=20, description="Transport number")
|
||||
transport_mode: Optional[str] = Field(
|
||||
None, max_length=15, description="Transport mode")
|
||||
30, max_length=15, description="Transport mode")
|
||||
driver_name: Optional[str] = Field(
|
||||
None, max_length=80, description="Driver name")
|
||||
is_rail: Optional[str] = Field(
|
||||
None, max_length=2, description="Is rail transport")
|
||||
is_rail: Optional[bool] = Field(
|
||||
False, description="Is rail transport")
|
||||
rail_id: Optional[str] = Field(
|
||||
None, max_length=31, description="Rail ID")
|
||||
vehicle_num: Optional[str] = Field(
|
||||
@@ -307,8 +303,8 @@ class InvoiceLogisticsBase(BaseModel):
|
||||
None, max_length=2, description="Identifier 2")
|
||||
complement_2: Optional[str] = Field(
|
||||
None, max_length=30, description="Complement 2")
|
||||
weight_type: Optional[str] = Field(
|
||||
None, max_length=6, description="Weight type")
|
||||
weight_type: WeightUnit = Field(
|
||||
default="kgs", max_length=3, description="Weight type")
|
||||
container_types: Optional[str] = Field(
|
||||
None, max_length=500, description="Container types")
|
||||
vehicle_data: Optional[str] = Field(
|
||||
@@ -333,8 +329,8 @@ class InvoiceLogisticsBase(BaseModel):
|
||||
None, description="Payment date")
|
||||
payment_receipt_num: Optional[str] = Field(
|
||||
None, max_length=20, description="Payment receipt number")
|
||||
is_ctm_process: Optional[str] = Field(
|
||||
None, max_length=2, description="Is CTM process")
|
||||
is_ctm_process: Optional[bool] = Field(
|
||||
False, description="Is CTM process")
|
||||
|
||||
|
||||
class InvoiceSalesDetailsBase(BaseModel):
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
import traceback
|
||||
from typing import Optional, List, Tuple
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import and_
|
||||
from core.exceptions import ErrorCollector, DuplicateResourceException
|
||||
from .common.mappers import clean_dict
|
||||
from .imports.temporary.validators.create import validate_create
|
||||
from .imports.temporary.validators.update import validate_update
|
||||
from .common.common_validators import invoice_exists
|
||||
|
||||
from . import models, schemas
|
||||
|
||||
|
||||
class InvoiceService:
|
||||
"""Service for Invoice Header operations"""
|
||||
|
||||
@staticmethod
|
||||
def get_by_id(db: Session, invoice_id: int, tenant_id: int, company_id: int) -> Optional[models.InvoiceHeader]:
|
||||
def get_by_id(
|
||||
db: Session, invoice_id: int, tenant_id: int, company_id: int
|
||||
) -> Optional[models.InvoiceHeader]:
|
||||
"""Get an invoice by ID with tenant/company validation"""
|
||||
return (
|
||||
db.query(models.InvoiceHeader)
|
||||
@@ -39,25 +46,32 @@ class InvoiceService:
|
||||
# Apply filters if provided
|
||||
if filters:
|
||||
if filters.get("status"):
|
||||
query = query.filter(
|
||||
models.InvoiceHeader.status == filters["status"])
|
||||
query = query.filter(models.InvoiceHeader.status == filters["status"])
|
||||
if filters.get("operation_type"):
|
||||
query = query.filter(
|
||||
models.InvoiceHeader.operation_type == filters["operation_type"])
|
||||
models.InvoiceHeader.operation_type == filters["operation_type"]
|
||||
)
|
||||
if filters.get("invoice_type"):
|
||||
query = query.filter(
|
||||
models.InvoiceHeader.invoice_type == filters["invoice_type"])
|
||||
models.InvoiceHeader.invoice_type == filters["invoice_type"]
|
||||
)
|
||||
if filters.get("invoice_number"):
|
||||
query = query.filter(models.InvoiceHeader.invoice_number.ilike(
|
||||
f"%{filters['invoice_number']}%"))
|
||||
query = query.filter(
|
||||
models.InvoiceHeader.invoice_number.ilike(
|
||||
f"%{filters['invoice_number']}%"
|
||||
)
|
||||
)
|
||||
if filters.get("pedimento"):
|
||||
query = query.join(models.InvoiceComplianceMx).filter(
|
||||
models.InvoiceComplianceMx.pedimento.ilike(
|
||||
f"%{filters['pedimento']}%")
|
||||
f"%{filters['pedimento']}%"
|
||||
)
|
||||
)
|
||||
if not filters.get("invoice_type") and filters.get("operation_type") == "exp":
|
||||
query = query.filter(
|
||||
models.InvoiceHeader.operation_type != "REPAR")
|
||||
if (
|
||||
not filters.get("invoice_type")
|
||||
and filters.get("operation_type") == "exp"
|
||||
):
|
||||
query = query.filter(models.InvoiceHeader.operation_type != "REPAR")
|
||||
|
||||
total = query.count()
|
||||
items = query.offset(skip).limit(limit).all()
|
||||
@@ -68,30 +82,19 @@ class InvoiceService:
|
||||
db: Session,
|
||||
invoice_data: schemas.InvoiceHeaderCreate,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
company_id: int,
|
||||
) -> models.InvoiceHeader:
|
||||
"""Create a new invoice with all related data"""
|
||||
|
||||
|
||||
def clean_dict(data_dict: dict) -> dict:
|
||||
cleaned = {}
|
||||
for key, value in data_dict.items():
|
||||
|
||||
if key == 'customs_agent':
|
||||
key = 'customs_broker_id'
|
||||
elif key == 'provider':
|
||||
key = 'provider_id'
|
||||
|
||||
|
||||
if isinstance(value, str) and not value.strip():
|
||||
cleaned[key] = None
|
||||
|
||||
elif value == 0 and (key.endswith('_id') or key == 'remesa'):
|
||||
cleaned[key] = None
|
||||
else:
|
||||
cleaned[key] = value
|
||||
return cleaned
|
||||
|
||||
|
||||
# Validaciones con ErrorCollector
|
||||
errors = ErrorCollector()
|
||||
|
||||
# Validar si la factura ya existe
|
||||
#invoice_exists(db, invoice_data.invoice_number, tenant_id, company_id, errors)
|
||||
#validate_create(db, invoice_data, tenant_id, company_id, errors)
|
||||
|
||||
# Si hay errores, lanzar excepción
|
||||
errors.raise_if_errors("Error al crear la factura")
|
||||
|
||||
try:
|
||||
# Extract nested data
|
||||
@@ -103,27 +106,32 @@ class InvoiceService:
|
||||
|
||||
# Create main invoice header
|
||||
raw_invoice_dict = invoice_data.model_dump(
|
||||
exclude={"compliance_mx", "financials",
|
||||
"logistics", "details", "collections"}
|
||||
exclude={
|
||||
"compliance_mx",
|
||||
"financials",
|
||||
"logistics",
|
||||
"details",
|
||||
"collections",
|
||||
}
|
||||
)
|
||||
invoice_dict = clean_dict(raw_invoice_dict)
|
||||
invoice_dict["tenant_id"] = tenant_id
|
||||
invoice_dict["company_id"] = company_id
|
||||
|
||||
new_invoice = models.InvoiceHeader(**invoice_dict)
|
||||
|
||||
db.add(new_invoice)
|
||||
db.flush() # Flush to get the invoice ID
|
||||
|
||||
# Create compliance_mx if provided
|
||||
if compliance_data:
|
||||
raw_comp_dict = compliance_data.model_dump()
|
||||
# Pasamos los datos por la lavadora para arreglar pedimento, aduana, etc.
|
||||
compliance_dict = clean_dict(raw_comp_dict)
|
||||
|
||||
|
||||
compliance_dict["invoice_id"] = new_invoice.id
|
||||
compliance_dict["tenant_id"] = tenant_id
|
||||
compliance_dict["company_id"] = company_id
|
||||
|
||||
|
||||
new_compliance = models.InvoiceComplianceMx(**compliance_dict)
|
||||
db.add(new_compliance)
|
||||
|
||||
@@ -131,11 +139,11 @@ class InvoiceService:
|
||||
if financials_data:
|
||||
raw_fin_dict = financials_data.model_dump()
|
||||
financials_dict = clean_dict(raw_fin_dict)
|
||||
|
||||
|
||||
financials_dict["invoice_id"] = new_invoice.id
|
||||
financials_dict["tenant_id"] = tenant_id
|
||||
financials_dict["company_id"] = company_id
|
||||
|
||||
|
||||
new_financials = models.InvoiceFinancials(**financials_dict)
|
||||
db.add(new_financials)
|
||||
|
||||
@@ -143,7 +151,7 @@ class InvoiceService:
|
||||
for logistics_item in logistics_data:
|
||||
raw_log_dict = logistics_item.model_dump()
|
||||
logistics_dict = clean_dict(raw_log_dict)
|
||||
|
||||
|
||||
logistics_dict["invoice_id"] = new_invoice.id
|
||||
logistics_dict["tenant_id"] = tenant_id
|
||||
logistics_dict["company_id"] = company_id
|
||||
@@ -154,7 +162,7 @@ class InvoiceService:
|
||||
for detail_item in details_data:
|
||||
raw_det_dict = detail_item.model_dump()
|
||||
detail_dict = clean_dict(raw_det_dict)
|
||||
|
||||
|
||||
detail_dict["invoice_id"] = new_invoice.id
|
||||
detail_dict["tenant_id"] = tenant_id
|
||||
detail_dict["company_id"] = company_id
|
||||
@@ -165,7 +173,7 @@ class InvoiceService:
|
||||
for collection_item in collections_data:
|
||||
raw_col_dict = collection_item.model_dump()
|
||||
collection_dict = clean_dict(raw_col_dict)
|
||||
|
||||
|
||||
collection_dict["invoice_id"] = new_invoice.id
|
||||
collection_dict["tenant_id"] = tenant_id
|
||||
collection_dict["company_id"] = company_id
|
||||
@@ -180,7 +188,7 @@ class InvoiceService:
|
||||
db.rollback()
|
||||
print("\n\n🔥 ERROR AL GUARDAR FACTURA 🔥")
|
||||
print(f"Error: {str(e)}")
|
||||
traceback.print_exc() # Esto imprime el error real en la consola
|
||||
traceback.print_exc() # Esto imprime el error real en la consola
|
||||
print("--------------------------------\n")
|
||||
raise e
|
||||
|
||||
@@ -190,20 +198,24 @@ class InvoiceService:
|
||||
invoice_id: int,
|
||||
tenant_id: int,
|
||||
invoice_data: schemas.InvoiceHeaderUpdate,
|
||||
company_id: int
|
||||
company_id: int,
|
||||
) -> Optional[models.InvoiceHeader]:
|
||||
# ... (El resto de tu código update se queda igual) ...
|
||||
# (Te recomiendo implementar clean_dict aquí también si tienes problemas al editar)
|
||||
invoice = InvoiceService.get_by_id(
|
||||
db, invoice_id, tenant_id, company_id)
|
||||
invoice = InvoiceService.get_by_id(db, invoice_id, tenant_id, company_id)
|
||||
if not invoice:
|
||||
return None
|
||||
|
||||
# Update main invoice header fields
|
||||
update_dict = invoice_data.model_dump(
|
||||
exclude={"compliance_mx", "financials",
|
||||
"logistics", "details", "collections"},
|
||||
exclude_unset=True
|
||||
exclude={
|
||||
"compliance_mx",
|
||||
"financials",
|
||||
"logistics",
|
||||
"details",
|
||||
"collections",
|
||||
},
|
||||
exclude_unset=True,
|
||||
)
|
||||
for key, value in update_dict.items():
|
||||
setattr(invoice, key, value)
|
||||
@@ -211,15 +223,21 @@ class InvoiceService:
|
||||
# Update compliance_mx if provided
|
||||
if invoice_data.compliance_mx is not None:
|
||||
if invoice.compliance_mx:
|
||||
for key, value in invoice_data.compliance_mx.model_dump(exclude_unset=True).items():
|
||||
for key, value in invoice_data.compliance_mx.model_dump(
|
||||
exclude_unset=True
|
||||
).items():
|
||||
# Parche rápido para update
|
||||
if value == "": value = None
|
||||
if value == "":
|
||||
value = None
|
||||
setattr(invoice.compliance_mx, key, value)
|
||||
else:
|
||||
compliance_dict = invoice_data.compliance_mx.model_dump()
|
||||
# Aplicar limpieza manual si es necesario
|
||||
if 'customs_agent' in compliance_dict: compliance_dict['customs_broker_id'] = compliance_dict.pop('customs_agent')
|
||||
|
||||
if "customs_agent" in compliance_dict:
|
||||
compliance_dict["customs_broker_id"] = compliance_dict.pop(
|
||||
"customs_agent"
|
||||
)
|
||||
|
||||
compliance_dict["invoice_id"] = invoice.id
|
||||
compliance_dict["tenant_id"] = tenant_id
|
||||
compliance_dict["company_id"] = company_id
|
||||
@@ -229,8 +247,11 @@ class InvoiceService:
|
||||
# Update financials if provided
|
||||
if invoice_data.financials is not None:
|
||||
if invoice.financials:
|
||||
for key, value in invoice_data.financials.model_dump(exclude_unset=True).items():
|
||||
if value == "": value = None
|
||||
for key, value in invoice_data.financials.model_dump(
|
||||
exclude_unset=True
|
||||
).items():
|
||||
if value == "":
|
||||
value = None
|
||||
setattr(invoice.financials, key, value)
|
||||
else:
|
||||
financials_dict = invoice_data.financials.model_dump()
|
||||
@@ -247,10 +268,9 @@ class InvoiceService:
|
||||
@staticmethod
|
||||
def delete(db: Session, invoice_id: int, tenant_id: int, company_id: int) -> bool:
|
||||
"""Delete an invoice and all related data (cascade delete)"""
|
||||
invoice = InvoiceService.get_by_id(
|
||||
db, invoice_id, tenant_id, company_id)
|
||||
invoice = InvoiceService.get_by_id(db, invoice_id, tenant_id, company_id)
|
||||
if invoice:
|
||||
db.delete(invoice)
|
||||
db.commit()
|
||||
return True
|
||||
return False
|
||||
return False
|
||||
|
||||
@@ -1,159 +1,241 @@
|
||||
from decimal import Decimal
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
from pydantic import BaseModel, Field, ConfigDict, field_validator
|
||||
|
||||
# Import nested schemas
|
||||
from ..line_customs.schemas import (
|
||||
LineCustomCreate,
|
||||
LineCustomUpdate,
|
||||
LineCustomResponse
|
||||
LineCustomResponse,
|
||||
)
|
||||
from ..line_descriptions.schemas import (
|
||||
LineDescriptionCreate,
|
||||
LineDescriptionUpdate,
|
||||
LineDescriptionResponse
|
||||
LineDescriptionResponse,
|
||||
)
|
||||
from ..line_quantities.schemas import (
|
||||
LineQuantityCreate,
|
||||
LineQuantityUpdate,
|
||||
LineQuantityResponse
|
||||
LineQuantityResponse,
|
||||
)
|
||||
from ..line_financials.schemas import (
|
||||
LineFinancialCreate,
|
||||
LineFinancialUpdate,
|
||||
LineFinancialResponse
|
||||
LineFinancialResponse,
|
||||
)
|
||||
from ..line_references.schemas import (
|
||||
LineReferenceCreate,
|
||||
LineReferenceUpdate,
|
||||
LineReferenceResponse
|
||||
LineReferenceResponse,
|
||||
)
|
||||
|
||||
# ============================================================================
|
||||
# LINE ITEM SCHEMAS
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class LineItemBase(BaseModel):
|
||||
"""Base schema for line items"""
|
||||
|
||||
line_number: int = Field(..., description="Line number")
|
||||
|
||||
|
||||
# Part identification
|
||||
part_number: Optional[str] = Field(None, max_length=50, description="Part number")
|
||||
component_part_number: Optional[str] = Field(None, max_length=50, description="Component part number")
|
||||
component_part_number: Optional[str] = Field(
|
||||
None, max_length=50, description="Component part number"
|
||||
)
|
||||
class_code: Optional[str] = Field(None, max_length=20, description="Class code")
|
||||
|
||||
|
||||
@field_validator(
|
||||
"class_code",
|
||||
"part_number",
|
||||
"component_part_number",
|
||||
"unit_of_measure",
|
||||
"alternate_unit",
|
||||
mode="before",
|
||||
)
|
||||
@classmethod
|
||||
def convert_to_string(cls, v):
|
||||
"""Convert integers to strings for FK fields"""
|
||||
if v is not None and not isinstance(v, str):
|
||||
return str(v)
|
||||
return v
|
||||
|
||||
# Unit of measure
|
||||
unit_of_measure: Optional[str] = Field(None, max_length=10, description="Unit of measure")
|
||||
alternate_unit: Optional[str] = Field(None, max_length=10, description="Alternate unit")
|
||||
unit_of_measure: Optional[str] = Field(
|
||||
None, max_length=10, description="Unit of measure"
|
||||
)
|
||||
alternate_unit: Optional[str] = Field(
|
||||
None, max_length=10, description="Alternate unit"
|
||||
)
|
||||
uma_key: Optional[str] = Field(None, max_length=2, description="UMA key")
|
||||
auxiliary_unit: Optional[str] = Field(None, max_length=5, description="Auxiliary unit")
|
||||
|
||||
auxiliary_unit: Optional[str] = Field(
|
||||
None, max_length=5, description="Auxiliary unit"
|
||||
)
|
||||
|
||||
# Permits and certificates
|
||||
permit_number: Optional[str] = Field(None, max_length=20, description="Permit number")
|
||||
permit_number: Optional[str] = Field(
|
||||
None, max_length=20, description="Permit number"
|
||||
)
|
||||
page_line: Optional[str] = Field(None, max_length=10, description="Page line")
|
||||
has_certificate: Optional[bool] = Field(None, description="Has certificate")
|
||||
certificate_number: Optional[str] = Field(None, max_length=10, description="Certificate number")
|
||||
octave_permit: Optional[str] = Field(None, max_length=20, description="Octave permit")
|
||||
certificate_number: Optional[str] = Field(
|
||||
None, max_length=10, description="Certificate number"
|
||||
)
|
||||
octave_permit: Optional[str] = Field(
|
||||
None, max_length=20, description="Octave permit"
|
||||
)
|
||||
permits_ped: Optional[str] = Field(None, max_length=500, description="PED permits")
|
||||
|
||||
|
||||
# FDA
|
||||
has_fda_code: Optional[bool] = Field(None, description="Has FDA code")
|
||||
fda_key: Optional[str] = Field(None, max_length=10, description="FDA key")
|
||||
|
||||
|
||||
# Subitem flags
|
||||
is_subitem: Optional[bool] = Field(None, description="Is subitem")
|
||||
contains_subitems: Optional[bool] = Field(None, description="Contains subitems")
|
||||
includes_subitems: Optional[bool] = Field(None, description="Includes subitems")
|
||||
subitem_number: Optional[bool] = Field(None, description="Subitem number")
|
||||
|
||||
|
||||
# Special flags
|
||||
is_military_mcia: Optional[bool] = Field(None, description="Is military merchandise")
|
||||
|
||||
is_military_mcia: Optional[bool] = Field(
|
||||
None, description="Is military merchandise"
|
||||
)
|
||||
|
||||
# IV32
|
||||
iv32_type_key: Optional[str] = Field(None, max_length=5, description="IV32 type key")
|
||||
iv32_type_key: Optional[str] = Field(
|
||||
None, max_length=5, description="IV32 type key"
|
||||
)
|
||||
iv32_number: Optional[str] = Field(None, max_length=35, description="IV32 number")
|
||||
|
||||
|
||||
# Export specific
|
||||
scrap_invoice: Optional[str] = Field(None, max_length=15, description="Scrap invoice")
|
||||
consecutive_destination: Optional[int] = Field(None, description="Consecutive destination")
|
||||
scrap_invoice: Optional[str] = Field(
|
||||
None, max_length=15, description="Scrap invoice"
|
||||
)
|
||||
consecutive_destination: Optional[int] = Field(
|
||||
None, description="Consecutive destination"
|
||||
)
|
||||
ctm_section: Optional[str] = Field(None, max_length=3, description="CTM section")
|
||||
|
||||
|
||||
# Tax payment
|
||||
tax_payment: Optional[bool] = Field(None, description="Tax payment")
|
||||
payment_method: Optional[str] = Field(None, max_length=9, description="Payment method")
|
||||
payment_method: Optional[str] = Field(
|
||||
None, max_length=9, description="Payment method"
|
||||
)
|
||||
igi_amount: Optional[Decimal] = Field(None, description="IGI amount")
|
||||
igi_payment_method: Optional[str] = Field(None, max_length=9, description="IGI payment method")
|
||||
|
||||
igi_payment_method: Optional[str] = Field(
|
||||
None, max_length=9, description="IGI payment method"
|
||||
)
|
||||
|
||||
# FCC
|
||||
fcc_key: Optional[str] = Field(None, max_length=30, description="FCC key")
|
||||
|
||||
|
||||
# Valuation method
|
||||
valuation_method: Optional[str] = Field(None, max_length=2, description="Valuation method")
|
||||
valuation_determined_value: Optional[Decimal] = Field(None, description="Valuation determined value")
|
||||
valuation_reason: Optional[str] = Field(None, max_length=500, description="Valuation reason")
|
||||
|
||||
valuation_method: Optional[str] = Field(
|
||||
None, max_length=2, description="Valuation method"
|
||||
)
|
||||
valuation_determined_value: Optional[Decimal] = Field(
|
||||
None, description="Valuation determined value"
|
||||
)
|
||||
valuation_reason: Optional[str] = Field(
|
||||
None, max_length=500, description="Valuation reason"
|
||||
)
|
||||
|
||||
# Container rules
|
||||
container_rule: Optional[str] = Field(None, max_length=50, description="Container rule")
|
||||
container_parts_ii: Optional[str] = Field(None, max_length=50, description="Container parts II")
|
||||
|
||||
container_rule: Optional[str] = Field(
|
||||
None, max_length=50, description="Container rule"
|
||||
)
|
||||
container_parts_ii: Optional[str] = Field(
|
||||
None, max_length=50, description="Container parts II"
|
||||
)
|
||||
|
||||
# APHIS
|
||||
consecutive_aphis: Optional[int] = Field(None, description="Consecutive APHIS")
|
||||
|
||||
|
||||
# BOM/Commercial
|
||||
bom_version: Optional[int] = Field(None, description="BOM version")
|
||||
bill_version: Optional[int] = Field(None, description="Bill version")
|
||||
|
||||
|
||||
# TLCAN value
|
||||
tlcan_value: Optional[Decimal] = Field(None, description="TLCAN value")
|
||||
|
||||
|
||||
# Identifier
|
||||
identifier: Optional[str] = Field(None, max_length=2, description="Identifier")
|
||||
|
||||
|
||||
# Validation fields
|
||||
validation_zero: Optional[int] = Field(None, description="Validation zero")
|
||||
validation_one: Optional[int] = Field(None, description="Validation one")
|
||||
|
||||
|
||||
# Material type
|
||||
material_type: Optional[str] = Field(None, max_length=50, description="Material type")
|
||||
|
||||
material_type: Optional[str] = Field(
|
||||
None, max_length=50, description="Material type"
|
||||
)
|
||||
|
||||
# Order concept
|
||||
order_type: Optional[str] = Field(None, max_length=50, description="Order type")
|
||||
line_concept: Optional[str] = Field(None, max_length=50, description="Line concept")
|
||||
|
||||
|
||||
# Review dispatch
|
||||
review_dispatch: Optional[str] = Field(None, max_length=10, description="Review dispatch")
|
||||
|
||||
review_dispatch: Optional[str] = Field(
|
||||
None, max_length=10, description="Review dispatch"
|
||||
)
|
||||
|
||||
# Take component from PT
|
||||
take_component_pt: Optional[int] = Field(None, description="Take component from PT")
|
||||
|
||||
|
||||
# Pallet
|
||||
pallet2: Optional[int] = Field(None, description="Pallet 2")
|
||||
|
||||
|
||||
# Wildcard field
|
||||
wildcard_field: Optional[str] = Field(None, max_length=100, description="Wildcard field")
|
||||
wildcard_field: Optional[str] = Field(
|
||||
None, max_length=100, description="Wildcard field"
|
||||
)
|
||||
|
||||
|
||||
class LineItemCreate(LineItemBase):
|
||||
"""Schema for creating line item with all nested data"""
|
||||
financial: Optional[LineFinancialCreate] = Field(None, description="Financial data for this line")
|
||||
quantity: Optional[LineQuantityCreate] = Field(None, description="Quantity data for this line")
|
||||
customs: Optional[LineCustomCreate] = Field(None, description="Customs data for this line")
|
||||
description: Optional[LineDescriptionCreate] = Field(None, description="Description data for this line")
|
||||
reference: Optional[LineReferenceCreate] = Field(None, description="Reference data for this line")
|
||||
|
||||
financial: Optional[LineFinancialCreate] = Field(
|
||||
None, description="Financial data for this line"
|
||||
)
|
||||
quantity: Optional[LineQuantityCreate] = Field(
|
||||
None, description="Quantity data for this line"
|
||||
)
|
||||
customs: Optional[LineCustomCreate] = Field(
|
||||
None, description="Customs data for this line"
|
||||
)
|
||||
description: Optional[LineDescriptionCreate] = Field(
|
||||
None, description="Description data for this line"
|
||||
)
|
||||
reference: Optional[LineReferenceCreate] = Field(
|
||||
None, description="Reference data for this line"
|
||||
)
|
||||
|
||||
|
||||
class LineItemUpdate(LineItemBase):
|
||||
"""Schema for updating line item with all nested data"""
|
||||
|
||||
line_number: Optional[int] = Field(None, description="Line number")
|
||||
financial: Optional[LineFinancialUpdate] = Field(None, description="Financial data for this line")
|
||||
quantity: Optional[LineQuantityUpdate] = Field(None, description="Quantity data for this line")
|
||||
customs: Optional[LineCustomUpdate] = Field(None, description="Customs data for this line")
|
||||
description: Optional[LineDescriptionUpdate] = Field(None, description="Description data for this line")
|
||||
reference: Optional[LineReferenceUpdate] = Field(None, description="Reference data for this line")
|
||||
financial: Optional[LineFinancialUpdate] = Field(
|
||||
None, description="Financial data for this line"
|
||||
)
|
||||
quantity: Optional[LineQuantityUpdate] = Field(
|
||||
None, description="Quantity data for this line"
|
||||
)
|
||||
customs: Optional[LineCustomUpdate] = Field(
|
||||
None, description="Customs data for this line"
|
||||
)
|
||||
description: Optional[LineDescriptionUpdate] = Field(
|
||||
None, description="Description data for this line"
|
||||
)
|
||||
reference: Optional[LineReferenceUpdate] = Field(
|
||||
None, description="Reference data for this line"
|
||||
)
|
||||
|
||||
|
||||
class LineItemResponse(LineItemBase):
|
||||
"""Schema for line item response with all nested data"""
|
||||
|
||||
id: int
|
||||
item_id: int
|
||||
financial: Optional[LineFinancialResponse] = None
|
||||
|
||||
@@ -106,7 +106,7 @@ class SubassemblyEntry(Base, TenantScopedMixin, TimestampMixin):
|
||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||
remission_line: Mapped[int] = mapped_column(Integer) # LINEAREMISION
|
||||
exit_invoice: Mapped[Optional[str]] = mapped_column(
|
||||
String(15)) # FACTURASALIDA
|
||||
String(15)) # FACTURASALIDA
|
||||
exit_line: Mapped[Optional[int]] = mapped_column(Integer) # LINEASALIDA
|
||||
|
||||
# ============================================================================
|
||||
|
||||
@@ -36,10 +36,7 @@ class ItemService:
|
||||
|
||||
@staticmethod
|
||||
def get_by_id(
|
||||
db: Session,
|
||||
item_id: int,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
db: Session, item_id: int, tenant_id: int, company_id: int
|
||||
) -> Optional[Item]:
|
||||
"""Get an item by ID with tenant/company validation"""
|
||||
return (
|
||||
@@ -91,8 +88,7 @@ class ItemService:
|
||||
if filters.get("item_type"):
|
||||
query = query.filter(Item.item_type == filters["item_type"])
|
||||
if filters.get("system_origin"):
|
||||
query = query.filter(Item.system_origin ==
|
||||
filters["system_origin"])
|
||||
query = query.filter(Item.system_origin == filters["system_origin"])
|
||||
if filters.get("search"):
|
||||
search_term = f"%{filters['search']}%"
|
||||
query = query.filter(
|
||||
@@ -151,6 +147,12 @@ class ItemService:
|
||||
lines_data = item_data.lines or []
|
||||
item_dict = item_data.model_dump(exclude={"lines"})
|
||||
|
||||
# DEBUG: Log incoming data
|
||||
print(f"\n🔍 DEBUG CREATE ITEM:")
|
||||
print(f" Item data: {item_dict}")
|
||||
print(f" Lines count: {len(lines_data)}")
|
||||
print(f" Tenant ID: {tenant_id}, Company ID: {company_id}")
|
||||
|
||||
# Add tenant and company
|
||||
item_dict["tenant_id"] = tenant_id
|
||||
item_dict["company_id"] = company_id
|
||||
@@ -160,8 +162,11 @@ class ItemService:
|
||||
db.add(db_item)
|
||||
db.flush() # Get the item ID
|
||||
|
||||
print(f" ✅ Item created with ID: {db_item.id}")
|
||||
|
||||
# Create line items if provided
|
||||
for line_data in lines_data:
|
||||
for idx, line_data in enumerate(lines_data):
|
||||
print(f"\n 📝 Processing line {idx + 1}/{len(lines_data)}")
|
||||
# Extract nested data from line
|
||||
financial_data = line_data.financial
|
||||
quantity_data = line_data.quantity
|
||||
@@ -169,16 +174,31 @@ class ItemService:
|
||||
description_data = line_data.description
|
||||
reference_data = line_data.reference
|
||||
|
||||
print(f" Line data: {line_data.model_dump()}")
|
||||
print(f" Has financial: {financial_data is not None}")
|
||||
print(f" Has quantity: {quantity_data is not None}")
|
||||
print(f" Has customs: {customs_data is not None}")
|
||||
print(f" Has description: {description_data is not None}")
|
||||
print(f" Has reference: {reference_data is not None}")
|
||||
|
||||
line_dict = line_data.model_dump(
|
||||
exclude={"financial", "quantity",
|
||||
"customs", "description", "reference"}
|
||||
exclude={
|
||||
"financial",
|
||||
"quantity",
|
||||
"customs",
|
||||
"description",
|
||||
"reference",
|
||||
}
|
||||
)
|
||||
line_dict["item_id"] = db_item.id
|
||||
line_dict["tenant_id"] = tenant_id
|
||||
line_dict["company_id"] = company_id
|
||||
|
||||
# Create line item
|
||||
db_line = LineItem(**line_dict)
|
||||
db.add(db_line)
|
||||
db.flush() # Get the line ID
|
||||
print(f" ✅ Line created with ID: {db_line.id}")
|
||||
|
||||
# Create financial data if provided
|
||||
if financial_data:
|
||||
@@ -186,6 +206,7 @@ class ItemService:
|
||||
financial_dict["item_line_id"] = db_line.id
|
||||
db_financial = LineFinancial(**financial_dict)
|
||||
db.add(db_financial)
|
||||
print(f" ✅ Financial data added")
|
||||
|
||||
# Create quantity data if provided
|
||||
if quantity_data:
|
||||
@@ -193,6 +214,7 @@ class ItemService:
|
||||
quantity_dict["item_line_id"] = db_line.id
|
||||
db_quantity = LineQuantity(**quantity_dict)
|
||||
db.add(db_quantity)
|
||||
print(f" ✅ Quantity data added")
|
||||
|
||||
# Create customs data if provided
|
||||
if customs_data:
|
||||
@@ -200,6 +222,7 @@ class ItemService:
|
||||
customs_dict["item_line_id"] = db_line.id
|
||||
db_customs = LineCustom(**customs_dict)
|
||||
db.add(db_customs)
|
||||
print(f" ✅ Customs data added")
|
||||
|
||||
# Create description data if provided
|
||||
if description_data:
|
||||
@@ -207,6 +230,7 @@ class ItemService:
|
||||
description_dict["item_line_id"] = db_line.id
|
||||
db_description = LineDescription(**description_dict)
|
||||
db.add(db_description)
|
||||
print(f" ✅ Description data added")
|
||||
|
||||
# Create reference data if provided
|
||||
if reference_data:
|
||||
@@ -214,9 +238,12 @@ class ItemService:
|
||||
reference_dict["item_line_id"] = db_line.id
|
||||
db_reference = LineReference(**reference_dict)
|
||||
db.add(db_reference)
|
||||
print(f" ✅ Reference data added")
|
||||
|
||||
print(f"\n 💾 Committing transaction...")
|
||||
db.commit()
|
||||
db.refresh(db_item)
|
||||
print(f" ✅ Transaction committed successfully!")
|
||||
return db_item
|
||||
|
||||
except IntegrityError as e:
|
||||
@@ -248,8 +275,7 @@ class ItemService:
|
||||
|
||||
# Extract lines data
|
||||
lines_data = item_data.lines
|
||||
item_dict = item_data.model_dump(
|
||||
exclude={"lines"}, exclude_unset=True)
|
||||
item_dict = item_data.model_dump(exclude={"lines"}, exclude_unset=True)
|
||||
|
||||
# Update item fields
|
||||
for key, value in item_dict.items():
|
||||
@@ -272,43 +298,48 @@ class ItemService:
|
||||
reference_data = line_data.reference
|
||||
|
||||
line_dict = line_data.model_dump(
|
||||
exclude={"financial", "quantity",
|
||||
"customs", "description", "reference"},
|
||||
exclude_unset=True
|
||||
exclude={
|
||||
"financial",
|
||||
"quantity",
|
||||
"customs",
|
||||
"description",
|
||||
"reference",
|
||||
},
|
||||
exclude_unset=True,
|
||||
)
|
||||
line_dict["item_id"] = db_item.id
|
||||
line_dict["tenant_id"] = tenant_id
|
||||
line_dict["company_id"] = company_id
|
||||
|
||||
db_line = LineItem(**line_dict)
|
||||
db.add(db_line)
|
||||
db.flush()
|
||||
|
||||
# Create nested data if provided
|
||||
if financial_data is not None:
|
||||
financial_dict = financial_data.model_dump(
|
||||
exclude_unset=True)
|
||||
financial_dict = financial_data.model_dump(exclude_unset=True)
|
||||
financial_dict["item_line_id"] = db_line.id
|
||||
db.add(LineFinancial(**financial_dict))
|
||||
|
||||
if quantity_data is not None:
|
||||
quantity_dict = quantity_data.model_dump(
|
||||
exclude_unset=True)
|
||||
quantity_dict = quantity_data.model_dump(exclude_unset=True)
|
||||
quantity_dict["item_line_id"] = db_line.id
|
||||
db.add(LineQuantity(**quantity_dict))
|
||||
|
||||
if customs_data is not None:
|
||||
customs_dict = customs_data.model_dump(
|
||||
exclude_unset=True)
|
||||
customs_dict = customs_data.model_dump(exclude_unset=True)
|
||||
customs_dict["item_line_id"] = db_line.id
|
||||
db.add(LineCustom(**customs_dict))
|
||||
|
||||
if description_data is not None:
|
||||
description_dict = description_data.model_dump(
|
||||
exclude_unset=True)
|
||||
exclude_unset=True
|
||||
)
|
||||
description_dict["item_line_id"] = db_line.id
|
||||
db.add(LineDescription(**description_dict))
|
||||
|
||||
if reference_data is not None:
|
||||
reference_dict = reference_data.model_dump(
|
||||
exclude_unset=True)
|
||||
reference_dict = reference_data.model_dump(exclude_unset=True)
|
||||
reference_dict["item_line_id"] = db_line.id
|
||||
db.add(LineReference(**reference_dict))
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ from pydantic import BaseModel, ConfigDict, Field
|
||||
class PedimentoDatesBase(BaseModel):
|
||||
"""Base schema for Pedimento Dates"""
|
||||
|
||||
entry_date: Optional[datetime] = Field(None, description="Entry date")
|
||||
entry_date: datetime = Field(..., description="Entry date")
|
||||
pedimento_date: Optional[datetime] = Field(None, description="Pedimento date")
|
||||
payment_date: Optional[datetime] = Field(None, description="Payment date")
|
||||
rectification_payment_date: Optional[datetime] = Field(
|
||||
@@ -18,7 +18,7 @@ class PedimentoDatesBase(BaseModel):
|
||||
eucan_date: Optional[datetime] = Field(None, description="EUCAN date")
|
||||
original_date: Optional[datetime] = Field(None, description="Original date")
|
||||
start_date: Optional[datetime] = Field(None, description="Start date")
|
||||
end_date: Optional[datetime] = Field(None, description="End date")
|
||||
end_date: datetime = Field(..., description="End date")
|
||||
|
||||
|
||||
class PedimentoDatesCreate(BaseModel):
|
||||
@@ -33,7 +33,7 @@ class PedimentoDatesCreate(BaseModel):
|
||||
eucan_date: Optional[datetime] = Field(None, description="EUCAN date")
|
||||
original_date: Optional[datetime] = Field(None, description="Original date")
|
||||
start_date: Optional[datetime] = Field(None, description="Start date")
|
||||
end_date: Optional[datetime] = Field(None, description="End date")
|
||||
end_date: datetime = Field(..., description="End date")
|
||||
|
||||
|
||||
class PedimentoDatesUpdate(BaseModel):
|
||||
|
||||
@@ -1,20 +1,47 @@
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from enum import IntEnum
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from .pedimento_config_additional import PedimentoConfigAdditionalCreate, PedimentoConfigAdditionalResponse
|
||||
from .pedimento_config_calculations import PedimentoConfigCalculationsCreate, PedimentoConfigCalculationsResponse
|
||||
from .pedimento_config_parameters import PedimentoConfigParametersCreate, PedimentoConfigParametersResponse
|
||||
from .pedimento_config_surcharges import PedimentoConfigSurchargesCreate, PedimentoConfigSurchargesResponse
|
||||
from .pedimento_config_update_rectification import PedimentoConfigUpdateRectificationCreate, PedimentoConfigUpdateRectificationResponse
|
||||
from .pedimento_config_updates import PedimentoConfigUpdatesCreate, PedimentoConfigUpdatesResponse
|
||||
from .pedimento_customs_offices import PedimentoCustomsOfficesCreate, PedimentoCustomsOfficesResponse
|
||||
from ..models.pedimentos import OperationType, PedimentoType
|
||||
from .pedimento_config_additional import (
|
||||
PedimentoConfigAdditionalCreate,
|
||||
PedimentoConfigAdditionalResponse,
|
||||
)
|
||||
from .pedimento_config_calculations import (
|
||||
PedimentoConfigCalculationsCreate,
|
||||
PedimentoConfigCalculationsResponse,
|
||||
)
|
||||
from .pedimento_config_parameters import (
|
||||
PedimentoConfigParametersCreate,
|
||||
PedimentoConfigParametersResponse,
|
||||
)
|
||||
from .pedimento_config_surcharges import (
|
||||
PedimentoConfigSurchargesCreate,
|
||||
PedimentoConfigSurchargesResponse,
|
||||
)
|
||||
from .pedimento_config_update_rectification import (
|
||||
PedimentoConfigUpdateRectificationCreate,
|
||||
PedimentoConfigUpdateRectificationResponse,
|
||||
)
|
||||
from .pedimento_config_updates import (
|
||||
PedimentoConfigUpdatesCreate,
|
||||
PedimentoConfigUpdatesResponse,
|
||||
)
|
||||
from .pedimento_customs_offices import (
|
||||
PedimentoCustomsOfficesCreate,
|
||||
PedimentoCustomsOfficesResponse,
|
||||
)
|
||||
from .pedimento_dates import PedimentoDatesCreate, PedimentoDatesResponse
|
||||
from .pedimento_decrementables import PedimentoDecrementablesCreate, PedimentoDecrementablesResponse
|
||||
from .pedimento_incrementables import PedimentoIncrementablesCreate, PedimentoIncrementablesResponse
|
||||
from .pedimento_decrementables import (
|
||||
PedimentoDecrementablesCreate,
|
||||
PedimentoDecrementablesResponse,
|
||||
)
|
||||
from .pedimento_incrementables import (
|
||||
PedimentoIncrementablesCreate,
|
||||
PedimentoIncrementablesResponse,
|
||||
)
|
||||
from .pedimento_indexes import PedimentoIndexesCreate, PedimentoIndexesResponse
|
||||
from .pedimento_packages_transport import (
|
||||
PedimentoContainerCreate,
|
||||
@@ -33,17 +60,21 @@ from .pedimento_contributions import (
|
||||
PedimentoContributionResponse,
|
||||
)
|
||||
from .pedimento_payments import PedimentoPaymentsCreate, PedimentoPaymentsResponse
|
||||
from .pedimento_rectification_destination import PedimentoRectificationDestinationCreate, PedimentoRectificationDestinationResponse
|
||||
from .pedimento_rectification_origin import PedimentoRectificationOriginCreate, PedimentoRectificationOriginResponse
|
||||
from .pedimento_transport_means import PedimentoTransportMeansCreate, PedimentoTransportMeansResponse
|
||||
from .pedimento_rectification_destination import (
|
||||
PedimentoRectificationDestinationCreate,
|
||||
PedimentoRectificationDestinationResponse,
|
||||
)
|
||||
from .pedimento_rectification_origin import (
|
||||
PedimentoRectificationOriginCreate,
|
||||
PedimentoRectificationOriginResponse,
|
||||
)
|
||||
from .pedimento_transport_means import (
|
||||
PedimentoTransportMeansCreate,
|
||||
PedimentoTransportMeansResponse,
|
||||
)
|
||||
from .pedimento_validation import PedimentoValidationCreate, PedimentoValidationResponse
|
||||
|
||||
|
||||
class OperationType(IntEnum):
|
||||
EXPORTACION = 1
|
||||
IMPORTACION = 2
|
||||
|
||||
|
||||
class PedimentosBase(BaseModel):
|
||||
"""Base schema for Pedimentos"""
|
||||
|
||||
@@ -56,18 +87,17 @@ class PedimentosBase(BaseModel):
|
||||
None, max_length=7, description="Pedimento number"
|
||||
)
|
||||
client_id: Optional[int] = Field(None, description="Client ID")
|
||||
operation_type: Optional[int] = Field(None, description="Operation type")
|
||||
pedimento_type: Optional[str] = Field(None, max_length=20, description="Pedimento type")
|
||||
pedimento_code: str = Field(
|
||||
..., max_length=2, description="Pedimento key"
|
||||
)
|
||||
operation_type: Optional[OperationType] = Field(None, description="Operation type")
|
||||
pedimento_type: Optional[PedimentoType] = Field(None, description="Pedimento type")
|
||||
pedimento_code: str = Field(..., max_length=2, description="Pedimento key")
|
||||
regime: str = Field(..., max_length=3, description="Regime")
|
||||
status: Optional[str] = Field(None, max_length=30, description="Status")
|
||||
usd_value: Optional[Decimal] = Field(None, description="USD value")
|
||||
paid_price: Optional[Decimal] = Field(None, description="Paid price")
|
||||
gross_weight: Optional[Decimal] = Field(None, description="Gross weight")
|
||||
exchange_rate: Optional[Decimal] = Field(None, description="Exchange rate")
|
||||
observations: Optional[str] = Field(None, description="Observations")
|
||||
observations: Optional[str] = Field(None, description="Observations")
|
||||
|
||||
|
||||
class PedimentosCreate(PedimentosBase):
|
||||
"""Schema for creating a new Pedimento"""
|
||||
@@ -79,26 +109,28 @@ class PedimentosCreate(PedimentosBase):
|
||||
pedimento_number: str = Field(..., max_length=7, description="Pedimento number")
|
||||
client_id: int = Field(..., description="Client ID")
|
||||
# operation_type, pedimento_type, status son opcionales - se pueden llenar después
|
||||
pedimento_code: str = Field(
|
||||
..., max_length=2, description="Pedimento key"
|
||||
)
|
||||
regime: str = Field(..., max_length=3, description="Regime")
|
||||
|
||||
pedimento_code: str = Field(..., max_length=2, description="Pedimento key")
|
||||
regime: str = Field(..., max_length=3, description="Regime")
|
||||
|
||||
pedimento_dates: Optional[PedimentoDatesCreate] = None
|
||||
pedimento_decrementables: Optional[PedimentoDecrementablesCreate] = None
|
||||
pedimento_incrementables: Optional[PedimentoIncrementablesCreate] = None
|
||||
pedimento_indexes: Optional[PedimentoIndexesCreate] = None
|
||||
pedimento_validation: Optional[PedimentoValidationCreate] = None
|
||||
pedimento_customs_offices: Optional[PedimentoCustomsOfficesCreate] = None
|
||||
pedimento_validation: Optional[PedimentoValidationCreate] = None
|
||||
pedimento_customs_offices: Optional[PedimentoCustomsOfficesCreate] = None
|
||||
pedimento_payments: Optional[PedimentoPaymentsCreate] = None
|
||||
pedimento_rectification_destination: Optional[PedimentoRectificationDestinationCreate] = None
|
||||
pedimento_rectification_destination: Optional[
|
||||
PedimentoRectificationDestinationCreate
|
||||
] = None
|
||||
pedimento_rectification_origin: Optional[PedimentoRectificationOriginCreate] = None
|
||||
pedimento_transport_means: Optional[PedimentoTransportMeansCreate] = None
|
||||
pedimento_transport_means: Optional[PedimentoTransportMeansCreate] = None
|
||||
pedimento_config_additional: Optional[PedimentoConfigAdditionalCreate] = None
|
||||
pedimento_config_calculations: Optional[PedimentoConfigCalculationsCreate] = None
|
||||
pedimento_config_parameters: Optional[PedimentoConfigParametersCreate] = None
|
||||
pedimento_config_surcharges: Optional[PedimentoConfigSurchargesCreate] = None
|
||||
pedimento_config_update_rectification: Optional[PedimentoConfigUpdateRectificationCreate] = None
|
||||
pedimento_config_update_rectification: Optional[
|
||||
PedimentoConfigUpdateRectificationCreate
|
||||
] = None
|
||||
pedimento_config_updates: Optional[PedimentoConfigUpdatesCreate] = None
|
||||
pedimento_packages: Optional[PedimentoPackagesCreate] = None
|
||||
pedimento_transport_carriers: Optional[list[PedimentoTransportCarrierCreate]] = None
|
||||
@@ -107,6 +139,7 @@ class PedimentosCreate(PedimentosBase):
|
||||
pedimento_seals: Optional[list[PedimentoSealCreate]] = None
|
||||
pedimento_containers: Optional[list[PedimentoContainerCreate]] = None
|
||||
|
||||
|
||||
class PedimentosUpdate(BaseModel):
|
||||
"""Schema for updating a Pedimento"""
|
||||
|
||||
@@ -115,7 +148,7 @@ class PedimentosUpdate(BaseModel):
|
||||
license: Optional[str] = Field(None, max_length=4)
|
||||
pedimento_number: Optional[str] = Field(None, max_length=7)
|
||||
client_id: Optional[int] = None
|
||||
operation_type: Optional[int] = None
|
||||
operation_type: Optional[str] = Field(None, max_length=3)
|
||||
pedimento_type: Optional[str] = Field(None, max_length=20)
|
||||
pedimento_code: Optional[str] = Field(None, max_length=2)
|
||||
regime: Optional[str] = Field(None, max_length=3)
|
||||
@@ -125,23 +158,27 @@ class PedimentosUpdate(BaseModel):
|
||||
gross_weight: Optional[Decimal] = None
|
||||
exchange_rate: Optional[Decimal] = None
|
||||
observations: Optional[str] = None
|
||||
|
||||
|
||||
# Sub-resources
|
||||
pedimento_dates: Optional[PedimentoDatesCreate] = None
|
||||
pedimento_decrementables: Optional[PedimentoDecrementablesCreate] = None
|
||||
pedimento_incrementables: Optional[PedimentoIncrementablesCreate] = None
|
||||
pedimento_indexes: Optional[PedimentoIndexesCreate] = None
|
||||
pedimento_validation: Optional[PedimentoValidationCreate] = None
|
||||
pedimento_customs_offices: Optional[PedimentoCustomsOfficesCreate] = None
|
||||
pedimento_validation: Optional[PedimentoValidationCreate] = None
|
||||
pedimento_customs_offices: Optional[PedimentoCustomsOfficesCreate] = None
|
||||
pedimento_payments: Optional[PedimentoPaymentsCreate] = None
|
||||
pedimento_rectification_destination: Optional[PedimentoRectificationDestinationCreate] = None
|
||||
pedimento_rectification_destination: Optional[
|
||||
PedimentoRectificationDestinationCreate
|
||||
] = None
|
||||
pedimento_rectification_origin: Optional[PedimentoRectificationOriginCreate] = None
|
||||
pedimento_transport_means: Optional[PedimentoTransportMeansCreate] = None
|
||||
pedimento_transport_means: Optional[PedimentoTransportMeansCreate] = None
|
||||
pedimento_config_additional: Optional[PedimentoConfigAdditionalCreate] = None
|
||||
pedimento_config_calculations: Optional[PedimentoConfigCalculationsCreate] = None
|
||||
pedimento_config_parameters: Optional[PedimentoConfigParametersCreate] = None
|
||||
pedimento_config_surcharges: Optional[PedimentoConfigSurchargesCreate] = None
|
||||
pedimento_config_update_rectification: Optional[PedimentoConfigUpdateRectificationCreate] = None
|
||||
pedimento_config_update_rectification: Optional[
|
||||
PedimentoConfigUpdateRectificationCreate
|
||||
] = None
|
||||
pedimento_config_updates: Optional[PedimentoConfigUpdatesCreate] = None
|
||||
pedimento_packages: Optional[PedimentoPackagesCreate] = None
|
||||
pedimento_transport_carriers: Optional[list[PedimentoTransportCarrierCreate]] = None
|
||||
@@ -157,22 +194,28 @@ class PedimentosResponse(PedimentosBase):
|
||||
id: int
|
||||
tenant_id: int
|
||||
created_at: datetime
|
||||
|
||||
|
||||
pedimento_dates: Optional[PedimentoDatesResponse] = None
|
||||
pedimento_decrementables: Optional[PedimentoDecrementablesResponse] = None
|
||||
pedimento_incrementables: Optional[PedimentoIncrementablesResponse] = None
|
||||
pedimento_indexes: Optional[PedimentoIndexesResponse] = None
|
||||
pedimento_validation: Optional[PedimentoValidationResponse] = None
|
||||
pedimento_customs_offices: Optional[PedimentoCustomsOfficesResponse] = None
|
||||
pedimento_validation: Optional[PedimentoValidationResponse] = None
|
||||
pedimento_customs_offices: Optional[PedimentoCustomsOfficesResponse] = None
|
||||
pedimento_payments: Optional[PedimentoPaymentsResponse] = None
|
||||
pedimento_rectification_destination: Optional[PedimentoRectificationDestinationResponse] = None
|
||||
pedimento_rectification_origin: Optional[PedimentoRectificationOriginResponse] = None
|
||||
pedimento_transport_means: Optional[PedimentoTransportMeansResponse] = None
|
||||
pedimento_rectification_destination: Optional[
|
||||
PedimentoRectificationDestinationResponse
|
||||
] = None
|
||||
pedimento_rectification_origin: Optional[PedimentoRectificationOriginResponse] = (
|
||||
None
|
||||
)
|
||||
pedimento_transport_means: Optional[PedimentoTransportMeansResponse] = None
|
||||
pedimento_config_additional: Optional[PedimentoConfigAdditionalResponse] = None
|
||||
pedimento_config_calculations: Optional[PedimentoConfigCalculationsResponse] = None
|
||||
pedimento_config_parameters: Optional[PedimentoConfigParametersResponse] = None
|
||||
pedimento_config_surcharges: Optional[PedimentoConfigSurchargesResponse] = None
|
||||
pedimento_config_update_rectification: Optional[PedimentoConfigUpdateRectificationResponse] = None
|
||||
pedimento_config_update_rectification: Optional[
|
||||
PedimentoConfigUpdateRectificationResponse
|
||||
] = None
|
||||
pedimento_config_updates: Optional[PedimentoConfigUpdatesResponse] = None
|
||||
pedimento_packages: Optional[PedimentoPackagesResponse] = None
|
||||
pedimento_transport_carriers: Optional[list[PedimentoTransportCarrierResponse]] = None
|
||||
|
||||
@@ -42,7 +42,7 @@ class PedimentoDates(Base, TenantScopedMixin, TimestampMixin):
|
||||
id: Mapped[int] = mapped_column(Integer)
|
||||
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
|
||||
entry_date: Mapped[Optional[datetime]] = mapped_column(DateTime)
|
||||
entry_date: Mapped[datetime] = mapped_column(DateTime)
|
||||
pedimento_date: Mapped[Optional[datetime]] = mapped_column(DateTime)
|
||||
payment_date: Mapped[Optional[datetime]] = mapped_column(DateTime)
|
||||
rectification_payment_date: Mapped[Optional[datetime]] = mapped_column(DateTime)
|
||||
@@ -51,7 +51,7 @@ class PedimentoDates(Base, TenantScopedMixin, TimestampMixin):
|
||||
eucan_date: Mapped[Optional[datetime]] = mapped_column(DateTime)
|
||||
original_date: Mapped[Optional[datetime]] = mapped_column(DateTime)
|
||||
start_date: Mapped[Optional[datetime]] = mapped_column(DateTime)
|
||||
end_date: Mapped[Optional[datetime]] = mapped_column(DateTime)
|
||||
end_date: Mapped[datetime] = mapped_column(DateTime)
|
||||
|
||||
capture_time: Mapped[datetime_time] = mapped_column(Time)
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from decimal import Decimal
|
||||
from enum import Enum
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from api.v1.common.base_models import TenantScopedMixin, TimestampMixin
|
||||
@@ -80,6 +81,17 @@ if TYPE_CHECKING:
|
||||
)
|
||||
|
||||
|
||||
class PedimentoType(str, Enum):
|
||||
NORMAL = "normal"
|
||||
CONSOLIDATED = "consolidated"
|
||||
COMPLEMENTARY = "complementary"
|
||||
AUTOMOBILE = "automobile"
|
||||
|
||||
class OperationType(str, Enum):
|
||||
IMP = "imp" # Importación
|
||||
EXP = "exp" # Exportación
|
||||
|
||||
|
||||
class Pedimentos(Base, TenantScopedMixin, TimestampMixin):
|
||||
__tablename__ = "pedimentos"
|
||||
__table_args__ = (
|
||||
@@ -117,8 +129,8 @@ class Pedimentos(Base, TenantScopedMixin, TimestampMixin):
|
||||
license: Mapped[str] = mapped_column(String(4))
|
||||
pedimento_number: Mapped[str] = mapped_column(String(7))
|
||||
client_id: Mapped[int] = mapped_column(Integer)
|
||||
operation_type: Mapped[int] = mapped_column(Integer)
|
||||
pedimento_type: Mapped[str] = mapped_column(String(20))
|
||||
operation_type: Mapped[OperationType] = mapped_column(String(3))
|
||||
pedimento_type: Mapped[PedimentoType] = mapped_column(String(20))
|
||||
pedimento_code: Mapped[str] = mapped_column(String(2))
|
||||
regime: Mapped[str] = mapped_column(String(3))
|
||||
status: Mapped[Optional[str]] = mapped_column(String(30))
|
||||
@@ -129,61 +141,106 @@ class Pedimentos(Base, TenantScopedMixin, TimestampMixin):
|
||||
observations: Mapped[Optional[str]] = mapped_column(Text)
|
||||
|
||||
pedimento_config_additional: Mapped["PedimentoConfigAdditional"] = relationship(
|
||||
"PedimentoConfigAdditional", uselist=False, back_populates="pedimento", cascade="all, delete-orphan"
|
||||
"PedimentoConfigAdditional",
|
||||
uselist=False,
|
||||
back_populates="pedimento",
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
pedimento_config_calculations: Mapped["PedimentoConfigCalculations"] = relationship(
|
||||
"PedimentoConfigCalculations", uselist=False, back_populates="pedimento", cascade="all, delete-orphan"
|
||||
"PedimentoConfigCalculations",
|
||||
uselist=False,
|
||||
back_populates="pedimento",
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
pedimento_config_parameters: Mapped["PedimentoConfigParameters"] = relationship(
|
||||
"PedimentoConfigParameters", uselist=False, back_populates="pedimento", cascade="all, delete-orphan"
|
||||
"PedimentoConfigParameters",
|
||||
uselist=False,
|
||||
back_populates="pedimento",
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
pedimento_config_surcharges: Mapped["PedimentoConfigSurcharges"] = relationship(
|
||||
"PedimentoConfigSurcharges", uselist=False, back_populates="pedimento", cascade="all, delete-orphan"
|
||||
"PedimentoConfigSurcharges",
|
||||
uselist=False,
|
||||
back_populates="pedimento",
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
pedimento_config_update_rectification: Mapped[
|
||||
"PedimentoConfigUpdateRectification"
|
||||
] = relationship(
|
||||
"PedimentoConfigUpdateRectification", uselist=False, back_populates="pedimento", cascade="all, delete-orphan"
|
||||
"PedimentoConfigUpdateRectification",
|
||||
uselist=False,
|
||||
back_populates="pedimento",
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
pedimento_config_updates: Mapped["PedimentoConfigUpdates"] = relationship(
|
||||
"PedimentoConfigUpdates", uselist=False, back_populates="pedimento", cascade="all, delete-orphan"
|
||||
"PedimentoConfigUpdates",
|
||||
uselist=False,
|
||||
back_populates="pedimento",
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
pedimento_customs_offices: Mapped["PedimentoCustomsOffices"] = relationship(
|
||||
"PedimentoCustomsOffices", uselist=False, back_populates="pedimento", cascade="all, delete-orphan"
|
||||
"PedimentoCustomsOffices",
|
||||
uselist=False,
|
||||
back_populates="pedimento",
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
pedimento_dates: Mapped["PedimentoDates"] = relationship(
|
||||
"PedimentoDates", uselist=False, back_populates="pedimento", cascade="all, delete-orphan"
|
||||
"PedimentoDates",
|
||||
uselist=False,
|
||||
back_populates="pedimento",
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
pedimento_decrementables: Mapped["PedimentoDecrementables"] = relationship(
|
||||
"PedimentoDecrementables", uselist=False, back_populates="pedimento", cascade="all, delete-orphan"
|
||||
"PedimentoDecrementables",
|
||||
uselist=False,
|
||||
back_populates="pedimento",
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
pedimento_incrementables: Mapped["PedimentoIncrementables"] = relationship(
|
||||
"PedimentoIncrementables", uselist=False, back_populates="pedimento", cascade="all, delete-orphan"
|
||||
"PedimentoIncrementables",
|
||||
uselist=False,
|
||||
back_populates="pedimento",
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
pedimento_indexes: Mapped["PedimentoIndexes"] = relationship(
|
||||
"PedimentoIndexes", uselist=False, back_populates="pedimento", cascade="all, delete-orphan"
|
||||
"PedimentoIndexes",
|
||||
uselist=False,
|
||||
back_populates="pedimento",
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
pedimento_payments: Mapped["PedimentoPayments"] = relationship(
|
||||
"PedimentoPayments", uselist=False, back_populates="pedimento", cascade="all, delete-orphan"
|
||||
"PedimentoPayments",
|
||||
uselist=False,
|
||||
back_populates="pedimento",
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
pedimento_rectification_destination: Mapped["PedimentoRectificationDestination"] = (
|
||||
relationship(
|
||||
"PedimentoRectificationDestination",
|
||||
uselist=False,
|
||||
back_populates="pedimento",
|
||||
cascade="all, delete-orphan"
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
)
|
||||
pedimento_rectification_origin: Mapped["PedimentoRectificationOrigin"] = (
|
||||
relationship(
|
||||
"PedimentoRectificationOrigin", uselist=False, back_populates="pedimento", cascade="all, delete-orphan"
|
||||
"PedimentoRectificationOrigin",
|
||||
uselist=False,
|
||||
back_populates="pedimento",
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
)
|
||||
pedimento_transport_means: Mapped["PedimentoTransportMeans"] = relationship(
|
||||
"PedimentoTransportMeans", uselist=False, back_populates="pedimento", cascade="all, delete-orphan"
|
||||
"PedimentoTransportMeans",
|
||||
uselist=False,
|
||||
back_populates="pedimento",
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
pedimento_validation: Mapped["PedimentoValidation"] = relationship(
|
||||
"PedimentoValidation", uselist=False, back_populates="pedimento", cascade="all, delete-orphan"
|
||||
"PedimentoValidation",
|
||||
uselist=False,
|
||||
back_populates="pedimento",
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
pedimento_packages: Mapped["PedimentoPackages"] = relationship(
|
||||
"PedimentoPackages", uselist=False, back_populates="pedimento", cascade="all, delete-orphan"
|
||||
|
||||
@@ -55,7 +55,7 @@ seed = [
|
||||
("LTT", "LITAS", "LITUANIA"),
|
||||
("LYD", "DINAR", "LIBIA"),
|
||||
("MAD", "DIRHAM", "MARRUECOS"),
|
||||
("MXP", "PESO", "MEXICO"),
|
||||
("MXN", "PESO", "MEXICO"),
|
||||
("MYR", "RINGGIT", "MALASIA"),
|
||||
("NGN", "NAIRA", "NIGERIA (FED)"),
|
||||
("NIC", "CORDOBA", "NICARAGUA"),
|
||||
|
||||
Reference in New Issue
Block a user