Merge pull request 'fix/item-validations' (#142) from fix/item-validations into development

Reviewed-on: ADUANASOFT/anexo76#142
This commit is contained in:
2026-02-13 23:08:00 +00:00
27 changed files with 446 additions and 257 deletions

View File

@@ -58,7 +58,7 @@ def validate_update(
# Columna A: Pedimento (si no viene en CSV, usar el existente)
if invoice_data.compliance_mx.pedimento_id:
invoice_data.compliance_mx.pedimento_id = clean_str(invoice_data.compliance_mx.pedimento_id)
invoice_data.compliance_mx.pedimento_id = invoice_data.compliance_mx.pedimento_id
else:
invoice_data.compliance_mx.pedimento_id = existing_invoice.compliance_mx.pedimento_id if existing_invoice.compliance_mx else None

View File

@@ -3,36 +3,31 @@ from core.exceptions import ErrorCollector
from ..line_items import models
from sqlalchemy.orm import Session
def item_exists(
db: Session,
item_line: int,
tenant_id: int,
company_id: int
):
def item_exists(db: Session, item_line: int, tenant_id: int, company_id: int):
item_exists = (
db.query(models.LineItem.id)
db.query(models.LineItem)
.filter(
models.LineItem.line_number == item_line,
models.LineItem.tenant_id == tenant_id,
models.LineItem.company_id == company_id,
)
.first()
)
return item_exists
def count_items(db: Session, invoice_id: int, tenant_id: int, company_id: int):
count = (
db.query(func.count())
.select_from(models.Item)
.filter(
models.Item.invoice_id == invoice_id,
models.Item.tenant_id == tenant_id,
models.Item.company_id == company_id,
)
.scalar()
)
if item_exists:
return item_exists
return None
def count_items(
db: Session,
invoice_id: int,
tenant_id: int,
company_id: int
):
count = db.query(func.count()).select_from(models.Item).filter(
models.Item.invoice_id == invoice_id,
models.Item.tenant_id == tenant_id,
models.Item.company_id == company_id,
).scalar()
return count
return count

View File

@@ -42,9 +42,7 @@ def validate_common(
invoice: InvoiceHeader = invoice_exists_by_id(
db, invoice_id, tenant_id, company_id, errors
)
line_item: LineItem = item_exists(
db, line.line_number, tenant_id, company_id
)
line_item: LineItem = item_exists(db, line.line_number, tenant_id, company_id)
fecha_factura = invoice.invoice_date if invoice else None
fraction = None
@@ -181,13 +179,15 @@ def validate_common(
fraction = line.customs.fraction if line.customs.fraction else fraction
country = line.customs.origin_country
if line_item:
if line_item and line_item.customs:
country = (
line_item.customs.fraction if line_item.customs.origin_country else country
line_item.customs.origin_country
if line_item.customs.origin_country
else country
)
fraction_type = line.customs.fraction_type.upper()
if line_item:
if line_item and line_item.customs:
fraction_type = (
line_item.customs.fraction_type
if line_item.customs.fraction_type
@@ -195,7 +195,7 @@ def validate_common(
)
sector = line.customs.sector
if line_item:
if line_item and line_item.customs:
sector = line_item.customs.sector if line_item.customs.sector else sector
country_m3 = db.query(Country.m3_key).filter(Country.m3_key == country).scalar()

View File

@@ -206,8 +206,8 @@ def validate_create(
net_weight_input = line.quantity.net_weight or Decimal("0")
# Determinar si la unidad de medida es de peso
unit_is_kgs = line.unit_of_measure and line.unit_of_measure.upper() == "KGS"
unit_is_lbs = line.unit_of_measure and line.unit_of_measure.upper() == "LB"
unit_is_kgs = line.unit_of_measure and line.unit_of_measure == "24" #KGS
unit_is_lbs = line.unit_of_measure and line.unit_of_measure == "25" #LBS
# Calcular peso neto en kilogramos (estándar interno)
if unit_is_kgs:
@@ -237,11 +237,11 @@ def validate_create(
package_weight_unit = Decimal("0")
# Obtener peso unitario del bulto si existe
if line.quantity.package_key:
if line.quantity.package_id:
package: Package = (
db.query(Package)
.filter(
Package.key == line.quantity.package_key,
Package.id == line.quantity.package_id,
Package.tenant_id == tenant_id,
Package.company_id == company_id,
)
@@ -278,22 +278,22 @@ def validate_create(
# ==========================================
# ASIGNAR DESCRIPCIÓN DE BULTOS
# ==========================================
if package_quantity and package_quantity > 0 and line.quantity.package_key:
if package_quantity and package_quantity > 0 and line.quantity.package_id:
package: Package = (
db.query(Package)
.filter(
Package.key == line.quantity.package_key,
Package.id == line.quantity.package_id,
Package.tenant_id == tenant_id,
Package.company_id == company_id,
)
.first()
)
if package:
line.quantity.package_description = package.description_es
line.description.package_description = package.description_es
else:
line.quantity.package_quantity = 0
line.quantity.package_key = None
line.quantity.package_description = None
line.quantity.package_id = None
line.description.package_description = None
# ==========================================
# ASIGNAR FRACCIÓN AMERICANA POR DEFECTO

View File

@@ -24,7 +24,8 @@ class LineDescription(Base):
description_english: Mapped[Optional[str]] = mapped_column(String(4999)) # DESCRIPCIONI
extra_description: Mapped[Optional[str]] = mapped_column(Text) # DESCRIPCIONEEXTRA
part_description: Mapped[Optional[str]] = mapped_column(String(500)) # DESCRIPCIONPARTE
class_description: Mapped[Optional[str]] = mapped_column(String(500)) # DESCRIPCIONCLASE
class_description: Mapped[Optional[str]] = mapped_column(String(500)) # DESCRIPCIONCLASE
package_description: Mapped[Optional[str]] = mapped_column(String(500)) # DESCRIPCIONBULTO
# Product attributes
brand: Mapped[Optional[str]] = mapped_column(String(50)) # MARCA

View File

@@ -13,6 +13,7 @@ class LineDescriptionBase(BaseModel):
extra_description: Optional[str] = Field(None, description="Extra description (DESCRIPCIONEEXTRA)")
part_description: Optional[str] = Field(None, max_length=500, description="Part description (DESCRIPCIONPARTE)")
class_description: Optional[str] = Field(None, max_length=500, description="Class description (DESCRIPCIONCLASE)")
package_description: Optional[str] = Field(None, max_length=500, description="Package description (DESCRIPCIONBULTO)")
# Product attributes
brand: Optional[str] = Field(None, max_length=50, description="Brand (MARCA)")

View File

@@ -17,6 +17,7 @@ from api.v1.modules.public.reference_data.customs_sections.dto import CustomsSec
from api.v1.modules.public.reference_data.code_pedimento_regimens.dto import CodePedimentoRegimenDTO
from api.v1.modules.a76.customs_brokers.dto import CustomsBrokerResponseDTO
from api.v1.modules.a76.clients_and_providers.dto import ClientProviderResponseDTO
from .dtos.pedimentos import PedimentosResponse
from .schemas import PedimentoCatalogsResponse, PedimentoCreationResponse, PedimentoEditionResponse
@@ -111,10 +112,13 @@ class PedimentoCatalogService:
if not pedimento:
return None
# Convert SQLAlchemy object to Pydantic DTO
pedimento_dto = PedimentosResponse.model_validate(pedimento)
return PedimentoEditionResponse(
**catalogs.model_dump(),
is_create=False,
pedimento=pedimento,
pedimento=pedimento_dto,
pedimento_id=pedimento_id
)

View File

@@ -2,7 +2,7 @@
Consolidated schemas for Pedimento catalog responses
"""
from typing import List, Optional, Any
from typing import List, Optional
from pydantic import BaseModel
# Import DTOs for catalog items
@@ -11,6 +11,7 @@ from api.v1.modules.public.reference_data.customs_sections.dto import CustomsSec
from api.v1.modules.public.reference_data.code_pedimento_regimens.dto import CodePedimentoRegimenDTO
from api.v1.modules.a76.customs_brokers.dto import CustomsBrokerResponseDTO
from api.v1.modules.a76.clients_and_providers.dto import ClientProviderResponseDTO
from .dtos.pedimentos import PedimentosResponse
class PedimentoCatalogsResponse(BaseModel):
@@ -33,5 +34,5 @@ class PedimentoEditionResponse(PedimentoCatalogsResponse):
"""Response for editing an existing pedimento (catalogs + pedimento data)"""
is_create: bool = False
pedimento: Optional[Any] = None # Will be PedimentosResponse but avoiding circular import
pedimento: Optional[PedimentosResponse] = None
pedimento_id: Optional[int] = None

View File

@@ -26,7 +26,7 @@
credentials: 'include'
});
if (response.ok) {
const data = await response.json();
const data = await response.json();
if (Array.isArray(data)) {
countries = data;
} else if (data.items && Array.isArray(data.items)) {
@@ -80,7 +80,7 @@
</script>
<Dialog.Root bind:open>
<Dialog.Content class="max-w-4xl w-full max-h-[85vh] p-0 flex flex-col">
<Dialog.Content class="!max-w-[70vw] w-[70vw] max-h-[90vh] p-0 flex flex-col">
<Dialog.Header class="px-6 py-4 border-b">
<Dialog.Title class="text-lg font-semibold">CATALOGO DE PAISES</Dialog.Title>
</Dialog.Header>
@@ -88,7 +88,11 @@
<div class="px-6 py-3 border-b bg-zinc-50 dark:bg-zinc-900">
<div class="flex items-center gap-2">
<Search class="w-4 h-4 text-zinc-400" />
<Input bind:value={searchTerm} placeholder="Buscando..." class="flex-1 h-9" />
<Input
bind:value={searchTerm}
placeholder="Buscando..."
class="flex-1 h-9"
/>
</div>
</div>
@@ -106,7 +110,9 @@
<table class="w-full text-sm">
<thead class="bg-zinc-900 dark:bg-zinc-800 text-white">
<tr>
<th class="px-3 py-2 text-left font-semibold border-r border-zinc-700">Clave M3</th>
<th class="px-3 py-2 text-left font-semibold border-r border-zinc-700"
>Clave M3</th
>
<th class="px-3 py-2 text-left font-semibold border-r border-zinc-700"
>Clave Mexicana</th
>
@@ -125,11 +131,11 @@
class="border-b hover:bg-zinc-100 dark:hover:bg-zinc-800 cursor-pointer transition-colors"
onclick={() => handleSelect(country)}
>
<td class="px-3 py-2 border-r">{country.m3_key || ''}</td>
<td class="px-3 py-2 border-r">{country.mex_key || ''}</td>
<td class="px-3 py-2 border-r">{country.description_es || ''}</td>
<td class="px-3 py-2 border-r text-center">{country.ame_key || ''}</td>
<td class="px-3 py-2">{country.description_en || ''}</td>
<td class="px-3 py-2 border-r">{country.m3_key || ''}</td>
<td class="px-3 py-2 border-r">{country.mex_key || ''}</td>
<td class="px-3 py-2 border-r">{country.description_es || ''}</td>
<td class="px-3 py-2 border-r text-center">{country.ame_key || ''}</td>
<td class="px-3 py-2">{country.description_en || ''}</td>
</tr>
{/each}
{#if filteredCountries.length === 0}
@@ -143,9 +149,7 @@
</table>
</div>
<div
class="mt-4 flex items-center justify-between text-sm text-zinc-600 dark:text-zinc-400"
>
<div class="mt-4 flex items-center justify-between text-sm text-zinc-600 dark:text-zinc-400">
<div class="flex items-center gap-4">
<button class="px-3 py-1 border rounded hover:bg-zinc-100 dark:hover:bg-zinc-800">
&lt;&lt;

View File

@@ -1,13 +1,11 @@
<script lang="ts">
import * as Dialog from '$lib/components/ui/dialog';
import * as Sheet from '$lib/components/ui/sheet';
import * as Tabs from '$lib/components/ui/tabs';
import { Button } from '$lib/components/ui/button';
import { Separator } from '$lib/components/ui/separator';
import { Loader2, Package, Save, X, FileText } from 'lucide-svelte';
import type { Invoice } from '$lib/api/dashboard/a76/invoices';
import type { Item } from '$lib/api/dashboard/a76/items';
import { useShortcuts } from '$lib/hooks/use-shortcuts';
import { obtenerAtajosPestanasItemFa } from '$lib/config/shortcuts/dashboard/invoices/item/fixed_asset';
// Child components
import MainData from './main-data.svelte';
@@ -26,7 +24,6 @@
invoice,
onSave,
onCancel,
isTargetingPreset = false,
isSaving = false
}: {
@@ -36,87 +33,39 @@
invoice: Invoice | null;
onSave: () => void;
onCancel?: () => void;
isTargetingPreset?: boolean;
isSaving?: boolean;
} = $props();
// Acceso directo a la primera línea para evitar repeticiones en el HTML
let line = $derived(editingItem.lines?.[0]);
let activeTab = $state('generales');
const tabMapping: Record<string, string> = {
'tab1': 'generales',
'tab2': 'continuacion',
'tab3': 'series',
'tab4': 'etiquetado',
'tab5': 'identificadores'
};
useShortcuts(
'Invoice Item Form (Fixed Asset)',
obtenerAtajosPestanasItemFa({
cambiarPestana: (target) => {
const tab = tabMapping[target];
if (tab) activeTab = tab;
},
manejarGuardar: onSave,
manejarCancelar: () => onCancel?.()
})
);
</script>
<Dialog.Root bind:open={open}>
<Dialog.Content class="sm:max-w-6xl max-h-[90vh] p-0 overflow-hidden z-[100] [&>button]:hidden">
<div class="bg-white dark:bg-zinc-950 border-b dark:border-zinc-800 px-4 py-3 shadow-sm flex items-start justify-between gap-3">
<div class="flex items-center gap-2">
<div class="bg-zinc-900 p-1 rounded">
<Package class="w-3.5 h-3.5 text-white" />
</div>
<div>
<Dialog.Title class="text-sm font-semibold text-zinc-900 dark:text-zinc-100 leading-tight flex items-center gap-2">
{#if isTargetingPreset}
{isEditMode ? 'Editar Item de Plantilla' : 'Nuevo Item para Plantilla'}
{:else}
<Sheet.Root bind:open={open}>
<Sheet.Content side="right" class="w-full sm:max-w-[95vw] lg:max-w-[85vw] xl:max-w-[75vw] p-0 flex flex-col h-full bg-slate-50 dark:bg-black">
<header class="bg-white dark:bg-zinc-950 border-b dark:border-zinc-800 px-3 py-1.5 shadow-sm shrink-0">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<div class="bg-zinc-900 p-1 rounded">
<Package class="w-3.5 h-3.5 text-white" />
</div>
<div>
<Sheet.Title class="text-sm font-semibold text-zinc-900 dark:text-zinc-100 leading-tight">
{isEditMode ? 'Editar Partida' : 'Nueva Partida - Activo Fijo'}
{/if}
{#if editingItem.lines && editingItem.lines.length > 1}
<span class="px-1.5 py-0.5 rounded-full bg-blue-100 dark:bg-blue-900/30 text-[10px] text-blue-700 dark:text-blue-300 font-bold border border-blue-200 dark:border-blue-800">
{editingItem.lines.length} lines
</span>
{/if}
</Dialog.Title>
{#if !isTargetingPreset}
</Sheet.Title>
<p class="text-[10px] text-muted-foreground flex items-center gap-1.5">
Factura: <span class="font-medium text-zinc-700 dark:text-zinc-300">{invoice?.invoice_number || 'N/A'}</span>
</p>
{:else}
<p class="text-[10px] text-muted-foreground flex items-center gap-1.5">
<span class="font-medium text-blue-600 dark:text-blue-400 uppercase tracking-wider">Modo Plantilla</span>
</p>
{/if}
</div>
</div>
</div>
<div class="flex items-center gap-2">
<Button variant="outline" size="sm" onclick={() => onCancel?.()} disabled={isSaving} class="h-7 text-xs px-2">
Cancelar
<Button variant="ghost" size="icon" onclick={() => onCancel?.()} class="h-7 w-7 text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-200 hover:bg-zinc-100 dark:hover:bg-zinc-800">
<X class="w-3.5 h-3.5" />
</Button>
<Button size="sm" onclick={onSave} disabled={isSaving} class="h-7 text-xs px-2 bg-blue-600 hover:bg-blue-700 dark:bg-blue-600 dark:hover:bg-blue-700 text-white">
{#if isSaving}
<Loader2 class="w-3 h-3 mr-1 animate-spin" />
Guardando...
{:else}
<Save class="w-3 h-3 mr-1" />
{isEditMode ? 'Actualizar' : 'Crear'}
{/if}
</Button>
</div>
</div>
</header>
<div class="p-3 overflow-y-auto max-h-[calc(90vh-88px)] bg-slate-50/70 dark:bg-black">
<div class="flex-1 overflow-y-auto px-2 py-1.5">
<div class="space-y-2">
{#if line}
@@ -152,7 +101,7 @@
</div>
</div>
<Tabs.Root bind:value={activeTab} class="w-full">
<Tabs.Root value="generales" class="w-full">
<Tabs.List class="grid w-full grid-cols-5 bg-zinc-100 dark:bg-zinc-800/50 rounded p-0.5 gap-0.5">
<Tabs.Trigger value="generales" class="text-xs font-medium py-1.5 px-2 rounded transition-all data-[state=active]:bg-white dark:data-[state=active]:bg-zinc-700 data-[state=active]:shadow-sm data-[state=active]:text-black dark:data-[state=active]:text-white">
General
@@ -216,5 +165,23 @@
{/if}
</div>
</div>
</Dialog.Content>
</Dialog.Root>
<footer class="bg-white dark:bg-zinc-950 border-t border-zinc-200 dark:border-zinc-800 px-3 py-1.5 shadow-sm shrink-0">
<div class="flex items-center justify-end gap-1.5">
<Button variant="outline" size="sm" onclick={() => onCancel?.()} disabled={isSaving} class="h-7 text-xs px-2">
Cancelar
</Button>
<Button size="sm" onclick={onSave} disabled={isSaving} class="h-7 text-xs px-2 bg-blue-600 hover:bg-blue-700 dark:bg-blue-600 dark:hover:bg-blue-700 text-white">
{#if isSaving}
<Loader2 class="w-3 h-3 mr-1 animate-spin" />
Guardando...
{:else}
<Save class="w-3 h-3 mr-1" />
{isEditMode ? 'Actualizar' : 'Crear'}
{/if}
</Button>
</div>
</footer>
</Sheet.Content>
</Sheet.Root>

View File

@@ -24,12 +24,11 @@
const filteredParts = $derived(
searchQuery
? parts.filter(
(p) =>
p.part_number?.toLowerCase().includes(searchQuery.toLowerCase()) ||
p.description_spanish?.toLowerCase().includes(searchQuery.toLowerCase()) ||
p.description_english?.toLowerCase().includes(searchQuery.toLowerCase())
)
? parts.filter(p =>
p.part_number?.toLowerCase().includes(searchQuery.toLowerCase()) ||
p.description_spanish?.toLowerCase().includes(searchQuery.toLowerCase()) ||
p.description_english?.toLowerCase().includes(searchQuery.toLowerCase())
)
: parts
);
@@ -53,12 +52,15 @@
isSearching = true;
try {
const response = await fetch(`/api-sveltekit/parts?company_id=${activeCompanyId}&limit=100`, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
const response = await fetch(
`/api-sveltekit/parts?company_id=${activeCompanyId}&limit=100`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
}
});
);
if (!response.ok) {
throw new Error('Error al buscar números de parte');
@@ -85,9 +87,8 @@
function handleScroll(e: Event) {
const target = e.target as HTMLDivElement;
const threshold = 100;
const scrolledToBottom =
target.scrollHeight - target.scrollTop - target.clientHeight < threshold;
const scrolledToBottom = target.scrollHeight - target.scrollTop - target.clientHeight < threshold;
if (scrolledToBottom && displayedParts.length < filteredParts.length) {
currentPage++;
loadMoreParts();
@@ -103,10 +104,12 @@
</script>
<Dialog.Root bind:open>
<Dialog.Content class="max-w-5xl w-full max-h-[90vh] p-0 flex flex-col">
<Dialog.Content class="!max-w-[50vw] w-[50vw] max-h-[80vh] flex flex-col">
<Dialog.Header>
<Dialog.Title>Seleccionar Número de Parte</Dialog.Title>
<Dialog.Description>Busca y selecciona un número de parte para la partida</Dialog.Description>
<Dialog.Description>
Busca y selecciona un número de parte para la partida
</Dialog.Description>
</Dialog.Header>
<div class="flex gap-2 mb-4">
@@ -145,18 +148,15 @@
</Table.Row>
{:else}
{#each displayedParts as part}
<Table.Row
class="cursor-pointer hover:bg-muted/50"
onclick={() => handleSelect(part)}
>
<Table.Row class="cursor-pointer hover:bg-muted/50" onclick={() => handleSelect(part)}>
<Table.Cell class="font-medium">{part.part_number}</Table.Cell>
<Table.Cell>{part.description_spanish || '-'}</Table.Cell>
<Table.Cell class="text-muted-foreground"
>{part.description_english || '-'}</Table.Cell
>
<Table.Cell class="text-muted-foreground">{part.description_english || '-'}</Table.Cell>
<Table.Cell class="text-muted-foreground">{part.part_class || '-'}</Table.Cell>
<Table.Cell>
<Button variant="ghost" size="sm" class="h-8">Seleccionar</Button>
<Button variant="ghost" size="sm" class="h-8">
Seleccionar
</Button>
</Table.Cell>
</Table.Row>
{/each}

View File

@@ -2,6 +2,12 @@
import type { LineFinancials, LineQuantities } from '$lib/api/dashboard/a76/items';
let { financials = $bindable(), quantities = $bindable() }: { financials: LineFinancials; quantities: LineQuantities } = $props();
// Helper function to safely format numbers
function formatNumber(value: any, decimals: number = 8): string {
const num = Number(value);
return isNaN(num) ? '0.00000000' : num.toFixed(decimals);
}
</script>
<div>
@@ -10,18 +16,18 @@
<div class="text-xs font-semibold">RETURN QUANTITY SUB-ITEMS</div>
<div class="grid grid-cols-2 gap-2 text-xs">
<div>Temporary: <span class="text-gray-900 dark:text-gray-100">{quantities.quantity_temp_export?.toFixed(8) || '0.00000000'}</span></div>
<div>Temporary: <span class="text-gray-900 dark:text-gray-100">{formatNumber(quantities.quantity_temp_export)}</span></div>
<div>Replacement or Change: <span class="text-gray-900 dark:text-gray-100">0.00000000</span></div>
<div>Definitive: <span class="text-gray-900 dark:text-gray-100">{quantities.quantity_returned?.toFixed(8) || '0.00000000'}</span></div>
<div>Returned Values: <span class="text-gray-900 dark:text-gray-100">{financials.value_returned_usd?.toFixed(8) || '0.00000000'}</span></div>
<div class="col-span-2">Returned Values: <span class="text-gray-900 dark:text-gray-100">{financials.value_returned_mxn?.toFixed(8) || '0.00000000'}</span></div>
<div>Definitive: <span class="text-gray-900 dark:text-gray-100">{formatNumber(quantities.quantity_returned)}</span></div>
<div>Returned Values: <span class="text-gray-900 dark:text-gray-100">{formatNumber(financials.value_returned_usd)}</span></div>
<div class="col-span-2">Returned Values: <span class="text-gray-900 dark:text-gray-100">{formatNumber(financials.value_returned_mxn)}</span></div>
</div>
<div class="grid grid-cols-2 gap-2 text-xs pt-2 border-t">
<div class="font-semibold">WEIGHTS (KILOS)</div>
<div class="font-semibold">WEIGHTS (Pounds)</div>
<div>Net: <span class="text-gray-900 dark:text-gray-100">{quantities.net_weight?.toFixed(8) || '0.00000000'}</span></div>
<div>Net: <span class="text-gray-900 dark:text-gray-100">{formatNumber(quantities.net_weight)}</span></div>
<div><span class="text-gray-900 dark:text-gray-100">0.00000000</span></div>
<div>Whole: <span class="text-gray-900 dark:text-gray-100">{quantities.gross_weight?.toFixed(8) || '0.00000000'}</span></div>
<div>Whole: <span class="text-gray-900 dark:text-gray-100">{formatNumber(quantities.gross_weight)}</span></div>
<div><span class="text-gray-900 dark:text-gray-100">0.00000000</span></div>
</div>
</fieldset>
@@ -33,13 +39,13 @@
<div class="grid grid-cols-2 gap-2 text-xs">
<div class="font-semibold">(Dollars)</div>
<div class="font-semibold">(Pesos)</div>
<div>Cost: <span class="text-gray-900 dark:text-gray-100">{financials.unit_cost_usd?.toFixed(8) || '0.00000000'}</span></div>
<div><span class="text-gray-900 dark:text-gray-100">{financials.unit_cost_mxn?.toFixed(8) || '0.00000000'}</span></div>
<div>Value: <span class="text-gray-900 dark:text-gray-100">{financials.value_usd?.toFixed(8) || '0.00000000'}</span></div>
<div><span class="text-gray-900 dark:text-gray-100">{financials.value_mxn?.toFixed(8) || '0.00000000'}</span></div>
<div class="text-xs">Capture Cost: <span class="text-gray-900 dark:text-gray-100">{financials.unit_cost_capture?.toFixed(8) || '0.00000000'}</span> <span class="text-gray-900 dark:text-gray-100">USD</span></div>
<div class="text-xs">Capture Value: <span class="text-gray-900 dark:text-gray-100">{financials.value_usd?.toFixed(8) || '0.00000000'}</span> <span class="text-gray-900 dark:text-gray-100">USD</span></div>
<div class="text-xs">Customs Value: <span class="text-gray-900 dark:text-gray-100">{financials.customs_value_usd?.toFixed(8) || '0.00000000'}</span> <span class="text-gray-900 dark:text-gray-100">USD</span></div>
<div>Cost: <span class="text-gray-900 dark:text-gray-100">{formatNumber(financials.unit_cost_usd)}</span></div>
<div><span class="text-gray-900 dark:text-gray-100">{formatNumber(financials.unit_cost_mxn)}</span></div>
<div>Value: <span class="text-gray-900 dark:text-gray-100">{formatNumber(financials.value_usd)}</span></div>
<div><span class="text-gray-900 dark:text-gray-100">{formatNumber(financials.value_mxn)}</span></div>
<div class="text-xs">Capture Cost: <span class="text-gray-900 dark:text-gray-100">{formatNumber(financials.unit_cost_capture)}</span> <span class="text-gray-900 dark:text-gray-100">USD</span></div>
<div class="text-xs">Capture Value: <span class="text-gray-900 dark:text-gray-100">{formatNumber(financials.value_usd)}</span> <span class="text-gray-900 dark:text-gray-100">USD</span></div>
<div class="text-xs">Customs Value: <span class="text-gray-900 dark:text-gray-100">{formatNumber(financials.customs_value_usd)}</span> <span class="text-gray-900 dark:text-gray-100">USD</span></div>
</div>
</fieldset>
</div>

View File

@@ -17,7 +17,7 @@
let loadingMore = $state(false);
let searchTerm = $state('');
let error = $state('');
// Pagination state
let currentPage = $state(1);
let totalPages = $state(1);
@@ -34,7 +34,7 @@
loadingMore = true;
}
error = '';
try {
const params = new URLSearchParams({
page: page.toString(),
@@ -45,10 +45,10 @@
const response = await fetch(`/api-sveltekit/tariff-fractions?${params}`, {
credentials: 'include'
});
if (response.ok) {
const data = await response.json();
if (data.items && Array.isArray(data.items)) {
if (append) {
fractions = [...fractions, ...data.items];
@@ -77,10 +77,10 @@
function handleScroll(e: Event) {
if (!scrollContainer || loading || loadingMore || !hasMore) return;
const target = e.target as HTMLDivElement;
const scrollBottom = target.scrollHeight - target.scrollTop - target.clientHeight;
// Load more when within 200px of bottom
if (scrollBottom < 200) {
loadFractions(currentPage + 1, true);
@@ -118,7 +118,7 @@
</script>
<Dialog.Root bind:open>
<Dialog.Content class="max-w-4xl w-full max-h-[80vh] flex flex-col px-6">
<Dialog.Content class="!max-w-[80vw] w-[80vw] max-h-[90vh] p-0 flex flex-col">
<Dialog.Header class="px-6 py-4 border-b">
<Dialog.Title class="text-lg font-semibold">FRACCIONES ARANCELARIAS</Dialog.Title>
</Dialog.Header>
@@ -137,7 +137,11 @@
</p>
</div>
<div bind:this={scrollContainer} onscroll={handleScroll} class="flex-1 overflow-auto px-6 py-4">
<div
bind:this={scrollContainer}
onscroll={handleScroll}
class="flex-1 overflow-auto px-6 py-4"
>
{#if loading}
<div class="flex items-center justify-center py-20">
<Loader2 class="w-8 h-8 animate-spin text-zinc-900 dark:text-zinc-100" />
@@ -151,12 +155,18 @@
<table class="w-full text-sm">
<thead class="bg-zinc-900 dark:bg-zinc-800 text-white sticky top-0">
<tr>
<th class="px-3 py-2 text-left font-semibold border-r border-zinc-700">Código</th>
<th class="px-3 py-2 text-left font-semibold border-r border-zinc-700">Fracción</th>
<th class="px-3 py-2 text-left font-semibold border-r border-zinc-700"
>Código</th
>
<th class="px-3 py-2 text-left font-semibold border-r border-zinc-700"
>Fracción</th
>
<th class="px-3 py-2 text-left font-semibold border-r border-zinc-700"
>Descripción</th
>
<th class="px-3 py-2 text-left font-semibold border-r border-zinc-700">NICO</th>
<th class="px-3 py-2 text-left font-semibold border-r border-zinc-700"
>NICO</th
>
<th class="px-3 py-2 text-left font-semibold">UMT</th>
</tr>
</thead>
@@ -192,7 +202,9 @@
{/if}
{#if !hasMore && fractions.length > 0}
<div class="text-center py-4 text-sm text-zinc-500">Todos los resultados cargados</div>
<div class="text-center py-4 text-sm text-zinc-500">
Todos los resultados cargados
</div>
{/if}
{/if}
</div>

View File

@@ -83,7 +83,7 @@
</script>
<Dialog.Root bind:open>
<Dialog.Content class="max-w-4xl w-full max-h-[90vh] p-0 flex flex-col">
<Dialog.Content class="!max-w-[70vw] w-[70vw] max-h-[90vh] p-0 flex flex-col">
<Dialog.Header class="px-6 py-4 border-b">
<Dialog.Title class="text-lg font-semibold">CATALOGOS DE UNIDADES DE MEDIDA</Dialog.Title>
</Dialog.Header>
@@ -91,7 +91,11 @@
<div class="px-6 py-3 border-b bg-zinc-50 dark:bg-zinc-900">
<div class="flex items-center gap-2">
<Search class="w-4 h-4 text-zinc-400" />
<Input bind:value={searchTerm} placeholder="Buscando..." class="flex-1 h-9" />
<Input
bind:value={searchTerm}
placeholder="Buscando..."
class="flex-1 h-9"
/>
</div>
</div>
@@ -109,7 +113,9 @@
<table class="w-full text-sm">
<thead class="bg-zinc-900 dark:bg-zinc-800 text-white">
<tr>
<th class="px-3 py-2 text-left font-semibold border-r border-zinc-700">U.M.</th>
<th class="px-3 py-2 text-left font-semibold border-r border-zinc-700"
>U.M.</th
>
<th class="px-3 py-2 text-left font-semibold border-r border-zinc-700"
>Descripción Español</th
>
@@ -150,9 +156,7 @@
</table>
</div>
<div
class="mt-4 flex items-center justify-between text-sm text-zinc-600 dark:text-zinc-400"
>
<div class="mt-4 flex items-center justify-between text-sm text-zinc-600 dark:text-zinc-400">
<div class="flex items-center gap-4">
<button class="px-3 py-1 border rounded hover:bg-zinc-100 dark:hover:bg-zinc-800">
&lt;&lt;

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import * as Dialog from '$lib/components/ui/dialog';
import * as Sheet from '$lib/components/ui/sheet';
import * as Tabs from '$lib/components/ui/tabs';
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
@@ -60,25 +60,29 @@
// Initialize missing nested objects if they don't exist
$effect(() => {
if (open && editingItem) {
if (!editingItem.lines) editingItem.lines = [{}];
if (!editingItem.lines[0].quantity) editingItem.lines[0].quantity = {};
if (!editingItem.lines[0].financial) editingItem.lines[0].financial = {};
if (!editingItem.lines[0].customs) editingItem.lines[0].customs = {};
if (!editingItem.lines[0].description) editingItem.lines[0].description = {};
if (!editingItem.lines) editingItem.lines = [{ line_number: 1 } as any];
if (editingItem.lines[0] && !editingItem.lines[0].quantity)
editingItem.lines[0].quantity = {} as any;
if (editingItem.lines[0] && !editingItem.lines[0].financial)
editingItem.lines[0].financial = {} as any;
if (editingItem.lines[0] && !editingItem.lines[0].customs)
editingItem.lines[0].customs = {} as any;
if (editingItem.lines[0] && !editingItem.lines[0].description)
editingItem.lines[0].description = {} as any;
}
});
</script>
<Dialog.Root bind:open>
<Dialog.Content class="sm:max-w-5xl max-h-[90vh] p-0 overflow-hidden z-[100] [&>button]:hidden">
<Sheet.Root bind:open>
<Sheet.Content side="right" class="w-[50vw] overflow-hidden p-0 sm:max-w-none">
<div
class="px-6 py-4 border-b bg-white dark:bg-zinc-950 flex items-start justify-between gap-3"
class="flex items-start justify-between gap-3 border-b bg-white px-6 py-4 dark:bg-zinc-950"
>
<div class="space-y-1">
<Dialog.Title class="text-lg font-semibold">
<Sheet.Title class="text-lg font-semibold">
{isEditMode ? 'Editar Item' : 'Agregar Nuevo Item'} - SCAII (Inventario)
</Dialog.Title>
<Dialog.Description class="text-sm text-muted-foreground">
</Sheet.Title>
<Sheet.Description class="text-sm text-muted-foreground">
{isEditMode
? 'Modifica los campos del inventario y guarda los cambios.'
: 'Completa la información del nuevo item de inventario.'}
@@ -87,13 +91,13 @@
{editingItem.lines.length} items en esta partida
</Badge>
{/if}
</Dialog.Description>
</Sheet.Description>
</div>
<div class="flex gap-2">
<Button variant="outline" onclick={() => onCancel?.()} disabled={isSaving}>Cancelar</Button>
<Button onclick={onSave} disabled={isSaving}>
{#if isSaving}
<Loader2 class="w-4 h-4 mr-2 animate-spin" />
<Loader2 class="mr-2 h-4 w-4 animate-spin" />
Guardando...
{:else}
{isEditMode ? 'Guardar Cambios' : 'Agregar Item'}
@@ -102,7 +106,7 @@
</div>
</div>
<div class="p-6 overflow-auto max-h-[calc(90vh-96px)] bg-slate-50/60 dark:bg-black">
<div class="max-h-[calc(90vh-96px)] overflow-auto bg-slate-50/60 p-6 dark:bg-black">
<Tabs.Root bind:value={activeTab} class="mt-0">
<Tabs.List class="grid w-full grid-cols-4">
<Tabs.Trigger value="general">General</Tabs.Trigger>
@@ -112,13 +116,13 @@
</Tabs.List>
<!-- Tab: General -->
<Tabs.Content value="general" class="space-y-4 mt-4">
<Tabs.Content value="general" class="mt-4 space-y-4">
<!-- Información de la Factura (Solo lectura) -->
{#if !isTargetingPreset}
<div class="rounded-lg border bg-muted/50 p-4 space-y-3">
<div class="space-y-3 rounded-lg border bg-muted/50 p-4">
<h4 class="text-sm font-medium">Información de la Factura (SCAII - Inventario)</h4>
{#if !invoice?.id}
<div class="text-sm text-amber-600 bg-amber-50 dark:bg-amber-950/20 p-3 rounded">
<div class="rounded bg-amber-50 p-3 text-sm text-amber-600 dark:bg-amber-950/20">
⚠️ Esta factura aún no se ha guardado. Los items se asociarán cuando guardes la
factura.
</div>
@@ -139,7 +143,7 @@
</div>
<div class="col-span-2">
<span class="text-muted-foreground">Sistema:</span>
<span class="ml-2 font-medium bg-blue-100 dark:bg-blue-900/30 px-2 py-1 rounded"
<span class="ml-2 rounded bg-blue-100 px-2 py-1 font-medium dark:bg-blue-900/30"
>SCAII (Inventory)</span
>
</div>
@@ -207,7 +211,7 @@
</Tabs.Content>
<!-- Tab: Clasificación -->
<Tabs.Content value="clasificacion" class="space-y-4 mt-4">
<Tabs.Content value="clasificacion" class="mt-4 space-y-4">
<div class="space-y-4">
<div class="space-y-2">
<Label for="tariff_fraction">Fracción Arancelaria</Label>
@@ -223,21 +227,21 @@
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
<Label for="product_type">Tipo de Producto</Label>
{#if line}
{#if line?.description}
<Input
id="product_type"
placeholder="Materia prima, producto terminado, etc."
bind:value={line.description.extra_description_2}
bind:value={(line.description as any).extra_description_2}
/>
{/if}
</div>
<div class="space-y-2">
<Label for="material_type">Tipo de Material</Label>
{#if line}
{#if line?.description}
<Input
id="material_type"
placeholder="Metal, plástico, etc."
bind:value={line.description.extra_description_3}
bind:value={(line.description as any).extra_description_3}
/>
{/if}
</div>
@@ -262,7 +266,7 @@
</Tabs.Content>
<!-- Tab: Cantidades -->
<Tabs.Content value="cantidades" class="space-y-4 mt-4">
<Tabs.Content value="cantidades" class="mt-4 space-y-4">
<div class="space-y-4">
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
@@ -350,7 +354,7 @@
id="packages"
type="number"
placeholder="0"
bind:value={line.quantity.packages}
bind:value={(line.quantity as any).packages}
/>
{/if}
</div>
@@ -360,7 +364,7 @@
<Input
id="package_type"
placeholder="Caja, pallet, etc."
bind:value={line.quantity.package_type}
bind:value={(line.quantity as any).package_type}
/>
{/if}
</div>
@@ -374,7 +378,7 @@
id="imported_quantity"
type="number"
placeholder="0"
bind:value={line.quantity.quantity_imported}
bind:value={(line.quantity as any).quantity_imported}
/>
{/if}
</div>
@@ -385,7 +389,8 @@
id="remaining_quantity"
type="number"
placeholder="0"
value={(line.quantity.quantity || 0) - (line.quantity.quantity_imported || 0)}
value={(line.quantity.quantity || 0) -
((line.quantity as any).quantity_imported || 0)}
disabled
/>
{/if}
@@ -395,7 +400,7 @@
</Tabs.Content>
<!-- Tab: Otros -->
<Tabs.Content value="otros" class="space-y-4 mt-4">
<Tabs.Content value="otros" class="mt-4 space-y-4">
<div class="space-y-4">
<div class="space-y-2">
<Label for="brand">Marca</Label>
@@ -435,7 +440,7 @@
{#if line?.description}
<textarea
id="observations"
class="flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
class="flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
placeholder="Notas adicionales sobre el inventario..."
bind:value={line.description.extra_description}
></textarea>
@@ -445,5 +450,5 @@
</Tabs.Content>
</Tabs.Root>
</div>
</Dialog.Content>
</Dialog.Root>
</Sheet.Content>
</Sheet.Root>

View File

@@ -120,7 +120,7 @@ class CompanyStore {
* @param company - La compañía a establecer como activa
* @param silent - Si es true, no dispara el evento companyChanged (para inicialización)
*/
setActiveCompany(company: Company, silent: boolean = false) {
async setActiveCompany(company: Company, silent: boolean = false) {
const previousCompanyId = this._activeCompany?.id;
this._activeCompany = company;
@@ -130,8 +130,20 @@ class CompanyStore {
}
// Guardar en cookie para acceso desde el servidor (SSR)
if (typeof document !== 'undefined') {
document.cookie = `active_company_id=${company.id}; path=/; max-age=${60 * 60 * 24 * 30}; SameSite=Lax`;
// Usar el endpoint del servidor para garantizar que la cookie esté disponible en SSR
if (browser) {
try {
await fetch('/api-sveltekit/company/set-active', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ companyId: company.id }),
credentials: 'include'
});
} catch (error) {
console.error('Error setting active company cookie:', error);
}
}
// Despachar evento personalizado solo si:

View File

@@ -36,7 +36,6 @@ export const GET: RequestHandler = async ({ cookies, url }) => {
try {
const fetchUrl = `${baseUrl}v1/a76/classes?${queryString}`;
console.log('Fetching classes from:', fetchUrl);
const response = await fetch(
fetchUrl,

View File

@@ -32,7 +32,6 @@ export const GET: RequestHandler = async ({ cookies, url, params }) => {
try {
const fetchUrl = `${baseUrl}v1/a76/classes/${id}?company_id=${companyId}`;
console.log('Fetching class from:', fetchUrl);
const response = await fetch(
fetchUrl,

View File

@@ -0,0 +1,29 @@
/**
* API route para establecer la compañía activa en una cookie
*/
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
export const POST: RequestHandler = async ({ cookies, request }) => {
try {
const { companyId } = await request.json();
if (!companyId || typeof companyId !== 'number') {
return json({ error: 'Invalid company ID' }, { status: 400 });
}
// Establecer la cookie desde el servidor
cookies.set('active_company_id', companyId.toString(), {
path: '/',
maxAge: 60 * 60 * 24 * 30, // 30 días
sameSite: 'lax',
httpOnly: false, // Permitir acceso desde JavaScript
secure: process.env.NODE_ENV === 'production'
});
return json({ success: true, companyId });
} catch (error) {
console.error('Error setting active company:', error);
return json({ error: 'Internal server error' }, { status: 500 });
}
};

View File

@@ -35,8 +35,7 @@ export const GET: RequestHandler = async ({ cookies, url }) => {
const queryString = searchParams.toString();
try {
const fetchUrl = `${baseUrl}v1/a76/parts?${queryString}`;
console.log('Fetching parts from:', fetchUrl);
const fetchUrl = `${baseUrl}v1/a76/parts?${queryString}`;
const response = await fetch(
fetchUrl,

View File

@@ -31,8 +31,7 @@ export const GET: RequestHandler = async ({ cookies, url, params }) => {
const baseUrl = apiUrl?.endsWith('/') ? apiUrl : `${apiUrl}/`;
try {
const fetchUrl = `${baseUrl}v1/a76/parts/${id}?company_id=${companyId}`;
console.log('Fetching part from:', fetchUrl);
const fetchUrl = `${baseUrl}v1/a76/parts/${id}?company_id=${companyId}`;
const response = await fetch(
fetchUrl,

View File

@@ -20,8 +20,6 @@ export const GET: RequestHandler = async ({ cookies, url }) => {
try {
const fetchUrl = `${baseUrl}v1/a76/tariff-fractions?${queryString}`;
console.log('Fetching tariff fractions from:', fetchUrl);
console.log('Token:', token ? 'Present' : 'Missing');
const response = await fetch(
fetchUrl,
@@ -35,8 +33,6 @@ export const GET: RequestHandler = async ({ cookies, url }) => {
);
const data = await response.json();
console.log('Response status:', response.status);
console.log('Response data:', JSON.stringify(data).substring(0, 200));
if (!response.ok) {
return new Response(JSON.stringify(data), {

View File

@@ -36,8 +36,6 @@ export const GET: RequestHandler = async ({ cookies, url }) => {
try {
const fetchUrl = `${baseUrl}v1/a76/units-of-measure?${queryString}`;
console.log('Fetching units from:', fetchUrl);
console.log('Token:', token ? 'Present' : 'Missing');
const response = await fetch(
fetchUrl,
@@ -51,8 +49,6 @@ export const GET: RequestHandler = async ({ cookies, url }) => {
);
const data = await response.json();
console.log('Response status:', response.status);
console.log('Response data:', JSON.stringify(data).substring(0, 200));
if (!response.ok) {
return new Response(JSON.stringify(data), {

View File

@@ -31,8 +31,7 @@ export const GET: RequestHandler = async ({ cookies, params, url }) => {
const baseUrl = apiUrl?.endsWith('/') ? apiUrl : `${apiUrl}/`;
try {
const fetchUrl = `${baseUrl}v1/a76/units-of-measure/${id}?company_id=${companyId}`;
console.log('Fetching unit of measure from:', fetchUrl);
const fetchUrl = `${baseUrl}v1/a76/units-of-measure/${id}?company_id=${companyId}`;
const response = await fetch(
fetchUrl,

View File

@@ -187,8 +187,7 @@
selectedInvoiceId = null;
} else {
selectedInvoiceId = invoice.id;
}
console.log('Selected Invoice ID:', selectedInvoiceId);
}
}
const selectedInvoice = $derived(

View File

@@ -222,29 +222,199 @@
return { ...skeleton, ...filtered };
}
// Función para mapear la factura existente a los formData
function mapInvoiceToTopFields(invoice: any) {
if (!invoice) return topFieldsSkeleton;
let operationType: string | null = null;
if (invoice.operation_type) {
operationType = invoice.operation_type;
} else if (data.filters?.operation_type !== undefined) {
operationType = data.filters.operation_type ?? null;
}
return {
is_pedimento_pending: invoice.compliance_mx?.is_pedimento_pending || false,
pedimento_id: invoice.compliance_mx?.pedimento_id || '',
remesa: invoice.compliance_mx?.remesa || '',
invoice_number: invoice.invoice_number || '',
invoice_date: invoice.invoice_date || new Date().toISOString().split('T')[0],
emission_date: invoice.emission_date || new Date().toISOString().split('T')[0],
operation_type: operationType,
invoice_type: invoice.invoice_type || (data.filters?.invoice_type ?? ''),
fecha_pedimento_del: '',
fecha_pedimento_al: '',
clave_pedimento: '',
regimen_pedimento: ''
};
}
function mapInvoiceToGeneral(invoice: any) {
if (!invoice) return generalSkeleton;
return {
provider_header: invoice.compliance_mx?.provider_header || 'proveedor',
provider_id: invoice.compliance_mx?.provider_id || null,
sold_to_header: invoice.compliance_mx?.sold_to_header || 'consignado_a',
sold_to_id: invoice.compliance_mx?.sold_to_id || null,
shipped_to_header: invoice.compliance_mx?.shipped_to_header || 'enviado_a',
shipped_to_id: invoice.compliance_mx?.shipped_to_id || null,
customs_broker_id: invoice.compliance_mx?.customs_broker_id || null,
customs_broker_us_id: invoice.compliance_mx?.customs_broker_us_id || null,
currency_type: invoice.financials?.currency_type || '',
currency: invoice.financials?.currency || 'foreign',
exchange_rate: invoice.financials?.exchange_rate || null,
weight_type: 'kgs',
iva_factor: invoice.financials?.iva_factor || null,
carrier_id: invoice.logistics?.carrier_id || null,
transport_id: invoice.logistics?.transport_id || '',
driver_name: invoice.logistics?.driver_name || '',
transport_type: invoice.logistics?.transport_type || '',
transport_num: invoice.logistics?.vehicle_num || '',
aduana: invoice.compliance_mx?.aduana || '',
document_type: invoice.document_type || ''
};
}
function mapInvoiceToObservations(invoice: any) {
if (!invoice) return observationSkeleton;
return {
observation_es: invoice.observation_es || '',
observation_en: invoice.observation_en || '',
freight: invoice.financials?.freight || null,
insurance_value: invoice.financials?.insurance_value || null,
insurance: invoice.financials?.insurance || null,
packaging: invoice.financials?.packaging || null,
other_increments: invoice.financials?.other_increments || null,
total_increments_mn: invoice.financials?.total_increments_mn || null,
total_increments_me: invoice.financials?.total_increments_me || null,
incoterm: invoice.logistics?.incoterm || null,
enclosure: invoice.compliance_mx?.enclosure || null,
num_seals: null,
movement_type: invoice.compliance_mx?.movement_type || '',
alternate_invoice: invoice.alternate_invoice || '',
valuation_method: invoice.compliance_mx?.value_method || null
};
}
function mapInvoiceToItems(invoice: any) {
if (!invoice) return ensureItemsFormData(null);
return {
items: invoice.items || []
};
}
function mapInvoiceToOthers(invoice: any) {
if (!invoice) return othersSkeleton;
return {
comments_status: invoice.comments_status || '',
transport_mode: invoice.logistics?.transport_mode || 'TRUCK',
is_mixed: invoice.compliance_mx?.is_mixed || false,
print_stamp: invoice.print_stamp || false,
rule_3121_parties_ii: invoice.compliance_mx?.rule_3121_parties_ii || false,
related_doc_id: invoice.related_doc_id || null,
code_signature: invoice.compliance_mx?.code_signature || '',
electronic_signature: invoice.compliance_mx?.electronic_signature || '',
mandatory_person: invoice.compliance_mx?.mandatory_person || '',
contingency_mode: invoice.compliance_mx?.contingency_mode || false,
cove: invoice.compliance_mx?.cove || '',
operation_num: invoice.compliance_mx?.operation_num || '',
adendas: invoice.compliance_mx?.adendas || '',
observations_vu: invoice.compliance_mx?.observations_vu || '',
certified_number: invoice.compliance_mx?.certified_number || '',
bill_number: invoice.logistics?.bill_number || '',
guide_number: invoice.logistics?.guide_number || '',
shipment_number: invoice.logistics?.shipment_number || '',
option_iv18: invoice.compliance_mx?.option_iv18 || '',
delivered_status: invoice.delivered_status || false,
received_by: invoice.received_by || '',
delivery_date: invoice.delivery_date || ''
};
}
function mapInvoiceToContinuation(invoice: any) {
if (!invoice) return continuationSkeleton;
return {
numero_tipo_transporte: invoice.logistics?.numero_tipo_transporte || '',
es_ferrocarril: invoice.logistics?.es_ferrocarril || 'no',
numero_bl: invoice.logistics?.numero_bl || '',
cantidad_guias_embarque: invoice.logistics?.cantidad_guias_embarque || null,
destino_origen: invoice.logistics?.destino_origen || '',
puerto_entrada: invoice.logistics?.puerto_entrada || '',
vehicle_data: invoice.logistics?.vehicle_data || '',
fue_revisado_equipo: invoice.logistics?.fue_revisado_equipo || false,
sub_division: invoice.compliance_mx?.subdivision || false,
funge_como_cd: invoice.logistics?.acts_as_cd || false,
llego_pedimento: invoice.compliance_mx?.llego_pedimento || false,
errores_facturacion: invoice.errores_facturacion || [],
semaforo_verde_aduana_mexicana: invoice.compliance_mx?.semaforo_verde_aduana_mexicana || false,
semaforo_verde_aduana_americana: invoice.compliance_mx?.semaforo_verde_aduana_americana || false,
semaforo_rojo_aduana_mexicana: invoice.compliance_mx?.semaforo_rojo_aduana_mexicana || false,
semaforo_rojo_aduana_americana: invoice.compliance_mx?.semaforo_rojo_aduana_americana || false,
is_mixed: invoice.compliance_mx?.is_mixed || false,
reason_export: invoice.compliance_mx?.reason_export || '1',
purchase_order: invoice.purchase_order || '',
payment_terms: invoice.payment_terms || '',
handling_fees: invoice.financials?.handling_fees || 0,
cfdi_uuid: invoice.cfdi_uuid || '',
path_pdf: invoice.path_pdf || '',
path_xml: invoice.path_xml || ''
};
}
// Referencias a los componentes de formulario para obtener sus datos
// Si estamos en modo edición (!data.isCreate) y tenemos una factura, usarla
// Si estamos en modo creación, usar defaultSettings
let InvoiceTopFieldsFormData = $state<any>(
mergeDefaults(topFieldsSkeleton, data.defaultSettings?.InvoiceTopFieldsFormData)
!data.isCreate && data.invoice
? mapInvoiceToTopFields(data.invoice)
: mergeDefaults(topFieldsSkeleton, data.defaultSettings?.InvoiceTopFieldsFormData)
);
let generalFormData = $state<any>(
mergeDefaults(generalSkeleton, data.defaultSettings?.generalFormData)
!data.isCreate && data.invoice
? mapInvoiceToGeneral(data.invoice)
: mergeDefaults(generalSkeleton, data.defaultSettings?.generalFormData)
);
let observationFormData = $state<any>(
mergeDefaults(observationSkeleton, data.defaultSettings?.observationFormData)
!data.isCreate && data.invoice
? mapInvoiceToObservations(data.invoice)
: mergeDefaults(observationSkeleton, data.defaultSettings?.observationFormData)
);
let itemsFormData = $state<any>(
!data.isCreate && data.invoice
? mapInvoiceToItems(data.invoice)
: ensureItemsFormData(data.defaultSettings?.itemsFormData)
);
let itemsFormData = $state<any>(ensureItemsFormData(data.defaultSettings?.itemsFormData));
let othersFormData = $state<any>(
mergeDefaults(othersSkeleton, data.defaultSettings?.othersFormData)
!data.isCreate && data.invoice
? mapInvoiceToOthers(data.invoice)
: mergeDefaults(othersSkeleton, data.defaultSettings?.othersFormData)
);
let continuationFormData = $state<any>(
mergeDefaults(continuationSkeleton, data.defaultSettings?.continuationFormData)
!data.isCreate && data.invoice
? mapInvoiceToContinuation(data.invoice)
: mergeDefaults(continuationSkeleton, data.defaultSettings?.continuationFormData)
);
// Estados para saber si existen datos previos
let observationExists = $state(!!data.defaultSettings?.observationFormData);
let itemsExists = $state(!!data.defaultSettings?.itemsFormData?.items?.length);
let othersExists = $state(!!data.defaultSettings?.othersFormData);
let continuationExists = $state(!!data.defaultSettings?.continuationFormData);
let observationExists = $state(
!data.isCreate ? !!data.invoice : !!data.defaultSettings?.observationFormData
);
let itemsExists = $state(
!data.isCreate
? !!(data.invoice?.items && data.invoice.items.length > 0)
: !!data.defaultSettings?.itemsFormData?.items?.length
);
let othersExists = $state(
!data.isCreate ? !!data.invoice : !!data.defaultSettings?.othersFormData
);
let continuationExists = $state(
!data.isCreate ? !!data.invoice : !!data.defaultSettings?.continuationFormData
);
let calculatedExchangeRate = $state<number | null>(
data.invoice?.financials?.exchange_rate ?? null
@@ -314,8 +484,7 @@
const actualResponse = response as any;
const items = actualResponse.data?.items || [];
if (items.length === 0) {
console.log('No exchange rate found for', date);
if (items.length === 0) {
if (!uiStore.isExchangeRateDialogOpen) {
missingExchangeRateDate = date;
showExchangeRateDialog = true;

View File

@@ -1,7 +0,0 @@
import { Dialog } from "bits-ui";
console.log("Dialog is:", Dialog);
try {
console.log("Dialog.Root is:", Dialog.Root);
} catch (e) {
console.log("Error accessing Dialog.Root:", e.message);
}