feat: Enhance invoice form data handling with new fields and improve invoice report export filter logic and UI.

This commit is contained in:
Galindo97
2026-02-09 12:57:20 -06:00
parent 84b507fbba
commit 893c154b85
5 changed files with 348 additions and 271 deletions

View File

@@ -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 ''

View File

@@ -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>

View File

@@ -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>

View File

@@ -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,
};
}

View File

@@ -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>