detelles en parametros de facturas, ajuste de posiciones y etiqueta de numero de factura Reviewed-on: ADUANASOFT/anexo76#480 Co-authored-by: hreyes <hreyes@aduanasoft.com.mx> Co-committed-by: hreyes <hreyes@aduanasoft.com.mx>
441 lines
14 KiB
Svelte
441 lines
14 KiB
Svelte
<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 { m } from '$lib/i18n/messages';
|
|
import type { Invoice } from '$lib/api/dashboard/a76/invoices';
|
|
import type { InvoiceType } from '$lib/api/dashboard/reference_data/invoice_types';
|
|
import type { Pedimento } from '$lib/api/dashboard/a76/pedimentos';
|
|
import { cn } from '$lib/utils';
|
|
|
|
let {
|
|
invoice,
|
|
formData = $bindable(),
|
|
invoiceTypes = [],
|
|
pedimentos = [],
|
|
defaultOperationType = undefined,
|
|
defaultInvoiceType = undefined,
|
|
invoiceType = undefined,
|
|
isSettings = false,
|
|
isCreate = false,
|
|
highlightFieldId = null,
|
|
onDismissHighlightForField = undefined
|
|
}: {
|
|
invoice: Invoice | null;
|
|
formData?: any;
|
|
invoiceTypes?: InvoiceType[];
|
|
pedimentos?: Pedimento[];
|
|
defaultOperationType?: string | null;
|
|
defaultInvoiceType?: string | null;
|
|
invoiceType?: string;
|
|
isSettings?: boolean;
|
|
isCreate?: boolean;
|
|
highlightFieldId?: string | null;
|
|
onDismissHighlightForField?: (fieldKey: string) => void;
|
|
} = $props();
|
|
|
|
function hl(id: string) {
|
|
return cn(highlightFieldId === id && 'ring-2 ring-destructive ring-offset-2 rounded-md');
|
|
}
|
|
|
|
function snapshotValueForHighlight(fd: typeof formData, key: string): unknown {
|
|
if (!fd) return undefined;
|
|
switch (key) {
|
|
case 'operation_type':
|
|
return fd.operation_type;
|
|
case 'invoice_type':
|
|
return fd.invoice_type;
|
|
case 'invoice_number':
|
|
return fd.invoice_number;
|
|
case 'invoice_date':
|
|
return fd.invoice_date;
|
|
case 'emission_date':
|
|
return fd.emission_date;
|
|
case 'remesa':
|
|
return fd.remesa;
|
|
case 'pedimento':
|
|
return fd.pedimento_id;
|
|
case 'iva_factor':
|
|
return fd.iva_factor;
|
|
case 'alternate_invoice':
|
|
return fd.alternate_invoice;
|
|
default:
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
let highlightSnapshot = $state<{ key: string; val: unknown } | null>(null);
|
|
|
|
$effect(() => {
|
|
const key = highlightFieldId;
|
|
if (!key || !formData) {
|
|
highlightSnapshot = null;
|
|
return;
|
|
}
|
|
const now = snapshotValueForHighlight(formData, key);
|
|
if (!highlightSnapshot || highlightSnapshot.key !== key) {
|
|
highlightSnapshot = { key, val: now };
|
|
return;
|
|
}
|
|
const same =
|
|
JSON.stringify(now) === JSON.stringify(highlightSnapshot.val) ||
|
|
String(now ?? '') === String(highlightSnapshot.val ?? '');
|
|
if (!same) {
|
|
onDismissHighlightForField?.(key);
|
|
highlightSnapshot = null;
|
|
}
|
|
});
|
|
|
|
function handlePedimentoChange(pedimentoId: string) {
|
|
if (!pedimentoId) return;
|
|
|
|
const selectedPedimento = pedimentos.find((p) => p.id === parseInt(pedimentoId));
|
|
if (!selectedPedimento) return;
|
|
|
|
// Si el tipo de operación está vacío, llenarlo con el del pedimento
|
|
if (selectedPedimento.operation_type && !formData.operation_type) {
|
|
formData.operation_type = selectedPedimento.operation_type;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
$effect(() => {
|
|
if (formData?.pedimento_id && pedimentos.length > 0 && !formData.pedimento) {
|
|
handlePedimentoChange(formData.pedimento_id);
|
|
}
|
|
});
|
|
|
|
// 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 opType: string | null = null;
|
|
if (invoice?.operation_type) {
|
|
opType = invoice.operation_type;
|
|
} else if (defaultOperationType !== undefined) {
|
|
opType = (defaultOperationType as string) ?? 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 || new Date().toISOString().split('T')[0],
|
|
emission_date: new Date().toISOString().split('T')[0],
|
|
operation_type: opType,
|
|
invoice_type: invoice?.invoice_type || (defaultInvoiceType ?? ''),
|
|
fecha_pedimento_del: '',
|
|
fecha_pedimento_al: '',
|
|
clave_pedimento: '',
|
|
regimen_pedimento: '',
|
|
invoice_prefix: '',
|
|
invoice_initial_number: '',
|
|
iva_factor: invoice?.financials?.iva_factor || '',
|
|
alternate_invoice: invoice?.alternate_invoice || ''
|
|
};
|
|
}
|
|
|
|
$effect.pre(() => {
|
|
if (formData) {
|
|
if (
|
|
!formData.operation_type &&
|
|
defaultOperationType !== undefined &&
|
|
defaultOperationType !== null
|
|
) {
|
|
formData.operation_type = defaultOperationType;
|
|
}
|
|
if (formData.iva_factor === undefined)
|
|
formData.iva_factor = invoice?.financials?.iva_factor || '';
|
|
if (formData.alternate_invoice === undefined)
|
|
formData.alternate_invoice = invoice?.alternate_invoice || '';
|
|
}
|
|
});
|
|
|
|
// Filter invoice types based on operation type
|
|
let filteredInvoiceTypes = $derived(
|
|
invoiceTypes.filter((type) => {
|
|
if (!formData.operation_type) return true;
|
|
return type.operation === 'both' || type.operation === formData.operation_type;
|
|
})
|
|
);
|
|
|
|
// Reset invoice_type if not valid for new operation_type
|
|
$effect(() => {
|
|
if (formData.operation_type && formData.invoice_type) {
|
|
const isValid = filteredInvoiceTypes.some((t) => t.key === formData.invoice_type);
|
|
if (!isValid) {
|
|
formData.invoice_type = '';
|
|
}
|
|
}
|
|
});
|
|
|
|
// Limpiar pedimento seleccionado si ya no está en la lista filtrada (p. ej. al cambiar tipo de factura)
|
|
$effect(() => {
|
|
const pid = formData?.pedimento_id;
|
|
const list = pedimentos || [];
|
|
if (pid == null || pid === '') return;
|
|
const id = typeof pid === 'string' ? parseInt(pid, 10) : pid;
|
|
if (Number.isNaN(id)) return;
|
|
const found = list.some((p) => p.id === id);
|
|
if (!found) {
|
|
formData.pedimento_id = null;
|
|
formData.pedimento = '';
|
|
formData.fecha_pedimento_del = '';
|
|
formData.fecha_pedimento_al = '';
|
|
formData.clave_pedimento = '';
|
|
formData.regimen_pedimento = '';
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<!-- Datos Principales en una fila compacta (reusable across tabs) -->
|
|
<div class="flex flex-wrap items-end gap-3 pb-3">
|
|
{#if !isSettings}
|
|
<!-- Campos no manipulables pero visibles -->
|
|
<div
|
|
id="invoice-field-operation_type"
|
|
tabindex="-1"
|
|
class={cn(
|
|
'min-w-[100px] flex-1 space-y-1 rounded-md outline-none',
|
|
hl('operation_type')
|
|
)}
|
|
>
|
|
<Label class="text-xs text-muted-foreground"
|
|
>{m.invoice_edit_form_operation_type_label()}</Label
|
|
>
|
|
<p class="flex h-8 items-center text-sm font-medium">
|
|
{formData.operation_type
|
|
? formData.operation_type === 'exp'
|
|
? m.invoice_edit_form_operation_type_export()
|
|
: m.invoice_edit_form_operation_type_import()
|
|
: '...'}
|
|
</p>
|
|
</div>
|
|
<div
|
|
id="invoice-field-invoice_type"
|
|
tabindex="-1"
|
|
class={cn(
|
|
'min-w-[140px] flex-[1.2] space-y-1 rounded-md outline-none',
|
|
hl('invoice_type')
|
|
)}
|
|
>
|
|
<Label class="text-xs text-muted-foreground"
|
|
>{m.invoice_edit_form_invoice_type_label()}</Label
|
|
>
|
|
{#if isCreate && !isSettings && formData.operation_type === 'exp'}
|
|
<Select.Root
|
|
type="single"
|
|
value={formData.invoice_type || ''}
|
|
onValueChange={(v) => {
|
|
formData.invoice_type = v || '';
|
|
}}
|
|
>
|
|
<Select.Trigger class={cn('h-8 w-full text-sm', hl('invoice_type'))}>
|
|
<span class="truncate">
|
|
{filteredInvoiceTypes.find((t) => t.key === formData.invoice_type)?.description ||
|
|
m.invoice_edit_form_invoice_type_placeholder()}
|
|
</span>
|
|
</Select.Trigger>
|
|
<Select.Content class="max-h-[min(60vh,320px)]">
|
|
{#each filteredInvoiceTypes as t}
|
|
<Select.Item value={t.key}>{t.description}</Select.Item>
|
|
{/each}
|
|
</Select.Content>
|
|
</Select.Root>
|
|
{:else}
|
|
<p class="flex h-8 items-center text-sm font-medium">
|
|
{formData.invoice_type ? `${formData.invoice_type}` : '...'}
|
|
</p>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
|
|
{#if invoiceType !== 'MEX'}
|
|
<div class="flex flex-col items-center space-y-1 pb-1">
|
|
<Label for="is_pedimento_pending" class="text-xs">{m.invoice_edit_form_pedimento_pending()}</Label>
|
|
<Switch
|
|
id="is_pedimento_pending"
|
|
checked={formData.is_pedimento_pending}
|
|
onCheckedChange={(checked) => {
|
|
formData.is_pedimento_pending = checked;
|
|
if (checked) {
|
|
formData.pedimento_id = null;
|
|
formData.pedimento = '';
|
|
formData.remesa = '';
|
|
formData.fecha_pedimento_del = '';
|
|
formData.fecha_pedimento_al = '';
|
|
formData.clave_pedimento = '';
|
|
formData.regimen_pedimento = '';
|
|
}
|
|
}}
|
|
/>
|
|
</div>
|
|
<div class="min-w-[75px] flex-[2] space-y-1">
|
|
<Label for="pedimento" class="text-xs">{m.invoice_edit_form_pedimento_label()}</Label>
|
|
<Select.Root
|
|
type="single"
|
|
value={formData.pedimento_id ? String(formData.pedimento_id) : ''}
|
|
disabled={formData.is_pedimento_pending}
|
|
onValueChange={(v) => {
|
|
formData.pedimento_id = v ? parseInt(v) : null;
|
|
if (v) {
|
|
handlePedimentoChange(v);
|
|
}
|
|
}}
|
|
>
|
|
<Select.Trigger id="pedimento" class={cn('h-8 text-sm', hl('pedimento'))}>
|
|
<span class="truncate">
|
|
{formData.pedimento || m.invoice_edit_form_pedimento_placeholder()}
|
|
</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>
|
|
{/if}
|
|
|
|
<!-- Parámetros legados: NÚMERO FACTURA (prefijo + consecutivo).
|
|
Visible en configuración para todos los tipos de factura (incl. MEX). -->
|
|
{#if isSettings}
|
|
<div class="flex min-w-[220px] flex-col gap-1">
|
|
<span class="text-xs font-semibold text-muted-foreground">Número Factura</span>
|
|
<div class="flex flex-wrap items-end gap-3">
|
|
<div class="min-w-[100px] flex-1 space-y-1">
|
|
<Label for="invoice_prefix" class="text-xs">Prefijo</Label>
|
|
<Input id="invoice_prefix" bind:value={formData.invoice_prefix} class="h-8 text-sm" />
|
|
</div>
|
|
|
|
<div class="min-w-[100px] flex-1 space-y-1">
|
|
<Label for="invoice_initial_number" class="text-xs">Número Inicial</Label>
|
|
<Input
|
|
id="invoice_initial_number"
|
|
bind:value={formData.invoice_initial_number}
|
|
class="h-8 text-sm"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
|
|
{#if invoiceType !== 'MEX'}
|
|
<div class="min-w-[80px] flex-1 space-y-1">
|
|
<Label for="remesa" class="text-xs">{m.invoice_edit_form_remesa_label()}</Label>
|
|
<Input id="remesa" bind:value={formData.remesa} class={cn('h-8 text-sm', hl('remesa'))} />
|
|
</div>
|
|
{/if}
|
|
|
|
{#if isSettings && invoiceType !== 'MEX'}
|
|
<div class="min-w-[90px] flex-1 space-y-1">
|
|
<Label for="clave_pedimento" class="text-xs">Clave</Label>
|
|
<Input id="clave_pedimento" bind:value={formData.clave_pedimento} class="h-8 text-sm" />
|
|
</div>
|
|
|
|
<div class="min-w-[90px] flex-1 space-y-1">
|
|
<Label for="regimen_pedimento" class="text-xs">Régimen</Label>
|
|
<Input id="regimen_pedimento" bind:value={formData.regimen_pedimento} class="h-8 text-sm" />
|
|
</div>
|
|
|
|
<div class="min-w-[140px] flex-[1.5] space-y-1">
|
|
<Label for="fecha_pedimento_del" class="text-xs">Rango de Fechas (Del)</Label>
|
|
<Input
|
|
id="fecha_pedimento_del"
|
|
type="date"
|
|
bind:value={formData.fecha_pedimento_del}
|
|
class="h-8 text-sm"
|
|
/>
|
|
</div>
|
|
|
|
<div class="min-w-[140px] flex-[1.5] space-y-1">
|
|
<Label for="fecha_pedimento_al" class="text-xs">Rango de Fechas (al)</Label>
|
|
<Input
|
|
id="fecha_pedimento_al"
|
|
type="date"
|
|
bind:value={formData.fecha_pedimento_al}
|
|
class="h-8 text-sm"
|
|
/>
|
|
</div>
|
|
{/if}
|
|
|
|
{#if !isSettings}
|
|
<div class="min-w-[140px] flex-[2] space-y-1">
|
|
<Label for="invoice_number" class="text-xs"
|
|
>{m.invoice_edit_form_invoice_number_label_short()} <span class="text-red-500">*</span></Label
|
|
>
|
|
<Input
|
|
id="invoice_number"
|
|
bind:value={formData.invoice_number}
|
|
class={cn('h-8 text-sm font-medium', hl('invoice_number'))}
|
|
required={!isSettings}
|
|
/>
|
|
</div>
|
|
|
|
<div class="min-w-[140px] flex-[2] space-y-1">
|
|
<Label for="invoice_date" class="text-xs">
|
|
{formData.operation_type === 'exp' || invoiceType === 'CR'
|
|
? m.invoice_edit_form_invoice_date_label_exp()
|
|
: invoiceType === 'MEX'
|
|
? m.invoice_edit_form_invoice_date_label_mex()
|
|
: m.invoice_edit_form_invoice_date_label_default()}
|
|
<span class="text-red-500">*</span>
|
|
</Label>
|
|
<Input
|
|
id="invoice_date"
|
|
type="date"
|
|
bind:value={formData.invoice_date}
|
|
class={cn('h-8 text-sm', hl('invoice_date'))}
|
|
/>
|
|
</div>
|
|
|
|
<div class="min-w-[140px] flex-[2] space-y-1">
|
|
<Label for="emission_date" class="text-xs">{m.invoice_edit_form_emission_date_label()}</Label>
|
|
<Input
|
|
id="emission_date"
|
|
type="date"
|
|
bind:value={formData.emission_date}
|
|
class={cn('h-8 text-sm', hl('emission_date'))}
|
|
/>
|
|
</div>
|
|
{/if}
|
|
|
|
{#if invoiceType === 'MEX'}
|
|
<div class="min-w-[90px] flex-1 space-y-1">
|
|
<Label for="iva_factor" class="text-xs">{m.invoice_edit_form_iva_factor_label()}</Label>
|
|
<Input id="iva_factor" bind:value={formData.iva_factor} class="h-8 text-sm" />
|
|
</div>
|
|
<div class="min-w-[140px] flex-[2] space-y-1">
|
|
<Label for="alternate_invoice" class="text-xs">{m.invoice_edit_form_alternate_invoice_label()}</Label>
|
|
<Input id="alternate_invoice" bind:value={formData.alternate_invoice} class="h-8 text-sm" />
|
|
</div>
|
|
{/if}
|
|
</div>
|