Formularios para cada uno de los tipos de importacion
This commit is contained in:
@@ -14,7 +14,7 @@ def validate_create(db: Session, invoice: InvoiceHeaderCreate, tenant_id: int, c
|
||||
if not invoice.invoice_type:
|
||||
errors.add_required_error("invoice_type")
|
||||
|
||||
if not invoice.document_type:
|
||||
if not invoice.document_type and invoice.invoice_type != "MEX":
|
||||
errors.add_required_error("document_type")
|
||||
|
||||
if not invoice.invoice_number:
|
||||
|
||||
@@ -224,9 +224,10 @@ def validate_update(
|
||||
else:
|
||||
invoice_data.compliance_mx.aduana = existing_invoice.compliance_mx.aduana if existing_invoice.compliance_mx else None
|
||||
|
||||
# Validar que aduana sea obligatorio
|
||||
if not invoice_data.compliance_mx or not invoice_data.compliance_mx.aduana:
|
||||
errors.add_required_error("aduana")
|
||||
# Validar que aduana sea obligatorio (excepto para MEX)
|
||||
if existing_invoice.invoice_type != "MEX":
|
||||
if not invoice_data.compliance_mx or not invoice_data.compliance_mx.aduana:
|
||||
errors.add_required_error("aduana")
|
||||
|
||||
# Columna AC: Sección de Despacho / Puerto de Entrada (Opcional)
|
||||
if invoice_data.compliance_mx and invoice_data.compliance_mx.port_of_entry:
|
||||
|
||||
@@ -58,7 +58,7 @@ class TransportType(str, Enum):
|
||||
PLATES = "licence plates"
|
||||
TRUCK = "truck"
|
||||
VESSEL = "vessel"
|
||||
BARGE = "rail barge"
|
||||
BARGE = "rail_barge"
|
||||
CONTAINER = "container"
|
||||
AIRPLANE = "airplane"
|
||||
GONDOLA = "gondola"
|
||||
@@ -82,9 +82,10 @@ class InvoiceHeader(Base, TenantScopedMixin, TimestampMixin):
|
||||
invoice_type: Mapped[str] = mapped_column(
|
||||
ForeignKey("public.invoice_types.key")
|
||||
) # TIPOFACTURA / TIPODOC
|
||||
document_type: Mapped[str] = mapped_column(
|
||||
ForeignKey("public.pedimento_regimens.code")
|
||||
) # CLAVEDOCUMENTO / Clave de documento
|
||||
document_type: Mapped[Optional[str]] = mapped_column(
|
||||
ForeignKey("public.pedimento_regimens.code"), nullable=True
|
||||
)
|
||||
# CLAVEDOCUMENTO / Clave de documento
|
||||
invoice_number: Mapped[str] = mapped_column(
|
||||
String(100)
|
||||
) # FACTURAIMPO/FACTURAEXPO/FACTURAREMISION/FACTURAENVIO/FACTURASALIDA
|
||||
@@ -299,6 +300,9 @@ class InvoiceComplianceMx(Base, TenantScopedMixin, TimestampMixin):
|
||||
act_value: Mapped[Optional[str]] = mapped_column(
|
||||
String(5)
|
||||
) # ACTVALOR / Actualizar valor
|
||||
rule_3121_parties_ii: Mapped[Optional[bool]] = mapped_column(
|
||||
Boolean, default=False, server_default="false"
|
||||
) # REGLA3121PARTESII / Regla 3.1.21 Partes II
|
||||
is_pedimento_pending: Mapped[Optional[bool]] = mapped_column(
|
||||
Boolean, default=False, server_default="false"
|
||||
) # PED_PENDIENTE_ASIGNAR (Mapear 1 -> True, 0 -> False)
|
||||
@@ -375,7 +379,7 @@ class InvoiceComplianceMx(Base, TenantScopedMixin, TimestampMixin):
|
||||
String(1)
|
||||
) # RAZONEXPORTACION / Razón de exportación
|
||||
signature_key: Mapped[Optional[str]] = mapped_column(
|
||||
String(10)
|
||||
String(100)
|
||||
) # CLAVEFIRMA / Clave de firma
|
||||
|
||||
# SM specific
|
||||
|
||||
@@ -22,8 +22,8 @@ class InvoiceHeaderBase(BaseModel):
|
||||
invoice_type: Optional[str] = Field(
|
||||
None, max_length=5, description="Invoice type key"
|
||||
)
|
||||
document_type: str = Field(
|
||||
..., max_length=3, description="Document type (Regimen Aduanero)"
|
||||
document_type: Optional[str] = Field(
|
||||
None, max_length=3, description="Document type (Regimen Aduanero)"
|
||||
)
|
||||
invoice_number: Optional[str] = Field(
|
||||
None, max_length=100, description="Invoice number"
|
||||
@@ -139,6 +139,9 @@ class InvoiceComplianceMxBase(BaseModel):
|
||||
which_exchange_rate: Optional[str] = Field(
|
||||
None, max_length=5, description="Which exchange rate"
|
||||
)
|
||||
rule_3121_parties_ii: Optional[bool] = Field(
|
||||
False, description="Is Rule 3.1.21 Parties II"
|
||||
)
|
||||
value_method: Optional[str] = Field(None, max_length=2, description="Value method")
|
||||
act_value: Optional[str] = Field(None, max_length=5, description="Act value")
|
||||
is_pedimento_pending: bool = Field(..., description="Is pedimento pending")
|
||||
@@ -187,7 +190,7 @@ class InvoiceComplianceMxBase(BaseModel):
|
||||
None, max_length=1, description="Reason for export"
|
||||
)
|
||||
signature_key: Optional[str] = Field(
|
||||
None, max_length=10, description="Signature key"
|
||||
None, max_length=100, description="Signature key"
|
||||
)
|
||||
sem_id: Optional[int] = Field(None, description="SEM ID")
|
||||
|
||||
|
||||
@@ -128,6 +128,10 @@ class InvoiceService:
|
||||
invoice_dict = clean_dict(raw_invoice_dict)
|
||||
invoice_dict["tenant_id"] = tenant_id
|
||||
invoice_dict["company_id"] = company_id
|
||||
|
||||
# Ensure document_type respects DB constraints for MEX invoices (bypass clean_dict)
|
||||
if invoice_dict.get("invoice_type") == "MEX" and not invoice_dict.get("document_type"):
|
||||
invoice_dict["document_type"] = None
|
||||
|
||||
new_invoice = models.InvoiceHeader(**invoice_dict)
|
||||
|
||||
|
||||
@@ -28,6 +28,13 @@ seed = [
|
||||
"both",
|
||||
"imp",
|
||||
),
|
||||
(
|
||||
"REP",
|
||||
"REPARACION",
|
||||
"REPARACION DE ACTIVOS FIJOS",
|
||||
"fixed asset",
|
||||
"imp",
|
||||
),
|
||||
|
||||
# === TIPOS DE EXPORTACION ===
|
||||
("DONAC", "DONACION", "", "both", "exp"),
|
||||
|
||||
35
backend/sync_invoice_types.py
Normal file
35
backend/sync_invoice_types.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from core.database import CoreSessionLocal
|
||||
from api.v1.modules.public.reference_data.invoice_types.models import InvoiceType
|
||||
from api.v1.modules.public.reference_data.invoice_types.seed import seed
|
||||
|
||||
def sync_invoice_types():
|
||||
db = CoreSessionLocal()
|
||||
try:
|
||||
for s in seed:
|
||||
key, description, note, type_, operation = s
|
||||
obj = db.query(InvoiceType).filter(InvoiceType.key == key).first()
|
||||
if obj:
|
||||
print(f"Updating {key}...")
|
||||
obj.description = description
|
||||
obj.note = note
|
||||
obj.type = type_
|
||||
obj.operation = operation
|
||||
else:
|
||||
print(f"Creating {key}...")
|
||||
obj = InvoiceType(
|
||||
key=key,
|
||||
description=description,
|
||||
note=note,
|
||||
type=type_,
|
||||
operation=operation
|
||||
)
|
||||
db.add(obj)
|
||||
db.commit()
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
db.rollback()
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
sync_invoice_types()
|
||||
@@ -82,7 +82,8 @@
|
||||
"temporary": "Temporary",
|
||||
"definitive": "Definitive",
|
||||
"mexican_purchases": "Mexican Purchases",
|
||||
"regime_change": "Regime Change"
|
||||
"regime_change": "Regime Change",
|
||||
"repair": "Repair"
|
||||
},
|
||||
"export_invoices": {
|
||||
"title": "Export Invoices",
|
||||
|
||||
@@ -82,7 +82,8 @@
|
||||
"temporary": "Temporal",
|
||||
"definitive": "Definitiva",
|
||||
"mexican_purchases": "Compras mexicanas",
|
||||
"regime_change": "Cambio de régimen"
|
||||
"regime_change": "Cambio de régimen",
|
||||
"repair": "Reparación"
|
||||
},
|
||||
"export_invoices": {
|
||||
"title": "Facturas de exportación",
|
||||
|
||||
@@ -73,6 +73,7 @@ export interface InvoiceComplianceMx {
|
||||
reason_export?: string | null;
|
||||
signature_key?: string | null;
|
||||
sem_id?: number | null;
|
||||
rule_3121_parties_ii?: boolean | null;
|
||||
}
|
||||
|
||||
export interface InvoiceFinancials {
|
||||
|
||||
@@ -269,7 +269,7 @@
|
||||
|
||||
// Helper to check if Repar request
|
||||
const isRepar = (t: string | undefined) => {
|
||||
return t && t.toUpperCase().includes('REPAR');
|
||||
return t === 'REPAR';
|
||||
};
|
||||
|
||||
// Categorize
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
<script lang="ts">
|
||||
import * as Dialog from '$lib/components/ui/dialog';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Search, LoaderCircle, X, Check } from 'lucide-svelte';
|
||||
import {
|
||||
customsSectionsApi,
|
||||
type CustomsSection
|
||||
} from '$lib/api/dashboard/reference_data/customs_sections';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
let { open = $bindable(false), onSelect } = $props<{
|
||||
open: boolean;
|
||||
onSelect: (section: CustomsSection) => void;
|
||||
}>();
|
||||
|
||||
let searchQuery = $state('');
|
||||
let sections = $state<CustomsSection[]>([]);
|
||||
let loading = $state(false);
|
||||
let selectedSection = $state<CustomsSection | null>(null);
|
||||
|
||||
async function loadSections() {
|
||||
loading = true;
|
||||
try {
|
||||
const response = await customsSectionsApi.list(1, 100);
|
||||
if (response?.data?.items) {
|
||||
sections = response.data.items;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading customs sections:', error);
|
||||
toast.error('Error al cargar las secciones aduaneras');
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (open) {
|
||||
loadSections();
|
||||
}
|
||||
});
|
||||
|
||||
const filteredSections = $derived(
|
||||
sections.filter(
|
||||
(s) =>
|
||||
s.customs_code.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
s.section_name.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
)
|
||||
);
|
||||
|
||||
function handleSelect(section: CustomsSection) {
|
||||
onSelect(section);
|
||||
open = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="max-w-2xl">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>Seleccionar Puerto (Aduana/Sección)</Dialog.Title>
|
||||
<Dialog.Description>Busca y selecciona una sección aduanera de la lista.</Dialog.Description>
|
||||
</Dialog.Header>
|
||||
|
||||
<div class="space-y-4 py-4">
|
||||
<div class="relative">
|
||||
<Search class="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
||||
<Input placeholder="Buscar por código o nombre..." class="pl-10" bind:value={searchQuery} />
|
||||
</div>
|
||||
|
||||
<div class="max-h-[400px] overflow-y-auto rounded-md border">
|
||||
{#if loading}
|
||||
<div class="flex items-center justify-center py-10">
|
||||
<LoaderCircle class="h-8 w-8 animate-spin text-primary" />
|
||||
</div>
|
||||
{:else if filteredSections.length > 0}
|
||||
<table class="w-full text-sm">
|
||||
<thead class="sticky top-0 bg-muted">
|
||||
<tr>
|
||||
<th class="p-2 text-left font-medium">Código</th>
|
||||
<th class="p-2 text-left font-medium">Nombre / Sección</th>
|
||||
<th class="w-10 p-2"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y">
|
||||
{#each filteredSections as section}
|
||||
<tr class="cursor-pointer hover:bg-muted/50" onclick={() => handleSelect(section)}>
|
||||
<td class="p-2 font-mono">{section.customs_code}</td>
|
||||
<td class="p-2">{section.section_name}</td>
|
||||
<td class="p-2">
|
||||
<Button variant="ghost" size="icon" class="h-8 w-8">
|
||||
<Check class="h-4 w-4 text-primary" />
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
{:else}
|
||||
<div class="py-10 text-center text-muted-foreground">No se encontraron resultados</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog.Footer>
|
||||
<Button variant="outline" onclick={() => (open = false)}>Cancelar</Button>
|
||||
</Dialog.Footer>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -4,18 +4,22 @@
|
||||
import { Checkbox } from '$lib/components/ui/checkbox';
|
||||
import { RadioGroup, RadioGroupItem } from '$lib/components/ui/radio-group';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Search } from 'lucide-svelte';
|
||||
import PortSelectorModal from './PortSelectorModal.svelte';
|
||||
import type { Invoice } from '$lib/api/dashboard/a76/invoices';
|
||||
|
||||
let {
|
||||
invoice,
|
||||
formData = $bindable(),
|
||||
exists = $bindable(),
|
||||
operationType = undefined
|
||||
operationType = undefined,
|
||||
invoiceType = undefined
|
||||
}: {
|
||||
invoice: Invoice | null;
|
||||
formData?: any;
|
||||
exists?: boolean;
|
||||
operationType?: number;
|
||||
invoiceType?: string;
|
||||
} = $props();
|
||||
|
||||
if (!formData && invoice) {
|
||||
@@ -28,6 +32,7 @@
|
||||
cantidad_guias_embarque: invoice.logistics?.guide_number || null,
|
||||
destino_origen: invoice.logistics?.destination_location || '',
|
||||
puerto_entrada: invoice.logistics?.origin_location || '',
|
||||
vehicle_data: invoice.logistics?.vehicle_data || '',
|
||||
// Checkboxes
|
||||
fue_revisado_equipo: invoice.logistics?.equipment_reviewed || false,
|
||||
sub_division: invoice.logistics?.is_subdivision || false,
|
||||
@@ -60,6 +65,7 @@
|
||||
cantidad_guias_embarque: null,
|
||||
destino_origen: '',
|
||||
puerto_entrada: '',
|
||||
vehicle_data: '',
|
||||
// Checkboxes
|
||||
fue_revisado_equipo: false,
|
||||
sub_division: false,
|
||||
@@ -84,6 +90,11 @@
|
||||
};
|
||||
exists = false;
|
||||
}
|
||||
let showPortModal = $state(false);
|
||||
|
||||
function handlePortSelect(section: any) {
|
||||
formData.puerto_entrada = section.customs_code;
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Layout de 2 columnas compacto -->
|
||||
@@ -104,7 +115,8 @@
|
||||
|
||||
<!-- DATOS VEHÍCULO -->
|
||||
<div class="space-y-2 rounded border bg-muted/30 p-2">
|
||||
<Label class="text-xs font-semibold">Datos Vehículo:</Label>
|
||||
<Label for="vehicle_data" class="text-xs font-semibold">Datos Vehículo:</Label>
|
||||
<Input id="vehicle_data" bind:value={formData.vehicle_data} class="mb-2 h-7 text-xs" />
|
||||
<div class="space-y-1.5">
|
||||
<Label class="text-xs">Es Ferrocarril?</Label>
|
||||
<RadioGroup bind:value={formData.es_ferrocarril} class="flex gap-4">
|
||||
@@ -147,7 +159,7 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if operationType === 1}
|
||||
{#if operationType === 1 || invoiceType === 'CR' || invoiceType === 'REP'}
|
||||
<div class="space-y-1.5">
|
||||
<Label class="text-xs">Es Mixto?</Label>
|
||||
<RadioGroup bind:value={formData.is_mixed} class="flex gap-4">
|
||||
@@ -164,14 +176,28 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if operationType !== 1}
|
||||
{#if operationType !== 1 && invoiceType !== 'CR'}
|
||||
<div class="space-y-1.5">
|
||||
<Label for="puerto_entrada" class="text-xs">Puerto Entrada:</Label>
|
||||
<Input id="puerto_entrada" bind:value={formData.puerto_entrada} class="h-7 text-xs" />
|
||||
<div class="flex gap-2">
|
||||
<Input
|
||||
id="puerto_entrada"
|
||||
bind:value={formData.puerto_entrada}
|
||||
class="h-7 flex-1 text-xs"
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
class="h-7 w-7"
|
||||
onclick={() => (showPortModal = true)}
|
||||
>
|
||||
<Search class="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if operationType === 1}
|
||||
{#if operationType === 1 || invoiceType === 'CR'}
|
||||
<div class="space-y-3 pt-1">
|
||||
<div class="space-y-1.5">
|
||||
<Label class="text-xs">Razón de exportación:</Label>
|
||||
@@ -216,25 +242,25 @@
|
||||
|
||||
<!-- CHECKBOXES -->
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
{#if operationType !== 1}
|
||||
{#if operationType !== 1 && invoiceType !== 'CR'}
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox id="fue_revisado" bind:checked={formData.fue_revisado_equipo} />
|
||||
<Label for="fue_revisado" class="text-xs">Fue Revisado el Equipo</Label>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox id="sub_division" bind:checked={formData.sub_division} />
|
||||
<Label for="sub_division" class="text-xs">Sub División</Label>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox id="funge_como_cd" bind:checked={formData.funge_como_cd} />
|
||||
<Label for="funge_como_cd" class="text-xs">Funge Como CD</Label>
|
||||
</div>
|
||||
{/if}
|
||||
{#if operationType !== 1}
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox id="llego_pedimento" bind:checked={formData.llego_pedimento} />
|
||||
<Label for="llego_pedimento" class="text-xs">Llegó el Pedimento</Label>
|
||||
</div>
|
||||
{#if invoiceType !== 'REP' && invoiceType !== 'REPAR'}
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox id="sub_division" bind:checked={formData.sub_division} />
|
||||
<Label for="sub_division" class="text-xs">Sub División</Label>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox id="funge_como_cd" bind:checked={formData.funge_como_cd} />
|
||||
<Label for="funge_como_cd" class="text-xs">Funge Como CD</Label>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox id="llego_pedimento" bind:checked={formData.llego_pedimento} />
|
||||
<Label for="llego_pedimento" class="text-xs">Llegó el Pedimento</Label>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
@@ -305,7 +331,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if operationType === 1}
|
||||
{#if operationType === 1 || invoiceType === 'CR'}
|
||||
<!-- CFDI Section (Export Only) -->
|
||||
<div class="space-y-3 rounded border bg-muted/20 p-3">
|
||||
<h4 class="border-b pb-1 text-[10px] font-bold text-muted-foreground uppercase">
|
||||
@@ -327,3 +353,5 @@
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PortSelectorModal bind:open={showPortModal} onSelect={handlePortSelect} />
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import * as Select from '$lib/components/ui/select';
|
||||
import * as RadioGroup from '$lib/components/ui/radio-group';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Search } from 'lucide-svelte';
|
||||
import { Search, Upload } from 'lucide-svelte';
|
||||
import ManifestSelectorModal from './ManifestSelectorModal.svelte';
|
||||
import type { Invoice } from '$lib/api/dashboard/a76/invoices';
|
||||
import type { InvoiceType } from '$lib/api/dashboard/reference_data/invoice_types';
|
||||
@@ -32,7 +32,8 @@
|
||||
codePedimentoRegimens = [],
|
||||
operationType = undefined,
|
||||
defaultOperationType = undefined,
|
||||
exchangeRate = undefined
|
||||
exchangeRate = undefined,
|
||||
invoiceType = undefined
|
||||
}: {
|
||||
invoice: Invoice | null;
|
||||
formData?: any;
|
||||
@@ -52,6 +53,7 @@
|
||||
defaultInvoiceType?: string | null;
|
||||
operationType?: number | null;
|
||||
exchangeRate?: number | null;
|
||||
invoiceType?: string;
|
||||
} = $props();
|
||||
|
||||
let showManifestModal = $state(false);
|
||||
@@ -96,7 +98,9 @@
|
||||
transport_num: invoice.logistics?.vehicle_num || '',
|
||||
aduana: invoice.compliance_mx?.aduana || '',
|
||||
document_type: invoice.document_type || '',
|
||||
manifest_number: invoice.compliance_mx?.manifest_number || ''
|
||||
manifest_number: invoice.compliance_mx?.manifest_number || '',
|
||||
code_signature: invoice.compliance_mx?.signature_key || '',
|
||||
electronic_signature: invoice.compliance_mx?.electronic_signature || ''
|
||||
};
|
||||
} else {
|
||||
// Creando una nueva factura
|
||||
@@ -126,7 +130,9 @@
|
||||
transport_num: '',
|
||||
aduana: '',
|
||||
document_type: '',
|
||||
manifest_number: ''
|
||||
manifest_number: '',
|
||||
code_signature: '',
|
||||
electronic_signature: ''
|
||||
};
|
||||
}
|
||||
} else {
|
||||
@@ -176,7 +182,7 @@
|
||||
];
|
||||
|
||||
const soldToHeaderOptions = $derived(
|
||||
operationType === 1
|
||||
operationType === 1 || invoiceType === 'CR'
|
||||
? [
|
||||
{ value: 'consignado_a', label: 'Consignado a' },
|
||||
{ value: 'vendido_a', label: 'Vendido a' },
|
||||
@@ -190,7 +196,7 @@
|
||||
);
|
||||
|
||||
const shippedToHeaderOptions = $derived(
|
||||
operationType === 1
|
||||
operationType === 1 || invoiceType === 'CR'
|
||||
? [
|
||||
{ value: 'enviado_a', label: 'Enviado a' },
|
||||
{ value: 'transferido_a', label: 'Transferido a' },
|
||||
@@ -204,7 +210,7 @@
|
||||
);
|
||||
|
||||
const shippedByHeaderOptions = $derived(
|
||||
operationType === 1
|
||||
operationType === 1 || invoiceType === 'CR'
|
||||
? [
|
||||
{ value: 'enviado_por', label: 'Enviado Por' },
|
||||
{ value: 'vendido_por', label: 'Vendido Por' },
|
||||
@@ -296,25 +302,27 @@
|
||||
<!-- Columna Izquierda: Clientes - Proveedores - Agente Aduanal -->
|
||||
<div class="space-y-3 rounded-md border p-3">
|
||||
<div class="space-y-2">
|
||||
<h4 class="text-xs font-semibold text-muted-foreground uppercase">Datos del pedimento</h4>
|
||||
<div class="grid grid-cols-4 gap-3 text-xs">
|
||||
<div>
|
||||
<span class="text-muted-foreground">Fecha del:</span>
|
||||
<p class="font-medium">{formData.fecha_pedimento_del || '-'}</p>
|
||||
{#if invoiceType !== 'MEX'}
|
||||
<h4 class="text-xs font-semibold text-muted-foreground uppercase">Datos del pedimento</h4>
|
||||
<div class="grid grid-cols-4 gap-3 text-xs">
|
||||
<div>
|
||||
<span class="text-muted-foreground">Fecha del:</span>
|
||||
<p class="font-medium">{formData.fecha_pedimento_del || '-'}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-muted-foreground">Fecha al:</span>
|
||||
<p class="font-medium">{formData.fecha_pedimento_al || '-'}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-muted-foreground">Clave:</span>
|
||||
<p class="font-medium">{formData.clave_pedimento || '-'}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-muted-foreground">Régimen:</span>
|
||||
<p class="font-medium">{formData.regimen_pedimento || '-'}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-muted-foreground">Fecha al:</span>
|
||||
<p class="font-medium">{formData.fecha_pedimento_al || '-'}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-muted-foreground">Clave:</span>
|
||||
<p class="font-medium">{formData.clave_pedimento || '-'}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-muted-foreground">Régimen:</span>
|
||||
<p class="font-medium">{formData.regimen_pedimento || '-'}</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<h4 class="text-xs font-semibold text-muted-foreground uppercase">
|
||||
@@ -471,7 +479,7 @@
|
||||
<span class="text-red-500">*</span>
|
||||
</div>
|
||||
|
||||
{#if operationType === 1}
|
||||
{#if operationType === 1 || invoiceType === 'CR'}
|
||||
<div class="grid grid-cols-4 gap-3 space-y-1.5">
|
||||
<Select.Root
|
||||
type="single"
|
||||
@@ -677,7 +685,7 @@
|
||||
</Select.Root>
|
||||
</div>
|
||||
|
||||
{#if operationType !== 1}
|
||||
{#if operationType !== 1 && invoiceType !== 'MEX' && invoiceType !== 'CR' && invoiceType !== 'REP' && invoiceType !== 'REPAR'}
|
||||
<div class="space-y-1.5">
|
||||
<Label for="iva_factor" class="text-xs">IVA:</Label>
|
||||
<Input
|
||||
@@ -690,7 +698,7 @@
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if operationType === 1}
|
||||
{#if operationType === 1 || invoiceType === 'CR'}
|
||||
<div class="space-y-1.5">
|
||||
<Label for="manifest_number" class="text-xs">Num. de Manifiesto:</Label>
|
||||
<div class="flex gap-2">
|
||||
@@ -719,37 +727,39 @@
|
||||
<h4 class="text-xs font-semibold text-muted-foreground uppercase">Transportista</h4>
|
||||
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<div class="space-y-1.5">
|
||||
<Label for="carrier_id" class="text-xs">Transportista:</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.carrier_id ? String(formData.carrier_id) : ''}
|
||||
onValueChange={(v) => {
|
||||
formData.carrier_id = v || null;
|
||||
}}
|
||||
>
|
||||
<Select.Trigger id="carrier_id" class="h-7 max-w-[250px] min-w-[120px] text-xs">
|
||||
<span class="truncate">
|
||||
{#if formData.carrier_id}
|
||||
{transporters.find(
|
||||
(t) => String(t.transporter_key) === String(formData.carrier_id)
|
||||
)?.name || formData.carrier_id}
|
||||
{:else if transporters.length > 0}
|
||||
Selecciona transportista...
|
||||
{:else}
|
||||
Sin datos
|
||||
{/if}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[300px]">
|
||||
{#each transporters as transporter}
|
||||
<Select.Item value={String(transporter.transporter_key)}>
|
||||
{transporter.transporter_key}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
{#if invoiceType !== 'MEX'}
|
||||
<div class="space-y-1.5">
|
||||
<Label for="carrier_id" class="text-xs">Transportista:</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.carrier_id ? String(formData.carrier_id) : ''}
|
||||
onValueChange={(v) => {
|
||||
formData.carrier_id = v || null;
|
||||
}}
|
||||
>
|
||||
<Select.Trigger id="carrier_id" class="h-7 max-w-[250px] min-w-[120px] text-xs">
|
||||
<span class="truncate">
|
||||
{#if formData.carrier_id}
|
||||
{transporters.find(
|
||||
(t) => String(t.transporter_key) === String(formData.carrier_id)
|
||||
)?.name || formData.carrier_id}
|
||||
{:else if transporters.length > 0}
|
||||
Selecciona transportista...
|
||||
{:else}
|
||||
Sin datos
|
||||
{/if}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[300px]">
|
||||
{#each transporters as transporter}
|
||||
<Select.Item value={String(transporter.transporter_key)}>
|
||||
{transporter.transporter_key}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="col-span-2 space-y-1.5">
|
||||
<Label for="transport_id" class="text-xs">Clave Transporte:</Label>
|
||||
@@ -872,71 +882,91 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<Label for="aduana" class="text-xs">Aduana y Sección de Despacho:</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.aduana || ''}
|
||||
onValueChange={(v) => {
|
||||
formData.aduana = v ?? '';
|
||||
}}
|
||||
>
|
||||
<Select.Trigger id="aduana" class="h-7 w-full text-xs">
|
||||
<span class="truncate">
|
||||
{#if formData.aduana}
|
||||
{customsSections.find((cs) => cs.customs_code === formData.aduana)?.section_name ||
|
||||
formData.aduana}
|
||||
{:else if customsSections.length > 0}
|
||||
Selecciona aduana...
|
||||
{:else}
|
||||
Sin datos
|
||||
{/if}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[300px]">
|
||||
{#each customsSections as section}
|
||||
<Select.Item value={section.customs_code}>
|
||||
{section.customs_code} - {section.section_name}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
{#if invoiceType !== 'MEX'}
|
||||
<div class="space-y-1.5">
|
||||
<Label for="aduana" class="text-xs">Aduana y Sección de Despacho:</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.aduana || ''}
|
||||
onValueChange={(v) => {
|
||||
formData.aduana = v ?? '';
|
||||
}}
|
||||
>
|
||||
<Select.Trigger id="aduana" class="h-7 w-full text-xs">
|
||||
<span class="truncate">
|
||||
{#if formData.aduana}
|
||||
{customsSections.find((cs) => cs.customs_code === formData.aduana)
|
||||
?.section_name || formData.aduana}
|
||||
{:else if customsSections.length > 0}
|
||||
Selecciona aduana...
|
||||
{:else}
|
||||
Sin datos
|
||||
{/if}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[300px]">
|
||||
{#each customsSections as section}
|
||||
<Select.Item value={section.customs_code}>
|
||||
{section.customs_code} - {section.section_name}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<Label for="document_type" class="text-xs"
|
||||
>Clave de Régimen Aduanero: <span class="text-red-500">*</span></Label
|
||||
>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.document_type || ''}
|
||||
onValueChange={(v) => {
|
||||
formData.document_type = v ?? '';
|
||||
}}
|
||||
>
|
||||
<Select.Trigger id="document_type" class="h-7 w-full text-xs">
|
||||
<span class="truncate">
|
||||
{#if formData.document_type}
|
||||
{codePedimentoRegimens.find((r) => r.regimen_code === formData.document_type)
|
||||
?.regimen_code || formData.document_type}
|
||||
{:else if filteredRegimens.length > 0}
|
||||
Selecciona régimen...
|
||||
{:else if operationType}
|
||||
Sin regímenes para tipo {operationType}
|
||||
{:else}
|
||||
Selecciona tipo de operación primero
|
||||
{/if}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[300px]">
|
||||
{#each filteredRegimens as regimen}
|
||||
<Select.Item value={regimen.regimen_code}>
|
||||
{regimen.regimen_code}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
{#if invoiceType !== 'MEX'}
|
||||
<div class="space-y-1.5">
|
||||
<Label for="document_type" class="text-xs"
|
||||
>Clave de Régimen Aduanero: <span class="text-red-500">*</span></Label
|
||||
>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.document_type || ''}
|
||||
onValueChange={(v) => {
|
||||
formData.document_type = v ?? '';
|
||||
}}
|
||||
>
|
||||
<Select.Trigger id="document_type" class="h-7 w-full text-xs">
|
||||
<span class="truncate">
|
||||
{#if formData.document_type}
|
||||
{codePedimentoRegimens.find((r) => r.regimen_code === formData.document_type)
|
||||
?.regimen_code || formData.document_type}
|
||||
{:else if filteredRegimens.length > 0}
|
||||
Selecciona régimen...
|
||||
{:else if operationType}
|
||||
Sin regímenes para tipo {operationType}
|
||||
{:else}
|
||||
Selecciona tipo de operación primero
|
||||
{/if}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[300px]">
|
||||
{#each filteredRegimens as regimen}
|
||||
<Select.Item value={regimen.regimen_code}>
|
||||
{regimen.regimen_code}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if invoiceType === 'MEX'}
|
||||
<div class="space-y-1.5 pt-1">
|
||||
<Label for="electronic-sig-gen" class="text-xs">Firma Electrónica:</Label>
|
||||
<div class="flex gap-2">
|
||||
<Input
|
||||
id="electronic-sig-gen"
|
||||
bind:value={formData.code_signature}
|
||||
class="h-7 flex-1 text-xs"
|
||||
/>
|
||||
<Button variant="outline" size="icon" class="h-7 w-7">
|
||||
<Upload class="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
invoiceTypes = [],
|
||||
pedimentos = [],
|
||||
defaultOperationType = undefined,
|
||||
defaultInvoiceType = undefined
|
||||
defaultInvoiceType = undefined,
|
||||
invoiceType = undefined
|
||||
}: {
|
||||
invoice: Invoice | null;
|
||||
formData?: any;
|
||||
@@ -21,6 +22,7 @@
|
||||
pedimentos?: Pedimento[];
|
||||
defaultOperationType?: string | null;
|
||||
defaultInvoiceType?: string | null;
|
||||
invoiceType?: string;
|
||||
} = $props();
|
||||
|
||||
function handlePedimentoChange(pedimentoId: string) {
|
||||
@@ -81,7 +83,10 @@
|
||||
fecha_pedimento_del: '',
|
||||
fecha_pedimento_al: '',
|
||||
clave_pedimento: '',
|
||||
regimen_pedimento: ''
|
||||
regimen_pedimento: '',
|
||||
// Campos específicos para MEX
|
||||
iva_factor: invoice?.financials?.iva_factor || '',
|
||||
alternate_invoice: invoice?.alternate_invoice || ''
|
||||
};
|
||||
} else {
|
||||
// Si formData ya existe pero operation_type está vacío, usar defaultOperationType
|
||||
@@ -92,7 +97,29 @@
|
||||
) {
|
||||
formData.operation_type = defaultOperationType;
|
||||
}
|
||||
// Ensure new fields exist if formData was created before
|
||||
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 = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Datos Principales en una fila compacta (reusable across tabs) -->
|
||||
@@ -136,7 +163,7 @@
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[300px]">
|
||||
{#each invoiceTypes as type}
|
||||
{#each filteredInvoiceTypes as type}
|
||||
<Select.Item value={type.key}>
|
||||
{type.key} - {type.description}
|
||||
</Select.Item>
|
||||
@@ -145,47 +172,52 @@
|
||||
</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>
|
||||
{#if invoiceType !== 'MEX'}
|
||||
<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>
|
||||
{/if}
|
||||
|
||||
<div class="col-span-2 space-y-1">
|
||||
<Label for="invoice_number" class="text-xs"
|
||||
@@ -201,7 +233,11 @@
|
||||
|
||||
<div class="col-span-2 space-y-1">
|
||||
<Label for="invoice_date" class="text-xs">
|
||||
{formData.operation_type === 'exp' ? 'Fecha' : 'Fecha Factura'}
|
||||
{formData.operation_type === 'exp' || invoiceType === 'CR'
|
||||
? 'Fecha'
|
||||
: invoiceType === 'MEX'
|
||||
? 'Fecha de Entrada'
|
||||
: '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" />
|
||||
@@ -211,4 +247,15 @@
|
||||
<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>
|
||||
|
||||
{#if invoiceType === 'MEX'}
|
||||
<div class="col-span-1 space-y-1">
|
||||
<Label for="iva_factor" class="text-xs">Factor IVA</Label>
|
||||
<Input id="iva_factor" bind:value={formData.iva_factor} class="h-8 text-sm" />
|
||||
</div>
|
||||
<div class="col-span-2 space-y-1">
|
||||
<Label for="alternate_invoice" class="text-xs">Factura Alterna</Label>
|
||||
<Input id="alternate_invoice" bind:value={formData.alternate_invoice} class="h-8 text-sm" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -16,12 +16,14 @@
|
||||
invoice,
|
||||
formData = $bindable(),
|
||||
exists = $bindable(),
|
||||
operationType = undefined
|
||||
operationType = undefined,
|
||||
invoiceType = undefined
|
||||
}: {
|
||||
invoice: Invoice | null;
|
||||
formData?: any;
|
||||
exists?: boolean;
|
||||
operationType?: number;
|
||||
invoiceType?: string;
|
||||
} = $props();
|
||||
|
||||
let imported = 0;
|
||||
@@ -258,10 +260,10 @@
|
||||
}
|
||||
|
||||
// Load part number data
|
||||
if (line.part_number_id) {
|
||||
if (line.part_number) {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api-sveltekit/parts/${line.part_number_id}?company_id=${activeCompanyId}`,
|
||||
`/api-sveltekit/parts/${line.part_number}?company_id=${activeCompanyId}`,
|
||||
{ method: 'GET', headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
if (response.ok) {
|
||||
@@ -733,6 +735,30 @@
|
||||
<Table.Head>Descripción Esp.</Table.Head>
|
||||
<Table.Head>Subpartidas</Table.Head>
|
||||
<Table.Head>Partida Princ.</Table.Head>
|
||||
{:else if invoiceType === 'CR'}
|
||||
<Table.Head>Línea</Table.Head>
|
||||
<Table.Head>Factura Impo</Table.Head>
|
||||
<Table.Head>Lin Impo</Table.Head>
|
||||
<Table.Head>P/S</Table.Head>
|
||||
<Table.Head>Cantidad Exportada</Table.Head>
|
||||
<Table.Head>Clase</Table.Head>
|
||||
<Table.Head>Número de Parte</Table.Head>
|
||||
<Table.Head>Descripción en Español</Table.Head>
|
||||
<Table.Head>Contiene Subpartida</Table.Head>
|
||||
<Table.Head>Partida Principal</Table.Head>
|
||||
{:else if invoiceType === 'REP' || invoiceType === 'REPAR'}
|
||||
<Table.Head>Línea</Table.Head>
|
||||
<Table.Head>No. Factura Expo</Table.Head>
|
||||
<Table.Head>Línea Expo</Table.Head>
|
||||
<Table.Head>P/S</Table.Head>
|
||||
<Table.Head>Clase</Table.Head>
|
||||
<Table.Head>Num. Parte</Table.Head>
|
||||
<Table.Head>Descripción en Español</Table.Head>
|
||||
<Table.Head>Cantidad Importada</Table.Head>
|
||||
<Table.Head>UM</Table.Head>
|
||||
<Table.Head>Preferencia</Table.Head>
|
||||
<Table.Head>Contiene Subpartida</Table.Head>
|
||||
<Table.Head>Partida Principal</Table.Head>
|
||||
{:else}
|
||||
<Table.Head>Línea</Table.Head>
|
||||
<Table.Head>P/S</Table.Head>
|
||||
@@ -782,6 +808,40 @@
|
||||
</Table.Cell>
|
||||
<Table.Cell>{item.fa_data?.contains_subitems ? 'Sí' : 'No'}</Table.Cell>
|
||||
<Table.Cell>{item.warehouse || '-'}</Table.Cell>
|
||||
{:else if invoiceType === 'CR'}
|
||||
<Table.Cell>{item.line_number}</Table.Cell>
|
||||
<Table.Cell>{item.fa_data?.search_invoice || '-'}</Table.Cell>
|
||||
<Table.Cell>{item.fa_data?.search_line || '-'}</Table.Cell>
|
||||
<Table.Cell>{item.is_subitem ? 'S' : 'P'}</Table.Cell>
|
||||
<Table.Cell>{item.quantity?.quantity || '0'}</Table.Cell>
|
||||
<Table.Cell>{item.class_code || '-'}</Table.Cell>
|
||||
<Table.Cell>{item.part_number || '-'}</Table.Cell>
|
||||
<Table.Cell
|
||||
class="max-w-[200px] truncate"
|
||||
title={item.description?.description_spanish}
|
||||
>
|
||||
{item.description?.description_spanish || '-'}
|
||||
</Table.Cell>
|
||||
<Table.Cell>{item.fa_data?.contains_subitems ? 'Sí' : 'No'}</Table.Cell>
|
||||
<Table.Cell>{item.warehouse || '-'}</Table.Cell>
|
||||
{:else if invoiceType === 'REP' || invoiceType === 'REPAR'}
|
||||
<Table.Cell>{item.line_number}</Table.Cell>
|
||||
<Table.Cell>{item.fa_data?.search_invoice || '-'}</Table.Cell>
|
||||
<Table.Cell>{item.fa_data?.search_line || '-'}</Table.Cell>
|
||||
<Table.Cell>{item.is_subitem ? 'S' : 'P'}</Table.Cell>
|
||||
<Table.Cell>{item.class_code || '-'}</Table.Cell>
|
||||
<Table.Cell>{item.part_number || '-'}</Table.Cell>
|
||||
<Table.Cell
|
||||
class="max-w-[200px] truncate"
|
||||
title={item.description?.description_spanish}
|
||||
>
|
||||
{item.description?.description_spanish || '-'}
|
||||
</Table.Cell>
|
||||
<Table.Cell>{item.quantity?.quantity || '0'}</Table.Cell>
|
||||
<Table.Cell>{item.unit_of_measure_code || '-'}</Table.Cell>
|
||||
<Table.Cell>{item.reference_number || '-'}</Table.Cell>
|
||||
<Table.Cell>{item.fa_data?.contains_subitems ? 'Sí' : 'No'}</Table.Cell>
|
||||
<Table.Cell>{item.warehouse || '-'}</Table.Cell>
|
||||
{:else}
|
||||
<Table.Cell>{item.line_number}</Table.Cell>
|
||||
<Table.Cell>{item.is_subitem ? 'S' : 'P'}</Table.Cell>
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
seals = [],
|
||||
incoterms = [],
|
||||
enclosure = [],
|
||||
operationType = undefined
|
||||
operationType = undefined,
|
||||
invoiceType = undefined
|
||||
}: {
|
||||
invoice: Invoice | null;
|
||||
formData?: any;
|
||||
@@ -21,6 +22,7 @@
|
||||
incoterms?: any[];
|
||||
enclosure?: any[];
|
||||
operationType?: number;
|
||||
invoiceType?: string;
|
||||
} = $props();
|
||||
|
||||
if (!formData && invoice) {
|
||||
@@ -53,7 +55,8 @@
|
||||
complement_1: invoice.logistics?.complement_1 || '',
|
||||
identifier_2: invoice.logistics?.identifier_2 || '',
|
||||
complement_2: invoice.logistics?.complement_2 || '',
|
||||
office_document: invoice.compliance_mx?.office_document || ''
|
||||
office_document: invoice.compliance_mx?.office_document || '',
|
||||
is_mixed: invoice.compliance_mx?.is_mixed || false
|
||||
};
|
||||
exists = true;
|
||||
} else if (!formData) {
|
||||
@@ -86,7 +89,8 @@
|
||||
complement_1: '',
|
||||
identifier_2: '',
|
||||
complement_2: '',
|
||||
office_document: ''
|
||||
office_document: '',
|
||||
is_mixed: false
|
||||
};
|
||||
exists = false;
|
||||
}
|
||||
@@ -99,9 +103,11 @@
|
||||
<div class="space-y-4 rounded-md border p-3">
|
||||
<div class="space-y-2">
|
||||
<h4 class="text-xs font-semibold text-muted-foreground uppercase">
|
||||
{operationType === 1
|
||||
{operationType === 1 || invoiceType === 'CR'
|
||||
? 'Observaciones de la factura mexicana:'
|
||||
: 'Observacion de la factura mexicana y bilingue:'}
|
||||
: invoiceType === 'MEX'
|
||||
? 'Observacion de la factura mexicana y bilingue:'
|
||||
: 'Observacion de la factura mexicana y bilingue:'}
|
||||
</h4>
|
||||
<Textarea
|
||||
id="observation_es"
|
||||
@@ -111,17 +117,19 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<h4 class="text-xs font-semibold text-muted-foreground uppercase">
|
||||
Observaciones de la factura americana:
|
||||
</h4>
|
||||
<Textarea
|
||||
id="observation_en"
|
||||
bind:value={formData.observation_en}
|
||||
class="max-h-[150px] min-h-[150px] text-sm font-medium"
|
||||
placeholder="Escribe tus observaciones aqui."
|
||||
/>
|
||||
</div>
|
||||
{#if invoiceType !== 'MEX'}
|
||||
<div class="space-y-2">
|
||||
<h4 class="text-xs font-semibold text-muted-foreground uppercase">
|
||||
Observaciones de la factura americana:
|
||||
</h4>
|
||||
<Textarea
|
||||
id="observation_en"
|
||||
bind:value={formData.observation_en}
|
||||
class="max-h-[150px] min-h-[150px] text-sm font-medium"
|
||||
placeholder="Escribe tus observaciones aqui."
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if operationType === 1}
|
||||
@@ -395,83 +403,123 @@
|
||||
{/if}
|
||||
|
||||
<!-- Incoterm & Recinto (Show for all, layout adjusted) -->
|
||||
<div class="space-y-3 rounded-md border p-3">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-1">
|
||||
<Label for="incoterm" class="text-xs">Incoterm:</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.incoterm ? String(formData.incoterm) : ''}
|
||||
onValueChange={(v) => {
|
||||
formData.incoterm = v ? parseInt(v) : null;
|
||||
}}
|
||||
>
|
||||
<Select.Trigger id="incoterm" class="h-7 text-xs">
|
||||
<span class="truncate"
|
||||
>{formData.incoterm
|
||||
? incoterms.find((p) => p.id === formData.incoterm)?.name || 'Selecciona...'
|
||||
: 'Selecciona...'}</span
|
||||
>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[300px]">
|
||||
{#each incoterms as inco}
|
||||
<Select.Item value={String(inco.id)}>{inco.name}</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
{#if invoiceType !== 'MEX'}
|
||||
<div class="space-y-3 rounded-md border p-3">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-1">
|
||||
<Label for="incoterm" class="text-xs">Incoterm:</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.incoterm ? String(formData.incoterm) : ''}
|
||||
onValueChange={(v) => {
|
||||
formData.incoterm = v ? parseInt(v) : null;
|
||||
}}
|
||||
>
|
||||
<Select.Trigger id="incoterm" class="h-7 text-xs">
|
||||
<span class="truncate"
|
||||
>{formData.incoterm
|
||||
? incoterms.find((p) => p.id === formData.incoterm)?.name || 'Selecciona...'
|
||||
: 'Selecciona...'}</span
|
||||
>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[300px]">
|
||||
{#each incoterms as inco}
|
||||
<Select.Item value={String(inco.id)}>{inco.name}</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<Label for="enclosure" class="text-xs">Recinto:</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.enclosure ? String(formData.enclosure) : ''}
|
||||
onValueChange={(v) => {
|
||||
formData.enclosure = v ? parseInt(v) : null;
|
||||
}}
|
||||
>
|
||||
<Select.Trigger id="enclosure" class="h-7 text-xs">
|
||||
<span class="truncate"
|
||||
>{formData.enclosure
|
||||
? enclosure.find((p) => p.id === formData.enclosure)?.name || 'Selecciona...'
|
||||
: 'Selecciona...'}</span
|
||||
>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[300px]">
|
||||
{#each enclosure as rec}
|
||||
<Select.Item value={String(rec.id)}>{rec.name}</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<Label for="enclosure" class="text-xs">Recinto:</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.enclosure ? String(formData.enclosure) : ''}
|
||||
onValueChange={(v) => {
|
||||
formData.enclosure = v ? parseInt(v) : null;
|
||||
}}
|
||||
>
|
||||
<Select.Trigger id="enclosure" class="h-7 text-xs">
|
||||
<span class="truncate"
|
||||
>{formData.enclosure
|
||||
? enclosure.find((p) => p.id === formData.enclosure)?.name || 'Selecciona...'
|
||||
: 'Selecciona...'}</span
|
||||
{#if operationType === 1}
|
||||
<div class="space-y-1 pt-1">
|
||||
<Label for="valuation_method_exp" class="text-xs font-semibold"
|
||||
>Método de Valoración:</Label
|
||||
>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.valuation_method ? String(formData.valuation_method) : ''}
|
||||
onValueChange={(v) => {
|
||||
formData.valuation_method = v;
|
||||
}}
|
||||
>
|
||||
<Select.Trigger id="valuation_method_exp" class="h-8 text-xs">
|
||||
<span class="truncate">{formData.valuation_method || 'Selecciona...'}</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
<Select.Item value="general">General</Select.Item>
|
||||
<Select.Item value="devalued">Devaluado</Select.Item>
|
||||
<Select.Item value="special">Especial</Select.Item>
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if invoiceType === 'MEX'}
|
||||
<div class="rounded-md border p-3">
|
||||
<div class="space-y-2">
|
||||
<Label class="mb-2 block text-xs font-semibold">Es mixto?</Label>
|
||||
<div class="flex items-center gap-4">
|
||||
<label class="group flex cursor-pointer items-center gap-2">
|
||||
<div
|
||||
class="relative flex h-4 w-4 items-center justify-center rounded-full border-2 border-zinc-400 transition-all {formData.is_mixed ===
|
||||
true
|
||||
? 'border-zinc-800 bg-zinc-800'
|
||||
: ''}"
|
||||
>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[300px]">
|
||||
{#each enclosure as rec}
|
||||
<Select.Item value={String(rec.id)}>{rec.name}</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
<input type="radio" value={true} bind:group={formData.is_mixed} class="sr-only" />
|
||||
{#if formData.is_mixed === true}
|
||||
<div class="h-1.5 w-1.5 rounded-full bg-white"></div>{/if}
|
||||
</div>
|
||||
<span class="text-xs font-medium transition-colors group-hover:text-zinc-800">Si</span
|
||||
>
|
||||
</label>
|
||||
<label class="group flex cursor-pointer items-center gap-2">
|
||||
<div
|
||||
class="relative flex h-4 w-4 items-center justify-center rounded-full border-2 border-zinc-400 transition-all {formData.is_mixed ===
|
||||
false
|
||||
? 'border-zinc-800 bg-zinc-800'
|
||||
: ''}"
|
||||
>
|
||||
<input type="radio" value={false} bind:group={formData.is_mixed} class="sr-only" />
|
||||
{#if formData.is_mixed === false}
|
||||
<div class="h-1.5 w-1.5 rounded-full bg-white"></div>{/if}
|
||||
</div>
|
||||
<span class="text-xs font-medium transition-colors group-hover:text-zinc-800">No</span
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{#if operationType === 1}
|
||||
<div class="space-y-1 pt-1">
|
||||
<Label for="valuation_method_exp" class="text-xs font-semibold"
|
||||
>Método de Valoración:</Label
|
||||
>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.valuation_method ? String(formData.valuation_method) : ''}
|
||||
onValueChange={(v) => {
|
||||
formData.valuation_method = v;
|
||||
}}
|
||||
>
|
||||
<Select.Trigger id="valuation_method_exp" class="h-8 text-xs">
|
||||
<span class="truncate">{formData.valuation_method || 'Selecciona...'}</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
<Select.Item value="general">General</Select.Item>
|
||||
<Select.Item value="devalued">Devaluado</Select.Item>
|
||||
<Select.Item value="special">Especial</Select.Item>
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Extra Bottom Row for Import Mode specific fields (Original Layout preserved for Import) -->
|
||||
{#if operationType !== 1}
|
||||
{#if operationType !== 1 && invoiceType !== 'MEX'}
|
||||
<div class="col-span-2 space-y-3 rounded-md border p-3">
|
||||
<div class="grid grid-cols-4 gap-3">
|
||||
<div class="space-y-1">
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Checkbox } from '$lib/components/ui/checkbox';
|
||||
import { RadioGroup, RadioGroupItem } from '$lib/components/ui/radio-group';
|
||||
import * as RadioGroup from '$lib/components/ui/radio-group';
|
||||
import * as Select from '$lib/components/ui/select';
|
||||
import { Textarea } from '$lib/components/ui/textarea';
|
||||
import { Plus, Upload } from 'lucide-svelte';
|
||||
@@ -15,13 +15,15 @@
|
||||
formData = $bindable(),
|
||||
exists = $bindable(),
|
||||
transportModes = [],
|
||||
operationType = undefined
|
||||
operationType = undefined,
|
||||
invoiceType = undefined
|
||||
}: {
|
||||
invoice: Invoice | null;
|
||||
formData?: any;
|
||||
exists?: boolean;
|
||||
transportModes?: any[];
|
||||
operationType?: number;
|
||||
invoiceType?: string;
|
||||
} = $props();
|
||||
|
||||
if (!formData && invoice) {
|
||||
@@ -30,9 +32,11 @@
|
||||
comments_status: invoice.comments_status || '',
|
||||
// Campos que van en diferentes recursos pero se editan aquí
|
||||
transport_mode: invoice.logistics?.transport_mode || null,
|
||||
is_mixed: invoice.compliance_mx?.is_mixed || null,
|
||||
is_mixed: invoice.compliance_mx?.is_mixed ? 'si' : 'no',
|
||||
print_stamp: invoice.financials?.seal_value_2500 || false,
|
||||
rule_3121_parties_ii: false,
|
||||
rule_3121_parties_ii: invoice.compliance_mx?.rule_3121_parties_ii || false,
|
||||
// Errores (Copied from Continuation Tab)
|
||||
errores_facturacion: [],
|
||||
related_doc_id: invoice.related_doc_id || null,
|
||||
code_signature: invoice.compliance_mx?.signature_key || '',
|
||||
electronic_signature: invoice.compliance_mx?.electronic_signature || '',
|
||||
@@ -64,6 +68,8 @@
|
||||
is_mixed: null,
|
||||
print_stamp: false,
|
||||
rule_3121_parties_ii: false,
|
||||
// Errores
|
||||
errores_facturacion: [],
|
||||
related_doc_id: null,
|
||||
code_signature: '',
|
||||
electronic_signature: '',
|
||||
@@ -94,231 +100,280 @@
|
||||
// Función para cargar información
|
||||
console.log('Cargar información');
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
console.log('OthersTabForm props:', {
|
||||
invoiceType,
|
||||
operationType,
|
||||
isMixed: formData.is_mixed,
|
||||
rule: formData.rule_3121_parties_ii
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="grid grid-cols-3 grid-rows-1 gap-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.Trigger id="transport-mode">
|
||||
{transportModes.find((m) => m.key === formData.transport_mode)?.name ||
|
||||
'Seleccionar modo'}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{#each transportModes as mode}
|
||||
<Select.Item value={mode.key}>
|
||||
{mode.name}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
|
||||
{#if operationType !== 1}
|
||||
<!-- Imprimir Sello -->
|
||||
{#if invoiceType !== 'MEX'}
|
||||
<div class="grid grid-cols-3 grid-rows-1 gap-3">
|
||||
<div class="space-y-3 rounded-md border p-3">
|
||||
<!-- Modo de Transporte -->
|
||||
<div class="space-y-2">
|
||||
<Label class="opacity-0">Spacer</Label>
|
||||
<div class="flex items-center space-x-2">
|
||||
<Checkbox id="print-stamp" bind:checked={formData.print_stamp} />
|
||||
<Label for="print-stamp" class="font-normal">
|
||||
Imprimir el Sello por Valor menor a 2500 dlls
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Es Mixto -->
|
||||
<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')}
|
||||
class="flex gap-4"
|
||||
>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroupItem value="yes" id="mixed-yes" />
|
||||
<Label for="mixed-yes" class="font-normal">Sí</Label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroupItem value="no" id="mixed-no" />
|
||||
<Label for="mixed-no" class="font-normal">No</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
<Label class="opacity-0">Spacer</Label>
|
||||
</div>
|
||||
{:else}
|
||||
<!-- New Export Fields -->
|
||||
<div class="space-y-2 pt-2">
|
||||
<Label for="bill_number">Número Master BOL:</Label>
|
||||
<Input id="bill_number" bind:value={formData.bill_number} class="h-8 text-xs" />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="guide_number">Número Guía:</Label>
|
||||
<Input id="guide_number" bind:value={formData.guide_number} class="h-8 text-xs" />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="shipment_number">Número Embarque:</Label>
|
||||
<Input id="shipment_number" bind:value={formData.shipment_number} class="h-8 text-xs" />
|
||||
</div>
|
||||
<div class="space-y-2 pb-2">
|
||||
<Label for="option_iv18">Opción IV 18:</Label>
|
||||
<Label for="transport-mode">Modo de Transporte:</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.option_iv18}
|
||||
onValueChange={(v: string | undefined) => (formData.option_iv18 = v || '')}
|
||||
value={formData.transport_mode}
|
||||
onValueChange={(value: string | undefined) =>
|
||||
(formData.transport_mode = value || 'TRUCK')}
|
||||
>
|
||||
<Select.Trigger id="option_iv18" class="h-8 text-xs">
|
||||
{formData.option_iv18 || 'Seleccionar opción'}
|
||||
<Select.Trigger id="transport-mode">
|
||||
{transportModes.find((m) => m.key === formData.transport_mode)?.name ||
|
||||
'Seleccionar modo'}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
<Select.Item value="OPCION 1">Opción 1</Select.Item>
|
||||
<!-- Add more options as needed -->
|
||||
{#each transportModes as mode}
|
||||
<Select.Item value={mode.key}>
|
||||
{mode.name}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
|
||||
<!-- DATOS ENTREGA -->
|
||||
<div class="mt-4 space-y-3 rounded-md border bg-muted/20 p-3">
|
||||
<h4 class="mb-2 border-b pb-1 text-xs font-bold text-muted-foreground uppercase">
|
||||
Datos Entrega
|
||||
</h4>
|
||||
<div class="mb-2 flex items-center space-x-2">
|
||||
<Checkbox id="delivered_status" bind:checked={formData.delivered_status} />
|
||||
<Label for="delivered_status" class="text-xs font-semibold">Entregado</Label>
|
||||
</div>
|
||||
{#if operationType !== 1 && invoiceType !== 'MEX'}
|
||||
<!-- Imprimir Sello -->
|
||||
<div class="space-y-2">
|
||||
<Label for="received_by" class="text-xs">Recibido por:</Label>
|
||||
<Input
|
||||
id="received_by"
|
||||
bind:value={formData.received_by}
|
||||
disabled={!formData.delivered_status}
|
||||
class="h-7 text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="delivery_date" class="text-xs">Fecha Entrega:</Label>
|
||||
<Input
|
||||
id="delivery_date"
|
||||
type="date"
|
||||
bind:value={formData.delivery_date}
|
||||
disabled={!formData.delivered_status}
|
||||
class="h-7 text-xs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if operationType !== 1}
|
||||
<div class="flex items-center space-x-2">
|
||||
<Checkbox id="rule-3121" bind:checked={formData.rule_3121_parties_ii} />
|
||||
<Label for="rule-3121" class="font-normal">Regla 3.1.21 Partes II</Label>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Comentario estatus -->
|
||||
<div class="space-y-2">
|
||||
<Label class="opacity-0">Spacer</Label>
|
||||
<Label>Comentario Estatus:</Label>
|
||||
<Textarea
|
||||
id="comments_status"
|
||||
bind:value={formData.comments_status}
|
||||
placeholder="Comentario estatus"
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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} />
|
||||
</div>
|
||||
|
||||
<!-- Firma Electrónica -->
|
||||
<div class="space-y-2 md:col-span-2">
|
||||
<Label for="electronic-sig-1">Firma Electrónica:</Label>
|
||||
<div class="flex gap-2">
|
||||
<Input id="electronic-sig-1" bind:value={formData.code_signature} class="flex-1" />
|
||||
<Button variant="outline" size="icon">
|
||||
<Upload class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mandatario/Persona Autorizada -->
|
||||
<div class="space-y-2">
|
||||
<Label for="mandatory-person">Mandatario/Persona Autorizada:</Label>
|
||||
<Input id="mandatory-person" bind:value={formData.mandatory_person} />
|
||||
</div>
|
||||
|
||||
<!-- RFC -->
|
||||
<div class="space-y-2">
|
||||
<Label id="rfc" for="rfc">RFC: {rfc}</Label>
|
||||
</div>
|
||||
|
||||
<!-- CURP -->
|
||||
<div class="space-y-2">
|
||||
<Label id="curp" for="curp">CURP: {curp}</Label>
|
||||
</div>
|
||||
|
||||
<!-- Modo Contingencia -->
|
||||
<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>
|
||||
<Input id="cove" bind:value={formData.cove} placeholder="COVE" />
|
||||
</div>
|
||||
|
||||
<!-- Número de Operación -->
|
||||
<div class="space-y-2">
|
||||
<Label for="operation-num">Núm Operación:</Label>
|
||||
<Input id="operation-num" bind:value={formData.operation_num} />
|
||||
</div>
|
||||
|
||||
<!-- Adenda(s) -->
|
||||
<div class="space-y-2 md:col-span-2">
|
||||
<Label for="adendas">Adenda(s):</Label>
|
||||
<Input id="adendas" bind:value={formData.adendas} />
|
||||
</div>
|
||||
|
||||
<!-- Observaciones VU -->
|
||||
<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="min-h-[60px] flex-1"
|
||||
/>
|
||||
<Button variant="outline" onclick={loadInfo}>Cargar Info.</Button>
|
||||
<Label class="opacity-0">Spacer</Label>
|
||||
<div class="flex items-center space-x-2">
|
||||
<Checkbox id="print-stamp" bind:checked={formData.print_stamp} />
|
||||
<Label for="print-stamp" class="text-xs font-normal">
|
||||
Imprimir el Sello por Valor menor a 2500 dlls
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Número Certificado -->
|
||||
{#if invoiceType !== 'CR' && invoiceType !== 'REP' && invoiceType !== 'REPAR'}
|
||||
<!-- Es Mixto -->
|
||||
<div class="space-y-2">
|
||||
<Label class="opacity-0">Spacer</Label>
|
||||
<Label class="text-xs">Es Mixto?</Label>
|
||||
<RadioGroup.Root bind:value={formData.is_mixed} class="flex gap-4">
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroup.Item value="si" id="mixed-si" class="h-4 w-4" />
|
||||
<Label for="mixed-si" class="cursor-pointer text-xs font-normal">Sí</Label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroup.Item value="no" id="mixed-no" class="h-4 w-4" />
|
||||
<Label for="mixed-no" class="cursor-pointer text-xs font-normal">No</Label>
|
||||
</div>
|
||||
</RadioGroup.Root>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if (operationType === 1 || invoiceType === 'CR') && invoiceType !== 'REP' && invoiceType !== 'REPAR'}
|
||||
<!-- Export Fields (Hidden for Repair) -->
|
||||
<div class="space-y-2 pt-2">
|
||||
<Label for="bill_number">Número Master BOL:</Label>
|
||||
<Input id="bill_number" bind:value={formData.bill_number} class="h-8 text-xs" />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="certified-num">Número Certificado:</Label>
|
||||
<Input id="certified-num" bind:value={formData.certified_number} />
|
||||
<Label for="guide_number">Número Guía:</Label>
|
||||
<Input id="guide_number" bind:value={formData.guide_number} class="h-8 text-xs" />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="shipment_number">Número Embarque:</Label>
|
||||
<Input id="shipment_number" bind:value={formData.shipment_number} class="h-8 text-xs" />
|
||||
</div>
|
||||
<div class="space-y-2 pb-2">
|
||||
<Label for="option_iv18">Opción IV 18:</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.option_iv18}
|
||||
onValueChange={(v: string | undefined) => (formData.option_iv18 = v || '')}
|
||||
>
|
||||
<Select.Trigger id="option_iv18" class="h-8 text-xs">
|
||||
{formData.option_iv18 || 'Seleccionar opción'}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
<Select.Item value="OPCION 1">Opción 1</Select.Item>
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
|
||||
<!-- Firma Electrónica 2 -->
|
||||
<!-- DATOS ENTREGA -->
|
||||
<div class="mt-4 space-y-3 rounded-md border bg-muted/20 p-3">
|
||||
<h4 class="mb-2 border-b pb-1 text-xs font-bold text-muted-foreground uppercase">
|
||||
Datos Entrega
|
||||
</h4>
|
||||
<div class="mb-2 flex items-center space-x-2">
|
||||
<Checkbox id="delivered_status" bind:checked={formData.delivered_status} />
|
||||
<Label for="delivered_status" class="text-xs font-semibold">Entregado</Label>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="received_by" class="text-xs">Recibido por:</Label>
|
||||
<Input
|
||||
id="received_by"
|
||||
bind:value={formData.received_by}
|
||||
disabled={!formData.delivered_status}
|
||||
class="h-7 text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="delivery_date" class="text-xs">Fecha Entrega:</Label>
|
||||
<Input
|
||||
id="delivery_date"
|
||||
type="date"
|
||||
bind:value={formData.delivery_date}
|
||||
disabled={!formData.delivered_status}
|
||||
class="h-7 text-xs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if operationType !== 1 && invoiceType !== 'CR' && invoiceType !== 'REP' && invoiceType !== 'REPAR'}
|
||||
<div class="flex items-center space-x-2 py-2">
|
||||
<Checkbox id="rule-3121" bind:checked={formData.rule_3121_parties_ii} />
|
||||
<Label for="rule-3121" class="text-xs font-normal">Regla 3.1.21 Partes II</Label>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Comentario estatus -->
|
||||
<div class="space-y-2">
|
||||
<Label class="opacity-0">Spacer</Label>
|
||||
<Label>Comentario Estatus:</Label>
|
||||
<Textarea
|
||||
id="comments_status"
|
||||
bind:value={formData.comments_status}
|
||||
placeholder="Comentario estatus"
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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} />
|
||||
</div>
|
||||
|
||||
<!-- Firma Electrónica -->
|
||||
<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} />
|
||||
<Label for="electronic-sig-1">Firma Electrónica:</Label>
|
||||
<div class="flex gap-2">
|
||||
<Input id="electronic-sig-1" bind:value={formData.code_signature} class="flex-1" />
|
||||
<Button variant="outline" size="icon">
|
||||
<Upload class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mandatario/Persona Autorizada -->
|
||||
<div class="space-y-2">
|
||||
<Label for="mandatory-person">Mandatario/Persona Autorizada:</Label>
|
||||
<Input id="mandatory-person" bind:value={formData.mandatory_person} />
|
||||
</div>
|
||||
|
||||
<!-- RFC -->
|
||||
<div class="space-y-2">
|
||||
<Label id="rfc" for="rfc">RFC: {rfc}</Label>
|
||||
</div>
|
||||
|
||||
<!-- CURP -->
|
||||
<div class="space-y-2">
|
||||
<Label id="curp" for="curp">CURP: {curp}</Label>
|
||||
</div>
|
||||
|
||||
<!-- Modo Contingencia -->
|
||||
<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>
|
||||
<Input id="cove" bind:value={formData.cove} placeholder="COVE" />
|
||||
</div>
|
||||
|
||||
<!-- Número de Operación -->
|
||||
<div class="space-y-2">
|
||||
<Label for="operation-num">Núm Operación:</Label>
|
||||
<Input id="operation-num" bind:value={formData.operation_num} />
|
||||
</div>
|
||||
|
||||
<!-- Adenda(s) -->
|
||||
<div class="space-y-2 md:col-span-2">
|
||||
<Label for="adendas">Adenda(s):</Label>
|
||||
<Input id="adendas" bind:value={formData.adendas} />
|
||||
</div>
|
||||
|
||||
<!-- Observaciones VU -->
|
||||
<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="min-h-[60px] flex-1"
|
||||
/>
|
||||
<Button variant="outline" onclick={loadInfo}>Cargar Info.</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Número Certificado -->
|
||||
<div class="space-y-2">
|
||||
<Label for="certified-num">Número Certificado:</Label>
|
||||
<Input id="certified-num" bind:value={formData.certified_number} />
|
||||
</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>
|
||||
{:else}
|
||||
<div class="space-y-4 rounded-md border p-4">
|
||||
<h4 class="text-xs font-semibold text-muted-foreground uppercase">Errores de Facturación</h4>
|
||||
|
||||
<div class="rounded border">
|
||||
<table class="w-full text-xs">
|
||||
<thead class="bg-muted">
|
||||
<tr>
|
||||
<th class="border px-2 py-1 text-left">Línea</th>
|
||||
<th class="border px-2 py-1 text-left">Clave</th>
|
||||
<th class="border px-2 py-1 text-left">Descripción</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#if formData.errores_facturacion?.length}
|
||||
{#each formData.errores_facturacion as error}
|
||||
<tr>
|
||||
<td class="border px-2 py-1">{error.linea}</td>
|
||||
<td class="border px-2 py-1">{error.clave}</td>
|
||||
<td class="border px-2 py-1">{error.descripcion}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
{:else}
|
||||
<tr>
|
||||
<td colspan="3" class="border px-2 py-12 text-center text-muted-foreground">
|
||||
Sin errores registrados
|
||||
</td>
|
||||
</tr>
|
||||
{/if}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button variant="outline" size="sm" class="h-7 text-xs">Insertar</Button>
|
||||
<Button variant="outline" size="sm" class="h-7 text-xs">Editar</Button>
|
||||
<Button variant="outline" size="sm" class="h-7 text-xs">Borrar</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -57,7 +57,7 @@ export async function saveInvoice(options: SaveInvoiceOptions): Promise<SaveInvo
|
||||
missingFields.push(requiredFields.invoice_number);
|
||||
}
|
||||
// Validar campos de generalFormData
|
||||
if (!generalFormData?.document_type) {
|
||||
if (!generalFormData?.document_type && InvoiceTopFieldsFormData?.invoice_type !== 'MEX') {
|
||||
missingFields.push(requiredFields.document_type);
|
||||
}
|
||||
|
||||
@@ -118,9 +118,9 @@ function buildInvoicePayload(formData: FormDataSet): CreateInvoiceData | UpdateI
|
||||
emission_date: InvoiceTopFieldsFormData?.emission_date || undefined,
|
||||
proforma_number: observationFormData?.proforma_number || undefined,
|
||||
// Observation fields from observationFormData
|
||||
observation_es: observationFormData?.observation_es || undefined,
|
||||
observation_en: observationFormData?.observation_en || undefined,
|
||||
alternate_invoice: observationFormData?.alternate_invoice || undefined,
|
||||
observation_es: observationFormData ? observationFormData.observation_es : undefined,
|
||||
observation_en: observationFormData ? observationFormData.observation_en : undefined,
|
||||
alternate_invoice: InvoiceTopFieldsFormData?.alternate_invoice || (observationFormData ? observationFormData.alternate_invoice : undefined),
|
||||
// Fields from othersFormData and continuationFormData that go to invoice header
|
||||
related_doc_id: othersFormData?.related_doc_id || undefined,
|
||||
vu_observations: othersFormData?.observations_vu || undefined,
|
||||
@@ -151,12 +151,13 @@ function buildInvoicePayload(formData: FormDataSet): CreateInvoiceData | UpdateI
|
||||
othersFormData?.code_signature || othersFormData?.electronic_signature ||
|
||||
othersFormData?.mandatory_person ||
|
||||
continuationFormData?.puerto_entrada || continuationFormData?.reason_export ||
|
||||
continuationFormData?.is_mixed ||
|
||||
generalFormData?.manifest_number ||
|
||||
generalFormData?.shipped_by_id || generalFormData?.shipped_by_header;
|
||||
continuationFormData?.is_mixed !== undefined ||
|
||||
observationFormData?.is_mixed !== undefined || observationFormData?.sub_division !== undefined || observationFormData?.office_document !== undefined ||
|
||||
generalFormData?.manifest_number !== undefined ||
|
||||
generalFormData?.shipped_by_id !== undefined || generalFormData?.shipped_by_header !== undefined;
|
||||
|
||||
if (hasComplianceValue) {
|
||||
payload.compliance_mx = buildComplianceMxData(InvoiceTopFieldsFormData, generalFormData, othersFormData, observationFormData, continuationFormData);
|
||||
payload.compliance_mx = buildComplianceMxData(InvoiceTopFieldsFormData?.invoice_type, InvoiceTopFieldsFormData, generalFormData, othersFormData, observationFormData, continuationFormData);
|
||||
}
|
||||
|
||||
// Financials
|
||||
@@ -168,7 +169,7 @@ function buildInvoicePayload(formData: FormDataSet): CreateInvoiceData | UpdateI
|
||||
observationFormData?.total_increments_me || othersFormData?.print_stamp;
|
||||
|
||||
if (hasFinancialsValue) {
|
||||
payload.financials = buildFinancialsData(generalFormData, observationFormData, othersFormData);
|
||||
payload.financials = buildFinancialsData(generalFormData, observationFormData, othersFormData, InvoiceTopFieldsFormData);
|
||||
}
|
||||
|
||||
// Logistics
|
||||
@@ -184,7 +185,7 @@ function buildInvoicePayload(formData: FormDataSet): CreateInvoiceData | UpdateI
|
||||
return payload;
|
||||
}
|
||||
|
||||
function buildComplianceMxData(InvoiceTopFieldsFormData: any, generalFormData: any, othersFormData: any, observationFormData: any, continuationFormData: any) {
|
||||
function buildComplianceMxData(invoiceType: string, InvoiceTopFieldsFormData: any, generalFormData: any, othersFormData: any, observationFormData: any, continuationFormData: any) {
|
||||
return {
|
||||
// Pedimento fields - desde InvoiceTopFieldsFormData
|
||||
pedimento_id: InvoiceTopFieldsFormData?.pedimento_id ? Number(InvoiceTopFieldsFormData.pedimento_id) : null,
|
||||
@@ -207,8 +208,13 @@ function buildComplianceMxData(InvoiceTopFieldsFormData: any, generalFormData: a
|
||||
// Fields from observationFormData
|
||||
movement_type: observationFormData?.movement_type || null,
|
||||
enclosure: observationFormData?.enclosure || null,
|
||||
// Fields from othersFormData or continuationFormData (priority to continuation in export)
|
||||
is_mixed: continuationFormData?.is_mixed === 'si' ? true : (othersFormData?.is_mixed !== undefined ? othersFormData.is_mixed : null),
|
||||
// Fields from othersFormData or continuationFormData (priority based on invoice type)
|
||||
is_mixed: invoiceType === 'MEX'
|
||||
? (observationFormData?.is_mixed || false)
|
||||
: (invoiceType === 'CR' || invoiceType === 'REP' || InvoiceTopFieldsFormData?.operation_type === 'exp'
|
||||
? (continuationFormData?.is_mixed === 'si')
|
||||
: (othersFormData?.is_mixed === 'si')),
|
||||
is_regime_change: invoiceType === 'CR' ? true : (othersFormData?.contingency_mode !== undefined ? othersFormData.contingency_mode : false), // Note: reuse check logic if exists elsewhere, but here we explicitly set it for CR
|
||||
reason_export: continuationFormData?.reason_export || null,
|
||||
acts_as: othersFormData?.mandatory_person || null,
|
||||
contingency_mode: othersFormData?.contingency_mode || false,
|
||||
@@ -217,19 +223,20 @@ function buildComplianceMxData(InvoiceTopFieldsFormData: any, generalFormData: a
|
||||
vucem_operation_num: othersFormData?.operation_num || null,
|
||||
addendum_vu: othersFormData?.adendas || null,
|
||||
certificate_number: othersFormData?.certified_number || null,
|
||||
signature_key: othersFormData?.code_signature || null,
|
||||
electronic_signature: othersFormData?.electronic_signature || null,
|
||||
signature_key: othersFormData?.code_signature || generalFormData?.code_signature || null,
|
||||
electronic_signature: othersFormData?.electronic_signature || generalFormData?.electronic_signature || null,
|
||||
subdivision: observationFormData?.sub_division || null,
|
||||
office_document: observationFormData?.office_document || null,
|
||||
rule_3121_parties_ii: othersFormData?.rule_3121_parties_ii || false,
|
||||
};
|
||||
}
|
||||
|
||||
function buildFinancialsData(generalFormData: any, observationFormData: any, othersFormData: any) {
|
||||
function buildFinancialsData(generalFormData: any, observationFormData: any, othersFormData: any, InvoiceTopFieldsFormData?: any) {
|
||||
return {
|
||||
// Currency from generalFormData
|
||||
currency_type: generalFormData?.currency_type || null,
|
||||
currency: generalFormData?.currency || null,
|
||||
iva_factor: generalFormData?.iva_factor !== null && generalFormData?.iva_factor !== undefined ? String(generalFormData.iva_factor) : null,
|
||||
iva_factor: (InvoiceTopFieldsFormData?.iva_factor || generalFormData?.iva_factor) ? String(InvoiceTopFieldsFormData?.iva_factor || generalFormData.iva_factor) : null,
|
||||
// Costs & increments from observationFormData
|
||||
freight: observationFormData?.freight || null,
|
||||
insurance: observationFormData?.insurance || null,
|
||||
@@ -278,5 +285,6 @@ function buildLogisticsData(generalFormData: any, observationFormData: any, othe
|
||||
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,
|
||||
vehicle_data: continuationFormData?.vehicle_data || null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -355,6 +355,10 @@ export function getSidebarData(): SidebarData {
|
||||
{
|
||||
title: m["sidebar.import_invoices.regime_change"](),
|
||||
url: "/dashboard/invoices?operation_type=imp&invoice_type=CR",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.import_invoices.repair"](),
|
||||
url: "/dashboard/invoices?operation_type=imp&invoice_type=REP",
|
||||
}
|
||||
],
|
||||
},
|
||||
|
||||
@@ -17,7 +17,15 @@
|
||||
import type { PageData } from './$types';
|
||||
import { browser } from '$app/environment';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import { Plus, RefreshCw, FileText, RotateCcw, Boxes, Package, ClipboardList } from 'lucide-svelte';
|
||||
import {
|
||||
Plus,
|
||||
RefreshCw,
|
||||
FileText,
|
||||
RotateCcw,
|
||||
Boxes,
|
||||
Package,
|
||||
ClipboardList
|
||||
} from 'lucide-svelte';
|
||||
|
||||
// IMPORTANTE: Asegúrate de tener instalada svelte-sonner para las notificaciones
|
||||
import { toast } from 'svelte-sonner';
|
||||
@@ -39,7 +47,7 @@
|
||||
year: data.filters?.year || ''
|
||||
});
|
||||
|
||||
let isDownloadModalOpen = $state(false);
|
||||
let isDownloadModalOpen = $state(false);
|
||||
|
||||
// Efecto reactivo para actualizar filtros cuando cambian los query parameters en la URL
|
||||
$effect(() => {
|
||||
@@ -611,32 +619,36 @@
|
||||
currentStatusFunction = null;
|
||||
}
|
||||
|
||||
// --- AQUÍ PASAMOS LA FUNCIÓN DE DESCARGA A LAS COLUMNAS ---
|
||||
const columns = createColumns(handleSuccess);
|
||||
async function handleModalConfirm(type: string, format: string, currency: string, uomSource: string, weightUnit: string) {
|
||||
if (!selectedInvoice || !companyStore.activeCompany) return;
|
||||
// --- AQUÍ PASAMOS LA FUNCIÓN DE DESCARGA A LAS COLUMNAS ---
|
||||
const columns = createColumns(handleSuccess);
|
||||
async function handleModalConfirm(
|
||||
type: string,
|
||||
format: string,
|
||||
currency: string,
|
||||
uomSource: string,
|
||||
weightUnit: string
|
||||
) {
|
||||
if (!selectedInvoice || !companyStore.activeCompany) return;
|
||||
|
||||
try {
|
||||
// 1. Trigger: Start celery task with selected options
|
||||
// Note: uomSource and weightUnit are UI-only for now
|
||||
const { task_id } = await invoicesReportsApi.triggerPdfGeneration(
|
||||
selectedInvoice.id,
|
||||
companyStore.activeCompany.id,
|
||||
type,
|
||||
currency
|
||||
);
|
||||
|
||||
// 2. Open Progress Dialog
|
||||
currentTaskId = task_id;
|
||||
currentStatusFunction = invoicesReportsApi.getTaskStatus;
|
||||
showProgressDialog = true;
|
||||
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.error("No se pudo iniciar la descarga");
|
||||
}
|
||||
}
|
||||
try {
|
||||
// 1. Trigger: Start celery task with selected options
|
||||
// Note: uomSource and weightUnit are UI-only for now
|
||||
const { task_id } = await invoicesReportsApi.triggerPdfGeneration(
|
||||
selectedInvoice.id,
|
||||
companyStore.activeCompany.id,
|
||||
type,
|
||||
currency
|
||||
);
|
||||
|
||||
// 2. Open Progress Dialog
|
||||
currentTaskId = task_id;
|
||||
currentStatusFunction = invoicesReportsApi.getTaskStatus;
|
||||
showProgressDialog = true;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.error('No se pudo iniciar la descarga');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="space-y-6">
|
||||
@@ -659,13 +671,13 @@
|
||||
>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<div class="grid grid-cols-1 md:grid-cols-5 gap-4">
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-5">
|
||||
<div class="space-y-2">
|
||||
<Label for="filter-operation-type">Tipo de Operación</Label>
|
||||
<select
|
||||
id="filter-operation-type"
|
||||
bind:value={filters.operation_type}
|
||||
class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
||||
class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-none"
|
||||
>
|
||||
{#each operationTypeOptions as option}
|
||||
<option value={option.value}>{option.label}</option>
|
||||
@@ -678,7 +690,7 @@
|
||||
<select
|
||||
id="filter-invoice-type"
|
||||
bind:value={filters.invoice_type}
|
||||
class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
||||
class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-none"
|
||||
>
|
||||
{#each invoiceTypeOptions() as option}
|
||||
<option value={option.value}>{option.label}</option>
|
||||
@@ -759,82 +771,77 @@
|
||||
|
||||
<!-- Footer fijo con botones de acción -->
|
||||
<div
|
||||
class="fixed bottom-0 left-0 right-0 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/80 border-t shadow-lg z-[5] group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] ml-[calc(var(--sidebar-width))]"
|
||||
class="fixed right-0 bottom-0 left-0 z-[5] ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
|
||||
>
|
||||
<div class="px-4 py-4 max-w-[1400px] mx-auto">
|
||||
<div class="mx-auto max-w-[1400px] px-4 py-4">
|
||||
<!-- Botones de acción -->
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button variant="outline" size="sm" disabled={!selectedInvoice}>
|
||||
<RefreshCw class="h-4 w-4 mr-2" />
|
||||
<RefreshCw class="mr-2 h-4 w-4" />
|
||||
Actualizar
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" disabled={!selectedInvoice}>
|
||||
<RotateCcw class="h-4 w-4 mr-2" />
|
||||
<RotateCcw class="mr-2 h-4 w-4" />
|
||||
Desactualizar
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => isDownloadModalOpen = true}
|
||||
onclick={() => (isDownloadModalOpen = true)}
|
||||
disabled={!selectedInvoice}
|
||||
>
|
||||
<FileText class="h-4 w-4 mr-2" />
|
||||
<FileText class="mr-2 h-4 w-4" />
|
||||
Factura
|
||||
</Button>
|
||||
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => selectedInvoice && handleDownloadConsolidated(selectedInvoice)}
|
||||
disabled={!selectedInvoice}
|
||||
>
|
||||
<Boxes class="h-4 w-4 mr-2" />
|
||||
<Boxes class="mr-2 h-4 w-4" />
|
||||
Consolidado
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => selectedInvoice && handleDownloadAvisoConsolidado(selectedInvoice)}
|
||||
disabled={!selectedInvoice}
|
||||
>
|
||||
<Boxes class="mr-2 h-4 w-4" />
|
||||
Aviso Consolidado
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => selectedInvoice && handleDownloadPackingList(selectedInvoice)}
|
||||
disabled={!selectedInvoice}
|
||||
>
|
||||
<Package class="mr-2 h-4 w-4" />
|
||||
Packing List
|
||||
</Button>
|
||||
|
||||
{#if selectedInvoice?.operation_type === 'exp'}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => selectedInvoice && handleDownloadDescargo(selectedInvoice)}
|
||||
onclick={() => selectedInvoice && handleDownloadAvisoConsolidado(selectedInvoice)}
|
||||
disabled={!selectedInvoice}
|
||||
>
|
||||
<ClipboardList class="mr-2 h-4 w-4" />
|
||||
Descargo PEPS
|
||||
<Boxes class="mr-2 h-4 w-4" />
|
||||
Aviso Consolidado
|
||||
</Button>
|
||||
{/if}
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => selectedInvoice && handleDownloadPackingList(selectedInvoice)}
|
||||
disabled={!selectedInvoice}
|
||||
>
|
||||
<Package class="mr-2 h-4 w-4" />
|
||||
Packing List
|
||||
</Button>
|
||||
|
||||
{#if selectedInvoice?.operation_type === 'exp'}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => selectedInvoice && handleDownloadDescargo(selectedInvoice)}
|
||||
disabled={!selectedInvoice}
|
||||
>
|
||||
<ClipboardList class="mr-2 h-4 w-4" />
|
||||
Descargo PEPS
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ... -->
|
||||
|
||||
|
||||
<!-- ... -->
|
||||
|
||||
{#if selectedInvoice && companyStore.activeCompany}
|
||||
<InvoiceDownloadModal
|
||||
bind:open={isDownloadModalOpen}
|
||||
onConfirm={handleModalConfirm}
|
||||
/>
|
||||
{/if}
|
||||
{#if selectedInvoice && companyStore.activeCompany}
|
||||
<InvoiceDownloadModal bind:open={isDownloadModalOpen} onConfirm={handleModalConfirm} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -145,9 +145,9 @@ export const load: PageServerLoad = async ({ params, cookies, fetch, url }) => {
|
||||
if (params.id === 'new') {
|
||||
try {
|
||||
const [
|
||||
invoiceTypesResponse,
|
||||
customsBrokersResponse,
|
||||
clientsResponse,
|
||||
invoiceTypesResponse,
|
||||
customsBrokersResponse,
|
||||
clientsResponse,
|
||||
providersResponse,
|
||||
currencyTypesResponse,
|
||||
transportTypesResponse,
|
||||
@@ -191,7 +191,7 @@ export const load: PageServerLoad = async ({ params, cookies, fetch, url }) => {
|
||||
const drivers = driversResponse.ok ? await driversResponse.json() : { items: [] };
|
||||
const trailers = trailersResponse.ok ? await trailersResponse.json() : { items: [] };
|
||||
const customsSections = customsSectionsResponse.ok ? await customsSectionsResponse.json() : { items: [] };
|
||||
const codePedimentoRegimens = codePedimentoRegimensResponse.ok ? await codePedimentoRegimensResponse.json() : { items: [] };
|
||||
const codePedimentoRegimens = codePedimentoRegimensResponse.ok ? await codePedimentoRegimensResponse.json() : { items: [] };
|
||||
const seals = sealsResponse.ok ? await sealsResponse.json() : { items: [] };
|
||||
const incoterms = incotermsResponse.ok ? await incotermsResponse.json() : { items: [] };
|
||||
const pedimentos = pedimentosResponse.ok ? await pedimentosResponse.json() : { items: [] };
|
||||
@@ -276,9 +276,9 @@ export const load: PageServerLoad = async ({ params, cookies, fetch, url }) => {
|
||||
|
||||
// Cargar también los datos de referencia para edición
|
||||
const [
|
||||
invoiceTypesResponse,
|
||||
customsBrokersResponse,
|
||||
clientsResponse,
|
||||
invoiceTypesResponse,
|
||||
customsBrokersResponse,
|
||||
clientsResponse,
|
||||
providersResponse,
|
||||
currencyTypesResponse,
|
||||
transportTypesResponse,
|
||||
|
||||
@@ -112,12 +112,22 @@
|
||||
let showExchangeRateDialog = $state(false);
|
||||
let missingExchangeRateDate = $state('');
|
||||
|
||||
// Derivados reactivos para el tipo de operación
|
||||
// Derivados reactivos para el tipo de factura y operación
|
||||
let invoiceType = $derived.by(() => {
|
||||
return (
|
||||
InvoiceTopFieldsFormData?.invoice_type ||
|
||||
data.filters?.invoice_type ||
|
||||
data.invoice?.invoice_type ||
|
||||
'TEM'
|
||||
); // Default to TEM if not found
|
||||
});
|
||||
|
||||
let operationTypeText = $derived.by(() => {
|
||||
const type =
|
||||
InvoiceTopFieldsFormData?.operation_type ||
|
||||
data.invoice?.operation_type ||
|
||||
data.filters?.operation_type;
|
||||
if (invoiceType === 'CR') return 'CAMBIO DE RÉGIMEN';
|
||||
if (type === 'exp') return 'EXPORTACIÓN';
|
||||
if (type === 'imp') return 'IMPORTACIÓN';
|
||||
return 'TIPO NO DEFINIDO';
|
||||
@@ -390,6 +400,7 @@
|
||||
pedimentos={data.pedimentos || []}
|
||||
defaultOperationType={data.filters?.operation_type ?? undefined}
|
||||
defaultInvoiceType={data.filters?.invoice_type ?? undefined}
|
||||
{invoiceType}
|
||||
/>
|
||||
|
||||
<Tabs.Content value="general">
|
||||
@@ -397,6 +408,7 @@
|
||||
invoice={data.invoice}
|
||||
bind:formData={generalFormData}
|
||||
invoiceTypes={data.invoiceTypes || []}
|
||||
{invoiceType}
|
||||
customsBrokers={data.customsBrokers || []}
|
||||
clients={data.clients || []}
|
||||
providers={data.providers || []}
|
||||
@@ -431,6 +443,7 @@
|
||||
seals={data.seals || []}
|
||||
incoterms={data.incoterms || []}
|
||||
enclosure={data.enclosure || []}
|
||||
{invoiceType}
|
||||
operationType={InvoiceTopFieldsFormData?.operation_type === 'exp'
|
||||
? 1
|
||||
: InvoiceTopFieldsFormData?.operation_type === 'imp'
|
||||
@@ -448,6 +461,7 @@
|
||||
invoice={data.invoice}
|
||||
bind:formData={itemsFormData}
|
||||
bind:exists={itemsExists}
|
||||
{invoiceType}
|
||||
operationType={InvoiceTopFieldsFormData?.operation_type === 'exp'
|
||||
? 1
|
||||
: InvoiceTopFieldsFormData?.operation_type === 'imp'
|
||||
@@ -465,6 +479,7 @@
|
||||
invoice={data.invoice}
|
||||
bind:formData={othersFormData}
|
||||
bind:exists={othersExists}
|
||||
{invoiceType}
|
||||
operationType={InvoiceTopFieldsFormData?.operation_type === 'exp'
|
||||
? 1
|
||||
: InvoiceTopFieldsFormData?.operation_type === 'imp'
|
||||
@@ -478,22 +493,25 @@
|
||||
/>
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value="continuation">
|
||||
<ContinuationTabForm
|
||||
invoice={data.invoice}
|
||||
bind:formData={continuationFormData}
|
||||
bind:exists={continuationExists}
|
||||
operationType={InvoiceTopFieldsFormData?.operation_type === 'exp'
|
||||
? 1
|
||||
: InvoiceTopFieldsFormData?.operation_type === 'imp'
|
||||
? 2
|
||||
: data.invoice?.operation_type === 'exp'
|
||||
? 1
|
||||
: data.invoice?.operation_type === 'imp'
|
||||
? 2
|
||||
: undefined}
|
||||
/>
|
||||
</Tabs.Content>
|
||||
{#if invoiceType !== 'MEX'}
|
||||
<Tabs.Content value="continuation">
|
||||
<ContinuationTabForm
|
||||
invoice={data.invoice}
|
||||
bind:formData={continuationFormData}
|
||||
bind:exists={continuationExists}
|
||||
{invoiceType}
|
||||
operationType={InvoiceTopFieldsFormData?.operation_type === 'exp'
|
||||
? 1
|
||||
: InvoiceTopFieldsFormData?.operation_type === 'imp'
|
||||
? 2
|
||||
: data.invoice?.operation_type === 'exp'
|
||||
? 1
|
||||
: data.invoice?.operation_type === 'imp'
|
||||
? 2
|
||||
: undefined}
|
||||
/>
|
||||
</Tabs.Content>
|
||||
{/if}
|
||||
</Tabs.Root>
|
||||
</div>
|
||||
</div>
|
||||
@@ -521,7 +539,11 @@
|
||||
<!-- Tabs Navigation -->
|
||||
<Tabs.Root bind:value={activeTab}>
|
||||
<div class="w-full overflow-x-auto pb-2">
|
||||
<Tabs.List class="inline-flex md:grid md:w-full md:grid-cols-5">
|
||||
<Tabs.List
|
||||
class="inline-flex md:grid md:w-full {invoiceType === 'MEX'
|
||||
? 'md:grid-cols-4'
|
||||
: 'md:grid-cols-5'}"
|
||||
>
|
||||
<Tabs.Trigger value="general" disabled={false} class="whitespace-nowrap">
|
||||
<FileText size={16} class="mr-2" />
|
||||
General
|
||||
@@ -538,10 +560,12 @@
|
||||
<Truck size={16} class="mr-2" />
|
||||
Otros
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger value="continuation" disabled={false} class="whitespace-nowrap">
|
||||
<Package size={16} class="mr-2" />
|
||||
Cont.
|
||||
</Tabs.Trigger>
|
||||
{#if invoiceType !== 'MEX'}
|
||||
<Tabs.Trigger value="continuation" disabled={false} class="whitespace-nowrap">
|
||||
<Package size={16} class="mr-2" />
|
||||
Cont.
|
||||
</Tabs.Trigger>
|
||||
{/if}
|
||||
</Tabs.List>
|
||||
</div>
|
||||
</Tabs.Root>
|
||||
|
||||
Reference in New Issue
Block a user