Modulo de tipo de cambio inteligente

This commit is contained in:
2026-02-09 11:30:08 -06:00
parent 33a53d95b4
commit b76bd71265
3 changed files with 272 additions and 235 deletions

View File

@@ -1,59 +1,56 @@
<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';
import { onMount } from 'svelte';
import { companyStore } from '$lib/stores/company.svelte';
import { getExchangeRates } from '$lib/api/dashboard/a76/general_catalogs/exchange-rate';
import { page } from '$app/state';
import CreateEditDialog from './create-edit-dialog.svelte';
let open = $state(false);
let checked = $state(false);
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;
}
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));
// Use local date instead of UTC
const today = new Date().toLocaleDateString('fr-CA'); // YYYY-MM-DD
console.log('[ExchangeRateGuard] Date:', today);
// api.get returns { data: ..., status: ... } and types now reflect that
const items = response.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;
}
}
try {
const response = await getExchangeRates(companyStore.activeCompany.id, {
date: today,
page_size: 1
});
console.log('[ExchangeRateGuard] Response (stringified):', JSON.stringify(response, null, 2));
$effect(() => {
if (companyStore.activeCompany?.id && !checked) {
checkExchangeRate();
}
});
// api.get returns { data: ..., status: ... } and types now reflect that
const items = response.data?.items || [];
function handleSuccess() {
console.log('Exchange rate created successfully via guard');
checked = true;
}
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(() => {
const isDashboard = page.url.pathname === '/dashboard' || page.url.pathname === '/dashboard/';
if (companyStore.activeCompany?.id && !checked && isDashboard) {
checkExchangeRate();
}
});
function handleSuccess() {
console.log('Exchange rate created successfully via guard');
checked = true;
}
</script>
<CreateEditDialog
bind:open
onSuccess={handleSuccess}
overlayClass="bg-black/20"
/>
<CreateEditDialog bind:open onSuccess={handleSuccess} overlayClass="bg-black/20" />

View File

@@ -1,196 +1,214 @@
<script lang="ts">
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import * as Select from '$lib/components/ui/select';
import { Switch } from '$lib/components/ui/switch';
import type { Invoice } from '$lib/api/dashboard/a76/invoices';
import type { InvoiceType } from '$lib/api/dashboard/refrence_data/invoice_types';
import type { Pedimento } from '$lib/api/dashboard/a76/pedimentos';
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import * as Select from '$lib/components/ui/select';
import { Switch } from '$lib/components/ui/switch';
import type { Invoice } from '$lib/api/dashboard/a76/invoices';
import type { InvoiceType } from '$lib/api/dashboard/refrence_data/invoice_types';
import type { Pedimento } from '$lib/api/dashboard/a76/pedimentos';
let {
invoice,
formData = $bindable(),
invoiceTypes = [],
pedimentos = [],
defaultOperationType = undefined,
defaultInvoiceType = undefined,
}: {
invoice: Invoice | null;
formData?: any;
invoiceTypes?: InvoiceType[];
pedimentos?: Pedimento[];
defaultOperationType?: string | null;
defaultInvoiceType?: string | null;
} = $props();
let {
invoice,
formData = $bindable(),
invoiceTypes = [],
pedimentos = [],
defaultOperationType = undefined,
defaultInvoiceType = undefined
}: {
invoice: Invoice | null;
formData?: any;
invoiceTypes?: InvoiceType[];
pedimentos?: Pedimento[];
defaultOperationType?: string | null;
defaultInvoiceType?: string | null;
} = $props();
function handlePedimentoChange(pedimentoId: string) {
if (!pedimentoId) return;
const selectedPedimento = pedimentos.find(p => p.id === parseInt(pedimentoId));
if (!selectedPedimento) return;
function handlePedimentoChange(pedimentoId: string) {
if (!pedimentoId) return;
// Actualizar los campos del pedimento en formData
formData.fecha_pedimento_del = selectedPedimento.pedimento_dates?.start_date || '';
formData.fecha_pedimento_al = selectedPedimento.pedimento_dates?.end_date || '';
formData.clave_pedimento = selectedPedimento.pedimento_code || '';
formData.regimen_pedimento = selectedPedimento.regime || '';
// Construir el número de pedimento completo
const pedimentoNumber = `${selectedPedimento.customs_office?.slice(0,2) || ''}-${selectedPedimento.license || ''}-${selectedPedimento.pedimento_number || ''}`.replace(/^-+|-+$/g, '');
formData.pedimento = pedimentoNumber;
}
const selectedPedimento = pedimentos.find((p) => p.id === parseInt(pedimentoId));
if (!selectedPedimento) return;
$effect(() => {
if (formData?.pedimento_id && pedimentos.length > 0 && !formData.pedimento) {
handlePedimentoChange(formData.pedimento_id);
}
});
// Actualizar los campos del pedimento en formData
formData.fecha_pedimento_del = selectedPedimento.pedimento_dates?.start_date || '';
formData.fecha_pedimento_al = selectedPedimento.pedimento_dates?.end_date || '';
formData.clave_pedimento = selectedPedimento.pedimento_code || '';
formData.regimen_pedimento = selectedPedimento.regime || '';
// 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;
}
}
});
// Construir el número de pedimento completo
const pedimentoNumber =
`${selectedPedimento.customs_office?.slice(0, 2) || ''}-${selectedPedimento.license || ''}-${selectedPedimento.pedimento_number || ''}`.replace(
/^-+|-+$/g,
''
);
formData.pedimento = pedimentoNumber;
}
if (!formData) {
let operationType: string | null = null;
if (invoice?.operation_type) {
operationType = invoice.operation_type;
} else if (defaultOperationType !== undefined) {
operationType = defaultOperationType ?? null;
}
$effect(() => {
if (formData?.pedimento_id && pedimentos.length > 0 && !formData.pedimento) {
handlePedimentoChange(formData.pedimento_id);
}
});
formData = {
is_pedimento_pending: false,
pedimento_id: invoice?.compliance_mx?.pedimento_id || '',
remesa: invoice?.compliance_mx?.remesa || '',
invoice_number: invoice?.invoice_number || '',
invoice_date: invoice?.invoice_date || new Date().toISOString().split('T')[0],
emission_date: new Date().toISOString().split('T')[0],
operation_type: operationType,
invoice_type: invoice?.invoice_type || (defaultInvoiceType ?? ''),
// Campos del pedimento (se llenarán al seleccionar un pedimento)
fecha_pedimento_del: '',
fecha_pedimento_al: '',
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;
}
}
// 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) {
operationType = invoice.operation_type;
} else if (defaultOperationType !== undefined) {
operationType = defaultOperationType ?? null;
}
formData = {
is_pedimento_pending: false,
pedimento_id: invoice?.compliance_mx?.pedimento_id || '',
remesa: invoice?.compliance_mx?.remesa || '',
invoice_number: invoice?.invoice_number || '',
invoice_date:
invoice?.invoice_date || (invoice ? '' : new Date().toISOString().split('T')[0]),
emission_date: invoice?.emission_date || new Date().toISOString().split('T')[0],
operation_type: operationType,
invoice_type: invoice?.invoice_type || (defaultInvoiceType ?? ''),
// Campos del pedimento (se llenarán al seleccionar un pedimento)
fecha_pedimento_del: '',
fecha_pedimento_al: '',
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;
}
}
</script>
<!-- Datos Principales en una fila compacta (reusable across tabs) -->
<div class="grid grid-cols-12 gap-3 items-end pb-3">
<div class="col-span-1 space-y-1">
<Label for="operation_type" class="text-xs">Tipo de Operación<span class="text-red-500">*</span></Label>
<Select.Root
type="single"
value={formData.operation_type || ''}
onValueChange={(v) => {
formData.operation_type = v;
}}
>
<Select.Trigger id="operation_type" class="h-8 text-sm">
<span class="truncate">
{formData.operation_type
? (formData.operation_type === 'exp' ? 'Exp' : 'Imp')
: '...'}
</span>
</Select.Trigger>
<Select.Content>
<Select.Item value="exp">Exportación</Select.Item>
<Select.Item value="imp">Importación</Select.Item>
</Select.Content>
</Select.Root>
</div>
<div class="col-span-1 space-y-1">
<Label for="operation_type" class="text-xs">Tipo de factura <span class="text-red-500">*</span></Label>
<Select.Root
type="single"
value={formData.invoice_type || ''}
onValueChange={(v) => {
formData.invoice_type = v ?? '';
}}
>
<Select.Trigger id="invoice_type" class="h-7 text-xs">
<span class="truncate">
{formData.invoice_type
? `${formData.invoice_type}`
: '...'}
</span>
</Select.Trigger>
<Select.Content class="max-h-[300px]">
{#each invoiceTypes as type}
<Select.Item value={type.key}>
{type.key} - {type.description}
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
<div class="grid grid-cols-12 items-end gap-3 pb-3">
<div class="col-span-1 space-y-1">
<Label for="operation_type" class="text-xs"
>Tipo de Operación<span class="text-red-500">*</span></Label
>
<Select.Root
type="single"
value={formData.operation_type || ''}
onValueChange={(v) => {
formData.operation_type = v;
}}
>
<Select.Trigger id="operation_type" class="h-8 text-sm">
<span class="truncate">
{formData.operation_type ? (formData.operation_type === 'exp' ? 'Exp' : 'Imp') : '...'}
</span>
</Select.Trigger>
<Select.Content>
<Select.Item value="exp">Exportación</Select.Item>
<Select.Item value="imp">Importación</Select.Item>
</Select.Content>
</Select.Root>
</div>
<div class="col-span-1 space-y-1">
<Label for="operation_type" class="text-xs"
>Tipo de factura <span class="text-red-500">*</span></Label
>
<Select.Root
type="single"
value={formData.invoice_type || ''}
onValueChange={(v) => {
formData.invoice_type = v ?? '';
}}
>
<Select.Trigger id="invoice_type" class="h-7 text-xs">
<span class="truncate">
{formData.invoice_type ? `${formData.invoice_type}` : '...'}
</span>
</Select.Trigger>
<Select.Content class="max-h-[300px]">
{#each invoiceTypes as type}
<Select.Item value={type.key}>
{type.key} - {type.description}
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
<div class="col-span-1 space-y-1 pb-1 items-center flex flex-col">
<Label for="is_pedimento_pending" class="text-xs">Pedimento Pendiente?</Label>
<Switch
id="is_pedimento_pending"
checked={formData.is_pedimento_pending}
onCheckedChange={(checked) => {
formData.is_pedimento_pending = checked;
}}
/>
</div>
<div class="col-span-2 space-y-1">
<Label for="pedimento" class="text-xs">Pedimento</Label>
<Select.Root
type="single"
value={formData.pedimento_id ? String(formData.pedimento_id) : ''}
onValueChange={(v) => {
formData.pedimento_id = v ? parseInt(v) : null;
if (v) {
handlePedimentoChange(v);
}
}}
>
<Select.Trigger id="pedimento" class="h-8 text-sm">
<span class="truncate">
{formData.pedimento || 'Selecciona pedimento...'}
</span>
</Select.Trigger>
<Select.Content class="max-h-[300px]">
{#each pedimentos as pedimento}
<Select.Item value={String(pedimento.id)}>
{pedimento.customs_office?.slice(0, 2)}-{pedimento.license}-{pedimento.pedimento_number}
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
<div class="col-span-1 flex flex-col items-center space-y-1 pb-1">
<Label for="is_pedimento_pending" class="text-xs">Pedimento Pendiente?</Label>
<Switch
id="is_pedimento_pending"
checked={formData.is_pedimento_pending}
onCheckedChange={(checked) => {
formData.is_pedimento_pending = checked;
}}
/>
</div>
<div class="col-span-2 space-y-1">
<Label for="pedimento" class="text-xs">Pedimento</Label>
<Select.Root
type="single"
value={formData.pedimento_id ? String(formData.pedimento_id) : ''}
onValueChange={(v) => {
formData.pedimento_id = v ? parseInt(v) : null;
if (v) {
handlePedimentoChange(v);
}
}}
>
<Select.Trigger id="pedimento" class="h-8 text-sm">
<span class="truncate">
{formData.pedimento || 'Selecciona pedimento...'}
</span>
</Select.Trigger>
<Select.Content class="max-h-[300px]">
{#each pedimentos as pedimento}
<Select.Item value={String(pedimento.id)}>
{pedimento.customs_office?.slice(0, 2)}-{pedimento.license}-{pedimento.pedimento_number}
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
<div class="col-span-1 space-y-1">
<Label for="remesa" class="text-xs">Remesa</Label>
<Input id="remesa" bind:value={formData.remesa} class="h-8 text-sm" />
</div>
<div class="col-span-1 space-y-1">
<Label for="remesa" class="text-xs">Remesa</Label>
<Input id="remesa" bind:value={formData.remesa} class="h-8 text-sm" />
</div>
<div class="col-span-2 space-y-1">
<Label for="invoice_number" class="text-xs">Núm. Factura <span class="text-red-500">*</span></Label>
<Input id="invoice_number" bind:value={formData.invoice_number} class="h-8 text-sm font-medium" required />
</div>
<div class="col-span-2 space-y-1">
<Label for="invoice_number" class="text-xs"
>Núm. Factura <span class="text-red-500">*</span></Label
>
<Input
id="invoice_number"
bind:value={formData.invoice_number}
class="h-8 text-sm font-medium"
required
/>
</div>
<div class="col-span-2 space-y-1">
<Label for="invoice_date" class="text-xs">Fecha Factura <span class="text-red-500">*</span></Label>
<Input id="invoice_date" type="date" bind:value={formData.invoice_date} class="h-8 text-sm" />
</div>
<div class="col-span-2 space-y-1">
<Label for="invoice_date" class="text-xs"
>Fecha Factura <span class="text-red-500">*</span></Label
>
<Input id="invoice_date" type="date" bind:value={formData.invoice_date} class="h-8 text-sm" />
</div>
<div class="col-span-2 space-y-1">
<Label for="emission_date" class="text-xs">Fecha Emisión</Label>
<Input id="emission_date" type="date" bind:value={formData.emission_date} class="h-8 text-sm" />
</div>
<div class="col-span-2 space-y-1">
<Label for="emission_date" class="text-xs">Fecha Emisión</Label>
<Input id="emission_date" type="date" bind:value={formData.emission_date} class="h-8 text-sm" />
</div>
</div>