From a4cf394447112c17b48029152bd4326693a259dd Mon Sep 17 00:00:00 2001 From: AlexeerCT Date: Sun, 15 Mar 2026 20:52:01 -0600 Subject: [PATCH] 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. --- .../7937209f9718_seed_initial_data.py | 10 +-- .../general_catalogs/units_of_measure/dto.py | 4 +- .../units_of_measure/seed_adua.py | 44 ++++++------- .../imports/process/pre_validators.py | 6 +- .../process/sub_process/review_classes.py | 59 +++++++++++++++--- .../api/v1/modules/a76/invoices/schemas.py | 17 +++++- .../v1/modules/sitar/fracciones/service.py | 24 ++++++++ .../sitar/fracciones_anteriores/service.py | 20 ++++++ backend/core/celery_app.py | 4 ++ docker-compose.prod.yml | 7 ++- docker-compose.yml | 3 + .../a76/general_catalogs/um-customs-mex.ts | 3 + .../a76/general_catalogs/units-of-measure.ts | 6 +- .../src/lib/api/dashboard/a76/invoices.ts | 6 +- .../units_of_measure/customs/columns.ts | 4 +- .../customs/create-edit-dialog.svelte | 39 +++++++----- .../components/dashboard/invoices/columns.ts | 4 +- .../invoices/pdf-progress-dialog.svelte | 61 ++++++++++--------- 18 files changed, 226 insertions(+), 95 deletions(-) diff --git a/backend/alembic/versions/7937209f9718_seed_initial_data.py b/backend/alembic/versions/7937209f9718_seed_initial_data.py index e41ecd80..f83e68bb 100644 --- a/backend/alembic/versions/7937209f9718_seed_initial_data.py +++ b/backend/alembic/versions/7937209f9718_seed_initial_data.py @@ -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;" diff --git a/backend/api/v1/modules/a76/general_catalogs/units_of_measure/dto.py b/backend/api/v1/modules/a76/general_catalogs/units_of_measure/dto.py index ef5997fe..0b593be6 100644 --- a/backend/api/v1/modules/a76/general_catalogs/units_of_measure/dto.py +++ b/backend/api/v1/modules/a76/general_catalogs/units_of_measure/dto.py @@ -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): diff --git a/backend/api/v1/modules/a76/general_catalogs/units_of_measure/seed_adua.py b/backend/api/v1/modules/a76/general_catalogs/units_of_measure/seed_adua.py index d0b91e84..62c67215 100644 --- a/backend/api/v1/modules/a76/general_catalogs/units_of_measure/seed_adua.py +++ b/backend/api/v1/modules/a76/general_catalogs/units_of_measure/seed_adua.py @@ -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"), ] \ No newline at end of file diff --git a/backend/api/v1/modules/a76/invoices/imports/process/pre_validators.py b/backend/api/v1/modules/a76/invoices/imports/process/pre_validators.py index c69039ab..9ab0af50 100644 --- a/backend/api/v1/modules/a76/invoices/imports/process/pre_validators.py +++ b/backend/api/v1/modules/a76/invoices/imports/process/pre_validators.py @@ -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.", diff --git a/backend/api/v1/modules/a76/invoices/imports/process/sub_process/review_classes.py b/backend/api/v1/modules/a76/invoices/imports/process/sub_process/review_classes.py index bdd60853..db009bd6 100644 --- a/backend/api/v1/modules/a76/invoices/imports/process/sub_process/review_classes.py +++ b/backend/api/v1/modules/a76/invoices/imports/process/sub_process/review_classes.py @@ -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: diff --git a/backend/api/v1/modules/a76/invoices/schemas.py b/backend/api/v1/modules/a76/invoices/schemas.py index d1cec36a..f0edde67 100644 --- a/backend/api/v1/modules/a76/invoices/schemas.py +++ b/backend/api/v1/modules/a76/invoices/schemas.py @@ -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") diff --git a/backend/api/v1/modules/sitar/fracciones/service.py b/backend/api/v1/modules/sitar/fracciones/service.py index 640cf969..ada6de61 100644 --- a/backend/api/v1/modules/sitar/fracciones/service.py +++ b/backend/api/v1/modules/sitar/fracciones/service.py @@ -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, + ) + ) diff --git a/backend/api/v1/modules/sitar/fracciones_anteriores/service.py b/backend/api/v1/modules/sitar/fracciones_anteriores/service.py index a0f6d136..1002c805 100644 --- a/backend/api/v1/modules/sitar/fracciones_anteriores/service.py +++ b/backend/api/v1/modules/sitar/fracciones_anteriores/service.py @@ -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, + ) + ) diff --git a/backend/core/celery_app.py b/backend/core/celery_app.py index 0066fbf8..ef6c93e3 100644 --- a/backend/core/celery_app.py +++ b/backend/core/celery_app.py @@ -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 diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 2c828760..938efb43 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -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/} diff --git a/docker-compose.yml b/docker-compose.yml index 3be97b57..6bb012de 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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 diff --git a/frontend/src/lib/api/dashboard/a76/general_catalogs/um-customs-mex.ts b/frontend/src/lib/api/dashboard/a76/general_catalogs/um-customs-mex.ts index 2eef1908..4f6cc20d 100644 --- a/frontend/src/lib/api/dashboard/a76/general_catalogs/um-customs-mex.ts +++ b/frontend/src/lib/api/dashboard/a76/general_catalogs/um-customs-mex.ts @@ -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 { diff --git a/frontend/src/lib/api/dashboard/a76/general_catalogs/units-of-measure.ts b/frontend/src/lib/api/dashboard/a76/general_catalogs/units-of-measure.ts index eb8a2807..44721369 100644 --- a/frontend/src/lib/api/dashboard/a76/general_catalogs/units-of-measure.ts +++ b/frontend/src/lib/api/dashboard/a76/general_catalogs/units-of-measure.ts @@ -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 { diff --git a/frontend/src/lib/api/dashboard/a76/invoices.ts b/frontend/src/lib/api/dashboard/a76/invoices.ts index 73e72294..4cf0a679 100644 --- a/frontend/src/lib/api/dashboard/a76/invoices.ts +++ b/frontend/src/lib/api/dashboard/a76/invoices.ts @@ -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 | null; financials?: Partial | null; logistics?: Partial[] | null; diff --git a/frontend/src/lib/components/dashboard/general_catalogs/units_of_measure/customs/columns.ts b/frontend/src/lib/components/dashboard/general_catalogs/units_of_measure/customs/columns.ts index 6f5d2136..f8106bcc 100644 --- a/frontend/src/lib/components/dashboard/general_catalogs/units_of_measure/customs/columns.ts +++ b/frontend/src/lib/components/dashboard/general_catalogs/units_of_measure/customs/columns.ts @@ -14,8 +14,8 @@ export function createColumns(onSuccess?: () => void): ColumnDef { @@ -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 @@
- - + +
- +
- - + +
diff --git a/frontend/src/lib/components/dashboard/invoices/columns.ts b/frontend/src/lib/components/dashboard/invoices/columns.ts index 0e6af76b..62148a60 100644 --- a/frontend/src/lib/components/dashboard/invoices/columns.ts +++ b/frontend/src/lib/components/dashboard/invoices/columns.ts @@ -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(); diff --git a/frontend/src/lib/components/dashboard/invoices/pdf-progress-dialog.svelte b/frontend/src/lib/components/dashboard/invoices/pdf-progress-dialog.svelte index 813c1f66..68fc0269 100644 --- a/frontend/src/lib/components/dashboard/invoices/pdf-progress-dialog.svelte +++ b/frontend/src/lib/components/dashboard/invoices/pdf-progress-dialog.svelte @@ -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); }