feat: Update Pedimento schemas and frontend components to enforce required fields and improve data handling
This commit is contained in:
@@ -8,7 +8,7 @@ class PedimentoDatesBase(BaseModel):
|
||||
"""Base schema for Pedimento Dates"""
|
||||
|
||||
entry_date: Optional[datetime] = Field(None, description="Entry date")
|
||||
payment_date: datetime = Field(None, description="Payment date")
|
||||
payment_date: datetime = Field(..., description="Payment date")
|
||||
rectification_payment_date: Optional[datetime] = Field(
|
||||
None, description="Rectification payment date"
|
||||
)
|
||||
|
||||
@@ -11,8 +11,8 @@ class PedimentoTransportMeansBase(BaseModel):
|
||||
tenant_id: int = Field(..., description="Tenant ID")
|
||||
destination: Optional[int] = Field(None, description="Destination")
|
||||
entry_exit: Optional[str] = Field(None, max_length=2, description="Entry/exit")
|
||||
arrival: Optional[str] = Field(None, max_length=2, description="Arrival")
|
||||
departure: Optional[str] = Field(None, max_length=2, description="Departure")
|
||||
arrival: str = Field(..., max_length=2, description="Arrival")
|
||||
departure: str = Field(..., max_length=2, description="Departure")
|
||||
|
||||
|
||||
class PedimentoTransportMeansCreate(BaseModel):
|
||||
@@ -20,8 +20,8 @@ class PedimentoTransportMeansCreate(BaseModel):
|
||||
|
||||
destination: Optional[int] = Field(None, description="Destination")
|
||||
entry_exit: Optional[str] = Field(None, max_length=2, description="Entry/exit")
|
||||
arrival: Optional[str] = Field(None, max_length=2, description="Arrival")
|
||||
departure: Optional[str] = Field(None, max_length=2, description="Departure")
|
||||
arrival: str = Field(..., max_length=2, description="Arrival")
|
||||
departure: str = Field(..., max_length=2, description="Departure")
|
||||
|
||||
|
||||
class PedimentoTransportMeansUpdate(BaseModel):
|
||||
|
||||
@@ -33,7 +33,7 @@ class PedimentosBase(BaseModel):
|
||||
|
||||
year: Optional[str] = Field(None, max_length=2, description="Year")
|
||||
customs_office: Optional[str] = Field(
|
||||
None, max_length=2, description="Customs office"
|
||||
None, max_length=3, description="Customs office"
|
||||
)
|
||||
license: Optional[str] = Field(None, max_length=4, description="License")
|
||||
pedimento_number: Optional[str] = Field(
|
||||
@@ -41,11 +41,11 @@ class PedimentosBase(BaseModel):
|
||||
)
|
||||
client_id: Optional[int] = Field(None, description="Client ID")
|
||||
operation_type: Optional[int] = Field(None, description="Operation type")
|
||||
pedimento_type: Optional[int] = Field(None, description="Pedimento type")
|
||||
pedimento_code: Optional[str] = Field(
|
||||
None, max_length=2, description="Pedimento key"
|
||||
pedimento_type: Optional[str] = Field(None, max_length=20, description="Pedimento type")
|
||||
pedimento_code: str = Field(
|
||||
..., max_length=2, description="Pedimento key"
|
||||
)
|
||||
regime: Optional[str] = Field(None, max_length=3, description="Regime")
|
||||
regime: str = Field(..., max_length=3, description="Regime")
|
||||
status: Optional[str] = Field(None, max_length=30, description="Status")
|
||||
usd_value: Optional[Decimal] = Field(None, description="USD value")
|
||||
paid_price: Optional[Decimal] = Field(None, description="Paid price")
|
||||
@@ -57,14 +57,17 @@ class PedimentosCreate(PedimentosBase):
|
||||
|
||||
# Override to make required fields non-optional
|
||||
year: str = Field(..., max_length=2, description="Year")
|
||||
customs_office: str = Field(..., max_length=2, description="Customs office")
|
||||
customs_office: str = Field(..., max_length=3, description="Customs office")
|
||||
license: str = Field(..., max_length=4, description="License")
|
||||
pedimento_number: str = Field(..., max_length=7, description="Pedimento number")
|
||||
client_id: int = Field(..., description="Client ID")
|
||||
operation_type: int = Field(..., description="Operation type")
|
||||
pedimento_type: int = Field(..., description="Pedimento type")
|
||||
pedimento_type: str = Field(..., max_length=20, description="Pedimento type")
|
||||
pedimento_code: str = Field(
|
||||
..., max_length=2, description="Pedimento key"
|
||||
)
|
||||
regime: str = Field(..., max_length=3, description="Regime")
|
||||
status: str = Field(..., max_length=30, description="Status")
|
||||
status: str = Field(..., max_length=30, description="Status")
|
||||
|
||||
pedimento_dates: Optional[PedimentoDatesCreate] = None
|
||||
pedimento_decrementables: Optional[PedimentoDecrementablesCreate] = None
|
||||
@@ -87,12 +90,12 @@ class PedimentosUpdate(BaseModel):
|
||||
"""Schema for updating a Pedimento"""
|
||||
|
||||
year: Optional[str] = Field(None, max_length=2)
|
||||
customs_office: Optional[str] = Field(None, max_length=2)
|
||||
customs_office: Optional[str] = Field(None, max_length=3)
|
||||
license: Optional[str] = Field(None, max_length=4)
|
||||
pedimento_number: Optional[str] = Field(None, max_length=7)
|
||||
client_id: Optional[int] = None
|
||||
operation_type: Optional[int] = None
|
||||
pedimento_type: Optional[int] = None
|
||||
pedimento_type: Optional[str] = Field(None, max_length=20)
|
||||
pedimento_code: Optional[str] = Field(None, max_length=2)
|
||||
regime: Optional[str] = Field(None, max_length=3)
|
||||
status: Optional[str] = Field(None, max_length=30)
|
||||
|
||||
@@ -94,12 +94,12 @@ class Pedimentos(Base, TenantScopedMixin, TimestampMixin):
|
||||
id: Mapped[int] = mapped_column(Integer)
|
||||
|
||||
year: Mapped[str] = mapped_column(String(2))
|
||||
customs_office: Mapped[str] = mapped_column(String(2))
|
||||
customs_office: Mapped[str] = mapped_column(String(3))
|
||||
license: Mapped[str] = mapped_column(String(4))
|
||||
pedimento_number: Mapped[str] = mapped_column(String(7))
|
||||
client_id: Mapped[int] = mapped_column(Integer)
|
||||
operation_type: Mapped[int] = mapped_column(Integer)
|
||||
pedimento_type: Mapped[int] = mapped_column(Integer)
|
||||
pedimento_type: Mapped[str] = mapped_column(String(20))
|
||||
pedimento_code: Mapped[str] = mapped_column(String(2))
|
||||
regime: Mapped[str] = mapped_column(String(3))
|
||||
status: Mapped[str] = mapped_column(String(30))
|
||||
|
||||
44
frontend/src/lib/api/dashboard/a76/exchange-rate.ts
Normal file
44
frontend/src/lib/api/dashboard/a76/exchange-rate.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* API Client para Exchange Rate
|
||||
*/
|
||||
import { api } from '$lib/api';
|
||||
|
||||
export interface ExchangeRate {
|
||||
id: number;
|
||||
date: string;
|
||||
value: number;
|
||||
local_currency: string | null;
|
||||
foreign_currency: string | null;
|
||||
company_id: number;
|
||||
tenant_id: number;
|
||||
}
|
||||
|
||||
export interface ExchangeRateListResponse {
|
||||
items: ExchangeRate[];
|
||||
total: number;
|
||||
page: number;
|
||||
size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get exchange rate by date
|
||||
*/
|
||||
export async function getExchangeRateByDate(date: string): Promise<ExchangeRate | null> {
|
||||
try {
|
||||
// Convert date to ISO format with time for backend datetime field
|
||||
const dateWithTime = `${date}T00:00:00`;
|
||||
const response = await api.get<ExchangeRateListResponse>(`/v1/a76/exchange-rate?date=${dateWithTime}`);
|
||||
|
||||
if (response.data && response.data.items && response.data.items.length > 0) {
|
||||
// Find USD exchange rate
|
||||
const usdRate = response.data.items.find(rate => rate.foreign_currency === 'USD');
|
||||
return usdRate || null;
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('Error fetching exchange rate:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,7 @@ export interface Pedimento {
|
||||
pedimento_number?: string | null;
|
||||
client_id?: number | null;
|
||||
operation_type?: number | null;
|
||||
pedimento_type?: number | null;
|
||||
pedimento_type?: string | null;
|
||||
pedimento_code?: string | null;
|
||||
regime?: string | null;
|
||||
status?: string | null;
|
||||
@@ -89,7 +89,7 @@ export interface CreatePedimentoData {
|
||||
pedimento_number?: string | null;
|
||||
client_id?: number | null;
|
||||
operation_type?: number | null;
|
||||
pedimento_type?: number | null;
|
||||
pedimento_type?: string | null;
|
||||
pedimento_code?: string | null;
|
||||
regime?: string | null;
|
||||
status?: string | null;
|
||||
@@ -111,7 +111,7 @@ export interface UpdatePedimentoData {
|
||||
pedimento_number?: string | null;
|
||||
client_id?: number | null;
|
||||
operation_type?: number | null;
|
||||
pedimento_type?: number | null;
|
||||
pedimento_type?: string | null;
|
||||
pedimento_code?: string | null;
|
||||
regime?: string | null;
|
||||
status?: string | null;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import * as Select from '$lib/components/ui/select';
|
||||
import { getExchangeRateByDate } from '$lib/api/dashboard/a76/exchange-rate';
|
||||
import type { Pedimento } from '$lib/api/dashboard/a76/pedimentos';
|
||||
import type { PedimentoCode } from '$lib/api/dashboard/refrence_data/pedimento_codes';
|
||||
import type { CustomsSection } from '$lib/api/dashboard/refrence_data/customs_sections';
|
||||
@@ -52,77 +53,96 @@
|
||||
return null;
|
||||
}
|
||||
|
||||
// Opciones constantes
|
||||
const operationOptions = [
|
||||
{ value: 1, label: 'Exportación' },
|
||||
{ value: 2, label: 'Importación' },
|
||||
];
|
||||
|
||||
// Opciones filtradas para Régimen y Tipo de Operación basadas en las selecciones actuales
|
||||
// NOTA: La Clave NO se filtra, siempre muestra todas las opciones
|
||||
|
||||
const filteredRegimens = $derived(() => {
|
||||
const filteredRegimens = $derived.by(() => {
|
||||
if (!formData) return uniqueRegimens;
|
||||
|
||||
// Si hay clave o tipo de operación seleccionado, filtrar
|
||||
if (formData.pedimento_code || formData.operation_type !== null) {
|
||||
if (formData.pedimento_code || formData.operation_type !== null && formData.operation_type !== undefined) {
|
||||
const expectedTypeCode = operationTypeToTypeCode(formData.operation_type);
|
||||
|
||||
const matches = codePedimentoRegimens.filter(r => {
|
||||
const matchesCode = !formData.pedimento_code || r.pedimento_code === formData.pedimento_code;
|
||||
const matchesType = formData.operation_type === null || r.type_code === operationTypeToTypeCode(formData.operation_type);
|
||||
const matchesType = !expectedTypeCode || r.type_code === expectedTypeCode;
|
||||
return matchesCode && matchesType;
|
||||
});
|
||||
|
||||
const validRegimens = new Set(matches.map(m => m.regimen_code).filter((code): code is string => code !== null));
|
||||
return Array.from(validRegimens).sort().map(code => ({ code, label: code }));
|
||||
}
|
||||
return uniqueRegimens;
|
||||
});
|
||||
|
||||
const filteredOperationTypes = $derived(() => {
|
||||
// Si hay clave o régimen seleccionado, filtrar
|
||||
if (formData.pedimento_code || formData.regime) {
|
||||
const matches = codePedimentoRegimens.filter(r => {
|
||||
const matchesCode = !formData.pedimento_code || r.pedimento_code === formData.pedimento_code;
|
||||
const matchesRegime = !formData.regime || r.regimen_code === formData.regime;
|
||||
return matchesCode && matchesRegime;
|
||||
});
|
||||
// Determinar si el régimen debe ser seleccionable o readonly
|
||||
const hasMultipleRegimens = $derived.by(() => {
|
||||
if (!formData) return false;
|
||||
return filteredRegimens.length > 1;
|
||||
});
|
||||
|
||||
const filteredOperationTypes = $derived.by(() => {
|
||||
if (!formData) return operationOptions;
|
||||
// Solo filtrar por la clave del pedimento (no por régimen)
|
||||
// Esto permite cambiar entre Exportación e Importación libremente
|
||||
if (formData.pedimento_code) {
|
||||
const matches = codePedimentoRegimens.filter(r => r.pedimento_code === formData.pedimento_code);
|
||||
const validTypes = new Set(matches.map(m => typeCodeToOperationType(m.type_code)).filter(t => t !== null));
|
||||
return operationOptions.filter(opt => validTypes.has(opt.value));
|
||||
}
|
||||
return operationOptions;
|
||||
});
|
||||
|
||||
// Track previous values to detect changes
|
||||
let previousPedimentoCode = $state(formData?.pedimento_code || '');
|
||||
let previousOperationType = $state(formData?.operation_type ?? null);
|
||||
|
||||
// Reactive synchronization between Clave, Régimen, and Tipo de Operación
|
||||
// REGLA: La Clave es el campo principal y NUNCA se modifica automáticamente
|
||||
// Solo se auto-llenan Régimen y Tipo de Operación basándose en la Clave
|
||||
|
||||
// Cuando cambia la Clave del Pedimento
|
||||
$effect(() => {
|
||||
if (!formData) return;
|
||||
|
||||
const currentCode = formData.pedimento_code;
|
||||
|
||||
// Detectar si la clave cambió
|
||||
const codeChanged = currentCode !== previousPedimentoCode;
|
||||
|
||||
// Solo procesar si la clave cambió
|
||||
if (!codeChanged) return;
|
||||
|
||||
// Actualizar el tracking
|
||||
previousPedimentoCode = currentCode;
|
||||
|
||||
if (!currentCode) return;
|
||||
|
||||
const matches = codePedimentoRegimens.filter(r => r.pedimento_code === currentCode);
|
||||
|
||||
if (matches.length === 0) return;
|
||||
|
||||
// Verificar si los valores actuales de régimen y tipo son válidos para esta clave
|
||||
const currentIsValid = matches.some(m => {
|
||||
const matchesRegime = !formData.regime || m.regimen_code === formData.regime;
|
||||
const matchesType = formData.operation_type === null || m.type_code === operationTypeToTypeCode(formData.operation_type);
|
||||
return matchesRegime && matchesType;
|
||||
});
|
||||
|
||||
// Si los valores actuales son válidos, NO auto-llenar
|
||||
if (currentIsValid && (formData.regime || formData.operation_type !== null)) {
|
||||
return;
|
||||
// Auto-llenar con el primer match
|
||||
const firstMatch = matches[0];
|
||||
if (firstMatch.regimen_code) {
|
||||
formData.regime = firstMatch.regimen_code;
|
||||
}
|
||||
|
||||
// Si solo hay un match y no hay valores válidos, auto-llenar
|
||||
if (matches.length === 1) {
|
||||
const match = matches[0];
|
||||
if (match.regimen_code && formData.regime !== match.regimen_code) {
|
||||
formData.regime = match.regimen_code;
|
||||
}
|
||||
const expectedOpType = typeCodeToOperationType(match.type_code);
|
||||
if (expectedOpType !== null && formData.operation_type !== expectedOpType) {
|
||||
formData.operation_type = expectedOpType;
|
||||
}
|
||||
const expectedOpType = typeCodeToOperationType(firstMatch.type_code);
|
||||
if (expectedOpType !== null) {
|
||||
formData.operation_type = expectedOpType;
|
||||
}
|
||||
});
|
||||
|
||||
// Cuando cambia el Régimen
|
||||
$effect(() => {
|
||||
if (!formData) return;
|
||||
|
||||
const currentRegime = formData.regime;
|
||||
if (!currentRegime) return;
|
||||
|
||||
@@ -140,31 +160,40 @@
|
||||
|
||||
// Cuando cambia el Tipo de Operación
|
||||
$effect(() => {
|
||||
if (!formData) return;
|
||||
|
||||
const currentType = formData.operation_type;
|
||||
|
||||
// Detectar si el tipo de operación cambió
|
||||
const typeChanged = currentType !== previousOperationType;
|
||||
previousOperationType = currentType;
|
||||
|
||||
// Si no hay clave seleccionada, no hacer nada
|
||||
if (!formData.pedimento_code) return;
|
||||
|
||||
// Si el tipo es null/undefined, no hacer nada
|
||||
if (currentType === null || currentType === undefined) return;
|
||||
|
||||
const expectedTypeCode = operationTypeToTypeCode(currentType);
|
||||
const matches = codePedimentoRegimens.filter(r => r.type_code === expectedTypeCode);
|
||||
|
||||
// Buscar matches para esta combinación de clave + tipo de operación
|
||||
const matches = codePedimentoRegimens.filter(r =>
|
||||
r.pedimento_code === formData.pedimento_code &&
|
||||
r.type_code === expectedTypeCode
|
||||
);
|
||||
|
||||
if (matches.length === 0) return;
|
||||
|
||||
// Si hay clave seleccionada, actualizar régimen (forzar si no hay match exacto)
|
||||
if (formData.pedimento_code) {
|
||||
const exactMatch = matches.find(m => m.pedimento_code === formData.pedimento_code);
|
||||
if (exactMatch?.regimen_code && formData.regime !== exactMatch.regimen_code) {
|
||||
formData.regime = exactMatch.regimen_code;
|
||||
} else if (!exactMatch) {
|
||||
// No hay match exacto - buscar cualquier match con la clave actual
|
||||
const allMatchesForClave = codePedimentoRegimens.filter(r => r.pedimento_code === formData.pedimento_code);
|
||||
if (allMatchesForClave.length > 0) {
|
||||
// Forzar el régimen al primer match disponible para esta clave
|
||||
const firstMatch = allMatchesForClave[0];
|
||||
if (firstMatch.regimen_code) formData.regime = firstMatch.regimen_code;
|
||||
}
|
||||
// Obtener regímenes válidos para esta combinación
|
||||
const validRegimens = new Set(matches.map(m => m.regimen_code).filter((code): code is string => code !== null));
|
||||
|
||||
// Si el tipo de operación cambió O el régimen actual no es válido, actualizar el régimen
|
||||
if (typeChanged || !formData.regime || !validRegimens.has(formData.regime)) {
|
||||
const firstMatch = matches[0];
|
||||
if (firstMatch?.regimen_code) {
|
||||
formData.regime = firstMatch.regimen_code;
|
||||
}
|
||||
}
|
||||
// Si hay régimen pero no clave, no hacer nada
|
||||
// (el usuario debe seleccionar la clave primero)
|
||||
});
|
||||
|
||||
// Obtener el año actual (últimos 2 dígitos)
|
||||
@@ -180,7 +209,7 @@
|
||||
pedimento_number: pedimento?.pedimento_number || '',
|
||||
client_id: pedimento?.client_id ?? null,
|
||||
operation_type: pedimento?.operation_type ?? null,
|
||||
pedimento_type: pedimento?.pedimento_type ?? null,
|
||||
pedimento_type: pedimento?.pedimento_type || '',
|
||||
pedimento_code: pedimento?.pedimento_code || '',
|
||||
regime: pedimento?.regime || '',
|
||||
status: pedimento?.status || '',
|
||||
@@ -205,10 +234,24 @@
|
||||
}
|
||||
});
|
||||
|
||||
const operationOptions = [
|
||||
{ value: 1, label: 'Exportación' },
|
||||
{ value: 2, label: 'Importación' },
|
||||
];
|
||||
// Obtener automáticamente el tipo de cambio cuando cambie la fecha de entrada
|
||||
$effect(() => {
|
||||
if (formData && formData.entry_date) {
|
||||
console.log('Buscando tipo de cambio para fecha:', formData.entry_date);
|
||||
|
||||
getExchangeRateByDate(formData.entry_date)
|
||||
.then(usdRate => {
|
||||
console.log('Tipo de cambio USD encontrado:', usdRate);
|
||||
if (usdRate && formData) {
|
||||
formData.exchange_rate = usdRate.value;
|
||||
console.log('Tipo de cambio actualizado a:', usdRate.value);
|
||||
} else {
|
||||
console.warn('No se encontró tipo de cambio USD para la fecha:', formData.entry_date);
|
||||
}
|
||||
})
|
||||
.catch(err => console.error('Error al obtener tipo de cambio:', err));
|
||||
}
|
||||
});
|
||||
|
||||
const statusOptions = [
|
||||
{ value: 'MODIFICABLE', label: 'Modificable' },
|
||||
@@ -227,6 +270,13 @@
|
||||
{ value: 'CARTA CUPO', label: 'Carta cupo' },
|
||||
{ value: 'ESPERA CANCELAR CARTA CUPO', label: 'Espera cancelar carta cupo' }
|
||||
];
|
||||
|
||||
const pedimentoTypeOptions = [
|
||||
{ value: 'automovil', label: 'Automóvil' },
|
||||
{ value: 'complementario', label: 'Complementario' },
|
||||
{ value: 'consolidado', label: 'Consolidado' },
|
||||
{ value: 'normal', label: 'Normal' }
|
||||
];
|
||||
</script>
|
||||
|
||||
<Card.Root>
|
||||
@@ -241,7 +291,7 @@
|
||||
<div class="grid grid-cols-1 md:grid-cols-[auto_auto_auto_auto_auto_auto_auto_auto_auto_1fr_auto] md:items-end gap-4 md:gap-2">
|
||||
<!-- Año -->
|
||||
<div class="space-y-2">
|
||||
<Label for="year">Año</Label>
|
||||
<Label for="year">Año <span class="text-red-500">*</span></Label>
|
||||
<Input
|
||||
id="year"
|
||||
bind:value={formData.year}
|
||||
@@ -258,7 +308,7 @@
|
||||
|
||||
<!-- Aduana -->
|
||||
<div class="space-y-2">
|
||||
<Label for="customs_office">Aduana</Label>
|
||||
<Label for="customs_office">Aduana <span class="text-red-500">*</span></Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.customs_office || ''}
|
||||
@@ -266,17 +316,23 @@
|
||||
>
|
||||
<Select.Trigger class="w-full md:w-20">
|
||||
<span class="truncate">
|
||||
{formData.customs_office || 'Sel...'}
|
||||
{formData.customs_office ? formData.customs_office.substring(0, 2) : 'Sel...'}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-w-[300px] max-h-[300px]">
|
||||
{#each customsSections as section}
|
||||
<Select.Item value={section.customs_code}>
|
||||
<span class="truncate overflow-hidden text-ellipsis whitespace-nowrap" title={`${section.customs_code} - ${section.section_name}`}>
|
||||
{section.customs_code} - {section.section_name}
|
||||
</span>
|
||||
</Select.Item>
|
||||
{/each}
|
||||
{#if customsSections.length === 0}
|
||||
<div class="px-2 py-1.5 text-sm text-muted-foreground">
|
||||
No hay aduanas disponibles
|
||||
</div>
|
||||
{:else}
|
||||
{#each customsSections as section}
|
||||
<Select.Item value={section.customs_code}>
|
||||
<span class="truncate overflow-hidden text-ellipsis whitespace-nowrap" title={`${section.customs_code} - ${section.section_name}`}>
|
||||
{section.customs_code} - {section.section_name}
|
||||
</span>
|
||||
</Select.Item>
|
||||
{/each}
|
||||
{/if}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
@@ -286,7 +342,7 @@
|
||||
|
||||
<!-- Patente -->
|
||||
<div class="space-y-2">
|
||||
<Label for="license">Patente</Label>
|
||||
<Label for="license">Patente <span class="text-red-500">*</span></Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.license || ''}
|
||||
@@ -298,13 +354,20 @@
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-w-[300px] max-h-[300px]">
|
||||
{#each customsBrokers as broker}
|
||||
<Select.Item value={broker.license}>
|
||||
<span class="truncate overflow-hidden text-ellipsis whitespace-nowrap" title={`${broker.broker_key} - ${broker.name || ''}`}>
|
||||
{broker.broker_key} - {broker.name || ''}
|
||||
</span>
|
||||
</Select.Item>
|
||||
{/each}
|
||||
{#if customsBrokers.length === 0}
|
||||
<div class="px-2 py-1.5 text-sm text-muted-foreground">
|
||||
No hay patentes disponibles
|
||||
</div>
|
||||
{:else}
|
||||
{#each customsBrokers as broker}
|
||||
{@const displayName = broker.name || broker.broker_key || ''}
|
||||
<Select.Item value={broker.license}>
|
||||
<span class="truncate overflow-hidden text-ellipsis whitespace-nowrap" title={displayName ? `${displayName} - ${broker.license}` : broker.license}>
|
||||
{displayName ? `${displayName} - ${broker.license}` : broker.license}
|
||||
</span>
|
||||
</Select.Item>
|
||||
{/each}
|
||||
{/if}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
@@ -314,7 +377,7 @@
|
||||
|
||||
<!-- Número de Pedimento -->
|
||||
<div class="space-y-2">
|
||||
<Label for="pedimento_number">Número de Pedimento</Label>
|
||||
<Label for="pedimento_number">Número de Pedimento <span class="text-red-500">*</span></Label>
|
||||
<Input
|
||||
id="pedimento_number"
|
||||
bind:value={formData.pedimento_number}
|
||||
@@ -326,13 +389,16 @@
|
||||
|
||||
<!-- Tipo de Cambio -->
|
||||
<div class="space-y-2">
|
||||
<Label for="exchange_rate">Tipo de Cambio</Label>
|
||||
<Label for="exchange_rate">Tipo de Cambio <span class="text-red-500">*</span></Label>
|
||||
<Input
|
||||
id="exchange_rate"
|
||||
type="number"
|
||||
step="0.0001"
|
||||
bind:value={formData.exchange_rate}
|
||||
placeholder="Ej: 17.5000"
|
||||
placeholder="En fecha de entrada"
|
||||
readonly
|
||||
disabled
|
||||
class="bg-muted cursor-not-allowed"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -340,7 +406,7 @@
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 md:gap-2">
|
||||
<!-- Clave del Pedimento -->
|
||||
<div class="space-y-2">
|
||||
<Label for="pedimento_code">Clave</Label>
|
||||
<Label for="pedimento_code">Clave <span class="text-red-500">*</span></Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.pedimento_code || ''}
|
||||
@@ -351,37 +417,14 @@
|
||||
{pedimentoCodes.find(o => o.code === formData.pedimento_code)?.code || 'Sel...'}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-w-[200px]">
|
||||
<Select.Content class="max-w-[400px] max-h-[300px]">
|
||||
{#each pedimentoCodes as code}
|
||||
<Select.Item value={code.code}>
|
||||
<span class="truncate overflow-hidden text-ellipsis whitespace-nowrap" title={`${code.code} - ${code.description}`}>
|
||||
{code.code} - {code.description}
|
||||
</span>
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
|
||||
<!-- Régimen -->
|
||||
<div class="space-y-2">
|
||||
<Label for="regime">Régimen</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.regime || ''}
|
||||
onValueChange={(v: string) => formData.regime = v ?? ''}
|
||||
>
|
||||
<Select.Trigger class="w-full">
|
||||
<span class="truncate">
|
||||
{formData.regime || 'Sel...'}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-w-[200px]">
|
||||
{#each filteredRegimens() as regimen}
|
||||
<Select.Item value={regimen.code}>
|
||||
<span class="truncate overflow-hidden text-ellipsis whitespace-nowrap" title={regimen.code}>
|
||||
{regimen.code}
|
||||
</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium">{code.code}</span>
|
||||
<span class="text-muted-foreground">-</span>
|
||||
<span class="flex-1 truncate">{code.description}</span>
|
||||
</div>
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
@@ -400,18 +443,54 @@
|
||||
{operationOptions.find(o => o.value === formData.operation_type)?.label || 'Seleccionar...'}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{#each filteredOperationTypes() as option}
|
||||
{#each filteredOperationTypes as option}
|
||||
<Select.Item value={String(option.value)} label={option.label} />
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
|
||||
<!-- Régimen -->
|
||||
<div class="space-y-2">
|
||||
<Label for="regime">Régimen <span class="text-red-500">*</span></Label>
|
||||
{#if hasMultipleRegimens}
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.regime || ''}
|
||||
onValueChange={(v: string) => formData.regime = v ?? ''}
|
||||
>
|
||||
<Select.Trigger class="w-full">
|
||||
<span class="truncate">
|
||||
{formData.regime || 'Sel...'}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-w-[200px]">
|
||||
{#each filteredRegimens as regimen}
|
||||
<Select.Item value={regimen.code}>
|
||||
<span class="truncate overflow-hidden text-ellipsis whitespace-nowrap" title={regimen.code}>
|
||||
{regimen.code}
|
||||
</span>
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
{:else}
|
||||
<Input
|
||||
id="regime"
|
||||
bind:value={formData.regime}
|
||||
placeholder="Automático"
|
||||
class="text-left"
|
||||
readonly
|
||||
disabled
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<!-- ID del Cliente -->
|
||||
<div class="space-y-2">
|
||||
<Label for="client_id">Cliente</Label>
|
||||
<Label for="client_id">Cliente <span class="text-red-500">*</span></Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={String(formData.client_id ?? '')}
|
||||
@@ -423,26 +502,40 @@
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[300px]">
|
||||
{#each clients as client}
|
||||
<Select.Item value={String(client.id)} label={client.name}>
|
||||
<span class="truncate overflow-hidden text-ellipsis whitespace-nowrap" title={client.name}>
|
||||
{client.name}
|
||||
</span>
|
||||
</Select.Item>
|
||||
{/each}
|
||||
{#if clients.length === 0}
|
||||
<div class="px-2 py-1.5 text-sm text-muted-foreground">
|
||||
No hay clientes disponibles
|
||||
</div>
|
||||
{:else}
|
||||
{#each clients as client}
|
||||
<Select.Item value={String(client.id)} label={client.name}>
|
||||
<span class="truncate overflow-hidden text-ellipsis whitespace-nowrap" title={client.name}>
|
||||
{client.name}
|
||||
</span>
|
||||
</Select.Item>
|
||||
{/each}
|
||||
{/if}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
|
||||
<!-- Tipo de Pedimento -->
|
||||
<div class="space-y-2">
|
||||
<Label for="pedimento_type">Tipo de Pedimento</Label>
|
||||
<Input
|
||||
id="pedimento_type"
|
||||
type="number"
|
||||
bind:value={formData.pedimento_type}
|
||||
placeholder="Ej: 1"
|
||||
/>
|
||||
<Label for="pedimento_type">Tipo de Pedimento <span class="text-red-500">*</span></Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.pedimento_type || ''}
|
||||
onValueChange={(v: string) => formData.pedimento_type = v ?? ''}
|
||||
>
|
||||
<Select.Trigger class="w-full">
|
||||
{pedimentoTypeOptions.find(o => o.value === formData.pedimento_type)?.label || 'Seleccionar...'}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{#each pedimentoTypeOptions as option}
|
||||
<Select.Item value={option.value} label={option.label} />
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -451,7 +544,7 @@
|
||||
|
||||
<!-- Estado -->
|
||||
<div class="space-y-2">
|
||||
<Label for="status">Estado</Label>
|
||||
<Label for="status">Estado <span class="text-red-500">*</span></Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.status || ''}
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
? {
|
||||
name: userData.name || userData.preferred_username || "",
|
||||
email: userData.email || "",
|
||||
avatar: "/avatars/default.jpg", // Puedes agregar avatar desde Keycloak si está disponible
|
||||
// avatar: "/avatars/default.jpg", // Puedes agregar avatar desde Keycloak si está disponible
|
||||
}
|
||||
: sidebarData.user,
|
||||
});
|
||||
|
||||
@@ -56,7 +56,7 @@ export function getSidebarData(): SidebarData {
|
||||
user: {
|
||||
name: "", // Se llena dinámicamente desde Keycloak
|
||||
email: "", // Se llena dinámicamente desde Keycloak
|
||||
avatar: "/avatars/default.jpg", // Avatar por defecto
|
||||
// avatar: "/avatars/default.jpg", // Avatar por defecto
|
||||
},
|
||||
teams: [
|
||||
{
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
client_id: 'ID del Cliente',
|
||||
operation_type: 'Tipo de Operación',
|
||||
pedimento_type: 'Tipo de Pedimento',
|
||||
pedimento_code: 'Clave',
|
||||
regime: 'Régimen',
|
||||
status: 'Estado'
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user