feat: Enhance invoice form data handling with new fields and improve invoice report export filter logic and UI.
This commit is contained in:
@@ -120,7 +120,7 @@ def generate_csv_from_movements(
|
||||
def _format_datetime(dt) -> str:
|
||||
"""Format datetime for CSV export."""
|
||||
if isinstance(dt, datetime):
|
||||
return dt.strftime('%Y-%m-%d %H:%M:%S')
|
||||
return dt.strftime('%Y-%m-%d')
|
||||
elif isinstance(dt, str):
|
||||
return dt
|
||||
return ''
|
||||
|
||||
@@ -1,196 +1,224 @@
|
||||
<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 || '',
|
||||
purchase_order: invoice?.purchase_order || '',
|
||||
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;
|
||||
}
|
||||
}
|
||||
</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-1 space-y-1">
|
||||
<Label for="purchase_order" class="text-xs">Orden de Compra</Label>
|
||||
<Input
|
||||
id="purchase_order"
|
||||
bind:value={formData.purchase_order}
|
||||
class="h-8 text-sm"
|
||||
placeholder="O.C."
|
||||
/>
|
||||
</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_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="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="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>
|
||||
|
||||
@@ -10,12 +10,12 @@
|
||||
import { Plus, Upload } from 'lucide-svelte';
|
||||
import type { Invoice } from '$lib/api/dashboard/a76/invoices';
|
||||
|
||||
let {
|
||||
let {
|
||||
invoice,
|
||||
formData = $bindable(),
|
||||
exists = $bindable(),
|
||||
transportModes = []
|
||||
}: {
|
||||
}: {
|
||||
invoice: Invoice | null;
|
||||
formData?: any;
|
||||
exists?: boolean;
|
||||
@@ -36,11 +36,14 @@
|
||||
electronic_signature: invoice.compliance_mx?.electronic_signature || '',
|
||||
mandatory_person: '',
|
||||
contingency_mode: invoice.compliance_mx?.contingency_mode || false,
|
||||
cove: invoice.compliance_mx?.origin_destination_cove || '',
|
||||
cove: invoice.compliance_mx?.edocument || '',
|
||||
operation_num: invoice.compliance_mx?.vucem_operation_num || '',
|
||||
adendas: invoice.compliance_mx?.addendum_vu || '',
|
||||
observations_vu: invoice.vu_observations || '',
|
||||
certified_number: invoice.compliance_mx?.certificate_number || '',
|
||||
certified_number: invoice.compliance_mx?.certificate_number || '',
|
||||
entry_exit_date: invoice.logistics?.entry_exit_date || '',
|
||||
payment_date: invoice.logistics?.payment_date || '',
|
||||
delivery_date: invoice.logistics?.delivery_date || ''
|
||||
};
|
||||
exists = true;
|
||||
} else if (!formData) {
|
||||
@@ -61,7 +64,10 @@
|
||||
operation_num: '',
|
||||
adendas: '',
|
||||
observations_vu: '',
|
||||
certified_number: '',
|
||||
certified_number: '',
|
||||
entry_exit_date: '',
|
||||
payment_date: '',
|
||||
delivery_date: ''
|
||||
};
|
||||
exists = false;
|
||||
}
|
||||
@@ -70,7 +76,6 @@
|
||||
let rfc = $state('');
|
||||
let curp = $state('');
|
||||
|
||||
|
||||
function loadInfo() {
|
||||
// Función para cargar información
|
||||
console.log('Cargar información');
|
||||
@@ -78,18 +83,18 @@
|
||||
</script>
|
||||
|
||||
<div class="grid grid-cols-3 grid-rows-1 gap-3">
|
||||
<div class="border rounded-md p-3 space-y-3">
|
||||
|
||||
<div class="space-y-3 rounded-md border p-3">
|
||||
<!-- Modo de Transporte -->
|
||||
<div class="space-y-2">
|
||||
<Label for="transport-mode">Modo de Transporte:</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.transport_mode}
|
||||
onValueChange={(value: string | undefined) => formData.transport_mode = value || 'TRUCK'}
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.transport_mode}
|
||||
onValueChange={(value: string | undefined) => (formData.transport_mode = value || 'TRUCK')}
|
||||
>
|
||||
<Select.Trigger id="transport-mode">
|
||||
{transportModes.find(m => m.key === formData.transport_mode)?.name || 'Seleccionar modo'}
|
||||
{transportModes.find((m) => m.key === formData.transport_mode)?.name ||
|
||||
'Seleccionar modo'}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{#each transportModes as mode}
|
||||
@@ -116,9 +121,9 @@
|
||||
<div class="space-y-2">
|
||||
<Label class="opacity-0">Spacer</Label>
|
||||
<Label>Es Mixto?</Label>
|
||||
<RadioGroup
|
||||
value={formData.is_mixed === null ? 'no' : (formData.is_mixed ? 'yes' : 'no')}
|
||||
onValueChange={(v) => formData.is_mixed = v === 'yes'}
|
||||
<RadioGroup
|
||||
value={formData.is_mixed === null ? 'no' : formData.is_mixed ? 'yes' : 'no'}
|
||||
onValueChange={(v) => (formData.is_mixed = v === 'yes')}
|
||||
class="flex gap-4"
|
||||
>
|
||||
<div class="flex items-center space-x-2">
|
||||
@@ -131,7 +136,7 @@
|
||||
</div>
|
||||
</RadioGroup>
|
||||
<Label class="opacity-0">Spacer</Label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center space-x-2">
|
||||
<Checkbox id="rule-3121" bind:checked={formData.rule_3121_parties_ii} />
|
||||
@@ -145,22 +150,18 @@
|
||||
<Textarea
|
||||
id="comments_status"
|
||||
bind:value={formData.comments_status}
|
||||
placeholder="Comentario estatus"
|
||||
placeholder="Comentario estatus"
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border rounded-md p-3 space-y-3 col-span-2">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<div class="col-span-2 space-y-3 rounded-md border p-3">
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
<!-- ID Relación Docs -->
|
||||
<div class="space-y-2">
|
||||
<Label for="relation-docs-id">ID Relación Docs:</Label>
|
||||
<Input
|
||||
id="relation-docs-id"
|
||||
type="number"
|
||||
bind:value={formData.related_doc_id}
|
||||
/>
|
||||
<Input id="relation-docs-id" type="number" bind:value={formData.related_doc_id} />
|
||||
</div>
|
||||
|
||||
<!-- Firma Electrónica -->
|
||||
@@ -189,15 +190,14 @@
|
||||
<div class="space-y-2">
|
||||
<Label id="curp" for="curp">CURP: {curp}</Label>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Modo Contingencia -->
|
||||
<div class="space-y-2 col-span-4">
|
||||
<div class="col-span-4 space-y-2">
|
||||
<div class="flex items-center space-x-2">
|
||||
<Checkbox id="contingency-mode" bind:checked={formData.contingency_mode} />
|
||||
<Label for="contingency-mode" class="font-normal">Modo Contingencia</Label>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- COVE -->
|
||||
<div class="space-y-2">
|
||||
<Label for="cove">COVE:</Label>
|
||||
@@ -220,10 +220,12 @@
|
||||
<div class="space-y-2 md:col-span-2">
|
||||
<Label for="observations-vu">Observaciones VU:</Label>
|
||||
<div class="flex gap-2">
|
||||
<Textarea id="observations-vu" bind:value={formData.observations_vu} class="flex-1 min-h-[60px]" />
|
||||
<Button variant="outline" onclick={loadInfo}>
|
||||
Cargar Info.
|
||||
</Button>
|
||||
<Textarea
|
||||
id="observations-vu"
|
||||
bind:value={formData.observations_vu}
|
||||
class="min-h-[60px] flex-1"
|
||||
/>
|
||||
<Button variant="outline" onclick={loadInfo}>Cargar Info.</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -233,12 +235,26 @@
|
||||
<Input id="certified-num" bind:value={formData.certified_number} />
|
||||
</div>
|
||||
|
||||
<!-- Fechas Logísticas -->
|
||||
<div class="space-y-2">
|
||||
<Label for="entry_exit_date">Fecha Entrada/Salida:</Label>
|
||||
<Input id="entry_exit_date" type="date" bind:value={formData.entry_exit_date} />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="payment_date">Fecha Pago:</Label>
|
||||
<Input id="payment_date" type="date" bind:value={formData.payment_date} />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="delivery_date">Fecha Entrega:</Label>
|
||||
<Input id="delivery_date" type="date" bind:value={formData.delivery_date} />
|
||||
</div>
|
||||
|
||||
<!-- Firma Electrónica 2 -->
|
||||
<div class="space-y-2 md:col-span-2">
|
||||
<Label for="electronic-sig-2">Firma Electrónica:</Label>
|
||||
<Input id="electronic-sig-2" bind:value={formData.electronic_signature} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -45,7 +45,7 @@ export async function saveInvoice(options: SaveInvoiceOptions): Promise<SaveInvo
|
||||
};
|
||||
|
||||
const missingFields: string[] = [];
|
||||
|
||||
|
||||
// Validar campos de InvoiceTopFieldsFormData
|
||||
if (!InvoiceTopFieldsFormData?.operation_type) {
|
||||
missingFields.push(requiredFields.operation_type);
|
||||
@@ -73,11 +73,11 @@ export async function saveInvoice(options: SaveInvoiceOptions): Promise<SaveInvo
|
||||
|
||||
if (isCreate) {
|
||||
// Crear nueva factura con todos sus sub-recursos
|
||||
const response = await invoicesApi.create(companyId, payload as CreateInvoiceData);
|
||||
const response = await invoicesApi.create(companyId, payload as CreateInvoiceData);
|
||||
if (response.error) {
|
||||
const errorMsg = typeof response.error === 'string' ? response.error : 'Error al crear la factura';
|
||||
const error: any = new Error(errorMsg);
|
||||
error.validationErrors = response.validationErrors;
|
||||
error.validationErrors = response.validationErrors;
|
||||
throw error;
|
||||
}
|
||||
if (!response.data?.id) throw new Error('No se recibió el ID de la factura creada');
|
||||
@@ -88,10 +88,10 @@ export async function saveInvoice(options: SaveInvoiceOptions): Promise<SaveInvo
|
||||
} else {
|
||||
// Actualizar factura existente con todos sus sub-recursos
|
||||
const updatePayload = { ...payload, id: invoiceId } as UpdateInvoiceData;
|
||||
const response = await invoicesApi.update(invoiceId!, companyId, updatePayload);
|
||||
const response = await invoicesApi.update(invoiceId!, companyId, updatePayload);
|
||||
if (response.error) {
|
||||
const error: any = new Error(response.error);
|
||||
error.validationErrors = response.validationErrors;
|
||||
error.validationErrors = response.validationErrors;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -114,6 +114,7 @@ function buildInvoicePayload(formData: FormDataSet): CreateInvoiceData | UpdateI
|
||||
invoice_type: InvoiceTopFieldsFormData?.invoice_type || undefined,
|
||||
document_type: generalFormData?.document_type || undefined,
|
||||
invoice_number: InvoiceTopFieldsFormData?.invoice_number || undefined,
|
||||
purchase_order: InvoiceTopFieldsFormData?.purchase_order || undefined,
|
||||
invoice_date: InvoiceTopFieldsFormData?.invoice_date || undefined,
|
||||
emission_date: InvoiceTopFieldsFormData?.emission_date || undefined,
|
||||
// Observation fields from observationFormData
|
||||
@@ -157,7 +158,7 @@ function buildInvoicePayload(formData: FormDataSet): CreateInvoiceData | UpdateI
|
||||
}
|
||||
|
||||
// Logistics
|
||||
payload.logistics = buildLogisticsData(generalFormData, observationFormData, continuationFormData);
|
||||
payload.logistics = buildLogisticsData(generalFormData, observationFormData, continuationFormData, othersFormData);
|
||||
|
||||
// Eliminar campos undefined para no enviarlos
|
||||
Object.keys(payload).forEach(key => {
|
||||
@@ -192,7 +193,8 @@ function buildComplianceMxData(InvoiceTopFieldsFormData: any, generalFormData: a
|
||||
// Fields from othersFormData
|
||||
is_mixed: othersFormData?.is_mixed || null,
|
||||
contingency_mode: othersFormData?.contingency_mode || false,
|
||||
origin_destination_cove: othersFormData?.cove || null,
|
||||
origin_destination_cove: null,
|
||||
edocument: othersFormData?.cove || null,
|
||||
vucem_operation_num: othersFormData?.operation_num || null,
|
||||
addendum_vu: othersFormData?.adendas || null,
|
||||
certificate_number: othersFormData?.certified_number || null,
|
||||
@@ -204,8 +206,9 @@ function buildComplianceMxData(InvoiceTopFieldsFormData: any, generalFormData: a
|
||||
function buildFinancialsData(generalFormData: any, observationFormData: any, othersFormData: any) {
|
||||
return {
|
||||
// Currency from generalFormData
|
||||
currency_type: generalFormData?.currency_type || null,
|
||||
currency_type: generalFormData?.currency_type || (generalFormData?.currency === 'foreign' ? 'USD' : null),
|
||||
currency: generalFormData?.currency || null,
|
||||
exchange_rate: generalFormData?.exchange_rate ? Number(generalFormData.exchange_rate) : null,
|
||||
iva_factor: generalFormData?.iva_factor || null,
|
||||
// Costs & increments from observationFormData
|
||||
freight: observationFormData?.freight || null,
|
||||
@@ -220,13 +223,15 @@ function buildFinancialsData(generalFormData: any, observationFormData: any, oth
|
||||
};
|
||||
}
|
||||
|
||||
function buildLogisticsData(generalFormData: any, observationFormData: any, continuationFormData: any) {
|
||||
function buildLogisticsData(generalFormData: any, observationFormData: any, continuationFormData: any, othersFormData: any) {
|
||||
// Retornar logistics como objeto único con datos de continuación
|
||||
return {
|
||||
carrier_id: generalFormData?.carrier_id || null,
|
||||
transport_id: generalFormData?.transport_id || null,
|
||||
transport_type: generalFormData?.transport_type || 'none',
|
||||
driver_name: generalFormData?.driver_name || null,
|
||||
vehicle_num: generalFormData?.transport_num || continuationFormData?.numero_tipo_transporte || null,
|
||||
license_plate: generalFormData?.transport_num || null,
|
||||
vehicle_num: continuationFormData?.numero_tipo_transporte || null,
|
||||
incoterm: observationFormData?.incoterm || null,
|
||||
// Campos de continuación mapeados a logistics
|
||||
transport_num: continuationFormData?.numero_tipo_transporte || null,
|
||||
@@ -245,5 +250,9 @@ function buildLogisticsData(generalFormData: any, observationFormData: any, cont
|
||||
green_light_us: continuationFormData?.semaforo_verde_aduana_americana || false,
|
||||
red_light_mx: continuationFormData?.semaforo_rojo_aduana_mexicana || false,
|
||||
red_light_us: continuationFormData?.semaforo_rojo_aduana_americana || false,
|
||||
// Fechas Logísticas de OthersTabForm (se pasan en othersFormData)
|
||||
entry_exit_date: othersFormData?.entry_exit_date || null,
|
||||
payment_date: othersFormData?.payment_date || null,
|
||||
delivery_date: othersFormData?.delivery_date || null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -195,6 +195,39 @@
|
||||
}
|
||||
}
|
||||
|
||||
// --- DERIVED STATE FOR EXPORT VALIDATION ---
|
||||
|
||||
/**
|
||||
* Determine which export additional options should be disabled based on main export type selection.
|
||||
* Rules:
|
||||
* - If REP (Reparación) is selected: disable REEXP, DONAC, SCRAP
|
||||
* - If CREG (C. Régimen) is selected: disable REEXP, DONAC, NODES
|
||||
*/
|
||||
const isExportAdditionalDisabled = $derived({
|
||||
REEXP: types.export.main.REP || types.export.main.CREG,
|
||||
DONAC: types.export.main.REP || types.export.main.CREG,
|
||||
SCRAP: types.export.main.REP,
|
||||
NODES: types.export.main.CREG,
|
||||
AFIJO: false,
|
||||
TODAS: false
|
||||
});
|
||||
|
||||
// Auto-uncheck disabled export additional options
|
||||
$effect(() => {
|
||||
if (isExportAdditionalDisabled.REEXP && types.export.additional.REEXP) {
|
||||
types.export.additional.REEXP = false;
|
||||
}
|
||||
if (isExportAdditionalDisabled.DONAC && types.export.additional.DONAC) {
|
||||
types.export.additional.DONAC = false;
|
||||
}
|
||||
if (isExportAdditionalDisabled.SCRAP && types.export.additional.SCRAP) {
|
||||
types.export.additional.SCRAP = false;
|
||||
}
|
||||
if (isExportAdditionalDisabled.NODES && types.export.additional.NODES) {
|
||||
types.export.additional.NODES = false;
|
||||
}
|
||||
});
|
||||
|
||||
// --- VALIDACIONES ---
|
||||
|
||||
function formatDateToYYYYMMDD(dateStr: string): string {
|
||||
@@ -372,8 +405,7 @@
|
||||
const filter = buildAllMovementsFilter();
|
||||
if (!filter) return;
|
||||
|
||||
// Forzar envío de correo para esta acción
|
||||
filter.send_email = true;
|
||||
// Email sending is controlled by config.sendEmail checkbox
|
||||
|
||||
loading = true;
|
||||
// Initial toast
|
||||
@@ -568,6 +600,7 @@
|
||||
allMovementsFilter.export_rep = true;
|
||||
allMovementsFilter.operation_type = null;
|
||||
} else if (types.export.additional.TODAS) {
|
||||
// TODAS for exports: only search export movements
|
||||
allMovementsFilter.import_temp = false;
|
||||
allMovementsFilter.import_def = false;
|
||||
allMovementsFilter.import_rep = false;
|
||||
@@ -1071,14 +1104,14 @@
|
||||
|
||||
<Separator />
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-4">
|
||||
<!-- Importación -->
|
||||
<div>
|
||||
<Label
|
||||
class="mb-2 block border-b pb-1 text-xs font-bold text-muted-foreground uppercase"
|
||||
>Importación</Label
|
||||
>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
{#each Object.keys(types.import) as key}
|
||||
<div class="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
@@ -1095,45 +1128,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Otras -->
|
||||
<!-- Exportación -->
|
||||
<div>
|
||||
<Label
|
||||
class="mb-2 block border-b pb-1 text-xs font-bold text-muted-foreground uppercase"
|
||||
>Otras</Label
|
||||
>Exportación</Label
|
||||
>
|
||||
<div class="flex flex-col gap-2">
|
||||
{#each Object.keys(types.other) as key}
|
||||
<div class="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="other-{key}"
|
||||
class="h-3.5 w-3.5"
|
||||
disabled={types.other.TODAS && key !== 'TODAS'}
|
||||
bind:checked={types.other[key as keyof typeof types.other]}
|
||||
onCheckedChange={(v) => {
|
||||
if (key === 'TODAS') handleTodasChange(v as boolean);
|
||||
}}
|
||||
/>
|
||||
<Label for="other-{key}" class="cursor-pointer text-xs font-normal">
|
||||
{key === 'CREGEXP' ? 'C. REG. EXP.' : key}
|
||||
</Label>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Exportación -->
|
||||
<div class="pt-1">
|
||||
<Label class="mb-2 block border-b pb-1 text-xs font-bold text-muted-foreground uppercase"
|
||||
>Exportación</Label
|
||||
>
|
||||
|
||||
<div class="space-y-3">
|
||||
<!-- Main Types (Checkboxes ahora) -->
|
||||
<div>
|
||||
<Label class="mb-1.5 block text-[10px] font-bold text-muted-foreground uppercase"
|
||||
>Tipo Principal</Label
|
||||
>
|
||||
<div class="space-y-3">
|
||||
<!-- Main Types -->
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<div class="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
@@ -1169,20 +1171,16 @@
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Additional (Checkboxes) -->
|
||||
<div>
|
||||
<Label class="mb-1.5 block text-[10px] font-bold text-muted-foreground uppercase"
|
||||
>Opciones Adicionales</Label
|
||||
>
|
||||
<!-- Additional Options (Sin subtítulo) -->
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
{#each Object.keys(types.export.additional) as key}
|
||||
<div class="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="exp-add-{key}"
|
||||
class="h-3.5 w-3.5"
|
||||
disabled={types.other.TODAS && key !== 'TODAS'}
|
||||
disabled={(types.other.TODAS && key !== 'TODAS') ||
|
||||
isExportAdditionalDisabled[key as keyof typeof isExportAdditionalDisabled]}
|
||||
bind:checked={
|
||||
types.export.additional[key as keyof typeof types.export.additional]
|
||||
}
|
||||
@@ -1195,6 +1193,32 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Otras -->
|
||||
<div>
|
||||
<Label
|
||||
class="mb-2 block border-b pb-1 text-xs font-bold text-muted-foreground uppercase"
|
||||
>Otras</Label
|
||||
>
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
{#each Object.keys(types.other) as key}
|
||||
<div class="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="other-{key}"
|
||||
class="h-3.5 w-3.5"
|
||||
disabled={types.other.TODAS && key !== 'TODAS'}
|
||||
bind:checked={types.other[key as keyof typeof types.other]}
|
||||
onCheckedChange={(v) => {
|
||||
if (key === 'TODAS') handleTodasChange(v as boolean);
|
||||
}}
|
||||
/>
|
||||
<Label for="other-{key}" class="cursor-pointer text-xs font-normal">
|
||||
{key === 'CREGEXP' ? 'C. REG. EXP.' : key}
|
||||
</Label>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
Reference in New Issue
Block a user