Files
plantillas-proyectos/frontend/src/lib/components/dashboard/invoices/edit/invoice-top-fields.svelte
hreyes 96b3beed4f fix/etiquetas-y-ajustes-en-parametros-invoices (#480)
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>
2026-06-02 18:28:33 +00:00

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>