Update Docker Compose files for production environment and enhance unit of measure handling
- Changed the default environment variable from 'development' to 'production' in docker-compose.prod.yml and docker-compose.yml. - Added SITAR API credentials to the environment variables in both Docker Compose files. - Updated the seed data for customs units of measure to include a76_unit_code. - Refactored the UnitOfMeasureCustoms model and related DTOs to replace scaii_unit_code with a76_unit_code for consistency. - Adjusted frontend components to reflect the updated unit of measure structure and ensure proper handling of the new a76_unit_code field.
This commit is contained in:
@@ -23,7 +23,7 @@ class UnitOfMeasureAmericanBase(BaseModel):
|
||||
class UnitOfMeasureCustomsBase(BaseModel):
|
||||
code: str = Field(..., max_length=10, description="Customs Code")
|
||||
description: Optional[str] = Field(None, max_length=50)
|
||||
scaii_unit_code: Optional[str] = Field(None, max_length=5)
|
||||
a76_unit_code: Optional[str] = Field(None, max_length=5, description="Unidad SCAII")
|
||||
|
||||
|
||||
class UnitOfMeasureBase(BaseModel):
|
||||
@@ -86,7 +86,7 @@ class UnitOfMeasureAmericanUpdate(BaseModel):
|
||||
class UnitOfMeasureCustomsUpdate(BaseModel):
|
||||
code: Optional[str] = Field(None, max_length=10)
|
||||
description: Optional[str] = Field(None, max_length=50)
|
||||
scaii_unit_code: Optional[str] = Field(None, max_length=5)
|
||||
a76_unit_code: Optional[str] = Field(None, max_length=5)
|
||||
|
||||
|
||||
class UnitOfMeasureUpdate(BaseModel):
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
seed = [
|
||||
("1", "Kilo"),
|
||||
("2", "Gramo"),
|
||||
("3", "Metro Lineal"),
|
||||
("4", "Metro Cuadrado"),
|
||||
("5", "Metro Cubico"),
|
||||
("6", "Pieza"),
|
||||
("7", "Cabeza"),
|
||||
("8", "Litro"),
|
||||
("9", "Par"),
|
||||
("10", "Kilowatt"),
|
||||
("11", "Millar"),
|
||||
("12", "Juego"),
|
||||
("13", "Kilowatt/Hora"),
|
||||
("14", "Tonelada"),
|
||||
("15", "Barril"),
|
||||
("16", "Gramo Neto"),
|
||||
("17", "Decenas"),
|
||||
("18", "Cientos"),
|
||||
("19", "Decenas"),
|
||||
("20", "Caja"),
|
||||
("21", "Botella"),
|
||||
("22", "Carat"),
|
||||
("1", "Kilo", "KGS"),
|
||||
("2", "Gramo", "GR"),
|
||||
("3", "Metro Lineal", "MT"),
|
||||
("4", "Metro Cuadrado", "M2"),
|
||||
("5", "Metro Cubico", "M3"),
|
||||
("6", "Pieza", "PZA"),
|
||||
("7", "Cabeza", "PZA"),
|
||||
("8", "Litro", "LT"),
|
||||
("9", "Par", "PAR "),
|
||||
("10", "Kilowatt", ""),
|
||||
("11", "Millar", "MILLR"),
|
||||
("12", "Juego", "JGO"),
|
||||
("13", "Kilowatt/Hora", ""),
|
||||
("14", "Tonelada", "TON"),
|
||||
("15", "Barril", "BARR"),
|
||||
("16", "Gramo Neto", ""),
|
||||
("17", "Decenas", "DEC"),
|
||||
("18", "Cientos", "CIEN"),
|
||||
("19", "Decenas", "DOCE"),
|
||||
("20", "Caja", "CAJA"),
|
||||
("21", "Botella", "PZA"),
|
||||
("22", "Carat", "CARAT"),
|
||||
]
|
||||
@@ -7,7 +7,7 @@ from core.exceptions import ErrorCollector
|
||||
|
||||
def pre_validators(db: Session, invoice: InvoiceHeader, tenant_id: str, company_id: str, errors: ErrorCollector):
|
||||
if invoice.status == InvoiceStatus.PROCESSED:
|
||||
errors.add(
|
||||
errors.add_error(
|
||||
"status",
|
||||
"La factura ya fue procesada y no puede ser exportada",
|
||||
solution=["Verifique el estatus de la factura antes de intentar exportarla"],
|
||||
@@ -79,7 +79,7 @@ def pre_validators(db: Session, invoice: InvoiceHeader, tenant_id: str, company_
|
||||
LineItem.company_id == company_id,
|
||||
).all()
|
||||
|
||||
fractions = {line.fraction for line in lines if line.fraction}
|
||||
fractions = {line.customs.fraction for line in lines if line.customs.fraction}
|
||||
if fractions:
|
||||
warned_fractions = {
|
||||
row.fraction
|
||||
@@ -88,7 +88,7 @@ def pre_validators(db: Session, invoice: InvoiceHeader, tenant_id: str, company_
|
||||
.all()
|
||||
}
|
||||
for line in lines:
|
||||
if line.fraction in warned_fractions:
|
||||
if line.customs.fraction in warned_fractions:
|
||||
errors.add_warning(
|
||||
field="fraction",
|
||||
message="Advertencia: Esta mercancía, sólo podrá entrar al territorio nacional por las aduanas del país, de lunes a sábado de 8:00 a 13:00 hrs. Ley 10, 18, LIGIE 1, Capítulo 87, RGCE 4.5.31., Anexo 4.",
|
||||
|
||||
@@ -1,30 +1,65 @@
|
||||
|
||||
from typing import List
|
||||
|
||||
from sqlalchemy import func, or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from api.v1.modules.a76.classes.models import Class
|
||||
from api.v1.modules.a76.general_catalogs.fractions.historical_tariff_fractions.models import HistoricalTariffFraction
|
||||
from api.v1.modules.a76.general_catalogs.fractions.historical_tariff_fractions.models import (
|
||||
HistoricalTariffFraction,
|
||||
)
|
||||
from api.v1.modules.a76.general_catalogs.fractions.tariff_fractions.models import TariffFraction
|
||||
from api.v1.modules.a76.invoices.models import InvoiceHeader
|
||||
from api.v1.modules.a76.items.models import LineItem
|
||||
from core.exceptions import ErrorCollector
|
||||
|
||||
|
||||
def _fraction_exists_in_catalog(db: Session, fraction_code: str) -> bool:
|
||||
"""Returns True if the fraction exists in TariffFraction (SFracciones) or
|
||||
HistoricalTariffFraction (GFraccionesHistorico).
|
||||
def _fraction_exists_via_sitar(fraction_code: str) -> bool:
|
||||
"""Returns True if the fraction exists in SITAR (fracciones o fracciones-anteriores).
|
||||
|
||||
Fraction format: first 8 chars = base fraction, chars 9-10 = NICO/country (optional).
|
||||
Falls back to False if SITAR is not configured or request fails.
|
||||
"""
|
||||
if not fraction_code:
|
||||
return True
|
||||
|
||||
base_frac = fraction_code[:8].strip()
|
||||
nico = fraction_code[8:10].strip() if len(fraction_code) > 8 else ""
|
||||
|
||||
try:
|
||||
from api.v1.modules.sitar.fracciones.service import FraccionesService
|
||||
from api.v1.modules.sitar.fracciones_anteriores.service import (
|
||||
FraccionesAnterioresService,
|
||||
)
|
||||
|
||||
# 1. Buscar en fracciones arancelarias (SITAR)
|
||||
results = FraccionesService.search_sync(
|
||||
fraccion=base_frac,
|
||||
nico=nico if nico else None,
|
||||
limit=1,
|
||||
)
|
||||
if results:
|
||||
return True
|
||||
|
||||
# 2. Buscar en fracciones anteriores / histórico (SITAR)
|
||||
hist_results = FraccionesAnterioresService.search_sync(
|
||||
fraccion_anterior=base_frac,
|
||||
limit=1,
|
||||
)
|
||||
return len(hist_results) > 0
|
||||
|
||||
except (ValueError, Exception):
|
||||
# SITAR no configurado o error de red: se usa fallback a BD local
|
||||
return False
|
||||
|
||||
|
||||
def _fraction_exists_in_local_db(db: Session, fraction_code: str) -> bool:
|
||||
"""Fallback: valida contra TariffFraction e HistoricalTariffFraction locales."""
|
||||
if not fraction_code:
|
||||
return True
|
||||
|
||||
base_frac = fraction_code[:8]
|
||||
nico = fraction_code[8:10] if len(fraction_code) > 8 else ""
|
||||
|
||||
# Check SFracciones (TariffFraction)
|
||||
tariff_q = db.query(TariffFraction).filter(
|
||||
func.left(TariffFraction.code, 8) == base_frac
|
||||
)
|
||||
@@ -37,7 +72,6 @@ def _fraction_exists_in_catalog(db: Session, fraction_code: str) -> bool:
|
||||
if tariff_q.first() is not None:
|
||||
return True
|
||||
|
||||
# Check GFraccionesHistorico (HistoricalTariffFraction)
|
||||
hist_q = db.query(HistoricalTariffFraction).filter(
|
||||
HistoricalTariffFraction.historical_fraction == base_frac
|
||||
)
|
||||
@@ -53,6 +87,17 @@ def _fraction_exists_in_catalog(db: Session, fraction_code: str) -> bool:
|
||||
return hist_q.first() is not None
|
||||
|
||||
|
||||
def _fraction_exists_in_catalog(db: Session, fraction_code: str) -> bool:
|
||||
"""Returns True if the fraction exists in SITAR (fracciones) or fallback a BD local.
|
||||
|
||||
Usa las funciones de SITAR de fracciones como fuente principal.
|
||||
Si SITAR no está configurado o falla, valida contra TariffFraction e HistoricalTariffFraction.
|
||||
"""
|
||||
if _fraction_exists_via_sitar(fraction_code):
|
||||
return True
|
||||
return _fraction_exists_in_local_db(db, fraction_code)
|
||||
|
||||
|
||||
def _validate_line_fraction(
|
||||
db: Session, line: LineItem, errors: ErrorCollector
|
||||
) -> None:
|
||||
|
||||
@@ -4,6 +4,7 @@ from decimal import Decimal
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
from .models import (
|
||||
DestinationOriginCove,
|
||||
InvoiceStatus,
|
||||
OperationType,
|
||||
Currency,
|
||||
TransportType,
|
||||
@@ -56,7 +57,21 @@ class InvoiceHeaderBase(BaseModel):
|
||||
)
|
||||
invoice_date: date = Field(..., description="Invoice date")
|
||||
emission_date: Optional[date] = Field(None, description="Emission date")
|
||||
status: bool = Field(False, description="Status")
|
||||
status: Optional[InvoiceStatus] = Field(None, description="Status: pending, processed, reversed")
|
||||
|
||||
@field_validator("status", mode="before")
|
||||
@classmethod
|
||||
def normalize_status(cls, v):
|
||||
"""Coerce legacy boolean strings ('false'/'true') to InvoiceStatus."""
|
||||
if v is None:
|
||||
return v
|
||||
if isinstance(v, str):
|
||||
v_lower = v.lower()
|
||||
if v_lower == "false":
|
||||
return InvoiceStatus.PENDING.value
|
||||
if v_lower == "true":
|
||||
return InvoiceStatus.PROCESSED.value
|
||||
return v
|
||||
processed_date: Optional[datetime] = Field(None, description="Update date")
|
||||
who_processed: Optional[str] = Field(None, max_length=20, description="Who processed")
|
||||
capture_user: Optional[str] = Field(None, max_length=20, description="Capture user")
|
||||
|
||||
Reference in New Issue
Block a user