From 07729b970f5c7b03497b6764ca0611ca804a4501 Mon Sep 17 00:00:00 2001 From: Galindo97 Date: Thu, 22 Jan 2026 14:19:22 -0600 Subject: [PATCH 1/3] fix(clients_and_providers): include 'both' type in client/provider filters and update UI indicators --- .../a76/clients_and_providers/routes.py | 9 +++++- .../a76/clients_and_providers/service.py | 12 ++++++-- frontend/messages/en.json | 5 ++++ frontend/messages/es.json | 5 ++++ .../invoices/edit/general-tab-form.svelte | 27 +++++++++++++++-- .../dashboard/pedimentos/columns.ts | 29 +------------------ 6 files changed, 53 insertions(+), 34 deletions(-) diff --git a/backend/api/v1/modules/a76/clients_and_providers/routes.py b/backend/api/v1/modules/a76/clients_and_providers/routes.py index e1db85d7..b1857de0 100644 --- a/backend/api/v1/modules/a76/clients_and_providers/routes.py +++ b/backend/api/v1/modules/a76/clients_and_providers/routes.py @@ -46,7 +46,14 @@ async def get_clients_and_providers( ) if type is not None: - query = query.filter(ClientProvider.client_or_provider == type) + # Include 'both' type when filtering by client or provider + from sqlalchemy import or_ + query = query.filter( + or_( + ClientProvider.client_or_provider == type, + ClientProvider.client_or_provider == ClientOrProviderEnum.BOTH + ) + ) if active is not None: query = query.filter(ClientProvider.is_active == active) diff --git a/backend/api/v1/modules/a76/clients_and_providers/service.py b/backend/api/v1/modules/a76/clients_and_providers/service.py index 5a329673..6bf28427 100644 --- a/backend/api/v1/modules/a76/clients_and_providers/service.py +++ b/backend/api/v1/modules/a76/clients_and_providers/service.py @@ -56,8 +56,12 @@ class ClientProviderService: ) ) if filters.get("client_or_provider"): + from .models import ClientOrProviderEnum query = query.filter( - ClientProvider.client_or_provider == filters["client_or_provider"] + or_( + ClientProvider.client_or_provider == filters["client_or_provider"], + ClientProvider.client_or_provider == ClientOrProviderEnum.BOTH + ) ) if filters.get("status"): enabled = 1 if filters["status"] == "enabled" else 0 @@ -345,8 +349,12 @@ class ClientProviderService: ) if client_or_provider: + from .models import ClientOrProviderEnum query = query.filter( - ClientProvider.client_or_provider == client_or_provider + or_( + ClientProvider.client_or_provider == client_or_provider, + ClientProvider.client_or_provider == ClientOrProviderEnum.BOTH + ) ) if enabled_only: diff --git a/frontend/messages/en.json b/frontend/messages/en.json index 541dac72..793c38e9 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -91,6 +91,11 @@ }, "clients_and_providers": "Clients and Providers", "customs_brokers": "Customs Brokers", + "client_provider_type": { + "client_indicator": "C", + "provider_indicator": "P", + "both_indicator": "B" + }, "nav_user": { "profile": "Profile", "settings": "Settings", diff --git a/frontend/messages/es.json b/frontend/messages/es.json index 9e2a85c5..4c845f07 100644 --- a/frontend/messages/es.json +++ b/frontend/messages/es.json @@ -91,6 +91,11 @@ }, "clients_and_providers": "Clientes y Proveedores", "customs_brokers": "Agentes Aduanales", + "client_provider_type": { + "client_indicator": "C", + "provider_indicator": "P", + "both_indicator": "A" + }, "nav_user": { "profile": "Perfil", "settings": "Configuración" diff --git a/frontend/src/lib/components/dashboard/invoices/edit/general-tab-form.svelte b/frontend/src/lib/components/dashboard/invoices/edit/general-tab-form.svelte index c7875be8..b312246a 100644 --- a/frontend/src/lib/components/dashboard/invoices/edit/general-tab-form.svelte +++ b/frontend/src/lib/components/dashboard/invoices/edit/general-tab-form.svelte @@ -7,6 +7,11 @@ import type { InvoiceType } from '$lib/api/dashboard/refrence_data/invoice_types'; import type { CustomsBroker } from '$lib/api/dashboard/a76/customs-brokers'; import type { ClientProvider } from '$lib/api/dashboard/a76/clients-providers'; + import { + "sidebar.client_provider_type.client_indicator" as clientIndicator, + "sidebar.client_provider_type.provider_indicator" as providerIndicator, + "sidebar.client_provider_type.both_indicator" as bothIndicator + } from '$lib/paraglide/messages.js'; let { invoice, @@ -176,8 +181,24 @@ ] ); - // Combinar clientes y proveedores para shipped_to - const allClientsProviders = [...clients, ...providers]; + // Combinar clientes y proveedores para shipped_to, evitando duplicados de tipo "both" + const allClientsProviders = $derived.by(() => { + const uniqueMap = new Map(); + + // Agregar todos los clientes + clients.forEach(c => { + uniqueMap.set(c.id, { ...c, type: c.client_or_provider }); + }); + + // Agregar proveedores solo si no existen (evita duplicados de "both") + providers.forEach(p => { + if (!uniqueMap.has(p.id)) { + uniqueMap.set(p.id, { ...p, type: p.client_or_provider }); + } + }); + + return Array.from(uniqueMap.values()); + }); // Filtrar regímenes por tipo de operación (1='E' exp, 2='I' imp) y obtener valores únicos const filteredRegimens = $derived.by(() => { @@ -363,7 +384,7 @@ {#each allClientsProviders as cp} - {cp.name} ({cp.type === 'client' ? 'C' : 'P'}) + {cp.name} ({cp.type === 'client' ? clientIndicator() : cp.type === 'provider' ? providerIndicator() : bothIndicator()}) {/each} diff --git a/frontend/src/lib/components/dashboard/pedimentos/columns.ts b/frontend/src/lib/components/dashboard/pedimentos/columns.ts index 2bdfbe51..4c81aa92 100644 --- a/frontend/src/lib/components/dashboard/pedimentos/columns.ts +++ b/frontend/src/lib/components/dashboard/pedimentos/columns.ts @@ -1,6 +1,7 @@ import type { ColumnDef } from "@tanstack/table-core"; import { renderComponent, renderSnippet } from "$lib/components/ui/data-table/index.js"; import { createRawSnippet } from "svelte"; +// @ts-ignore - Svelte component import import DataTableActions from "./data-table-actions.svelte"; import type { Pedimento } from "$lib/api/dashboard/a76/pedimentos"; @@ -201,34 +202,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef[] { return renderSnippet(dateSnippet, { date: formatDate(row.original.pedimento_dates?.payment_date) }); } }, - { - accessorKey: "pedimento_config_update_rectification.pediment_rectifed_18", - header: "Pedimento 18", - cell: ({ row }) => { - const ped18Snippet = createRawSnippet<[{ value?: string | null }]>((getValue) => { - const { value } = getValue(); - return { - render: () => - `
${value || '-'}
` - }; - }); - return renderSnippet(ped18Snippet, { value: row.original.pedimento_config_update_rectification?.pediment_rectifed_18 }); - } - }, - { - accessorKey: "pedimento_config_update_rectification.r1", - header: "Pedimento R1", - cell: ({ row }) => { - const r1Snippet = createRawSnippet<[{ value?: string | null }]>((getValue) => { - const { value } = getValue(); - return { - render: () => - `
${value || '-'}
` - }; - }); - return renderSnippet(r1Snippet, { value: row.original.pedimento_config_update_rectification?.r1 }); - } - }, { accessorKey: "pedimento_validation.electronic_signature", header: "Acuse Electrónico", From 1f01d926c21f5443c4cf442eecd9f83948a08704 Mon Sep 17 00:00:00 2001 From: Galindo97 Date: Fri, 23 Jan 2026 11:03:09 -0600 Subject: [PATCH 2/3] feat(validators): add validation for required fields based on operation type in invoice processing --- .../imports/temporary/validators/common.py | 207 ++++++++++++------ .../imports/temporary/validators/create.py | 39 ++-- .../imports/temporary/validators/update.py | 17 ++ .../api/v1/modules/a76/invoices/schemas.py | 14 +- .../invoices/edit/general-tab-form.svelte | 84 ++++--- .../invoices/edit/invoice-top-fields.svelte | 15 ++ .../dashboard/invoices/edit/save-invoice.ts | 11 +- .../dashboard/invoices/edit/[id]/+page.svelte | 13 +- 8 files changed, 275 insertions(+), 125 deletions(-) diff --git a/backend/api/v1/modules/a76/invoices/imports/temporary/validators/common.py b/backend/api/v1/modules/a76/invoices/imports/temporary/validators/common.py index 0440bf81..afba527a 100644 --- a/backend/api/v1/modules/a76/invoices/imports/temporary/validators/common.py +++ b/backend/api/v1/modules/a76/invoices/imports/temporary/validators/common.py @@ -12,6 +12,76 @@ from api.v1.modules.public.reference_data.currency_types.models import CurrencyT from api.v1.modules.public.reference_data.customs_sections.models import CustomsSection from ....models import TransportType, Currency, WeightUnit from core.exceptions import ErrorCollector +from typing import Dict, Any + + +def validate_required_fields_by_operation( + invoice_data: Dict[str, Any], + operation_type: str, + errors: ErrorCollector +) -> None: + """ + Valida campos obligatorios según tipo de operación. + Usar ANTES de guardar en BD. + """ + + # PROVEEDOR (SIEMPRE OBLIGATORIO - mensaje dinámico) + if not invoice_data.get('provider_id'): + # Mensaje dinámico según el header seleccionado + provider_labels = { + 'proveedor': 'Proveedor', + 'exportador': 'Exportador' + } + provider_header = invoice_data.get('provider_header') or 'proveedor' + field_label = provider_labels.get(provider_header, 'Proveedor') + + errors.add_error( + field="provider_id", + message=f"Debe seleccionar {field_label}", + solution=["Seleccione un proveedor de la lista desplegable"], + code="REQUIRED", + value=None + ) + + # VENDIDO A / CONSIGNADO A (SIEMPRE OBLIGATORIO - mensaje dinámico) + if not invoice_data.get('sold_to_id'): + # Mensaje dinámico según el header seleccionado + sold_to_labels = { + 'consignado_a': 'Consignado a', + 'vendido_a': 'Vendido a', + 'exportado_a': 'Exportado a', + 'importador': 'Importador' + } + sold_to_header = invoice_data.get('sold_to_header') or 'consignado_a' + field_label = sold_to_labels.get(sold_to_header, 'Cliente') + + errors.add_error( + field="sold_to_id", + message=f"Debe seleccionar {field_label}", + solution=["Seleccione una opción de la lista desplegable"], + code="REQUIRED", + value=None + ) + + # ENVIADO A (SIEMPRE OBLIGATORIO - mensaje fijo) + if not invoice_data.get('shipped_to_id'): + errors.add_error( + field="shipped_to_id", + message="Debe seleccionar el Destinatario", + solution=["Seleccione un destinatario de la lista desplegable"], + code="REQUIRED", + value=None + ) + + # AGENTE ADUANAL (OBLIGATORIO si hay pedimento) + if invoice_data.get('pedimento_id') and not invoice_data.get('customs_broker_id'): + errors.add_error( + field="customs_broker_id", + message="Debe seleccionar un Agente Aduanal", + solution=["Seleccione un agente aduanal de la lista desplegable"], + code="REQUIRED", + value=None + ) def validate_common( @@ -245,78 +315,87 @@ def validate_common( value=invoice.document_type, ) - provider_exists = ( - db.query(ClientProvider) - .filter( - ClientProvider.id == invoice.compliance_mx.provider_id, - ClientProvider.tenant_id == tenant_id, - ClientProvider.company_id == company_id, - ) - .first() - ) - if not provider_exists: - errors.add_error( - field="compliance_mx.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.compliance_mx.provider_id, + # Validar proveedor solo si se proporciona + if invoice.compliance_mx.provider_id: + provider_exists = ( + db.query(ClientProvider) + .filter( + ClientProvider.id == invoice.compliance_mx.provider_id, + ClientProvider.tenant_id == tenant_id, + ClientProvider.company_id == company_id, + ) + .first() ) + if not provider_exists: + errors.add_error( + field="compliance_mx.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.compliance_mx.provider_id, + ) - selled_to_exists = ( - db.query(ClientProvider) - .filter( - ClientProvider.id == invoice.compliance_mx.sold_to_id, - ClientProvider.tenant_id == tenant_id, - ClientProvider.company_id == company_id, - ) - .first() - ) - if not selled_to_exists: - errors.add_error( - field="compliance_mx.sold_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.compliance_mx.sold_to_id, + # Validar vendido a solo si se proporciona + if invoice.compliance_mx.sold_to_id: + selled_to_exists = ( + db.query(ClientProvider) + .filter( + ClientProvider.id == invoice.compliance_mx.sold_to_id, + ClientProvider.tenant_id == tenant_id, + ClientProvider.company_id == company_id, + ) + .first() ) + if not selled_to_exists: + errors.add_error( + field="compliance_mx.sold_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.compliance_mx.sold_to_id, + ) - shipped_to_exists = ( - db.query(ClientProvider) - .filter( - ClientProvider.id == invoice.compliance_mx.shipped_to_id, - ClientProvider.tenant_id == tenant_id, - ClientProvider.company_id == company_id, - ) - .first() - ) - if not shipped_to_exists: - errors.add_error( - field="compliance_mx.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.compliance_mx.shipped_to_id, + # Validar destinatario solo si se proporciona + if invoice.compliance_mx.shipped_to_id: + shipped_to_exists = ( + db.query(ClientProvider) + .filter( + ClientProvider.id == invoice.compliance_mx.shipped_to_id, + ClientProvider.tenant_id == tenant_id, + ClientProvider.company_id == company_id, + ) + .first() ) + if not shipped_to_exists: + errors.add_error( + field="compliance_mx.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.compliance_mx.shipped_to_id, + ) - customs_broker_exists = ( - db.query(CustomsBroker) - .filter( - CustomsBroker.id == invoice.compliance_mx.customs_broker_id, - CustomsBroker.tenant_id == tenant_id, - CustomsBroker.company_id == company_id, - ) - .first() - ) - if not customs_broker_exists: - errors.add_error( - field="compliance_mx.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.compliance_mx.customs_broker_id, + # Validar agente aduanal solo si se proporciona + if invoice.compliance_mx.customs_broker_id: + customs_broker_exists = ( + db.query(CustomsBroker) + .filter( + CustomsBroker.id == invoice.compliance_mx.customs_broker_id, + CustomsBroker.tenant_id == tenant_id, + CustomsBroker.company_id == company_id, + ) + .first() ) + if not customs_broker_exists: + errors.add_error( + field="compliance_mx.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.compliance_mx.customs_broker_id, + ) + # Validar transportista solo si se proporciona if invoice.logistics.carrier_id: carrier_exists = ( db.query(ClientProvider) diff --git a/backend/api/v1/modules/a76/invoices/imports/temporary/validators/create.py b/backend/api/v1/modules/a76/invoices/imports/temporary/validators/create.py index be6f6fc1..56800ff5 100644 --- a/backend/api/v1/modules/a76/invoices/imports/temporary/validators/create.py +++ b/backend/api/v1/modules/a76/invoices/imports/temporary/validators/create.py @@ -3,7 +3,7 @@ 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 +from .common import validate_common, validate_required_fields_by_operation 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 """ @@ -21,22 +21,31 @@ def validate_create(db: Session, invoice: InvoiceHeaderCreate, tenant_id: int, c 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") + errors.add_required_error("invoice_date") - 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""" + """Se retorna porque hay campos obligatorios básicos que deben ser llenados""" + return + + # Validar campos obligatorios según tipo de operación + invoice_data = { + 'provider_header': invoice.compliance_mx.provider_header if invoice.compliance_mx else None, + 'provider_id': invoice.compliance_mx.provider_id if invoice.compliance_mx else None, + 'sold_to_id': invoice.compliance_mx.sold_to_id if invoice.compliance_mx else None, + 'sold_to_header': invoice.compliance_mx.sold_to_header if invoice.compliance_mx else None, + 'shipped_to_id': invoice.compliance_mx.shipped_to_id if invoice.compliance_mx else None, + 'customs_broker_id': invoice.compliance_mx.customs_broker_id if invoice.compliance_mx else None, + 'pedimento_id': invoice.compliance_mx.pedimento_id if invoice.compliance_mx else None, + } + + validate_required_fields_by_operation( + invoice_data=invoice_data, + operation_type=invoice.operation_type, + errors=errors + ) + + if errors.has_errors(): + """Se retorna porque hay campos obligatorios según el tipo de operación que deben ser llenados""" return validate_common(db, invoice, tenant_id, company_id, errors) diff --git a/backend/api/v1/modules/a76/invoices/imports/temporary/validators/update.py b/backend/api/v1/modules/a76/invoices/imports/temporary/validators/update.py index 58efd41a..da4cc970 100644 --- a/backend/api/v1/modules/a76/invoices/imports/temporary/validators/update.py +++ b/backend/api/v1/modules/a76/invoices/imports/temporary/validators/update.py @@ -5,6 +5,7 @@ from decimal import Decimal from core.exceptions import ErrorCollector from ....schemas import InvoiceHeaderUpdate from ....models import InvoiceHeader +from .common import validate_required_fields_by_operation # Helper function para limpiar strings (equivalente a Clip()) @@ -33,6 +34,22 @@ def validate_update( None (modifica invoice_data in-place y acumula errores en errors) """ + # Validar campos requeridos según el tipo de operación + invoice_dict = { + 'provider_id': invoice_data.compliance_mx.provider_id if invoice_data.compliance_mx else None, + 'sold_to_id': invoice_data.compliance_mx.sold_to_id if invoice_data.compliance_mx else None, + 'sold_to_header': invoice_data.compliance_mx.sold_to_header if invoice_data.compliance_mx else None, + 'shipped_to_id': invoice_data.compliance_mx.shipped_to_id if invoice_data.compliance_mx else None, + 'customs_broker_id': invoice_data.compliance_mx.customs_broker_id if invoice_data.compliance_mx else None, + 'pedimento_id': invoice_data.compliance_mx.pedimento_id if invoice_data.compliance_mx else None, + } + + validate_required_fields_by_operation( + invoice_data=invoice_dict, + operation_type=invoice_data.operation_type or 'IMP', + errors=errors + ) + # Primero ejecutar validaciones comunes # validate_common(invoice_data, errors) diff --git a/backend/api/v1/modules/a76/invoices/schemas.py b/backend/api/v1/modules/a76/invoices/schemas.py index af111dc8..a1324e69 100644 --- a/backend/api/v1/modules/a76/invoices/schemas.py +++ b/backend/api/v1/modules/a76/invoices/schemas.py @@ -113,17 +113,17 @@ class InvoiceComplianceMxBase(BaseModel): manifest_number: Optional[str] = Field( None, max_length=15, description="Manifest number" ) - provider_header: str = Field(None, max_length=20, description="Provider header") - provider_id: int = Field(None, description="Provider ID") - sold_to_header: str = Field(None, max_length=20, description="Sold to header") - sold_to_id: int = Field(None, description="Sold to ID") - shipped_to_header: str = Field(None, max_length=20, description="Shipped to header") - shipped_to_id: int = Field(None, description="Shipped to ID") + provider_header: Optional[str] = Field(None, max_length=20, description="Provider header") + provider_id: Optional[int] = Field(None, description="Provider ID") + sold_to_header: Optional[str] = Field(None, max_length=20, description="Sold to header") + sold_to_id: Optional[int] = Field(None, description="Sold to ID") + shipped_to_header: Optional[str] = Field(None, max_length=20, description="Shipped to header") + shipped_to_id: Optional[int] = Field(None, description="Shipped to ID") shipped_by_header: Optional[int] = Field( None, max_length=20, description="Shipped by header" ) shipped_by_id: Optional[int] = Field(None, description="Shipped by ID") - customs_broker_id: int = Field(None, description="Customs broker ID") + customs_broker_id: Optional[int] = Field(None, description="Customs broker ID") customs_broker_us_id: Optional[int] = Field( None, description="US customs broker ID" ) diff --git a/frontend/src/lib/components/dashboard/invoices/edit/general-tab-form.svelte b/frontend/src/lib/components/dashboard/invoices/edit/general-tab-form.svelte index b312246a..f9c18642 100644 --- a/frontend/src/lib/components/dashboard/invoices/edit/general-tab-form.svelte +++ b/frontend/src/lib/components/dashboard/invoices/edit/general-tab-form.svelte @@ -7,11 +7,12 @@ import type { InvoiceType } from '$lib/api/dashboard/refrence_data/invoice_types'; import type { CustomsBroker } from '$lib/api/dashboard/a76/customs-brokers'; import type { ClientProvider } from '$lib/api/dashboard/a76/clients-providers'; - import { - "sidebar.client_provider_type.client_indicator" as clientIndicator, - "sidebar.client_provider_type.provider_indicator" as providerIndicator, - "sidebar.client_provider_type.both_indicator" as bothIndicator - } from '$lib/paraglide/messages.js'; + + interface CodePedimentoRegimen { + regimen_code: string; + type_code: string; + [key: string]: any; + } let { invoice, @@ -27,6 +28,7 @@ customsSections = [], codePedimentoRegimens = [], operationType = undefined, + defaultOperationType = undefined, exchangeRate = undefined }: { invoice: Invoice | null; @@ -42,8 +44,8 @@ drivers?: any[]; trailers?: any[]; customsSections?: any[]; - codePedimentoRegimens?: any[]; - defaultOperationType?: string | null; + codePedimentoRegimens?: CodePedimentoRegimen[]; + defaultOperationType?: string | number | null; defaultInvoiceType?: string | null; operationType?: number | null; exchangeRate?: number | null; @@ -200,21 +202,35 @@ return Array.from(uniqueMap.values()); }); + // Determinar tipo de código basado en operationType prop, defaultOperationType o invoice.operation_type + const typeCode = $derived( + operationType === 1 ? 'E' + : operationType === 2 ? 'I' + : defaultOperationType === 1 ? 'E' + : defaultOperationType === 2 ? 'I' + : defaultOperationType === 'exp' ? 'E' + : defaultOperationType === 'imp' ? 'I' + : invoice?.operation_type === 'exp' ? 'E' + : invoice?.operation_type === 'imp' ? 'I' + : null + ); + // Filtrar regímenes por tipo de operación (1='E' exp, 2='I' imp) y obtener valores únicos - const filteredRegimens = $derived.by(() => { - const typeCode = operationType === 1 ? 'E' : operationType === 2 ? 'I' : null; - const filtered = codePedimentoRegimens.filter(r => r.type_code === typeCode); - - // Obtener solo regímenes únicos por regimen_code - const uniqueMap = new Map(); - filtered.forEach(r => { - if (r.regimen_code && !uniqueMap.has(r.regimen_code)) { - uniqueMap.set(r.regimen_code, r); - } - }); - - return Array.from(uniqueMap.values()); - }); + const filteredRegimens = $derived( + !codePedimentoRegimens || codePedimentoRegimens.length === 0 || !typeCode + ? [] + : Array.from( + codePedimentoRegimens + .filter(r => r.type_code === typeCode) + .reduce((map, r) => { + if (r.regimen_code && !map.has(r.regimen_code)) { + map.set(r.regimen_code, r); + } + return map; + }, new Map()) + .values() + ) + ); // Efecto: Limpiar régimen si no existe en los regímenes filtrados al cambiar operation_type $effect(() => { @@ -284,9 +300,11 @@ > - {formData.provider_id - ? providers.find(p => p.id === formData.provider_id)?.name || 'Selecciona...' - : 'Selecciona...'} + {#if formData.provider_id} + {providers.find(p => p.id === formData.provider_id)?.name || 'Selecciona...'} + {:else} + Selecciona... + {/if} @@ -330,9 +348,11 @@ > - {formData.sold_to_id - ? clients.find(c => c.id === formData.sold_to_id)?.name || 'Selecciona...' - : 'Selecciona...'} + {#if formData.sold_to_id} + {clients.find(c => c.id === formData.sold_to_id)?.name || 'Selecciona...'} + {:else} + Selecciona... + {/if} @@ -376,15 +396,17 @@ > - {formData.shipped_to_id - ? allClientsProviders.find(cp => cp.id === formData.shipped_to_id)?.name || 'Selecciona...' - : 'Selecciona...'} + {#if formData.shipped_to_id} + {allClientsProviders.find(cp => cp.id === formData.shipped_to_id)?.name || 'Selecciona...'} + {:else} + Selecciona... + {/if} {#each allClientsProviders as cp} - {cp.name} ({cp.type === 'client' ? clientIndicator() : cp.type === 'provider' ? providerIndicator() : bothIndicator()}) + {cp.name} {/each} diff --git a/frontend/src/lib/components/dashboard/invoices/edit/invoice-top-fields.svelte b/frontend/src/lib/components/dashboard/invoices/edit/invoice-top-fields.svelte index 47743eef..e3549f05 100644 --- a/frontend/src/lib/components/dashboard/invoices/edit/invoice-top-fields.svelte +++ b/frontend/src/lib/components/dashboard/invoices/edit/invoice-top-fields.svelte @@ -46,6 +46,16 @@ } }); + // Efecto para actualizar operation_type cuando cambia defaultOperationType + $effect(() => { + if (formData && defaultOperationType !== undefined && defaultOperationType !== null) { + // Si operation_type está vacío, null, o undefined, actualizarlo con defaultOperationType + if (!formData.operation_type) { + formData.operation_type = defaultOperationType; + } + } + }); + if (!formData) { let operationType: string | null = null; if (invoice?.operation_type) { @@ -69,6 +79,11 @@ clave_pedimento: '', regimen_pedimento: '', }; + } else { + // Si formData ya existe pero operation_type está vacío, usar defaultOperationType + if (!formData.operation_type && defaultOperationType !== undefined && defaultOperationType !== null) { + formData.operation_type = defaultOperationType; + } } diff --git a/frontend/src/lib/components/dashboard/invoices/edit/save-invoice.ts b/frontend/src/lib/components/dashboard/invoices/edit/save-invoice.ts index fbd26b64..03982769 100644 --- a/frontend/src/lib/components/dashboard/invoices/edit/save-invoice.ts +++ b/frontend/src/lib/components/dashboard/invoices/edit/save-invoice.ts @@ -97,7 +97,7 @@ export async function saveInvoice(options: SaveInvoiceOptions): Promise import { onMount } from 'svelte'; import { browser } from '$app/environment'; - import { goto } from '$app/navigation'; + import { goto } from '$app/navigation'; + import { page } from '$app/stores'; import * as Tabs from '$lib/components/ui/tabs'; import { Button } from '$lib/components/ui/button'; import { Badge } from '$lib/components/ui/badge'; @@ -169,7 +170,7 @@ if (e && typeof e === 'object' && 'validationErrors' in e && Array.isArray((e as any).validationErrors)) { const validationErrors = (e as any).validationErrors; const errorList = validationErrors.map((err: any) => - `• ${err.field}: ${err.message}${err.solution ? ' - ' + err.solution.join(', ') : ''}` + `• ${err.message}` ).join('\n'); toast.error(errorMessage, { @@ -259,7 +260,13 @@ codePedimentoRegimens={data.codePedimentoRegimens || []} defaultOperationType={data.filters?.operation_type ?? undefined} defaultInvoiceType={data.filters?.invoice_type ?? undefined} - operationType={InvoiceTopFieldsFormData?.operation_type} + operationType={ + InvoiceTopFieldsFormData?.operation_type === 'exp' ? 1 + : InvoiceTopFieldsFormData?.operation_type === 'imp' ? 2 + : data.invoice?.operation_type === 'exp' ? 1 + : data.invoice?.operation_type === 'imp' ? 2 + : undefined + } exchangeRate={calculatedExchangeRate} /> From 123310badd83b5be95f5340dadbb908901847f06 Mon Sep 17 00:00:00 2001 From: Galindo97 Date: Fri, 23 Jan 2026 11:44:13 -0600 Subject: [PATCH 3/3] fix(invoices): simplify operation type handling in invoice payload and server load --- .../dashboard/invoices/edit/save-invoice.ts | 4 +--- frontend/src/routes/dashboard/invoices/+page.svelte | 5 ----- .../dashboard/invoices/edit/[id]/+page.server.ts | 11 ++++------- 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/frontend/src/lib/components/dashboard/invoices/edit/save-invoice.ts b/frontend/src/lib/components/dashboard/invoices/edit/save-invoice.ts index 03982769..e6f6b550 100644 --- a/frontend/src/lib/components/dashboard/invoices/edit/save-invoice.ts +++ b/frontend/src/lib/components/dashboard/invoices/edit/save-invoice.ts @@ -110,9 +110,7 @@ function buildInvoicePayload(formData: FormDataSet): CreateInvoiceData | UpdateI const payload: any = { // Datos generales desde InvoiceTopFieldsFormData system: 'fixed_asset', - operation_type: InvoiceTopFieldsFormData?.operation_type !== null && InvoiceTopFieldsFormData?.operation_type !== undefined - ? (InvoiceTopFieldsFormData.operation_type === 1 ? 'exp' : 'imp') as OperationType - : undefined, + operation_type: InvoiceTopFieldsFormData?.operation_type || undefined, invoice_type: InvoiceTopFieldsFormData?.invoice_type || undefined, document_type: generalFormData?.document_type || undefined, invoice_number: InvoiceTopFieldsFormData?.invoice_number || undefined, diff --git a/frontend/src/routes/dashboard/invoices/+page.svelte b/frontend/src/routes/dashboard/invoices/+page.svelte index 06086755..61138a73 100644 --- a/frontend/src/routes/dashboard/invoices/+page.svelte +++ b/frontend/src/routes/dashboard/invoices/+page.svelte @@ -427,11 +427,6 @@ function handleCreateClick() { const params = new URLSearchParams(window.location.search); - const operationType = params.get('operation_type'); - if (operationType) { - const operationTypeNumber = operationType === 'exp' ? 1 : 2; - params.set('operation_type', operationTypeNumber.toString()); - } const queryString = params.toString(); const url = queryString diff --git a/frontend/src/routes/dashboard/invoices/edit/[id]/+page.server.ts b/frontend/src/routes/dashboard/invoices/edit/[id]/+page.server.ts index 2ec29c64..78a4d101 100644 --- a/frontend/src/routes/dashboard/invoices/edit/[id]/+page.server.ts +++ b/frontend/src/routes/dashboard/invoices/edit/[id]/+page.server.ts @@ -20,13 +20,10 @@ export const load: PageServerLoad = async ({ params, cookies, fetch, url }) => { const operationTypeParam = url.searchParams.get('operation_type'); const invoiceTypeParam = url.searchParams.get('invoice_type'); - // Parsear operation_type de forma segura - let parsedOperationType: number | null = null; - if (operationTypeParam) { - const parsed = parseInt(operationTypeParam, 10); - if (!isNaN(parsed)) { - parsedOperationType = parsed; - } + // Validar que operation_type sea 'exp' o 'imp' + let parsedOperationType: string | null = null; + if (operationTypeParam && (operationTypeParam === 'exp' || operationTypeParam === 'imp')) { + parsedOperationType = operationTypeParam; } // Cargar datos de referencia necesarios