Se integro la ventana de tipo de dato en pedimentos y facturas

This commit is contained in:
2026-01-22 17:30:04 -06:00
parent 5a254fb2b1
commit bf26268f23
8 changed files with 525 additions and 64 deletions

View File

@@ -1,27 +1,40 @@
<script lang="ts">
import { Button } from "$lib/components/ui/button";
import * as Dialog from "$lib/components/ui/dialog";
import * as AlertDialog from "$lib/components/ui/alert-dialog";
import { Input } from "$lib/components/ui/input";
import { Label } from "$lib/components/ui/label";
import { toast } from "svelte-sonner";
import {
createExchangeRate,
updateExchangeRate,
type ExchangeRate
} from "$lib/api/dashboard/a76/general_catalogs/exchange-rate";
import { companyStore } from "$lib/stores/company.svelte";
import { Scale, BadgeDollarSign, Info, AlertCircle, ArrowRight } from "lucide-svelte";
import { fly, scale } from 'svelte/transition';
import { cubicOut } from 'svelte/easing';
let {
open = $bindable(false),
item = null,
onSuccess
onSuccess,
overlayClass = "bg-black/80 backdrop-blur-sm",
initialDate = ""
}: {
open: boolean;
item?: ExchangeRate | null;
onSuccess?: () => void;
overlayClass?: string;
initialDate?: string;
} = $props();
const isEdit = $derived(!!item);
const title = $derived(isEdit ? "Editar Tipo de Cambio" : "Nuevo Tipo de Cambio");
const isMissingRateContext = $derived(!isEdit && initialDate);
const title = $derived(
isEdit ? "Editar Tipo de Cambio" :
isMissingRateContext ? "Tipo de Cambio Requerido" : "Nuevo Tipo de Cambio"
);
let formData = $state({
date: '',
@@ -32,6 +45,7 @@
let loading = $state(false);
let error = $state<string | null>(null);
let showConfirmation = $state(false);
// Cargar datos al abrir
$effect(() => {
@@ -46,7 +60,7 @@
};
} else {
formData = {
date: new Date().toISOString().split('T')[0],
date: initialDate || new Date().toISOString().split('T')[0],
value: null,
local_currency: 'MXN',
foreign_currency: 'USD'
@@ -56,16 +70,27 @@
}
});
async function handleSubmit() {
function handleSubmit() {
error = null;
try {
const companyId = companyStore.activeCompany?.id;
if (!companyId) throw new Error('No hay una compañía seleccionada');
if (!formData.date) throw new Error('La fecha es requerida');
if (formData.value === null) throw new Error('El valor es requerido');
showConfirmation = true;
} catch (e) {
error = e instanceof Error ? e.message : 'Error al validar';
}
}
async function confirmSubmit() {
loading = true;
error = null;
try {
const companyId = companyStore.activeCompany?.id;
if (!companyId) throw new Error('No hay una compañía seleccionada');
if (!formData.date) throw new Error('La fecha es requerida');
if (formData.value === null) throw new Error('El valor es requerido');
const dataToSend = {
date: formData.date,
value: Number(formData.value),
@@ -75,16 +100,18 @@
if (isEdit && item) {
await updateExchangeRate(item.id, dataToSend, companyId);
alert(`✅ Tipo de cambio actualizado correctamente`);
toast.success('Tipo de cambio actualizado correctamente');
} else {
await createExchangeRate(dataToSend, companyId);
alert(`✅ Tipo de cambio creado correctamente`);
toast.success('Tipo de cambio creado correctamente');
}
showConfirmation = false;
open = false;
if (onSuccess) onSuccess();
} catch (e) {
error = e instanceof Error ? e.message : 'Error al guardar';
showConfirmation = false;
} finally {
loading = false;
}
@@ -93,59 +120,121 @@
<Dialog.Root bind:open>
<Dialog.Portal>
<Dialog.Overlay class="fixed inset-0 z-[9999] bg-black/80 backdrop-blur-sm" />
<Dialog.Overlay class="fixed inset-0 z-[9999] {overlayClass}" />
<Dialog.Content class="fixed left-[50%] top-[50%] z-[10000] w-full max-w-[500px] translate-x-[-50%] translate-y-[-50%] border bg-background p-6 shadow-lg sm:rounded-lg">
<Dialog.Header>
<Dialog.Title>{title}</Dialog.Title>
</Dialog.Header>
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }} class="space-y-4 py-4">
{#if error}
<div class="rounded-md bg-destructive/15 p-3 text-sm text-destructive">
{error}
<Dialog.Content
class="fixed left-[50%] top-[50%] z-[10000] w-full max-w-[480px] translate-x-[-50%] translate-y-[-50%] border-0 bg-transparent shadow-2xl p-0 sm:rounded-xl overflow-hidden"
transition={scale}
params={{ duration: 300, easing: cubicOut, start: 0.95 }}
>
<div class="bg-background flex flex-col h-full rounded-xl overflow-hidden border border-border">
{#if isMissingRateContext}
<div class="bg-amber-50 dark:bg-amber-950/40 p-5 flex gap-4 border-b border-amber-100 dark:border-amber-900/50">
<div class="bg-amber-100 dark:bg-amber-900/60 p-2.5 rounded-full h-fit shadow-sm shrink-0">
<AlertCircle class="text-amber-600 dark:text-amber-400" size={24} />
</div>
<div class="space-y-1">
<h3 class="font-semibold text-amber-900 dark:text-amber-100 text-lg leading-tight">
{title}
</h3>
<p class="text-sm text-amber-800/80 dark:text-amber-200/80 leading-relaxed">
Para continuar con el guardado, es necesario registrar el tipo de cambio oficial para esta fecha.
</p>
</div>
</div>
{:else}
<Dialog.Header class="p-6 pb-2">
<Dialog.Title class="flex items-center gap-2 text-xl">
<BadgeDollarSign class="text-primary" />
{title}
</Dialog.Title>
</Dialog.Header>
{/if}
<div class="grid gap-4">
<div class="grid grid-cols-4 items-center gap-4">
<Label for="date" class="text-right">Fecha *</Label>
<div class="col-span-3">
<Input id="date" type="date" bind:value={formData.date} disabled={loading} required />
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }} class="p-6 pt-4 space-y-6">
{#if error}
<div class="rounded-lg bg-destructive/10 border border-destructive/20 p-3 flex gap-3 text-sm text-destructive" transition:fly={{ y: -10 }}>
<AlertCircle size={16} class="mt-0.5 shrink-0" />
<span class="font-medium">{error}</span>
</div>
{/if}
<div class="grid gap-5">
<div class="grid gap-2">
<Label for="date" class="text-sm font-medium text-muted-foreground ml-1">Fecha Aplicable</Label>
<div class="relative">
<Input
id="date"
type="date"
bind:value={formData.date}
disabled={loading || (isMissingRateContext && !!initialDate)}
required
class="pl-3 h-11 text-base bg-muted/30 focus:bg-background transition-colors"
/>
{#if isMissingRateContext}
<div class="absolute right-3 top-1/2 -translate-y-1/2 text-xs text-amber-600 font-medium bg-amber-100 px-2 py-0.5 rounded-full">
Requerida
</div>
{/if}
</div>
</div>
<div class="grid gap-2">
<Label for="value" class="text-sm font-medium text-muted-foreground ml-1">Tipo de Cambio (MXN/USD)</Label>
<div class="relative group">
<div class="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground font-semibold">$</div>
<Input
id="value"
type="number"
step="0.0001"
bind:value={formData.value}
disabled={loading}
required
placeholder="0.0000"
class="pl-7 h-11 text-lg font-mono tracking-wide focus:ring-2 ring-primary/20 transition-all group-hover:border-primary/50"
autofocus
/>
</div>
<p class="text-[11px] text-muted-foreground text-right px-1">
Ej. 24.1234
</p>
</div>
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label for="value" class="text-right">Valor *</Label>
<div class="col-span-3">
<Input id="value" type="number" step="0.000001" bind:value={formData.value} disabled={loading} required />
</div>
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label for="local_currency" class="text-right">Local</Label>
<div class="col-span-3">
<Input id="local_currency" bind:value={formData.local_currency} maxlength={3} disabled={loading} />
</div>
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label for="foreign_currency" class="text-right">Extranjera</Label>
<div class="col-span-3">
<Input id="foreign_currency" bind:value={formData.foreign_currency} maxlength={3} disabled={loading} />
</div>
</div>
</div>
<Dialog.Footer>
<Button type="button" variant="outline" onclick={() => open = false} disabled={loading}>
Cancelar
</Button>
<Button type="submit" disabled={loading}>
{loading ? 'Guardando...' : isEdit ? 'Actualizar' : 'Crear'}
</Button>
</Dialog.Footer>
</form>
<Dialog.Footer class="gap-2 sm:gap-0 pt-2">
<Button type="button" variant="ghost" onclick={() => open = false} disabled={loading} class="hover:bg-muted/50">
Cancelar
</Button>
<Button type="submit" disabled={loading} class="min-w-[140px] shadow-sm font-medium" size="lg">
{#if loading}
<span class="animate-spin mr-2"></span> Guardando...
{:else}
{#if isMissingRateContext}
Guardar y Continuar
{:else}
{isEdit ? 'Actualizar' : 'Crear Registro'}
{/if}
{/if}
</Button>
</Dialog.Footer>
</form>
</div>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
</Dialog.Root>
<AlertDialog.Root bind:open={showConfirmation}>
<AlertDialog.Content class="z-[10002]">
<AlertDialog.Header>
<AlertDialog.Title>¿Estás seguro?</AlertDialog.Title>
<AlertDialog.Description>
Se {isEdit ? 'actualizará' : 'creará'} el tipo de cambio con valor {formData.value} para el día {formData.date}.
</AlertDialog.Description>
</AlertDialog.Header>
<AlertDialog.Footer>
<AlertDialog.Cancel onclick={() => showConfirmation = false}>Cancelar</AlertDialog.Cancel>
<AlertDialog.Action onclick={confirmSubmit}>Confirmar</AlertDialog.Action>
</AlertDialog.Footer>
</AlertDialog.Content>
</AlertDialog.Root>

View File

@@ -0,0 +1,60 @@
<script lang="ts">
import { onMount } from 'svelte';
import { companyStore } from '$lib/stores/company.svelte';
import { getExchangeRates } from '$lib/api/dashboard/a76/general_catalogs/exchange-rate';
import CreateEditDialog from './create-edit-dialog.svelte';
let open = $state(false);
let checked = $state(false);
async function checkExchangeRate() {
console.log('[ExchangeRateGuard] Checking...', companyStore.activeCompany);
if (!companyStore.activeCompany?.id) {
console.log('[ExchangeRateGuard] No active company');
return;
}
// Use local date instead of UTC
const today = new Date().toLocaleDateString('fr-CA'); // YYYY-MM-DD
console.log('[ExchangeRateGuard] Date:', today);
try {
const response = await getExchangeRates(companyStore.activeCompany.id, {
date: today,
page_size: 1
});
console.log('[ExchangeRateGuard] Response (stringified):', JSON.stringify(response, null, 2));
// api.get returns { data: ..., status: ... } but types say otherwise
const actualResponse = response as any;
const items = actualResponse.data?.items || [];
if (items.length === 0) {
console.log('[ExchangeRateGuard] No rate found, opening modal');
open = true;
}
} catch (error) {
console.error('[ExchangeRateGuard] Error checking exchange rate:', error);
} finally {
checked = true;
}
}
$effect(() => {
if (companyStore.activeCompany?.id && !checked) {
checkExchangeRate();
}
});
function handleSuccess() {
console.log('Exchange rate created successfully via guard');
checked = true;
}
</script>
<CreateEditDialog
bind:open
onSuccess={handleSuccess}
overlayClass="bg-black/20"
/>

View File

@@ -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<string | null>(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<boolean> {
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 @@
</form>
</Dialog.Content>
</Dialog.Root>
<ExchangeRateDialog
bind:open={showExchangeRateDialog}
initialDate={missingExchangeRateDate}
overlayClass="bg-black/20"
onSuccess={() => {/* Optional: maybe refresh something or just let user continue */}}
/>

View File

@@ -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<boolean> {
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;
}
</script>
<Card.Root>
@@ -1159,3 +1190,10 @@
</div>
</Card.Content>
</Card.Root>
<ExchangeRateDialog
bind:open={showExchangeRateDialog}
initialDate={missingExchangeRateDate}
overlayClass="bg-black/20"
onSuccess={() => {/* Optional: maybe refresh something or just let user continue */}}
/>

View File

@@ -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 @@
</div>
</Sidebar.Inset>
</Sidebar.Provider>
<ExchangeRateGuard />

View File

@@ -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<number | null>(data.invoice?.financials?.exchange_rate ?? null);
let showExchangeRateDialog = $state(false);
let missingExchangeRateDate = $state("");
async function checkExchangeRate(date: string): Promise<boolean> {
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 @@
</div>
<!-- Footer fijo en la parte inferior -->
<ExchangeRateDialog
bind:open={showExchangeRateDialog}
initialDate={missingExchangeRateDate}
onSuccess={() => {
// 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;
});
}
}}
/>
<div
class="fixed bottom-0 left-0 right-0 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/80 border-t shadow-lg z-[5] group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] ml-[calc(var(--sidebar-width))]"
>

View File

@@ -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<any>(null); // Todavía útil para otras cosas
let saving = $state(false);
let error = $state<string | null>(null);
let success = $state(false);
// Dialog state lifted up
let showExchangeRateDialog = $state(false);
let missingExchangeRateDate = $state("");
async function checkPaymentDateRate(date: string): Promise<boolean> {
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<number | null>(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 @@
<div class="pb-56">
<Tabs.Content value="general">
<GeneralTabForm
bind:this={generalTabInstance}
pedimento={data.pedimento}
bind:formData={generalFormData}
bind:identificadoresFormData={identificadoresFormData}

View File

@@ -0,0 +1,30 @@
import sys
import os
from datetime import datetime
# Add backend directory to path
sys.path.append('/home/josmar/dev/anexo76/backend')
from api.v1.modules.a76.general_catalogs.exchange_rate.services import ExchangeRateService
from core.database import get_core_db
# from api.v1.common.tenant_crud_routes import get_core_db # This might be needing another import path
db = next(get_core_db())
today = datetime.now().strftime("%Y-%m-%d")
tenant_id = 1
company_id = 1
print(f"Checking for exchange rate on {today}...")
filters = {"date": today}
rates, total = ExchangeRateService.get_all(db, tenant_id, company_id, filters=filters)
if total > 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()