Merge remote-tracking branch 'origin/development' into feature/fraction-catalogs

This commit is contained in:
2026-02-19 17:22:37 -06:00
58 changed files with 8857 additions and 8230 deletions

View File

@@ -17,7 +17,11 @@ export interface LineCustoms {
origin_country?: string;
destination_country?: string;
advalorem?: string;
advalorem_american?: number; // Backend: Decimal
advalorem_numeric?: number;
advalorem_american?: number;
advalorem_tlcan?: number;
rate?: string;
depreciation_rate?: number;
sector?: string;
}
@@ -31,11 +35,13 @@ export interface LineFinancials {
unit_cost_commercial_usd?: number;
// Values
value_mc?: number;
value_usd?: number;
value_mxn?: number;
value_returned_usd?: number;
value_returned_mxn?: number;
customs_value_usd?: number;
customs_value_mxn?: number;
}
export interface LineQuantities {
@@ -105,8 +111,7 @@ export interface FaLineItem {
// Subitems
is_subitem?: boolean;
contains_subitems?: boolean;
includes_subitems?: boolean;
contains_subitems?: boolean;
subitem_number?: number;
// Special flags
@@ -119,9 +124,9 @@ export interface FaLineItem {
updated_at?: string;
}
export interface LineItem {
id?: number;
item_id?: number;
export interface Item {
id?: number;
invoice_id: number;
line_number: number;
// Identification
@@ -159,20 +164,7 @@ export interface LineItem {
class_description?: string;
// Computed field from unit_of_measure_info relation
unit_of_measure_code?: string;
// Nested relations (Singular names to match backend Pydantic models)
customs?: LineCustoms;
financial?: LineFinancials;
quantity?: LineQuantities;
description?: LineDescriptions;
reference?: LineReferences;
fa_data?: FaLineItem; // Fixed Asset specific data
}
export interface Item {
id?: number;
invoice_id: number;
unit_of_measure_code?: string;
reference_number?: string;
order?: string;
guide_number?: string;
@@ -182,8 +174,14 @@ export interface Item {
location?: string;
created_at?: string;
updated_at?: string;
lines?: LineItem[];
// Nested relations (Singular names to match backend Pydantic models)
customs?: LineCustoms;
financial?: LineFinancials;
quantity?: LineQuantities;
description?: LineDescriptions;
reference?: LineReferences;
fa_data?: FaLineItem; // Fixed Asset specific data
}
export interface ItemListResponse {
@@ -193,28 +191,11 @@ export interface ItemListResponse {
limit: number;
}
export interface CreateItemData {
export interface CreateItemData extends Omit<Item, 'id' | 'created_at' | 'updated_at'> {
invoice_id: number;
reference_number?: string;
order?: string;
guide_number?: string;
depreciation_date?: number;
rectification?: number;
warehouse?: string;
location?: string;
lines?: LineItem[];
}
export interface UpdateItemData {
reference_number?: string;
order?: string;
guide_number?: string;
depreciation_date?: number;
rectification?: boolean;
warehouse?: string;
location?: string;
lines?: LineItem[];
}
export interface UpdateItemData extends Partial<Omit<Item, 'id' | 'invoice_id' | 'created_at' | 'updated_at'>> {}
/**
* API para Items

View File

@@ -5,13 +5,13 @@
import { Button } from '$lib/components/ui/button';
import { Folder } from 'lucide-svelte';
import PartNumberDialog from './part-number-dialog.svelte';
import type { LineItem, LineDescriptions } from '$lib/api/dashboard/a76/items';
import type { Item, LineDescriptions } from '$lib/api/dashboard/a76/items';
let {
lineItem = $bindable(),
descriptions = $bindable()
}: {
lineItem: LineItem;
lineItem: Partial<Item>;
descriptions: LineDescriptions;
} = $props();
@@ -35,6 +35,13 @@
lineItem.fa_data.contains_subitems = val === 'si';
}
// Helper for subitem_number binding
let subitemNumber = $derived.by(() => lineItem.fa_data?.subitem_number ?? 0);
function setSubitemNumber(val: number) {
if (!lineItem.fa_data) lineItem.fa_data = {};
lineItem.fa_data.subitem_number = val;
}
function handlePartSelect(part: any) {
lineItem.part_number = part.id;
// Store part number for display
@@ -65,23 +72,36 @@
</div>
</RadioGroup.Root>
</fieldset>
<fieldset class="border rounded-md p-2 space-y-2">
<legend class="text-xs font-semibold px-2 bg-zinc-200 dark:bg-zinc-700">Contains Sub-Items</legend>
<RadioGroup.Root
value={containsSubPartidasValue}
onValueChange={setContinueSubPartidas}
class="flex gap-3">
<div class="flex items-center space-x-2">
<RadioGroup.Item value="si" id="continue_si" />
<Label for="continue_si" class="text-xs font-normal">Yes</Label>
</div>
<div class="flex items-center space-x-2">
<RadioGroup.Item value="no" id="continue_no" />
<Label for="continue_no" class="text-xs font-normal">No</Label>
</div>
</RadioGroup.Root>
</fieldset>
{#if isSubPartidaValue === 'partida'}
<fieldset class="border rounded-md p-2 space-y-2">
<legend class="text-xs font-semibold px-2 bg-zinc-200 dark:bg-zinc-700">Contains Sub-Items</legend>
<RadioGroup.Root
value={containsSubPartidasValue}
onValueChange={setContinueSubPartidas}
class="flex gap-3">
<div class="flex items-center space-x-2">
<RadioGroup.Item value="si" id="continue_si" />
<Label for="continue_si" class="text-xs font-normal">Yes</Label>
</div>
<div class="flex items-center space-x-2">
<RadioGroup.Item value="no" id="continue_no" />
<Label for="continue_no" class="text-xs font-normal">No</Label>
</div>
</RadioGroup.Root>
</fieldset>
{:else if isSubPartidaValue === 'subpartida'}
<fieldset class="border rounded-md p-1">
<legend class="text-xs font-semibold px-2 bg-zinc-200 dark:bg-zinc-700">Main Item Number</legend>
<Input
id="subitem_number"
type="number"
value={subitemNumber}
oninput={(e) => setSubitemNumber(e.currentTarget.valueAsNumber || 0)}
class="h-7 text-xs"
placeholder="Enter main item number"
/>
</fieldset>
{/if}
</div>
<!-- Descriptions -->

View File

@@ -38,7 +38,7 @@
} = $props();
// Acceso directo a la primera línea para evitar repeticiones en el HTML
let line = $derived(editingItem.lines?.[0]);
let line = $derived(editingItem);
</script>
<Sheet.Root bind:open={open}>
@@ -77,10 +77,11 @@
</div>
<div class="p-2">
<MainData
bind:lineItem={editingItem.lines![0]}
bind:quantities={editingItem.lines![0].quantity!}
bind:financials={editingItem.lines![0].financial!}
bind:customs={editingItem.lines![0].customs!}
bind:lineItem={editingItem}
bind:quantities={editingItem.quantity!}
bind:financials={editingItem.financial!}
bind:customs={editingItem.customs!}
{invoice}
/>
</div>
</div>
@@ -93,8 +94,8 @@
</div>
<div class="p-2">
<ItemConfiguration
bind:lineItem={editingItem.lines![0]}
bind:descriptions={editingItem.lines![0].description!}
bind:lineItem={editingItem}
bind:descriptions={editingItem.description!}
/>
</div>
</div>
@@ -125,36 +126,38 @@
<div class="grid grid-cols-1 md:grid-cols-2 gap-2">
<PackagesSection
bind:item={editingItem}
bind:lineItem={editingItem.lines![0]}
bind:descriptions={editingItem.lines![0].description!}
bind:customs={editingItem.lines![0].customs!}
bind:quantities={editingItem.lines![0].quantity!}
bind:lineItem={editingItem}
bind:descriptions={editingItem.description!}
bind:customs={editingItem.customs!}
bind:quantities={editingItem.quantity!}
invoice={invoice}
/>
<SummarySection
bind:financials={editingItem.lines![0].financial!}
bind:quantities={editingItem.lines![0].quantity!}
bind:financials={editingItem.financial!}
bind:quantities={editingItem.quantity!}
lineItem={editingItem}
{invoice}
/>
</div>
</Tabs.Content>
<Tabs.Content value="continuacion" class="m-0 focus-visible:outline-none">
<TabContinuation
bind:lineItem={editingItem.lines![0]}
bind:descriptions={editingItem.lines![0].description!}
bind:lineItem={editingItem}
bind:descriptions={editingItem.description!}
/>
</Tabs.Content>
<Tabs.Content value="series" class="m-0 focus-visible:outline-none">
<TabSeries bind:descriptions={editingItem.lines![0].description!} />
<TabSeries bind:descriptions={editingItem.description!} />
</Tabs.Content>
<Tabs.Content value="etiquetado" class="m-0 focus-visible:outline-none">
<TabLabeling bind:descriptions={editingItem.lines![0].description!} />
<TabLabeling bind:descriptions={editingItem.description!} />
</Tabs.Content>
<Tabs.Content value="identificadores" class="m-0 focus-visible:outline-none">
<TabIdentifiers bind:lineItem={editingItem.lines![0]} />
<TabIdentifiers bind:lineItem={editingItem} />
</Tabs.Content>
</div>
</Tabs.Root>

View File

@@ -7,19 +7,22 @@
import CountryDialog from './country-dialog.svelte';
import TariffFractionDialog from './tariff-fraction-dialog.svelte';
import ClassDialog from './class-dialog.svelte';
import type { LineItem, LineQuantities, LineFinancials, LineCustoms } from '$lib/api/dashboard/a76/items';
import { companyStore } from '$lib/stores/company.svelte';
import type { Item, LineQuantities, LineFinancials, LineCustoms } from '$lib/api/dashboard/a76/items';
import type { Invoice } from '$lib/api/dashboard/a76/invoices';
import { companyStore } from '$lib/stores/company.svelte';
let {
lineItem = $bindable(),
quantities = $bindable(),
financials = $bindable(),
customs = $bindable()
customs = $bindable(),
invoice
}: {
lineItem: LineItem;
lineItem: Partial<Item>;
quantities: LineQuantities;
financials: LineFinancials;
customs: LineCustoms;
invoice: Invoice | null;
} = $props();
let showClassDialog = $state(false);
@@ -27,16 +30,26 @@
let showCountryDialog = $state(false);
let showFractionDialog = $state(false);
// Format fraction with dots for display
let fractionDisplay = $derived(() => {
const frac = customs.fraction;
if (!frac) return '';
// If already has dots, return as is
if (frac.includes('.')) return frac;
// Format 8-digit fraction as XX.XX.XX.XX
if (frac.length === 8) {
return `${frac.slice(0, 4)}.${frac.slice(4, 6)}.${frac.slice(6, 8)}`;
}
// Format 10-digit fraction as XX.XX.XX.XXXX
if (frac.length === 10) {
return `${frac.slice(0, 4)}.${frac.slice(4, 6)}.${frac.slice(6, 8)}.${frac.slice(8, 10)}`;
}
return frac;
});
// Track previous class_id to detect changes
let previousClassId = $state<number | undefined>(undefined);
// Initialize from existing data
$effect(() => {
if (lineItem.class_code) {
// Do nothing, it's already set
}
});
// Watch for class_id changes and update descriptions automatically
$effect(() => {
const currentClassId = lineItem.class_id;
@@ -97,10 +110,10 @@
(lineItem as any).description.description_english = classItem.description_en;
}
}
}
};
function handleUnitSelect(unit: any) {
lineItem.unit_of_measure = unit.id;
lineItem.unit_of_measure = unit.id;
// Store unit code for display
(lineItem as any).unit_code = unit.code;
(lineItem as any).unit_description = unit.description || unit.description_en;
@@ -112,9 +125,50 @@
}
function handleFractionSelect(fraction: any) {
customs.fraction = fraction.fraction;
// Save without dots for backend, concatenating fraction + nico (8 + 2 = 10 chars)
const fractionBase = fraction.fraction?.replace(/\./g, '') || '';
const nico = fraction.nico || '';
customs.fraction = fractionBase + nico;
(customs as any).fraction_description = fraction.description;
}
// Auto-fetch historical tariff rate when fraction, country, type, and date are available
$effect(() => {
const fraction = customs.fraction?.replace(/\./g, '') || '';
const nico = fraction.substring(8, 10);
const fractionType = customs.fraction_type;
const invoiceDate = invoice?.invoice_date;
// Only fetch if all required fields are present and fraction has at least 8 chars
if (fraction && fraction.length >= 8 && nico && fractionType && invoiceDate) {
const historicalFraction = fraction.substring(0, 8);
const params = new URLSearchParams({
historical_fraction: historicalFraction,
nico: nico,
fraction_type: fractionType,
invoice_date: invoiceDate
});
fetch(`/api-sveltekit/historical-tariff-fractions/rate?${params}`)
.then(response => {
if (response.ok) {
return response.json();
}
throw new Error('Failed to fetch tariff rate');
})
.then(data => {
if (data.found && data.rate !== null) {
customs.rate = data.rate;
} else {
customs.rate = '0';
}
})
.catch(error => {
console.error('Error fetching historical tariff rate:', error);
// Keep current value on error
});
}
});
</script>
<ClassDialog bind:open={showClassDialog} onSelect={handleClassSelect} />
@@ -186,7 +240,7 @@
<div class="space-y-1">
<Label for="costo_unitario" class="text-xs font-medium">Unit Cost: <span class="text-red-500">*</span></Label>
<div class="flex items-center gap-2">
<Input id="costo_unitario" type="number" step="0.00000001" min="0" bind:value={financials.unit_cost_usd} class="h-8 text-xs text-right flex-1" />
<Input id="costo_unitario" type="number" step="0.00000001" min="0" bind:value={financials.unit_cost_capture} class="h-8 text-xs text-right flex-1" />
<span class="text-xs text-zinc-900 dark:text-zinc-100 font-semibold">USD</span>
</div>
</div>
@@ -196,7 +250,7 @@
<div class="flex gap-1">
<Input
id="fraccion"
value={customs.fraction || ''}
value={fractionDisplay()}
readonly
class="h-8 text-xs text-center flex-1 bg-muted cursor-pointer"
placeholder="Selecciona fracción"
@@ -233,17 +287,13 @@
>
<Folder class="w-4 h-4" />
</Button>
</div>
{#if (customs as any).origin_country_name}
<p class="text-xs text-muted-foreground truncate">{(customs as any).origin_country_name}</p>
{/if}
</div>
</div>
<div class="flex items-end gap-6">
<div class="space-y-1">
<Label for="tipo_tarifa" class="text-xs font-medium">Tariff Type: <span class="text-red-500">*</span></Label>
<select id="tipo_tarifa" bind:value={customs.fraction_type} class="flex h-8 w-auto min-w-[140px] rounded-md border border-input bg-background px-2 py-1 text-xs ring-offset-background">
<option value={undefined}>Selecciona...</option>
<select id="tipo_tarifa" bind:value={customs.fraction_type} class="flex h-8 w-auto min-w-[140px] rounded-md border border-input bg-background px-2 py-1 text-xs ring-offset-background">
<option value="GENERAL">GENERAL</option>
<option value="PROSEC">PROSEC</option>
<option value="ALADI">ALADI</option>
@@ -255,7 +305,7 @@
<div class="flex items-center gap-2 h-8 pb-0.5">
<span class="text-xs font-medium text-muted-foreground">Advalorem:</span>
<span class="text-xs text-zinc-900 dark:text-zinc-100 font-semibold">
{customs.advalorem || '0'}
{customs.rate || '0'}
</span>
</div>
</div>

View File

@@ -1,13 +1,82 @@
<script lang="ts">
import type { LineFinancials, LineQuantities } from '$lib/api/dashboard/a76/items';
import type { LineFinancials, LineQuantities, Item } from '$lib/api/dashboard/a76/items';
import type { Invoice } from '$lib/api/dashboard/a76/invoices';
import type { Part } from '$lib/api/dashboard/a76/parts';
import { companyStore } from '$lib/stores/company.svelte';
let { financials = $bindable(), quantities = $bindable() }: { financials: LineFinancials; quantities: LineQuantities } = $props();
let {
financials = $bindable(),
quantities = $bindable(),
lineItem,
invoice
}: {
financials: LineFinancials;
quantities: LineQuantities;
lineItem?: Partial<Item>;
invoice?: Invoice | null;
} = $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);
}
// ========== ESTRUCTURA DE COMPARACIÓN DE MONEDAS ==========
// Extraer las monedas del invoice y del part
let invoiceCurrency = $derived(invoice?.financials?.currency_type);
// Effect para actualizar los costos unitarios en diferentes monedas cuando cambie unit_cost_capture
$effect(() => {
const exchangeRate = invoice?.financials?.exchange_rate || 1;
const unitCostCapture = financials.unit_cost_capture || 1;
// Calcular unit_cost_usd y unit_cost_mxn basado en la moneda del invoice
if (invoiceCurrency === 'USD') {
financials.unit_cost_usd = unitCostCapture;
financials.unit_cost_mxn = unitCostCapture * exchangeRate;
} else if (invoiceCurrency === 'MXN') {
financials.unit_cost_mxn = unitCostCapture;
financials.unit_cost_usd = unitCostCapture / exchangeRate;
} else {
// Por defecto, si no hay moneda definida, asumir USD
financials.unit_cost_usd = unitCostCapture;
financials.unit_cost_mxn = unitCostCapture * exchangeRate;
}
});
let calculatedValueCapture = $derived.by(() => {
const quantity = quantities.quantity || 0;
const unitCost = financials.unit_cost_capture;
// Calcular el valor total (costo unitario * cantidad)
return (unitCost || 0) * quantity;
});
// Effect para actualizar los valores en diferentes monedas cuando cambie calculatedValueCapture
$effect(() => {
const exchangeRate = invoice?.financials?.exchange_rate || 1;
const valueCapture = calculatedValueCapture;
// value_mc es el valor en la moneda de captura (invoice currency)
financials.value_mc = valueCapture;
// Calcular value_usd y value_mxn basado en la moneda del invoice
if (invoiceCurrency === 'USD') {
financials.value_usd = valueCapture;
financials.value_mxn = valueCapture * exchangeRate;
} else if (invoiceCurrency === 'MXN') {
financials.value_mxn = valueCapture;
financials.value_usd = valueCapture / exchangeRate;
} else {
// Por defecto, si no hay moneda definida, asumir USD
financials.value_usd = valueCapture;
financials.value_mxn = valueCapture * exchangeRate;
}
});
// ========== FIN ESTRUCTURA DE COMPARACIÓN ==========
</script>
<div>
@@ -27,7 +96,7 @@
<div class="font-semibold">WEIGHTS (Pounds)</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">{formatNumber(quantities.gross_weight)}</span></div>
<div>Gross: <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>
@@ -43,9 +112,11 @@
<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 class="text-xs">Customs Value: <span class="text-gray-900 dark:text-gray-100">{formatNumber(financials.customs_value_usd)}</span></div>
<div class="text-xs">Customs Value: <span class="text-gray-900 dark:text-gray-100">{formatNumber(financials.customs_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">{invoiceCurrency}</span></div>
<div></div>
<div class="text-xs">Capture Value: <span class="text-gray-900 dark:text-gray-100">{formatNumber(financials.value_mc)}</span> <span class="text-gray-900 dark:text-gray-100">{invoiceCurrency}</span></div>
</div>
</fieldset>
</div>

View File

@@ -5,14 +5,14 @@
import { Checkbox } from '$lib/components/ui/checkbox';
import { Button } from '$lib/components/ui/button';
import { Folder } from 'lucide-svelte';
import type { LineItem, LineDescriptions } from '$lib/api/dashboard/a76/items';
import type { Item, LineDescriptions } from '$lib/api/dashboard/a76/items';
import PaymentMethodDialog from './payment-method-dialog.svelte';
let {
lineItem = $bindable(),
descriptions = $bindable()
}: {
lineItem: LineItem;
lineItem: Partial<Item>;
descriptions: LineDescriptions;
} = $props();
@@ -26,6 +26,14 @@
lineItem.has_certificate = val === 'si';
}
// Initialize boolean fields to prevent bind:checked={undefined} error
if (lineItem.is_military_mcia === undefined) {
lineItem.is_military_mcia = false;
}
if (descriptions.consider_a31 === undefined) {
descriptions.consider_a31 = false;
}
let paymentMethodDialogOpen = $state(false);
let payment_method_description = $state('');

View File

@@ -1,9 +1,9 @@
<script lang="ts">
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import type { LineItem } from '$lib/api/dashboard/a76/items';
import type { Item } from '$lib/api/dashboard/a76/items';
let { lineItem = $bindable() }: { lineItem: LineItem } = $props();
let { lineItem = $bindable() }: { lineItem: Partial<Item> } = $props();
</script>
<fieldset class="border rounded-md p-3 space-y-3">

View File

@@ -55,20 +55,19 @@
);
// Derived state for easier binding and safety
let line = $derived(editingItem.lines?.[0]);
let line = $derived(editingItem);
// Initialize missing nested objects if they don't exist
$effect(() => {
if (open && editingItem) {
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;
if (editingItem && !editingItem.quantity)
editingItem.quantity = {} as any;
if (editingItem && !editingItem.financial)
editingItem.financial = {} as any;
if (editingItem && !editingItem.customs)
editingItem.customs = {} as any;
if (editingItem && !editingItem.description)
editingItem.description = {} as any;
}
});
</script>
@@ -86,9 +85,9 @@
{isEditMode
? 'Modifica los campos del inventario y guarda los cambios.'
: 'Completa la información del nuevo item de inventario.'}
{#if editingItem.lines && editingItem.lines.length > 1}
{#if editingItem}
<Badge variant="secondary" class="ml-2">
{editingItem.lines.length} items en esta partida
{editingItem} items en esta partida
</Badge>
{/if}
</Sheet.Description>

View File

@@ -74,26 +74,23 @@
// 4. Derived Values (Ordered correctly to avoid TDZ)
const flattenedLines = $derived.by(() => {
const sourceItems = items?.length ? items : formData?.items || [];
return (sourceItems || []).flatMap((item: any, itemIndex: number) => {
const lines = item?.lines || [];
return lines.map((line: any, idx: number) => ({
...line,
id: line?.id || `${item?.id || itemIndex}-line-${line?.line_number ?? idx + 1}`,
line_number: line?.line_number ?? idx + 1,
reference_number: line?.reference_number ?? item?.reference_number,
is_subitem: line?.is_subitem ?? false,
class_code: line?.class_code ?? line?.class_id,
class_description:
line?.class_description ||
line?.description?.description_spanish ||
line?.description?.description_english ||
'',
unit_of_measure_code: line?.quantity?.unit_of_measure || line?.unit_of_measure,
fa_data: line?.fa_data || {},
warehouse: line?.warehouse || item?.warehouse,
full_item: item
}));
});
return (sourceItems || []).map((item: any, itemIndex: number) => ({
...item,
id: item?.id || `item-${itemIndex}`,
line_number: item?.line_number ?? itemIndex + 1,
reference_number: item?.reference_number,
is_subitem: item?.is_subitem ?? false,
class_code: item?.class_code ?? item?.class_id,
class_description:
item?.class_description ||
item?.description?.description_spanish ||
item?.description?.description_english ||
'',
unit_of_measure_code: item?.quantity?.unit_of_measure || item?.unit_of_measure,
fa_data: item?.fa_data || {},
warehouse: item?.warehouse,
full_item: item
}));
});
const isAllSelected = $derived(
@@ -248,80 +245,76 @@
// Auto-asignar valores desde la factura con estructura completa
editingItem = {
invoice_id: invoice?.id,
line_number: 1,
// LineItem fields
part_number: undefined,
component_part_number: undefined,
class_id: undefined,
identifier: undefined,
unit_of_measure: undefined,
alternate_unit: undefined,
permit_number: undefined,
page_line: undefined,
has_certificate: false,
certificate_number: undefined,
tax_payment: false,
payment_method: undefined,
igi_amount: undefined,
is_military_mcia: false,
wildcard_field: undefined,
reference_number: '',
order: invoice?.purchase_order || '',
warehouse: '',
location: '',
lines: [
{
line_number: 1,
// LineItem fields
part_number: undefined,
component_part_number: undefined,
class_id: undefined,
identifier: undefined,
unit_of_measure: undefined,
alternate_unit: undefined,
permit_number: undefined,
page_line: undefined,
has_certificate: false,
certificate_number: undefined,
tax_payment: false,
payment_method: undefined,
igi_amount: undefined,
is_military_mcia: false,
wildcard_field: undefined,
// Nested relations
financial: {
unit_cost_usd: undefined,
unit_cost_mxn: undefined,
unit_cost_capture: undefined,
unit_cost_commercial_usd: undefined,
value_usd: undefined,
value_mxn: undefined,
value_returned_usd: undefined,
value_returned_mxn: undefined,
customs_value_usd: undefined
},
quantity: {
quantity: undefined,
unit_of_measure: undefined,
quantity_temp_export: undefined,
quantity_returned: undefined,
net_weight: undefined,
gross_weight: undefined,
package_id: undefined,
package_quantity: undefined,
package_description: undefined
},
customs: {
fraction: undefined,
fraction_type: undefined,
american_fraction: undefined,
origin_country: undefined,
destination_country: undefined,
advalorem: undefined,
advalorem_american: undefined,
sector: undefined
},
description: {
description_spanish: undefined,
description_english: undefined,
extra_description: undefined,
additional_info_spanish: undefined,
brand: undefined,
model: undefined,
has_serial: false,
eighth_rule_fraction: undefined,
eighth_rule_line: undefined,
consider_a31: false,
machinery_location: undefined
},
reference: {
serie_id: undefined
}
}
]
location: '',
// Nested relations
financial: {
unit_cost_usd: undefined,
unit_cost_mxn: undefined,
unit_cost_capture: undefined,
unit_cost_commercial_usd: undefined,
value_usd: undefined,
value_mxn: undefined,
value_returned_usd: undefined,
value_returned_mxn: undefined,
customs_value_usd: undefined
},
quantity: {
quantity: undefined,
unit_of_measure: undefined,
quantity_temp_export: undefined,
quantity_returned: undefined,
net_weight: undefined,
gross_weight: undefined,
package_id: undefined,
package_quantity: undefined,
package_description: undefined
},
customs: {
fraction: undefined,
fraction_type: undefined,
american_fraction: undefined,
origin_country: undefined,
destination_country: undefined,
advalorem: undefined,
advalorem_american: undefined,
sector: undefined
},
description: {
description_spanish: undefined,
description_english: undefined,
extra_description: undefined,
additional_info_spanish: undefined,
brand: undefined,
model: undefined,
has_serial: false,
eighth_rule_fraction: undefined,
eighth_rule_line: undefined,
consider_a31: false,
machinery_location: undefined
},
reference: {
serie_id: undefined
}
};
}
@@ -376,14 +369,14 @@
function saveItemToPreset() {
// Sanitizar datos para la plantilla
const cleanedItem = JSON.parse(JSON.stringify(editingItem));
let cleanedItem = JSON.parse(JSON.stringify(editingItem));
// Limpiar líneas para asegurar que son compatibles
if (cleanedItem.lines) {
cleanedItem.lines = cleanedItem.lines.map((line: any) => ({
...cleanLineData(line),
// Limpiar item para asegurar que es compatible
if (cleanedItem) {
cleanedItem = {
...cleanLineData(cleanedItem),
id: undefined // Las plantillas no deben tener IDs reales
}));
};
}
if (editingBuilderIndex !== null) {
@@ -409,35 +402,30 @@
function cloneItemForPreset(item: Item) {
const { id, tenant_id, company_id, created_at, updated_at, temp_id, ...rest } = item as any;
return {
...rest,
...sanitizeLineForPreset(rest),
id: undefined,
invoice_id: undefined,
lines: (item.lines || []).map(sanitizeLineForPreset)
invoice_id: undefined
};
}
function buildManualItem(draft: any, index: number) {
return {
return cleanLineData({
id: undefined,
temp_id: undefined,
invoice_id: undefined,
reference_number: draft.reference_number || undefined,
lines: [
cleanLineData({
line_number: index + 1,
description: {
description_spanish: draft.description || 'Sin descripción'
},
quantity: {
quantity: Number(draft.quantity) || 0
},
financial: {
unit_cost_usd:
draft.unit_cost_usd !== null ? Number(draft.unit_cost_usd) || 0 : undefined
}
})
]
};
line_number: index + 1,
description: {
description_spanish: draft.description || 'Sin descripción'
},
quantity: {
quantity: Number(draft.quantity) || 0
},
financial: {
unit_cost_usd:
draft.unit_cost_usd !== null ? Number(draft.unit_cost_usd) || 0 : undefined
}
});
}
function handleAddManualItem() {
@@ -466,15 +454,11 @@
}
// Inject into active sheet if open
// No se pueden inyectar múltiples líneas en un item, ya que ahora un item ES una línea
if (showItemSheet) {
const presetLines = selectedPreset.items.flatMap((item: any) => item.lines || []);
const cleanedNewLines = presetLines.map((line: any) => ({
...sanitizeLineForPreset(line),
id: undefined // Force new IDs
}));
editingItem.lines = [...(editingItem.lines || []), ...cleanedNewLines];
toast.success('Líneas inyectadas en la partida actual');
toast.warning('No se puede inyectar plantilla en modo edición', {
description: 'Las plantillas solo se pueden aplicar directamente a la factura'
});
showUsePresetDialog = false;
return;
}
@@ -517,13 +501,13 @@
isSavingPreset = true;
try {
// We group everything as ONE Partida Template for injection
const lines = builderItems.flatMap((item: Item, idx: number) => {
return (item.lines || []).map((line: any) => ({
...cleanLineData(line),
line_number: line.line_number || idx + 1, // Ensure line_number is present
// We group everything as items for the template
const lines = builderItems.map((item: Item, idx: number) => {
return {
...cleanLineData(item),
line_number: item.line_number || idx + 1, // Ensure line_number is present
id: undefined // Ensure no IDs are saved in the preset
}));
};
});
const payloadItems = [
@@ -569,22 +553,20 @@
// Enrich item with descriptive data for display
async function enrichItemData(item: Partial<Item>) {
if (!item.lines || item.lines.length === 0 || !activeCompanyId) return;
const line = item.lines[0];
if (!item || !activeCompanyId) return;
// Load class data
if (line.class_id) {
if (item.class_id) {
try {
const response = await fetch(
`/api-sveltekit/classes/${line.class_id}?company_id=${activeCompanyId}`,
`/api-sveltekit/classes/${item.class_id}?company_id=${activeCompanyId}`,
{ method: 'GET', headers: { 'Content-Type': 'application/json' } }
);
if (response.ok) {
const classData = await response.json();
(line as any).class_code = classData.class_code;
(line as any).class_unit_of_measure = classData.unit_of_measure;
(line as any).class_description = classData.description_es || classData.description_en;
(item as any).class_code = classData.class_code;
(item as any).class_unit_of_measure = classData.unit_of_measure;
(item as any).class_description = classData.description_es || classData.description_en;
}
} catch (error) {
console.error('Error loading class data:', error);
@@ -592,17 +574,17 @@
}
// Load part number data
if (line.part_number) {
if (item.part_number) {
try {
const response = await fetch(
`/api-sveltekit/parts/${line.part_number}?company_id=${activeCompanyId}`,
`/api-sveltekit/parts/${item.part_number}?company_id=${activeCompanyId}`,
{ method: 'GET', headers: { 'Content-Type': 'application/json' } }
);
if (response.ok) {
const partData = await response.json();
(line as any).part_number = partData.part_number;
(line as any).part_description_es = partData.description_spanish;
(line as any).part_description_en = partData.description_english;
(item as any).part_number = partData.part_number;
(item as any).part_description_es = partData.description_spanish;
(item as any).part_description_en = partData.description_english;
}
} catch (error) {
console.error('Error loading part data:', error);
@@ -610,16 +592,16 @@
}
// Load unit of measure data
if (line.unit_of_measure) {
if (item.unit_of_measure) {
try {
const response = await fetch(`/api-sveltekit/units-of-measure/${line.unit_of_measure}`, {
const response = await fetch(`/api-sveltekit/units-of-measure/${item.unit_of_measure}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
if (response.ok) {
const unitData = await response.json();
(line as any).unit_code = unitData.code;
(line as any).unit_description = unitData.description || unitData.description_en;
(item as any).unit_code = unitData.code;
(item as any).unit_description = unitData.description || unitData.description_en;
}
} catch (error) {
console.error('Error loading unit data:', error);
@@ -627,17 +609,17 @@
}
// Load country data (if needed)
if (line.customs?.origin_country) {
if (item.customs?.origin_country) {
try {
const response = await fetch(
`/api-sveltekit/countries?search=${line.customs.origin_country}`,
`/api-sveltekit/countries?search=${item.customs.origin_country}`,
{ method: 'GET', headers: { 'Content-Type': 'application/json' } }
);
if (response.ok) {
const data = await response.json();
if (data.items && data.items.length > 0) {
const country = data.items[0];
(line.customs as any).origin_country_name =
(item.customs as any).origin_country_name =
country.description || country.description_en;
}
}
@@ -647,17 +629,17 @@
}
// Load fraction data (if needed)
if (line.customs?.fraction) {
if (item.customs?.fraction) {
try {
const response = await fetch(
`/api-sveltekit/tariff-fractions?search=${line.customs.fraction}`,
`/api-sveltekit/tariff-fractions?search=${item.customs.fraction}`,
{ method: 'GET', headers: { 'Content-Type': 'application/json' } }
);
if (response.ok) {
const data = await response.json();
if (data.items && data.items.length > 0) {
const fraction = data.items[0];
(line.customs as any).fraction_description = fraction.description;
(item.customs as any).fraction_description = fraction.description;
}
}
} catch (error) {
@@ -666,8 +648,8 @@
}
// Load package data (if needed)
const packageId = line.quantity?.package_id;
if (packageId && line.quantity) {
const packageId = item.quantity?.package_id;
if (packageId && item.quantity) {
try {
const response = await fetch('/api-sveltekit/packages', {
method: 'GET',
@@ -679,9 +661,9 @@
if (Array.isArray(packages)) {
const pkg = packages.find((p: any) => p.id === packageId);
if (pkg) {
(line.quantity as any).package_description = pkg.description_es || pkg.description_en || pkg.key;
(line.quantity as any).package_key = pkg.key;
(line.quantity as any).package_weight_unit = pkg.weight_unit || 0;
(item.quantity as any).package_description = pkg.description_es || pkg.description_en || pkg.key;
(item.quantity as any).package_key = pkg.key;
(item.quantity as any).package_weight_unit = pkg.weight_unit || 0;
}
}
}
@@ -691,7 +673,7 @@
}
// Load payment method description (if needed)
if (line.payment_method) {
if (item.payment_method) {
try {
const response = await fetch('/api-sveltekit/payment-methods', {
method: 'GET',
@@ -701,9 +683,9 @@
const data = await response.json();
const methods = data.items || data.data || data;
if (Array.isArray(methods)) {
const method = methods.find((m: any) => m.key === line.payment_method);
const method = methods.find((m: any) => m.key === item.payment_method);
if (method) {
(line as any).payment_method_description = method.description;
(item as any).payment_method_description = method.description;
}
}
}
@@ -715,58 +697,56 @@
// Normalize numeric values from strings to numbers
function normalizeItemData(item: Partial<Item>): Partial<Item> {
if (item.lines && item.lines.length > 0) {
item.lines = item.lines.map((line) => {
const normalizedLine = { ...line };
if (item) {
const normalizedItem = { ...item };
// Normalize financials
if (normalizedLine.financial) {
normalizedLine.financial = {
...normalizedLine.financial,
unit_cost_usd:
normalizedLine.financial.unit_cost_usd != null
? Number(normalizedLine.financial.unit_cost_usd)
: undefined,
unit_cost_mxn:
normalizedLine.financial.unit_cost_mxn != null
? Number(normalizedLine.financial.unit_cost_mxn)
: undefined,
value_usd:
normalizedLine.financial.value_usd != null
? Number(normalizedLine.financial.value_usd)
: undefined,
value_mxn:
normalizedLine.financial.value_mxn != null
? Number(normalizedLine.financial.value_mxn)
: undefined
};
}
// Normalize financials
if (normalizedItem.financial) {
normalizedItem.financial = {
...normalizedItem.financial,
unit_cost_usd:
normalizedItem.financial.unit_cost_usd != null
? Number(normalizedItem.financial.unit_cost_usd)
: undefined,
unit_cost_mxn:
normalizedItem.financial.unit_cost_mxn != null
? Number(normalizedItem.financial.unit_cost_mxn)
: undefined,
value_usd:
normalizedItem.financial.value_usd != null
? Number(normalizedItem.financial.value_usd)
: undefined,
value_mxn:
normalizedItem.financial.value_mxn != null
? Number(normalizedItem.financial.value_mxn)
: undefined
};
}
// Normalize quantities
if (normalizedLine.quantity) {
normalizedLine.quantity = {
...normalizedLine.quantity,
quantity:
normalizedLine.quantity.quantity != null
? Number(normalizedLine.quantity.quantity)
: undefined,
net_weight:
normalizedLine.quantity.net_weight != null
? Number(normalizedLine.quantity.net_weight)
: undefined,
gross_weight:
normalizedLine.quantity.gross_weight != null
? Number(normalizedLine.quantity.gross_weight)
: undefined,
package_quantity:
normalizedLine.quantity.package_quantity != null
? Number(normalizedLine.quantity.package_quantity)
: undefined
};
}
// Normalize quantities
if (normalizedItem.quantity) {
normalizedItem.quantity = {
...normalizedItem.quantity,
quantity:
normalizedItem.quantity.quantity != null
? Number(normalizedItem.quantity.quantity)
: undefined,
net_weight:
normalizedItem.quantity.net_weight != null
? Number(normalizedItem.quantity.net_weight)
: undefined,
gross_weight:
normalizedItem.quantity.gross_weight != null
? Number(normalizedItem.quantity.gross_weight)
: undefined,
package_quantity:
normalizedItem.quantity.package_quantity != null
? Number(normalizedItem.quantity.package_quantity)
: undefined
};
}
return normalizedLine;
});
return normalizedItem;
}
return item;
@@ -782,16 +762,12 @@
isSaving = true;
try {
// Clean lines data before sending
const cleanedLines = (editingItem.lines || []).map(cleanLineData);
// Clean item data before sending
const cleanedItem = cleanLineData(editingItem);
const response = await itemsApi.create(activeCompanyId, {
invoice_id: invoice.id,
reference_number: editingItem.reference_number,
order: editingItem.order,
warehouse: editingItem.warehouse,
location: editingItem.location,
lines: cleanedLines
...cleanedItem,
invoice_id: invoice.id
});
// Verificar si hay errores de validación
@@ -851,16 +827,10 @@
isSaving = true;
try {
// Clean lines data before sending
const cleanedLines = (editingItem.lines || []).map(cleanLineData);
// Clean item data before sending
const cleanedItem = cleanLineData(editingItem);
const response = await itemsApi.update(selectedItem.id, activeCompanyId, {
reference_number: editingItem.reference_number,
order: editingItem.order,
warehouse: editingItem.warehouse,
location: editingItem.location,
lines: cleanedLines
});
const response = await itemsApi.update(selectedItem.id, activeCompanyId, cleanedItem);
// Verificar si hay errores de validación
if ('error' in response) {
@@ -916,52 +886,51 @@
function saveItem() {
// Validar campos obligatorios antes de guardar
const line = editingItem.lines?.[0];
const missingFields: string[] = [];
if (!line) {
if (!editingItem) {
toast.warning('Error de datos', {
description: 'No se encontró información de la línea del item'
description: 'No se encontró información del item'
});
return;
}
// 1. Clase
if (!line.class_id) {
if (!editingItem.class_id) {
missingFields.push('Clase');
}
// 2. Cantidad
if (!line.quantity?.quantity || line.quantity.quantity <= 0) {
if (!editingItem.quantity?.quantity || editingItem.quantity.quantity <= 0) {
missingFields.push('Cantidad');
}
// 3. Unidad de Medida
if (!line.unit_of_measure) {
if (!editingItem.unit_of_measure) {
missingFields.push('U.M. (Unidad de Medida)');
}
// 4. Costo Unitario (al menos uno debe estar presente)
const hasCost =
line.financial?.unit_cost_usd ||
line.financial?.unit_cost_mxn ||
line.financial?.unit_cost_capture;
editingItem.financial?.unit_cost_usd ||
editingItem.financial?.unit_cost_mxn ||
editingItem.financial?.unit_cost_capture;
if (!hasCost) {
missingFields.push('Costo Unitario (USD, MXN o Captura)');
}
// 5. País de Origen
if (!line.customs?.origin_country) {
if (!editingItem.customs?.origin_country) {
missingFields.push('País de Origen');
}
// 6. Tipo de Tarifa
if (!line.customs?.fraction_type) {
if (!editingItem.customs?.fraction_type) {
missingFields.push('Tipo de Tarifa');
}
// 7. Descripción en Español
if (!line.description?.description_spanish?.trim()) {
if (!editingItem.description?.description_spanish?.trim()) {
missingFields.push('Descripción en Español');
}
@@ -1453,7 +1422,7 @@
<Table.Cell>
<div class="flex flex-col py-0.5">
<span class="font-medium text-sm text-foreground/90">
{item.lines?.[0]?.description?.description_spanish ||
{item?.description?.description_spanish ||
'Sin descripción'}
</span>
{#if item.reference_number}
@@ -1464,10 +1433,10 @@
</div>
</Table.Cell>
<Table.Cell class="text-center font-medium tabular-nums">
{item.lines?.[0]?.quantity?.quantity || 0}
{item?.quantity?.quantity || 0}
</Table.Cell>
<Table.Cell class="text-right tabular-nums text-muted-foreground">
${(item.lines?.[0]?.financial?.unit_cost_usd || 0).toLocaleString(
${(item?.financial?.unit_cost_usd || 0).toLocaleString(
undefined,
{ minimumFractionDigits: 2 }
)}
@@ -1584,7 +1553,7 @@
<Table.Cell>
<div class="flex flex-col">
<span class="font-medium text-sm">
{item.lines?.[0]?.description?.description_spanish || 'Sin descripción'}
{item?.[0]?.description?.description_spanish || 'Sin descripción'}
</span>
<span class="text-[10px] text-muted-foreground"
>Ref: {item.reference_number || '-'}</span
@@ -1592,7 +1561,7 @@
</div>
</Table.Cell>
<Table.Cell class="text-center text-sm">
{item.lines?.[0]?.quantity?.quantity || 0}
{item?.[0]?.quantity?.quantity || 0}
</Table.Cell>
<Table.Cell class="text-right pr-2">
<div class="flex justify-end gap-1">

View File

@@ -1,7 +1,6 @@
export interface Item {
id?: number;
lines?: any[];
[key: string]: any;
}
@@ -71,36 +70,34 @@ export function cleanLineData(line: any) {
// Normalize numeric values from strings to numbers (for editing)
export function normalizeItemData(item: Partial<Item>): Partial<Item> {
if (item.lines && item.lines.length > 0) {
item.lines = item.lines.map((line) => {
const normalizedLine = { ...line };
if (item) {
const normalizedItem = { ...item };
// Normalize financials
if (normalizedLine.financial) {
const f = normalizedLine.financial;
normalizedLine.financial = {
...f,
unit_cost_usd: f.unit_cost_usd != null ? Number(f.unit_cost_usd) : undefined,
unit_cost_mxn: f.unit_cost_mxn != null ? Number(f.unit_cost_mxn) : undefined,
value_usd: f.value_usd != null ? Number(f.value_usd) : undefined,
value_mxn: f.value_mxn != null ? Number(f.value_mxn) : undefined
};
}
// Normalize financials
if (normalizedItem.financial) {
const f = normalizedItem.financial;
normalizedItem.financial = {
...f,
unit_cost_usd: f.unit_cost_usd != null ? Number(f.unit_cost_usd) : undefined,
unit_cost_mxn: f.unit_cost_mxn != null ? Number(f.unit_cost_mxn) : undefined,
value_usd: f.value_usd != null ? Number(f.value_usd) : undefined,
value_mxn: f.value_mxn != null ? Number(f.value_mxn) : undefined
};
}
// Normalize quantities
if (normalizedLine.quantity) {
const q = normalizedLine.quantity;
normalizedLine.quantity = {
...q,
quantity: q.quantity != null ? Number(q.quantity) : undefined,
net_weight: q.net_weight != null ? Number(q.net_weight) : undefined,
gross_weight: q.gross_weight != null ? Number(q.gross_weight) : undefined,
package_quantity: q.package_quantity != null ? Number(q.package_quantity) : undefined
};
}
// Normalize quantities
if (normalizedItem.quantity) {
const q = normalizedItem.quantity;
normalizedItem.quantity = {
...q,
quantity: q.quantity != null ? Number(q.quantity) : undefined,
net_weight: q.net_weight != null ? Number(q.net_weight) : undefined,
gross_weight: q.gross_weight != null ? Number(q.gross_weight) : undefined,
package_quantity: q.package_quantity != null ? Number(q.package_quantity) : undefined
};
}
return normalizedLine;
});
return normalizedItem;
}
return item;

View File

@@ -0,0 +1,71 @@
import type { RequestHandler } from './$types';
export const GET: RequestHandler = async ({ cookies, url }) => {
const token = cookies.get('access_token');
// Configurar la URL de la API usando las variables de entorno
let apiUrl = process.env.INTERNAL_API_URL;
if (!apiUrl) {
apiUrl = process.env.VITE_API_URL;
// Reemplazar 'localhost' con 'backend' para llamadas desde el servidor (SSR)
apiUrl = apiUrl?.replace('localhost', 'backend').replace('127.0.0.1', 'backend');
}
// Normalizar la URL
const baseUrl = apiUrl?.endsWith('/') ? apiUrl : `${apiUrl}/`;
// Get query parameters (historical_fraction, country, fraction_type, invoice_date, sector)
const searchParams = new URLSearchParams(url.search);
const queryString = searchParams.toString();
try {
const fetchUrl = `${baseUrl}v1/a76/historical-tariff-fractions/rate?${queryString}`;
const response = await fetch(fetchUrl, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
}
});
if (!response.ok) {
const errorText = await response.text();
console.error('Error response from backend:', errorText);
return new Response(
JSON.stringify({
error: 'Failed to fetch historical tariff rate',
details: errorText
}),
{
status: response.status,
headers: {
'Content-Type': 'application/json'
}
}
);
}
const data = await response.json();
return new Response(JSON.stringify(data), {
status: 200,
headers: {
'Content-Type': 'application/json'
}
});
} catch (error) {
console.error('Error in historical-tariff-fractions/rate API route:', error);
return new Response(
JSON.stringify({
error: 'Internal server error',
message: error instanceof Error ? error.message : 'Unknown error'
}),
{
status: 500,
headers: {
'Content-Type': 'application/json'
}
}
);
}
};

View File

@@ -153,17 +153,13 @@
function handleAddItem() {
isItemEditMode = false;
editingItemIndex = null;
editingItem = {
lines: [
{
line_number: (selectedPreset.items?.length || 0) + 1,
quantity: { quantity: 1 },
financial: { unit_cost_usd: 0 },
customs: {},
description: {},
reference: {}
}
]
editingItem = {
line_number: (selectedPreset.items?.length || 0) + 1,
quantity: { quantity: 1 },
financial: { unit_cost_usd: 0 },
customs: {},
description: {},
reference: {}
};
showItemSheet = true;
}
@@ -463,7 +459,7 @@
<Table.Cell>
<div class="flex flex-col">
<span class="font-medium text-sm">
{item.lines?.[0]?.description?.description_spanish || 'Sin descripción'}
{item?.description?.description_spanish || 'Sin descripción'}
</span>
{#if item.reference_number}
<span class="text-[10px] text-muted-foreground flex items-center gap-1">
@@ -473,12 +469,12 @@
</div>
</Table.Cell>
<Table.Cell class="text-center tabular-nums font-medium"
>{item.lines?.[0]?.quantity?.quantity || 0}</Table.Cell
>{item?.quantity?.quantity || 0}</Table.Cell
>
<Table.Cell
class="text-center tabular-nums font-medium text-emerald-600 dark:text-emerald-400"
>
${(item.lines?.[0]?.financial?.unit_cost_usd || 0).toLocaleString()}
${(item?.financial?.unit_cost_usd || 0).toLocaleString()}
</Table.Cell>
<Table.Cell class="text-right pr-6">
<div class="flex justify-end gap-1">