Se corrigio solapamientos y asi

This commit is contained in:
2026-02-26 09:24:04 -06:00
parent ccb643426d
commit b1dd237ec1
3 changed files with 567 additions and 588 deletions

View File

@@ -1,15 +1,20 @@
<script lang="ts">
import { Button } from "$lib/components/ui/button";
import * as Dialog from "$lib/components/ui/dialog";
import { Input } from "$lib/components/ui/input";
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 { Button } from '$lib/components/ui/button';
import * as Dialog from '$lib/components/ui/dialog';
import { Input } from '$lib/components/ui/input';
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";
import * as Tabs from '$lib/components/ui/tabs';
import ExchangeRateDialog from '$lib/components/dashboard/exchange_rate/create-edit-dialog.svelte';
let {
open = $bindable(false),
@@ -27,34 +32,34 @@
let formData = $state({
// Header fields
operation_type: "imp" as "imp" | "exp",
invoice_type: "",
invoice_number: "",
project_number: "",
purchase_order: "",
operation_type: 'imp' as 'imp' | 'exp',
invoice_type: '',
invoice_number: '',
project_number: '',
purchase_order: '',
related_doc_id: null as number | null,
invoice_date: "",
traffic_light_status: "",
observation_es: "",
observation_en: "",
comments_status: "",
cfdi_uuid: "",
path_pdf: "",
path_xml: "",
invoice_date: '',
traffic_light_status: '',
observation_es: '',
observation_en: '',
comments_status: '',
cfdi_uuid: '',
path_pdf: '',
path_xml: '',
// Compliance MX fields
remesa: null as number | null,
aduana: "",
customs_broker_id: "",
provider_id: "",
sold_to_id: "",
shipped_to_id: "",
shipped_by_id: "",
aduana: '',
customs_broker_id: '',
provider_id: '',
sold_to_id: '',
shipped_to_id: '',
shipped_by_id: '',
is_mixed: false,
waste_type: "",
waste_type: '',
appendix_17: null as number | null,
edocument: "",
edocument: '',
// Financials fields
currency: "MXN",
currency: 'MXN',
exchange_rate: null as number | null,
value_mn: null as number | null,
value_me: null as number | null,
@@ -73,38 +78,38 @@
let error = $state<string | null>(null);
let showExchangeRateDialog = $state(false);
let missingExchangeRateDate = $state("");
let missingExchangeRateDate = $state('');
// Actualizar formData cuando item cambia
$effect(() => {
if (item) {
formData = {
operation_type: item.operation_type || "imp",
invoice_type: item.invoice_type || "",
invoice_number: item.invoice_number || "",
project_number: item.project_number || "",
purchase_order: item.purchase_order || "",
operation_type: item.operation_type || 'imp',
invoice_type: item.invoice_type || '',
invoice_number: item.invoice_number || '',
project_number: item.project_number || '',
purchase_order: item.purchase_order || '',
related_doc_id: item.related_doc_id || null,
invoice_date: item.invoice_date || "",
traffic_light_status: item.traffic_light_status || "",
observation_es: item.observation_es || "",
observation_en: item.observation_en || "",
comments_status: item.comments_status || "",
cfdi_uuid: item.cfdi_uuid || "",
path_pdf: item.path_pdf || "",
path_xml: item.path_xml || "",
invoice_date: item.invoice_date || '',
traffic_light_status: item.traffic_light_status || '',
observation_es: item.observation_es || '',
observation_en: item.observation_en || '',
comments_status: item.comments_status || '',
cfdi_uuid: item.cfdi_uuid || '',
path_pdf: item.path_pdf || '',
path_xml: item.path_xml || '',
remesa: item.compliance_mx?.remesa || null,
aduana: item.compliance_mx?.aduana || "",
customs_broker_id: item.compliance_mx?.customs_broker_id?.toString() || "",
provider_id: item.compliance_mx?.provider_id?.toString() || "",
sold_to_id: item.compliance_mx?.sold_to_id?.toString() || "",
shipped_to_id: item.compliance_mx?.shipped_to_id?.toString() || "",
shipped_by_id: item.compliance_mx?.shipped_by_id?.toString() || "",
aduana: item.compliance_mx?.aduana || '',
customs_broker_id: item.compliance_mx?.customs_broker_id?.toString() || '',
provider_id: item.compliance_mx?.provider_id?.toString() || '',
sold_to_id: item.compliance_mx?.sold_to_id?.toString() || '',
shipped_to_id: item.compliance_mx?.shipped_to_id?.toString() || '',
shipped_by_id: item.compliance_mx?.shipped_by_id?.toString() || '',
is_mixed: item.compliance_mx?.is_mixed || false,
waste_type: item.compliance_mx?.waste_type || "",
waste_type: item.compliance_mx?.waste_type || '',
appendix_17: item.compliance_mx?.appendix_17 || null,
edocument: item.compliance_mx?.edocument || "",
currency: item.financials?.currency || "MXN",
edocument: item.compliance_mx?.edocument || '',
currency: item.financials?.currency || 'MXN',
exchange_rate: item.financials?.exchange_rate || null,
value_mn: item.financials?.value_mn || null,
value_me: item.financials?.value_me || null,
@@ -127,32 +132,32 @@
function resetForm() {
formData = {
operation_type: defaultOperationType || "imp",
invoice_type: defaultInvoiceType || "",
invoice_number: "",
project_number: "",
purchase_order: "",
operation_type: defaultOperationType || 'imp',
invoice_type: defaultInvoiceType || '',
invoice_number: '',
project_number: '',
purchase_order: '',
related_doc_id: null,
invoice_date: "",
traffic_light_status: "",
observation_es: "",
observation_en: "",
comments_status: "",
cfdi_uuid: "",
path_pdf: "",
path_xml: "",
invoice_date: '',
traffic_light_status: '',
observation_es: '',
observation_en: '',
comments_status: '',
cfdi_uuid: '',
path_pdf: '',
path_xml: '',
remesa: null,
aduana: "",
customs_broker_id: "",
provider_id: "",
sold_to_id: "",
shipped_to_id: "",
shipped_by_id: "",
aduana: '',
customs_broker_id: '',
provider_id: '',
sold_to_id: '',
shipped_to_id: '',
shipped_by_id: '',
is_mixed: false,
waste_type: "",
waste_type: '',
appendix_17: null,
edocument: "",
currency: "MXN",
edocument: '',
currency: 'MXN',
exchange_rate: null,
value_mn: null,
value_me: null,
@@ -170,9 +175,9 @@
async function handleSubmit(e: Event) {
e.preventDefault();
if (!companyStore.activeCompany) {
error = "No hay compañía seleccionada";
error = 'No hay compañía seleccionada';
return;
}
@@ -180,14 +185,14 @@
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;
}
}
// 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) {
@@ -210,7 +215,9 @@
compliance_mx: {
remesa: formData.remesa,
aduana: formData.aduana || null,
customs_broker_id: formData.customs_broker_id ? parseInt(formData.customs_broker_id) : null,
customs_broker_id: formData.customs_broker_id
? parseInt(formData.customs_broker_id)
: null,
provider_id: formData.provider_id ? parseInt(formData.provider_id) : null,
sold_to_id: formData.sold_to_id ? parseInt(formData.sold_to_id) : null,
shipped_to_id: formData.shipped_to_id ? parseInt(formData.shipped_to_id) : null,
@@ -238,9 +245,9 @@
};
response = await invoicesApi.update(item.id, companyStore.activeCompany.id, payload);
} else {
const payload: CreateInvoiceData = {
system: "a76",
document_type: "invoice",
const payload: CreateInvoiceData = {
system: 'a76',
document_type: 'invoice',
operation_type: formData.operation_type,
invoice_type: formData.invoice_type,
invoice_number: formData.invoice_number,
@@ -258,7 +265,9 @@
compliance_mx: {
remesa: formData.remesa,
aduana: formData.aduana || null,
customs_broker_id: formData.customs_broker_id ? parseInt(formData.customs_broker_id) : null,
customs_broker_id: formData.customs_broker_id
? parseInt(formData.customs_broker_id)
: null,
provider_id: formData.provider_id ? parseInt(formData.provider_id) : null,
sold_to_id: formData.sold_to_id ? parseInt(formData.sold_to_id) : null,
shipped_to_id: formData.shipped_to_id ? parseInt(formData.shipped_to_id) : null,
@@ -294,21 +303,23 @@
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
error = null;
const dateMatch = errorStr.match(/(\d{4}-\d{2}-\d{2})/);
missingExchangeRateDate = dateMatch ? dateMatch[0] : (formData.invoice_date || "");
showExchangeRateDialog = true;
return;
}
// 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
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;
}
@@ -316,13 +327,13 @@
}
// 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;
}
}
if (formData.invoice_date) {
const rateExists = await checkExchangeRate(formData.invoice_date);
if (!rateExists) {
loading = false;
return;
}
}
// Éxito
open = false;
@@ -330,8 +341,8 @@
onSuccess();
}
} catch (e) {
error = e instanceof Error ? e.message : "Error al guardar";
console.error("Error saving:", e);
error = e instanceof Error ? e.message : 'Error al guardar';
console.error('Error saving:', e);
} finally {
loading = false;
}
@@ -345,11 +356,11 @@
date: date,
page_size: 1
});
// api.get returns { data: ..., status: ... } and types now reflect that
const items = response.data?.items || [];
// Verificar estrictamente que haya items
// Verificar estrictamente que haya items
if (items.length === 0) {
missingExchangeRateDate = date;
showExchangeRateDialog = true;
@@ -360,7 +371,7 @@
console.error('Error checking exchange rate:', error);
missingExchangeRateDate = date;
showExchangeRateDialog = true;
return false;
return false;
}
}
@@ -374,15 +385,13 @@
</script>
<Dialog.Root {open} onOpenChange={handleOpenChange}>
<Dialog.Content class="sm:max-w-full max-h-[90vh] overflow-y-auto">
<Dialog.Content class="max-h-[90vh] overflow-y-auto sm:max-w-full">
<Dialog.Header>
<Dialog.Title>
{isEditing ? "Editar Factura" : "Nueva Factura"}
{isEditing ? 'Editar Factura' : 'Nueva Factura'}
</Dialog.Title>
<Dialog.Description>
{isEditing
? "Modifica los datos de la factura"
: "Ingresa los datos de la nueva factura"}
{isEditing ? 'Modifica los datos de la factura' : 'Ingresa los datos de la nueva factura'}
</Dialog.Description>
</Dialog.Header>
@@ -397,24 +406,29 @@
<!-- General Tab -->
<Tabs.Content value="general" class="space-y-4">
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
<Label for="operation_type">Tipo de Operación *</Label>
<Select.Root
type="single"
value={formData.operation_type}
onValueChange={(v: string) => {
if (v) formData.operation_type = v as "imp" | "exp";
}}
>
<Select.Trigger>
{formData.operation_type === 'imp' ? 'Importación' : formData.operation_type === 'exp' ? 'Exportación' : 'Seleccionar tipo'}
</Select.Trigger>
<Select.Content>
<Select.Item value="imp">Importación</Select.Item>
<Select.Item value="exp">Exportación</Select.Item>
</Select.Content>
</Select.Root>
</div> <div class="space-y-2">
<div class="space-y-2">
<Label for="operation_type">Tipo de Operación *</Label>
<Select.Root
type="single"
value={formData.operation_type}
onValueChange={(v: string) => {
if (v) formData.operation_type = v as 'imp' | 'exp';
}}
>
<Select.Trigger>
{formData.operation_type === 'imp'
? 'Importación'
: formData.operation_type === 'exp'
? 'Exportación'
: 'Seleccionar tipo'}
</Select.Trigger>
<Select.Content>
<Select.Item value="imp">Importación</Select.Item>
<Select.Item value="exp">Exportación</Select.Item>
</Select.Content>
</Select.Root>
</div>
<div class="space-y-2">
<Label for="invoice_number">Número de Factura</Label>
<Input
id="invoice_number"
@@ -452,11 +466,7 @@
<div class="space-y-2">
<Label for="invoice_date">Fecha de Factura</Label>
<Input
id="invoice_date"
type="date"
bind:value={formData.invoice_date}
/>
<Input id="invoice_date" type="date" bind:value={formData.invoice_date} />
</div>
<div class="space-y-2">
@@ -470,11 +480,7 @@
<div class="space-y-2">
<Label for="cfdi_uuid">CFDI UUID</Label>
<Input
id="cfdi_uuid"
bind:value={formData.cfdi_uuid}
placeholder="UUID del CFDI"
/>
<Input id="cfdi_uuid" bind:value={formData.cfdi_uuid} placeholder="UUID del CFDI" />
</div>
</div>
@@ -514,11 +520,7 @@
<div class="space-y-2">
<Label for="aduana">Aduana</Label>
<Input
id="aduana"
bind:value={formData.aduana}
placeholder="Código de aduana"
/>
<Input id="aduana" bind:value={formData.aduana} placeholder="Código de aduana" />
</div>
<div class="space-y-2">
@@ -548,7 +550,7 @@
/>
</div>
<div class="space-y-2 flex items-center gap-2 pt-8">
<div class="flex items-center gap-2 space-y-2 pt-8">
<input
id="is_mixed"
type="checkbox"
@@ -565,11 +567,7 @@
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
<Label for="currency">Moneda</Label>
<Input
id="currency"
bind:value={formData.currency}
placeholder="MXN, USD, etc."
/>
<Input id="currency" bind:value={formData.currency} placeholder="MXN, USD, etc." />
</div>
<div class="space-y-2">
@@ -696,34 +694,31 @@
</Tabs.Root>
{#if error}
<div class="p-3 text-sm text-destructive bg-destructive/10 rounded-md">
<div class="rounded-md bg-destructive/10 p-3 text-sm text-destructive">
{error}
</div>
{/if}
<Dialog.Footer>
<Button
type="button"
variant="outline"
onclick={() => (open = false)}
disabled={loading}
>
<Button type="button" variant="outline" onclick={() => (open = false)} disabled={loading}>
Cancelar
</Button>
<Button type="submit" disabled={loading}>
{#if loading}
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{/if}
{isEditing ? "Actualizar" : "Crear"}
{isEditing ? 'Actualizar' : 'Crear'}
</Button>
</Dialog.Footer>
</form>
</Dialog.Content>
</Dialog.Root>
<ExchangeRateDialog
bind:open={showExchangeRateDialog}
<ExchangeRateDialog
bind:open={showExchangeRateDialog}
initialDate={missingExchangeRateDate}
overlayClass="bg-black/20"
onSuccess={() => {/* Optional: maybe refresh something or just let user continue */}}
onSuccess={() => {
/* Optional: maybe refresh something or just let user continue */
}}
/>

View File

@@ -8,6 +8,7 @@
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';
import { page } from '$app/state';
let { data, children }: { data: LayoutData; children: any } = $props();
@@ -63,4 +64,6 @@
</Sidebar.Inset>
</Sidebar.Provider>
<ExchangeRateGuard overlayClass="bg-black/5" />
{#if !page.url.pathname.includes('/dashboard/invoices') && !page.url.pathname.includes('/dashboard/pedimentos')}
<ExchangeRateGuard overlayClass="bg-black/5" />
{/if}