Merge remote-tracking branch 'origin/development' into feature/fraction-catalogs
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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('');
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user