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:
@@ -364,12 +364,12 @@ def upgrade() -> None:
|
||||
f"INSERT INTO a76.unit_of_measure_american (code, description) VALUES {val_ame} ON CONFLICT ON CONSTRAINT uq_uom_american_code DO NOTHING;"
|
||||
)
|
||||
|
||||
# ADUA (Customs)
|
||||
# ADUA (Customs) - seed_adua: (code, description, a76_unit_code)
|
||||
val_adua = ", ".join(
|
||||
[f"({format_value(c)}, {format_value(d)})" for c, d in adua_seed]
|
||||
[f"({format_value(code)}, {format_value(desc)}, {format_value(a76_code)})" for code, desc, a76_code in adua_seed]
|
||||
)
|
||||
op.execute(
|
||||
f"INSERT INTO a76.unit_of_measure_customs (code, description) VALUES {val_adua} ON CONFLICT ON CONSTRAINT uq_uom_customs_code DO NOTHING;"
|
||||
f"INSERT INTO a76.unit_of_measure_customs (code, description, a76_unit_code) VALUES {val_adua} ON CONFLICT ON CONSTRAINT uq_uom_customs_code DO NOTHING;"
|
||||
)
|
||||
|
||||
# Recolectar códigos adicionales que faltan en los catálogos
|
||||
@@ -378,7 +378,7 @@ def upgrade() -> None:
|
||||
additional_ace = set()
|
||||
additional_oma = set()
|
||||
|
||||
existing_customs = {c for c, d in adua_seed}
|
||||
existing_customs = {code for code, desc, a76_code in adua_seed}
|
||||
existing_american = {c for c, d in ame_seed}
|
||||
existing_ace = {c for c, d in ace_seed}
|
||||
existing_oma = {c for c, d in oma_seed}
|
||||
@@ -396,7 +396,7 @@ def upgrade() -> None:
|
||||
# Insertar códigos adicionales
|
||||
if additional_customs:
|
||||
val_add_customs = ", ".join(
|
||||
[f"({format_value(c)}, {format_value(d)})" for c, d in additional_customs]
|
||||
[f"({format_value(c)}, {format_value(d)}, {format_value(s)})" for c, d, s in additional_customs]
|
||||
)
|
||||
op.execute(
|
||||
f"INSERT INTO a76.unit_of_measure_customs (code, description, a76_unit_code) VALUES {val_add_customs} ON CONFLICT ON CONSTRAINT uq_uom_customs_code DO NOTHING;"
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Fracciones Service"""
|
||||
|
||||
import asyncio
|
||||
from typing import Optional, List
|
||||
from ..common import SitarAPIBaseService
|
||||
from .schemas import FraccionesResponse
|
||||
@@ -44,3 +45,26 @@ class FraccionesService(SitarAPIBaseService):
|
||||
"""Get single Fraccion record by SYSID"""
|
||||
data = await self._make_request("GET", f"/api/v1/fracciones/{sysid}")
|
||||
return FraccionesResponse(**data)
|
||||
|
||||
@classmethod
|
||||
def search_sync(
|
||||
cls,
|
||||
fraccion: Optional[str] = None,
|
||||
nico: Optional[str] = None,
|
||||
description: Optional[str] = None,
|
||||
nivel: Optional[int] = None,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
) -> List[FraccionesResponse]:
|
||||
"""Search Mexican tariff fractions (sync wrapper for use in Celery/sync context)."""
|
||||
service = cls.get_instance()
|
||||
return asyncio.run(
|
||||
service.search(
|
||||
fraccion=fraccion,
|
||||
nico=nico,
|
||||
description=description,
|
||||
nivel=nivel,
|
||||
skip=skip,
|
||||
limit=limit,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""FraccionesAnteriores Service"""
|
||||
|
||||
import asyncio
|
||||
from typing import Optional, List
|
||||
from ..common import SitarAPIBaseService
|
||||
from .schemas import FraccionesAnterioresResponse
|
||||
@@ -34,3 +35,22 @@ class FraccionesAnterioresService(SitarAPIBaseService):
|
||||
async def get_by_id(self, sysid: int) -> FraccionesAnterioresResponse:
|
||||
data = await self._make_request("GET", f"/api/v1/fracciones-anteriores/{sysid}")
|
||||
return FraccionesAnterioresResponse(**data)
|
||||
|
||||
@classmethod
|
||||
def search_sync(
|
||||
cls,
|
||||
fraccion_actual: Optional[str] = None,
|
||||
fraccion_anterior: Optional[str] = None,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
) -> List[FraccionesAnterioresResponse]:
|
||||
"""Search historical fractions (sync wrapper for use in Celery/sync context)."""
|
||||
service = cls.get_instance()
|
||||
return asyncio.run(
|
||||
service.search(
|
||||
fraccion_actual=fraccion_actual,
|
||||
fraccion_anterior=fraccion_anterior,
|
||||
skip=skip,
|
||||
limit=limit,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -7,6 +7,10 @@ from api.v1.modules.public.reference_data.pedimento_regimens.models import Regim
|
||||
from api.v1.modules.public.reference_data.code_pedimento_regimens.models import (
|
||||
CodePedimentoRegimen,
|
||||
)
|
||||
# InvoiceType debe cargarse antes de InvoiceHeader (FK invoice_header.invoice_type -> public.invoice_types.key)
|
||||
from api.v1.modules.public.reference_data.invoice_types.models import InvoiceType # noqa: F401
|
||||
# CustomsSection debe cargarse antes de InvoiceComplianceMx (FK invoice_compliance_mx.aduana -> public.customs_sections.customs_code)
|
||||
from api.v1.modules.public.reference_data.customs_sections.models import CustomsSection # noqa: F401
|
||||
|
||||
# Import models in correct order for SQLAlchemy relationship resolution
|
||||
# CRITICAL: FaLineItem must be imported BEFORE LineItem
|
||||
|
||||
@@ -158,7 +158,7 @@ services:
|
||||
container_name: anexo76-backend
|
||||
environment:
|
||||
- DEBUG=${DEBUG:-True}
|
||||
- ENVIRONMENT=${ENVIRONMENT:-development}
|
||||
- ENVIRONMENT=${ENVIRONMENT:-production}
|
||||
- PYTHONUNBUFFERED=1
|
||||
- PYTHONDONTWRITEBYTECODE=1
|
||||
- CORE_DB_HOST=${CORE_DB_HOST:-postgres-a76}
|
||||
@@ -227,6 +227,9 @@ services:
|
||||
- CORE_DB_NAME=${CORE_DB_NAME:-anexo76_core}
|
||||
- CORE_DB_USER=${CORE_DB_USER:-postgres}
|
||||
- CORE_DB_PASSWORD=${POSTGRES_APP_PASSWORD:-postgres}
|
||||
- SITAR_API_URL=${SITAR_API_URL}
|
||||
- SITAR_API_USER=${SITAR_API_USER}
|
||||
- SITAR_API_PASSWORD=${SITAR_API_PASSWORD}
|
||||
depends_on:
|
||||
- backend
|
||||
- valkey
|
||||
@@ -247,7 +250,7 @@ services:
|
||||
image: dev.aduanasoft.com/anexo76/frontend:latest
|
||||
container_name: anexo76-frontend
|
||||
environment:
|
||||
- NODE_ENV=${NODE_ENV:-development}
|
||||
- NODE_ENV=${NODE_ENV:-production}
|
||||
- VITE_API_URL=${VITE_API_URL:-https://anexo76-dev.aduanasoft.com/api}
|
||||
- INTERNAL_API_URL=${INTERNAL_API_URL:-http://backend:8000/api/}
|
||||
- VITE_KEYCLOAK_URL=${VITE_KEYCLOAK_URL:-https://anexo76-dev.aduanasoft.com/kcauth/}
|
||||
|
||||
@@ -290,6 +290,9 @@ services:
|
||||
- KEYCLOAK_CLIENT_ID=${KEYCLOAK_CLIENT_ID:-anexo76-backend}
|
||||
- KEYCLOAK_CLIENT_SECRET=${KEYCLOAK_CLIENT_SECRET:-dev-secret}
|
||||
- VALKEY_URL=redis://valkey:6379/0
|
||||
- SITAR_API_URL=${SITAR_API_URL}
|
||||
- SITAR_API_USER=${SITAR_API_USER}
|
||||
- SITAR_API_PASSWORD=${SITAR_API_PASSWORD}
|
||||
depends_on:
|
||||
- backend
|
||||
- valkey
|
||||
|
||||
@@ -8,6 +8,7 @@ export interface UMCustomsMex {
|
||||
id: number;
|
||||
code: string;
|
||||
description: string | null;
|
||||
a76_unit_code: string | null;
|
||||
created_at: string | null;
|
||||
updated_at: string | null;
|
||||
}
|
||||
@@ -15,11 +16,13 @@ export interface UMCustomsMex {
|
||||
export interface UMCustomsMexCreate {
|
||||
code: string;
|
||||
description?: string | null;
|
||||
a76_unit_code?: string | null;
|
||||
}
|
||||
|
||||
export interface UMCustomsMexUpdate {
|
||||
code?: string;
|
||||
description?: string | null;
|
||||
a76_unit_code?: string | null;
|
||||
}
|
||||
|
||||
export interface UMCustomsMexListResponse {
|
||||
|
||||
@@ -222,7 +222,7 @@ export interface UnitOfMeasureCustoms {
|
||||
id: number;
|
||||
code: string;
|
||||
description: string | null;
|
||||
scaii_unit_code: string | null;
|
||||
a76_unit_code: string | null;
|
||||
created_at: string | null;
|
||||
updated_at: string | null;
|
||||
}
|
||||
@@ -230,13 +230,13 @@ export interface UnitOfMeasureCustoms {
|
||||
export interface UnitOfMeasureCustomsCreate {
|
||||
code: string;
|
||||
description?: string | null;
|
||||
scaii_unit_code?: string | null;
|
||||
a76_unit_code?: string | null;
|
||||
}
|
||||
|
||||
export interface UnitOfMeasureCustomsUpdate {
|
||||
code?: string;
|
||||
description?: string | null;
|
||||
scaii_unit_code?: string | null;
|
||||
a76_unit_code?: string | null;
|
||||
}
|
||||
|
||||
export interface UnitOfMeasureCustomsListResponse {
|
||||
|
||||
@@ -208,7 +208,7 @@ export interface Invoice {
|
||||
invoice_date?: string | null;
|
||||
capture_date: string;
|
||||
emission_date?: string | null;
|
||||
status?: boolean | null;
|
||||
status?: "pending" | "processed" | "reversed" | boolean | null;
|
||||
processed_date?: string | null;
|
||||
who_processed?: string | null;
|
||||
capture_user?: string | null;
|
||||
@@ -266,7 +266,7 @@ export interface CreateInvoiceData {
|
||||
proforma_number?: string | null;
|
||||
invoice_date?: string | null;
|
||||
emission_date?: string | null;
|
||||
status?: boolean | null;
|
||||
status?: "pending" | "processed" | "reversed" | boolean | null;
|
||||
processed_date?: string | null;
|
||||
who_processed?: string | null;
|
||||
capture_user?: string | null;
|
||||
@@ -319,7 +319,7 @@ export interface UpdateInvoiceData {
|
||||
cfdi_uuid?: string | null;
|
||||
path_pdf?: string | null;
|
||||
path_xml?: string | null;
|
||||
status?: boolean | null;
|
||||
status?: "pending" | "processed" | "reversed" | boolean | null;
|
||||
compliance_mx?: Partial<InvoiceComplianceMx> | null;
|
||||
financials?: Partial<InvoiceFinancials> | null;
|
||||
logistics?: Partial<InvoiceLogistics>[] | null;
|
||||
|
||||
@@ -14,8 +14,8 @@ export function createColumns(onSuccess?: () => void): ColumnDef<UnitOfMeasureCu
|
||||
header: "Descripción",
|
||||
},
|
||||
{
|
||||
accessorKey: "scaii_unit_code",
|
||||
header: "Código SCAII",
|
||||
accessorKey: "a76_unit_code",
|
||||
header: "Código A76 / SCAII",
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
let formData = $state({
|
||||
code: '',
|
||||
description: '',
|
||||
scaii_unit_code: ''
|
||||
a76_unit_code: ''
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
@@ -37,10 +37,10 @@
|
||||
formData = {
|
||||
code: unit.code,
|
||||
description: unit.description || '',
|
||||
scaii_unit_code: unit.scaii_unit_code || ''
|
||||
a76_unit_code: unit.a76_unit_code || ''
|
||||
};
|
||||
} else {
|
||||
formData = { code: '', description: '', scaii_unit_code: '' };
|
||||
formData = { code: '', description: '', a76_unit_code: '' };
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -53,15 +53,22 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const data: UnitOfMeasureCustomsCreate | UnitOfMeasureCustomsUpdate = {
|
||||
code: formData.code,
|
||||
description: formData.description || null,
|
||||
scaii_unit_code: formData.scaii_unit_code || null
|
||||
};
|
||||
|
||||
const response = unit
|
||||
? await updateUnitOfMeasureCustoms(unit.id, data, activeCompanyId)
|
||||
: await createUnitOfMeasureCustoms(data, activeCompanyId);
|
||||
? await updateUnitOfMeasureCustoms(unit.id, {
|
||||
code: formData.code,
|
||||
description: formData.description || null,
|
||||
a76_unit_code: formData.a76_unit_code || null
|
||||
} satisfies UnitOfMeasureCustomsUpdate,
|
||||
activeCompanyId
|
||||
)
|
||||
: await createUnitOfMeasureCustoms(
|
||||
{
|
||||
code: formData.code,
|
||||
description: formData.description || null,
|
||||
a76_unit_code: formData.a76_unit_code || null
|
||||
} satisfies UnitOfMeasureCustomsCreate,
|
||||
activeCompanyId
|
||||
);
|
||||
|
||||
if (response.error) {
|
||||
} else {
|
||||
@@ -78,16 +85,16 @@
|
||||
</Dialog.Header>
|
||||
<form onsubmit={handleSubmit} class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="code">Código * (máx. 2 caracteres)</Label>
|
||||
<Input id="code" bind:value={formData.code} required maxlength="2" />
|
||||
<Label for="code">Código * (máx. 10 caracteres)</Label>
|
||||
<Input id="code" bind:value={formData.code} required maxlength={10} />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="description">Descripción (máx. 20 caracteres)</Label>
|
||||
<Input id="description" bind:value={formData.description} maxlength="20" />
|
||||
<Input id="description" bind:value={formData.description} maxlength={20} />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="scaii_unit_code">Código SCAII</Label>
|
||||
<Input id="scaii_unit_code" bind:value={formData.scaii_unit_code} maxlength="5" />
|
||||
<Label for="a76_unit_code">Código A76 / SCAII</Label>
|
||||
<Input id="a76_unit_code" bind:value={formData.a76_unit_code} maxlength={5} />
|
||||
</div>
|
||||
<Dialog.Footer>
|
||||
<Button type="button" variant="outline" onclick={() => (open = false)}>Cancelar</Button>
|
||||
|
||||
@@ -287,7 +287,9 @@ export function createColumns(
|
||||
accessorKey: "status",
|
||||
header: "Actualizado",
|
||||
cell: ({ row }) => {
|
||||
const isprocessed = row.original.status;
|
||||
// status can be 'processed' | 'pending' | 'reversed' (string) or legacy boolean
|
||||
const s = row.original.status;
|
||||
const isprocessed = s === "processed" || s === true;
|
||||
|
||||
const processedSnippet = createRawSnippet<[{ isprocessed?: boolean | null }]>((getprocessed) => {
|
||||
const { isprocessed } = getprocessed();
|
||||
|
||||
@@ -38,41 +38,46 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function pollOnce() {
|
||||
if (!taskId) return;
|
||||
const apiCall = getStatus || invoicesReportsApi.getTaskStatus;
|
||||
const raw = await apiCall(taskId);
|
||||
const response = raw?.data !== undefined ? raw.data : raw;
|
||||
if (raw?.error) {
|
||||
hasError = true;
|
||||
statusMessage = `Error: ${raw.error}`;
|
||||
stopPolling();
|
||||
toast.error(raw.error);
|
||||
return;
|
||||
}
|
||||
if (response?.state === 'PROCESSING' && response.info) {
|
||||
progress = response.info.current || 0;
|
||||
statusMessage = response.info.status || 'Procesando...';
|
||||
} else if (response?.state === 'SUCCESS') {
|
||||
progress = 100;
|
||||
statusMessage = '¡Completado!';
|
||||
isComplete = true;
|
||||
stopPolling();
|
||||
setTimeout(() => onComplete(response.result), 500);
|
||||
} else if (response?.state === 'FAILURE') {
|
||||
hasError = true;
|
||||
const errMsg = response.result ? String(response.result) : 'Error desconocido';
|
||||
statusMessage = `Error: ${errMsg}`;
|
||||
stopPolling();
|
||||
toast.error(`Falló: ${errMsg}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function startPolling() {
|
||||
stopPolling(); // Asegurar limpieza previa
|
||||
await pollOnce(); // Primer poll inmediato para mostrar progreso sin esperar 1s
|
||||
|
||||
pollingInterval = setInterval(async () => {
|
||||
if (!taskId) return;
|
||||
|
||||
if (isComplete || hasError) return;
|
||||
try {
|
||||
const apiCall = getStatus || invoicesReportsApi.getTaskStatus;
|
||||
const response = await apiCall(taskId);
|
||||
|
||||
if (response.state === 'PROCESSING' && response.info) {
|
||||
progress = response.info.current || 0;
|
||||
statusMessage = response.info.status || 'Procesando...';
|
||||
} else if (response.state === 'SUCCESS') {
|
||||
progress = 100;
|
||||
statusMessage = '¡Completado!';
|
||||
isComplete = true;
|
||||
stopPolling();
|
||||
// Pequeña pausa para ver el 100%
|
||||
setTimeout(() => {
|
||||
onComplete(response.result);
|
||||
}, 500);
|
||||
} else if (response.state === 'FAILURE') {
|
||||
hasError = true;
|
||||
// Intenta mostrar el mensaje de error real si viene en 'result'
|
||||
const errMsg = response.result ? String(response.result) : 'Error desconocido';
|
||||
statusMessage = `Error: ${errMsg}`;
|
||||
stopPolling();
|
||||
toast.error(`Falló la generación: ${errMsg}`);
|
||||
console.error('Task failed with result:', response);
|
||||
}
|
||||
await pollOnce();
|
||||
} catch (error) {
|
||||
console.error('Error polling task status:', error);
|
||||
// No detenemos el polling inmediatamente por un error de red transitorio,
|
||||
// pero podríamos contar intentos fallidos si fuera necesario.
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user