From bf26268f23e71e73ef02525e5c3184b763e845fc Mon Sep 17 00:00:00 2001 From: Kevin_Ramirez Date: Thu, 22 Jan 2026 17:30:04 -0600 Subject: [PATCH] Se integro la ventana de tipo de dato en pedimentos y facturas --- .../exchange_rate/create-edit-dialog.svelte | 201 +++++++++++++----- .../exchange_rate/exchange-rate-guard.svelte | 60 ++++++ .../invoices/create-edit-dialog.svelte | 74 +++++++ .../pedimentos/edit/general-tab-form.svelte | 38 ++++ frontend/src/routes/dashboard/+layout.svelte | 3 + .../dashboard/invoices/edit/[id]/+page.svelte | 81 +++++++ .../pedimentos/edit/[id]/+page.svelte | 102 ++++++++- scripts/cleanup_exchange_rate.py | 30 +++ 8 files changed, 525 insertions(+), 64 deletions(-) create mode 100644 frontend/src/lib/components/dashboard/exchange_rate/exchange-rate-guard.svelte create mode 100644 scripts/cleanup_exchange_rate.py diff --git a/frontend/src/lib/components/dashboard/exchange_rate/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/exchange_rate/create-edit-dialog.svelte index 34c61657..47a47ca8 100644 --- a/frontend/src/lib/components/dashboard/exchange_rate/create-edit-dialog.svelte +++ b/frontend/src/lib/components/dashboard/exchange_rate/create-edit-dialog.svelte @@ -1,27 +1,40 @@ + + diff --git a/frontend/src/lib/components/dashboard/invoices/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/invoices/create-edit-dialog.svelte index 4e0eef19..f9c82247 100644 --- a/frontend/src/lib/components/dashboard/invoices/create-edit-dialog.svelte +++ b/frontend/src/lib/components/dashboard/invoices/create-edit-dialog.svelte @@ -5,9 +5,11 @@ import { Label } from "$lib/components/ui/label"; import * as Select from "$lib/components/ui/select"; import { invoicesApi, type Invoice, type CreateInvoiceData, type UpdateInvoiceData } from "$lib/api/dashboard/a76/invoices"; + import { getExchangeRates } from "$lib/api/dashboard/a76/general_catalogs/exchange-rate"; import { companyStore } from "$lib/stores/company.svelte"; import { LoaderCircle } from 'lucide-svelte'; import * as Tabs from "$lib/components/ui/tabs"; + import ExchangeRateDialog from "$lib/components/dashboard/exchange_rate/create-edit-dialog.svelte"; let { open = $bindable(false), @@ -72,6 +74,9 @@ let loading = $state(false); let error = $state(null); + let showExchangeRateDialog = $state(false); + let missingExchangeRateDate = $state(""); + // Actualizar formData cuando item cambia $effect(() => { if (item) { @@ -181,6 +186,15 @@ error = null; try { + // Verificar tipo de cambio antes de guardar + if (formData.invoice_date) { + const rateExists = await checkExchangeRate(formData.invoice_date); + if (!rateExists) { + loading = false; + return; + } + } + let response; if (isEditing && item) { const payload: UpdateInvoiceData = { @@ -287,11 +301,37 @@ window.location.reload(); }, 1500); } else { + // Convertir el error a string para buscar mensajes específicos (maneja objetos/arrays de DRF) + const errorStr = typeof response.error === 'string' + ? response.error + : JSON.stringify(response.error); + + if (errorStr.includes('No existe un Tipo de Cambio registrado') || errorStr.includes('financials.exchange_rate')) { + // Interceptar error de tipo de cambio + console.log("Interceptor: Exchange rate missing error caught (Invoice)."); + error = null; + + const dateMatch = errorStr.match(/(\d{4}-\d{2}-\d{2})/); + missingExchangeRateDate = dateMatch ? dateMatch[0] : (formData.invoice_date || ""); + + showExchangeRateDialog = true; + return; + } + error = response.error; } return; } + // Check exchange rate BEFORE calling API to avoid 400 error + if (formData.invoice_date) { + const rateExists = await checkExchangeRate(formData.invoice_date); + if (!rateExists) { + loading = false; + return; + } + } + // Éxito open = false; if (onSuccess) { @@ -305,6 +345,33 @@ } } + async function checkExchangeRate(date: string): Promise { + if (!date || !companyStore.activeCompany?.id) return true; + + try { + const response = await getExchangeRates(companyStore.activeCompany.id, { + date: date, + page_size: 1 + }); + + const actualResponse = response as any; + const items = actualResponse.data?.items || []; + + // Verificar estrictamente que haya items + if (items.length === 0) { + missingExchangeRateDate = date; + showExchangeRateDialog = true; + return false; + } + return true; + } catch (error) { + console.error('Error checking exchange rate:', error); + missingExchangeRateDate = date; + showExchangeRateDialog = true; + return false; + } + } + function handleOpenChange(newOpen: boolean) { if (!newOpen) { resetForm(); @@ -679,3 +746,10 @@ + + {/* Optional: maybe refresh something or just let user continue */}} +/> diff --git a/frontend/src/lib/components/dashboard/pedimentos/edit/general-tab-form.svelte b/frontend/src/lib/components/dashboard/pedimentos/edit/general-tab-form.svelte index 99f50cce..774be895 100644 --- a/frontend/src/lib/components/dashboard/pedimentos/edit/general-tab-form.svelte +++ b/frontend/src/lib/components/dashboard/pedimentos/edit/general-tab-form.svelte @@ -13,6 +13,7 @@ import type { ClientProvider } from '$lib/api/dashboard/a76/clients-providers'; import type { CodePedimentoRegimen } from '$lib/api/dashboard/refrence_data/code_pedimento_regimens'; import IdentificadoresTabForm from './identifiers-tab-form.svelte'; + import ExchangeRateDialog from '$lib/components/dashboard/exchange_rate/create-edit-dialog.svelte'; import { Calendar, Clock } from 'lucide-svelte'; import { loadServerDate, @@ -45,6 +46,10 @@ // Estado para controlar la sección activa de la navegación let activeSection = $state('fechas'); + // Exchange Rate Check State + let showExchangeRateDialog = $state(false); + let missingExchangeRateDate = $state(""); + // Extraer regímenes únicos de codePedimentoRegimens const uniqueRegimens = $derived( Array.from(new Set(codePedimentoRegimens.map(r => r.regimen_code).filter((code): code is string => code !== null))) @@ -314,6 +319,32 @@ { value: 'adicional', label: 'Adicional' }, { value: 'decrementable', label: 'Decrementable' } ]; + + export async function checkPaymentDateRate(date: string): Promise { + if (!date || !companyStore.activeCompany?.id) return true; + + try { + const rate = await getExchangeRateByDate(date, companyStore.activeCompany.id); + if (!rate) { + // Abrir modal preventivamente + missingExchangeRateDate = date; + showExchangeRateDialog = true; + return false; + } + return true; + } catch (error) { + console.error('Error checking payment date rate:', error); + // Si hay error de red, asumimos que falta para forzar reintento/captura segura + missingExchangeRateDate = date; + showExchangeRateDialog = true; + return false; + } + } + + export function openExchangeRateDialog(date: string) { + missingExchangeRateDate = date; + showExchangeRateDialog = true; + } @@ -1159,3 +1190,10 @@ + + {/* Optional: maybe refresh something or just let user continue */}} +/> diff --git a/frontend/src/routes/dashboard/+layout.svelte b/frontend/src/routes/dashboard/+layout.svelte index 60b5d95c..ce289bdc 100644 --- a/frontend/src/routes/dashboard/+layout.svelte +++ b/frontend/src/routes/dashboard/+layout.svelte @@ -7,6 +7,7 @@ import { Separator } from "$lib/components/ui/separator/index.js"; import * as Sidebar from "$lib/components/ui/sidebar/index.js"; import { companyStore } from "$lib/stores/company.svelte"; + import ExchangeRateGuard from "$lib/components/dashboard/exchange_rate/exchange-rate-guard.svelte"; let { data, children }: { data: LayoutData; children: any } = $props(); @@ -61,3 +62,5 @@ + + diff --git a/frontend/src/routes/dashboard/invoices/edit/[id]/+page.svelte b/frontend/src/routes/dashboard/invoices/edit/[id]/+page.svelte index 62065835..f00643cd 100644 --- a/frontend/src/routes/dashboard/invoices/edit/[id]/+page.svelte +++ b/frontend/src/routes/dashboard/invoices/edit/[id]/+page.svelte @@ -32,6 +32,8 @@ import type { ClientProvider } from '$lib/api/dashboard/a76/clients-providers'; import { saveInvoice } from '$lib/components/dashboard/invoices/edit/save-invoice'; import { getExchangeRateByDate } from '$lib/api/dashboard/a76/exchange-rate'; + import { getExchangeRates } from '$lib/api/dashboard/a76/general_catalogs/exchange-rate'; + import ExchangeRateDialog from '$lib/components/dashboard/exchange_rate/create-edit-dialog.svelte'; // Cargar companyStore solo en el cliente - no usamos sidebar en esta página let companyStore: any = $state(undefined); @@ -101,6 +103,44 @@ let continuationExists = $state(false); let calculatedExchangeRate = $state(data.invoice?.financials?.exchange_rate ?? null); + + let showExchangeRateDialog = $state(false); + let missingExchangeRateDate = $state(""); + + async function checkExchangeRate(date: string): Promise { + if (!date || !companyStore?.activeCompany?.id) return true; + + // Si ya tenemos un tipo de cambio calculado y es válido, asumimos que existe + if (calculatedExchangeRate && calculatedExchangeRate > 0) return true; + + try { + // Verificar explícitamente si existe + const response = await getExchangeRates(companyStore.activeCompany.id, { + date: date, + page_size: 1 + }); + + const actualResponse = response as any; + const items = actualResponse.data?.items || []; + + if (items.length === 0) { + console.log('No exchange rate found for', date); + missingExchangeRateDate = date; + showExchangeRateDialog = true; + return false; + } + return true; + } catch (error) { + console.error('Error checking exchange rate:', error); + // Ante la duda, si es un error de red, quizás deberíamos dejar pasar o bloquear. + // Si asumimos que falló la petición, mejor pedir al usuario que verifique. + // O podríamos asumir que falta si es 404/Empty. + // Por seguridad, abrimos modal. + missingExchangeRateDate = date; + showExchangeRateDialog = true; + return false; + } + } // Efecto para actualizar el tipo de cambio cuando cambia la fecha de factura $effect(() => { @@ -130,6 +170,22 @@ saving = true; try { + // Validar ID si es edición + if (!data.isCreate && !invoiceId) { + toast.error("Error interrrno: No se encuentra el ID de la factura para actualizar."); + saving = false; + return; + } + + // Verificar tipo de cambio antes de guardar + if (InvoiceTopFieldsFormData?.invoice_date) { + const rateExists = await checkExchangeRate(InvoiceTopFieldsFormData.invoice_date); + if (!rateExists) { + saving = false; + return; + } + } + const result = await saveInvoice({ invoiceId, isCreate: data.isCreate || false, @@ -165,6 +221,18 @@ } else { const errorMessage = e instanceof Error ? e.message : 'Error al guardar los cambios'; + // Intercept exchange rate error + if (errorMessage.includes('No existe un Tipo de Cambio registrado') || errorMessage.includes('financials.exchange_rate')) { + console.log("Interceptor: Exchange rate missing error caught (Invoice Page)."); + + const dateMatch = errorMessage.match(/(\d{4}-\d{2}-\d{2})/); + const missingDate = dateMatch ? dateMatch[0] : (InvoiceTopFieldsFormData?.invoice_date || ""); + + missingExchangeRateDate = missingDate; + showExchangeRateDialog = true; + return; + } + // Si hay errores de validación, mostrarlos en detalle if (e && typeof e === 'object' && 'validationErrors' in e && Array.isArray((e as any).validationErrors)) { const validationErrors = (e as any).validationErrors; @@ -305,6 +373,19 @@ + { + // Actualizar el tipo de cambio mostrado + if (InvoiceTopFieldsFormData.invoice_date && companyStore?.activeCompany?.id) { + getExchangeRateByDate(InvoiceTopFieldsFormData.invoice_date, companyStore.activeCompany.id) + .then(rate => { + if (rate) calculatedExchangeRate = rate.value; + }); + } + }} +/>
diff --git a/frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.svelte b/frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.svelte index 1138594e..5c18aa1a 100644 --- a/frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.svelte +++ b/frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.svelte @@ -64,12 +64,41 @@ authenticated?: boolean; } + import { getExchangeRateByDate } from '$lib/api/dashboard/a76/exchange-rate'; + import ExchangeRateDialog from '$lib/components/dashboard/exchange_rate/create-edit-dialog.svelte'; + import { companyStore } from '$lib/stores/company.svelte'; + let { data }: { data: ExtendedPageData } = $props(); let activeTab = $state('general'); + let generalTabInstance = $state(null); // Todavía útil para otras cosas let saving = $state(false); let error = $state(null); let success = $state(false); + + // Dialog state lifted up + let showExchangeRateDialog = $state(false); + let missingExchangeRateDate = $state(""); + + async function checkPaymentDateRate(date: string): Promise { + if (!date || !companyStore.activeCompany?.id) return true; + + try { + const rate = await getExchangeRateByDate(date, companyStore.activeCompany.id); + if (!rate) { + missingExchangeRateDate = date; + showExchangeRateDialog = true; + return false; + } + return true; + } catch (error) { + console.error('Error checking payment date rate:', error); + // Si falla, forzamos diálogo para seguridad + missingExchangeRateDate = date; + showExchangeRateDialog = true; + return false; + } + } // ID del pedimento let pedimentoId = $state(data.pedimentoId ?? null); @@ -274,6 +303,16 @@ success = false; try { + // Verificar tipo de cambio antes de guardar si hay instancia del tab general y hay fecha de pago + if (generalTabInstance && generalFormData?.payment_date) { + const rateExists = await generalTabInstance.checkPaymentDateRate(generalFormData.payment_date); + if (!rateExists) { + saving = false; + // Asegurar que se muestre el tab general + activeTab = 'general'; + return; + } + } // Validar campos requeridos para creación if (data.isCreate && generalFormData) { @@ -718,14 +757,60 @@ success = false; }, 3000); } catch (e) { - if (e instanceof Error && e.message.includes('401')) { - error = 'Sesión expirada. Recargando página...'; - setTimeout(() => { - window.location.reload(); - }, 1500); - } else { - error = e instanceof Error ? e.message : 'Error al guardar los cambios'; - } + if (e instanceof Error) { + if (e.message.includes('401')) { + error = 'Sesión expirada. Recargando página...'; + setTimeout(() => { + window.location.reload(); + }, 1500); + } else { + // Extracción segura del error + let errorStr = ""; + try { + if (typeof e.message === 'object') { + errorStr = JSON.stringify(e.message); + } else if (e.message === '[object Object]') { + // Si el mensaje es literalmente [object Object], intentamos ver si e tiene otras props + errorStr = JSON.stringify(e); + } else { + errorStr = e.message || String(e); + } + } catch (jsonErr) { + errorStr = String(e); + } + + if (errorStr.includes('No existe un Tipo de Cambio registrado') || errorStr.includes('financials.exchange_rate')) { + // Interceptar error de tipo de cambio + console.log("Interceptor: Exchange rate missing error caught (Pedimento)."); + error = null; + + const dateMatch = errorStr.match(/(\d{4}-\d{2}-\d{2})/); + const missingDate = dateMatch ? dateMatch[0] : (generalFormData?.payment_date || ""); + + missingExchangeRateDate = missingDate; + showExchangeRateDialog = true; + activeTab = 'general'; + return; + } + + // Fallback normal: intentar mostrar algo legible + if (errorStr.includes('{')) { + // Si parece JSON, intentar formatearlo un poco o mostrar mensaje genérico + try { + const errObj = JSON.parse(errorStr); + // Si es del formato {"field": ["msg"]} + const values = Object.values(errObj).flat(); + error = values.join(', '); + } catch { + error = "Error al guardar (ver consola)"; + } + } else { + error = errorStr; + } + } + } else { + error = 'Error al guardar los cambios'; + } console.error('Error saving all:', e); } finally { saving = false; @@ -794,6 +879,7 @@
0: + print(f"Found {total} exchange rate(s) for today. Deleting...") + for rate in rates: + ExchangeRateService.delete(db, rate.id, tenant_id, company_id) + print("Deleted.") +else: + print("No exchange rate found for today.") + +db.close()