feat: Enhance invoice item management with detailed line item structure

- Introduced nested interfaces for line items including customs, financials, quantities, descriptions, and references.
- Updated Item interface to include lines as an array of LineItem.
- Modified invoice top fields to handle pedimento ID and auto-assign values from the invoice.
- Enhanced item configuration component to manage line item properties and descriptions.
- Updated main data, packages section, summary section, and other components to bind new line item properties.
- Implemented normalization of numeric values when editing items to ensure consistent data types.
- Adjusted save invoice logic to accommodate new line item structure and compliance data.
This commit is contained in:
AlexeerCT
2026-01-08 16:24:34 -06:00
parent 09bee94ae6
commit c040679fc5
17 changed files with 466 additions and 75 deletions

View File

@@ -11,9 +11,9 @@ export type TransportType = 'none' | 'transport' | 'box' | 'licence plates' | 't
export interface InvoiceComplianceMx {
invoice_id?: number;
pedimento?: string | null;
pedimento_code?: string | null;
pedimento_k1?: string | null;
pedimento_id?: number | null;
pedimento_r1?: number | null;
pedimento_k1?: number | null;
remesa?: number | null;
aduana?: string | null;
port_of_entry?: string | null;
@@ -24,7 +24,7 @@ export interface InvoiceComplianceMx {
sold_to_header?: string | null;
sold_to_id?: number | null;
shipped_to_header?: string | null;
shipped_to_id?: string | null;
shipped_to_id?: number | null;
shipped_by_header?: string | null;
shipped_by_id?: number | null;
customs_broker_id?: number | null;

View File

@@ -6,6 +6,115 @@ import { api } from '$lib/api';
// --- Interfaces ---
// --- Nested Interfaces for Line Items ---
export interface LineCustoms {
id?: number;
line_item_id?: number;
fraction?: string;
fraction_type?: string;
american_fraction?: string;
origin_country?: string;
destination_country?: string;
advalorem?: string;
advalorem_american?: number; // Backend: Decimal
sector?: string;
}
export interface LineFinancials {
id?: number;
line_item_id?: number;
// Costs
unit_cost_usd?: number;
unit_cost_mxn?: number;
unit_cost_capture?: number;
unit_cost_commercial_usd?: number;
// Values
value_usd?: number;
value_mxn?: number;
value_returned_usd?: number;
value_returned_mxn?: number;
customs_value_usd?: number;
}
export interface LineQuantities {
id?: number;
line_item_id?: number;
quantity?: number;
unit_of_measure?: string;
// Special quantities
quantity_temp_export?: number;
quantity_returned?: number;
// Weight
net_weight?: number;
gross_weight?: number;
// Packaging
package_key?: string;
package_quantity?: number;
package_description?: string;
}
export interface LineDescriptions {
id?: number;
line_item_id?: number;
description_spanish?: string;
description_english?: string;
extra_description?: string;
additional_info_spanish?: string;
brand?: string;
model?: string;
has_serial?: boolean;
}
export interface LineReferences {
id?: number;
line_item_id?: number;
serie_id?: number;
}
export interface LineItem {
id?: number;
item_id?: number;
line_number: number;
// Identification
part_number?: string;
component_part_number?: string;
class_code?: string;
identifier?: string;
// Unit of Measure
unit_of_measure?: string;
alternate_unit?: string;
// Permits
permit_number?: string;
page_line?: string;
has_certificate?: boolean;
certificate_number?: string;
// Flags
is_subitem?: boolean;
includes_subitems?: boolean;
tax_payment?: boolean;
// Payment
payment_method?: string;
igi_amount?: number;
// Nested relations (Singular names to match backend Pydantic models)
customs?: LineCustoms;
financial?: LineFinancials;
quantity?: LineQuantities;
description?: LineDescriptions;
reference?: LineReferences;
}
export interface Item {
id?: number;
invoice_id: number;
@@ -18,6 +127,8 @@ export interface Item {
location?: string;
created_at?: string;
updated_at?: string;
lines?: LineItem[];
}
export interface ItemListResponse {
@@ -36,6 +147,7 @@ export interface CreateItemData {
rectification?: number;
warehouse?: string;
location?: string;
lines?: LineItem[];
}
export interface UpdateItemData {
@@ -46,6 +158,7 @@ export interface UpdateItemData {
rectification?: boolean;
warehouse?: string;
location?: string;
lines?: LineItem[];
}
/**

View File

@@ -36,10 +36,16 @@
formData.regimen_pedimento = selectedPedimento.regime || '';
// Construir el número de pedimento completo
const pedimentoNumber = `${selectedPedimento.year || ''}-${selectedPedimento.customs_office || ''}-${selectedPedimento.license || ''}-${selectedPedimento.pedimento_number || ''}`.replace(/^-+|-+$/g, '');
const pedimentoNumber = `${selectedPedimento.customs_office?.slice(0,2) || ''}-${selectedPedimento.license || ''}-${selectedPedimento.pedimento_number || ''}`.replace(/^-+|-+$/g, '');
formData.pedimento = pedimentoNumber;
}
$effect(() => {
if (formData?.pedimento_id && pedimentos.length > 0 && !formData.pedimento) {
handlePedimentoChange(formData.pedimento_id);
}
});
if (!formData) {
let operationType: string | null = null;
if (invoice?.operation_type) {
@@ -50,8 +56,7 @@
formData = {
is_pedimento_pending: false,
pedimento_id: null,
pedimento: invoice?.compliance_mx?.pedimento || '',
pedimento_id: invoice?.compliance_mx?.pedimento_id || '',
remesa: invoice?.compliance_mx?.remesa || '',
invoice_number: invoice?.invoice_number || '',
invoice_date: invoice?.invoice_date || new Date().toISOString().split('T')[0],
@@ -147,7 +152,7 @@
<Select.Content class="max-h-[300px]">
{#each pedimentos as pedimento}
<Select.Item value={String(pedimento.id)}>
{pedimento.year}-{pedimento.customs_office}-{pedimento.license}-{pedimento.pedimento_number}
{pedimento.customs_office?.slice(0, 2)}-{pedimento.license}-{pedimento.pedimento_number}
</Select.Item>
{/each}
</Select.Content>

View File

@@ -2,14 +2,26 @@
import * as RadioGroup from '$lib/components/ui/radio-group';
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import type { LineItem, LineDescriptions } from '$lib/api/dashboard/a76/items';
let {
isSubPartida = $bindable(),
continueSubPartidas = $bindable()
lineItem = $bindable(),
descriptions = $bindable()
}: {
isSubPartida: string;
continueSubPartidas: string;
lineItem: LineItem;
descriptions: LineDescriptions;
} = $props();
// Helper to map boolean to string for RadioGroup
let isSubPartidaValue = $derived(lineItem.is_subitem ? 'subpartida' : 'partida');
function setIsSubPartida(val: string) {
lineItem.is_subitem = val === 'subpartida';
}
let continueSubPartidasValue = $derived(lineItem.includes_subitems ? 'si' : 'no');
function setContinueSubPartidas(val: string) {
lineItem.includes_subitems = val === 'si';
}
</script>
<div class="lg:col-span-5 space-y-3">
@@ -17,7 +29,10 @@
<div class="grid grid-cols-2 gap-2">
<fieldset class="border rounded-md p-2 space-y-2">
<legend class="text-xs font-semibold px-2 bg-gray-200 dark:bg-gray-700">Is</legend>
<RadioGroup.Root bind:value={isSubPartida} class="flex gap-3">
<RadioGroup.Root
value={isSubPartidaValue}
onValueChange={setIsSubPartida}
class="flex gap-3">
<div class="flex items-center space-x-2">
<RadioGroup.Item value="partida" id="partida" />
<Label for="partida" class="text-xs font-normal">Item</Label>
@@ -31,7 +46,10 @@
<fieldset class="border rounded-md p-2 space-y-2">
<legend class="text-xs font-semibold px-2 bg-gray-200 dark:bg-gray-700">Continue Sub-Items</legend>
<RadioGroup.Root bind:value={continueSubPartidas} class="flex gap-3">
<RadioGroup.Root
value={continueSubPartidasValue}
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>
@@ -49,7 +67,7 @@
<div class="space-y-1">
<Label for="num_parte" class="text-xs">Part Number:</Label>
<div class="flex gap-1">
<Input id="num_parte" class="h-7 text-xs" />
<Input id="num_parte" bind:value={lineItem.part_number} class="h-7 text-xs" />
</div>
</div>
@@ -57,6 +75,7 @@
<Label for="desc_espanol" class="text-xs">Description in Spanish:</Label>
<textarea
id="desc_espanol"
bind:value={descriptions.description_spanish}
class="flex min-h-[60px] w-full rounded-md border border-input bg-background px-2 py-1 text-xs ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
></textarea>
</div>
@@ -65,6 +84,7 @@
<Label for="desc_ingles" class="text-xs">Description in English:</Label>
<textarea
id="desc_ingles"
bind:value={descriptions.description_english}
class="flex min-h-[60px] w-full rounded-md border border-input bg-background px-2 py-1 text-xs ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
></textarea>
</div>

View File

@@ -49,7 +49,7 @@
Temporary Import Item
</Sheet.Title>
<Sheet.Description class="text-xs text-purple-100">
Order Number: {invoice?.invoice_number || 'N/A'} | Line: 1
Order Number: {invoice?.invoice_number || 'N/A'} | Line: currentline
</Sheet.Description>
</Sheet.Header>
@@ -58,11 +58,23 @@
<div class="grid grid-cols-1 lg:grid-cols-12 gap-4 mb-6">
<!-- Left column (7 columns) -->
<div class="lg:col-span-7 space-y-3">
<MainData />
{#if editingItem.lines && editingItem.lines.length > 0}
<MainData
bind:lineItem={editingItem.lines[0]}
bind:quantities={editingItem.lines[0].quantity!}
bind:financials={editingItem.lines[0].financial!}
bind:customs={editingItem.lines[0].customs!}
/>
{/if}
</div>
<!-- Right column (5 columns) -->
<ItemConfiguration bind:isSubPartida={isSubPartida} bind:continueSubPartidas={continueSubPartidas} />
{#if editingItem.lines && editingItem.lines.length > 0}
<ItemConfiguration
bind:lineItem={editingItem.lines[0]}
bind:descriptions={editingItem.lines[0].description!}
/>
{/if}
</div>
<!-- Tabs with additional content -->
@@ -78,29 +90,48 @@
<!-- Tab: General -->
<Tabs.Content value="generales" class="space-y-3 mt-0">
<div class="grid grid-cols-2 gap-3">
<PackagesSection />
<SummarySection />
{#if editingItem.lines && editingItem.lines.length > 0}
<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!}
/>
<SummarySection
bind:financials={editingItem.lines[0].financial!}
bind:quantities={editingItem.lines[0].quantity!}
/>
{/if}
</div>
</Tabs.Content>
<!-- Tab: Continuation -->
<Tabs.Content value="continuacion" class="space-y-3 mt-0">
<TabContinuation />
{#if editingItem.lines && editingItem.lines.length > 0}
<TabContinuation bind:lineItem={editingItem.lines[0]} />
{/if}
</Tabs.Content>
<!-- Tab: Series -->
<Tabs.Content value="series" class="space-y-3 mt-0">
<TabSeries />
{#if editingItem.lines && editingItem.lines.length > 0}
<TabSeries bind:descriptions={editingItem.lines[0].description!} />
{/if}
</Tabs.Content>
<!-- Tab: Labeling -->
<Tabs.Content value="etiquetado" class="space-y-3 mt-0">
<TabLabeling />
{#if editingItem.lines && editingItem.lines.length > 0}
<TabLabeling bind:descriptions={editingItem.lines[0].description!} />
{/if}
</Tabs.Content>
<!-- Tab: Identifiers -->
<Tabs.Content value="identificadores" class="space-y-3 mt-0">
<TabIdentifiers />
{#if editingItem.lines && editingItem.lines.length > 0}
<TabIdentifiers bind:lineItem={editingItem.lines[0]} />
{/if}
</Tabs.Content>
</Tabs.Root>
</div>

View File

@@ -1,6 +1,19 @@
<script lang="ts">
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import type { LineItem, LineQuantities, LineFinancials, LineCustoms } from '$lib/api/dashboard/a76/items';
let {
lineItem = $bindable(),
quantities = $bindable(),
financials = $bindable(),
customs = $bindable()
}: {
lineItem: LineItem;
quantities: LineQuantities;
financials: LineFinancials;
customs: LineCustoms;
} = $props();
</script>
<fieldset class="border rounded-md p-3 space-y-3">
@@ -11,7 +24,7 @@
<div class="col-span-4 space-y-1">
<Label for="clase" class="text-xs font-medium">* Class:</Label>
<div class="flex gap-1">
<Input id="clase" class="h-8 text-xs" />
<Input id="clase" bind:value={lineItem.class_code} class="h-8 text-xs" />
</div>
</div>
</div>
@@ -20,13 +33,13 @@
<div class="grid grid-cols-12 gap-2">
<div class="col-span-4 space-y-1">
<Label for="cantidad" class="text-xs font-medium">* Quantity:</Label>
<Input id="cantidad" type="number" min="0" value="0.00000000" class="h-8 text-xs text-right" />
<Input id="cantidad" type="number" step="0.00000001" min="0" bind:value={quantities.quantity} class="h-8 text-xs text-right" />
</div>
<div class="col-span-4 space-y-1">
<Label class="text-xs font-medium">U.M.:</Label>
<div class="flex gap-1">
<div class="h-8 flex items-center flex-1">
<span class="text-xs text-muted-foreground">-</span>
<Input id="um" bind:value={lineItem.unit_of_measure} class="h-8 text-xs" placeholder="U.M." />
</div>
</div>
</div>
@@ -37,14 +50,14 @@
<div class="col-span-4 space-y-1">
<Label for="costo_unitario" class="text-xs font-medium">* Unit Cost:</Label>
<div class="flex items-center gap-2">
<Input id="costo_unitario" type="number" min="0" value="0.00000000" 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_usd} class="h-8 text-xs text-right flex-1" />
<span class="text-xs text-blue-600 font-semibold">USD</span>
</div>
</div>
<div class="col-span-5 space-y-1">
<Label for="fraccion" class="text-xs font-medium">Fraction:</Label>
<div class="flex gap-1">
<Input id="fraccion" value="0000 00 00" class="h-8 text-xs text-center" />
<Input id="fraccion" bind:value={customs.fraction} class="h-8 text-xs text-center" />
</div>
</div>
</div>
@@ -54,20 +67,20 @@
<div class="col-span-4 space-y-1">
<Label for="pais_origen" class="text-xs font-medium">* Origin Country:</Label>
<div class="flex gap-1">
<Input id="pais_origen" class="h-8 text-xs" />
<Input id="pais_origen" bind:value={customs.origin_country} class="h-8 text-xs" />
</div>
</div>
<div class="col-span-4 space-y-1">
<Label for="tipo_tarifa" class="text-xs font-medium">* Tariff Type:</Label>
<select id="tipo_tarifa" class="flex h-8 w-full rounded-md border border-input bg-background px-2 py-1 text-xs ring-offset-background">
<option>GENERAL</option>
<option>PREFERENCIAL</option>
<select id="tipo_tarifa" bind:value={customs.fraction_type} class="flex h-8 w-full rounded-md border border-input bg-background px-2 py-1 text-xs ring-offset-background">
<option value="GENERAL">GENERAL</option>
<option value="PREFERENCIAL">PREFERENCIAL</option>
</select>
</div>
<div class="col-span-4 space-y-1">
<Label class="text-xs font-medium">Advalorem:</Label>
<div class="h-8 flex items-center">
<span class="text-xs text-muted-foreground">0.00</span>
<Input id="advalorem" bind:value={customs.advalorem} class="h-8 text-xs text-right" placeholder="0.00" />
</div>
</div>
</div>

View File

@@ -1,6 +1,21 @@
<script lang="ts">
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import type { Item, LineItem, LineDescriptions, LineCustoms, LineQuantities } from '$lib/api/dashboard/a76/items';
let {
item = $bindable(),
lineItem = $bindable(),
descriptions = $bindable(),
customs = $bindable(),
quantities = $bindable()
}: {
item: Partial<Item>;
lineItem: LineItem;
descriptions: LineDescriptions;
customs: LineCustoms;
quantities: LineQuantities;
} = $props();
</script>
<fieldset class="border rounded-md p-2 space-y-2">
@@ -9,11 +24,11 @@
<div class="grid grid-cols-12 gap-2 items-end">
<div class="col-span-2 space-y-1">
<Label for="cantidad_bultos" class="text-xs">Quantity:</Label>
<Input id="cantidad_bultos" type="number" min="0" value="0" class="h-7 text-xs text-right" />
<Input id="cantidad_bultos" type="number" step="0.00000001" min="0" bind:value={quantities.package_quantity} class="h-7 text-xs text-right" />
</div>
<div class="col-span-3 space-y-1">
<Label for="clave_bultos" class="text-xs">Package Code:</Label>
<Input id="clave_bultos" class="h-7 text-xs" />
<Input id="clave_bultos" bind:value={quantities.package_key} class="h-7 text-xs" />
</div>
<div class="col-span-1 flex items-end">
</div>
@@ -25,6 +40,7 @@
</div>
<div class="col-span-4 space-y-1">
<Label for="descripcion_bultos" class="text-xs">Description:</Label>
<Input id="descripcion_bultos" bind:value={quantities.package_description} class="h-7 text-xs" />
</div>
</div>
@@ -34,11 +50,11 @@
<div class="grid grid-cols-6 gap-2 items-end">
<div class="col-span-2 space-y-1">
<Label for="peso_neto" class="text-xs">Net:</Label>
<Input id="peso_neto" type="number" min="0" value="0.00000000" class="h-7 text-xs text-right" />
<Input id="peso_neto" type="number" step="0.00000001" min="0" bind:value={quantities.net_weight} class="h-7 text-xs text-right" />
</div>
<div class="col-span-2 space-y-1">
<Label for="peso_bruto" class="text-xs">Gross:</Label>
<Input id="peso_bruto" type="number" min="0" value="0.00000000" class="h-7 text-xs text-right" />
<Input id="peso_bruto" type="number" step="0.00000001" min="0" bind:value={quantities.gross_weight} class="h-7 text-xs text-right" />
</div>
<div class="col-span-2 space-y-1">
<Label class="text-xs invisible">Space</Label>
@@ -50,37 +66,37 @@
<div class="grid grid-cols-12 gap-2 items-end">
<div class="col-span-2 space-y-1">
<Label for="no_permiso" class="text-xs">Permit No.:</Label>
<Input id="no_permiso" class="h-7 text-xs" />
<Input id="no_permiso" bind:value={lineItem.permit_number} class="h-7 text-xs" />
</div>
<div class="col-span-3 space-y-1">
<Label for="pag_region" class="text-xs">Page/Region:</Label>
<Input id="pag_region" class="h-7 text-xs" />
<Input id="pag_region" bind:value={lineItem.page_line} class="h-7 text-xs" />
</div>
</div>
<div class="grid grid-cols-12 gap-2 items-end">
<div class="col-span-4 space-y-1">
<Label for="fraccion_americana" class="text-xs">American Fraction:</Label>
<Input id="fraccion_americana" class="h-7 text-xs" />
<Input id="fraccion_americana" bind:value={customs.american_fraction} class="h-7 text-xs" />
</div>
<div class="col-span-3 space-y-1">
<Label class="text-xs invisible">Space</Label>
<span class="text-xs">Advalorem: 0.00</span>
<span class="text-xs">Advalorem: {customs.advalorem_american || '0.00'}</span>
</div>
</div>
<div class="grid grid-cols-9 gap-2 items-end">
<div class="col-span-3 space-y-1">
<Label for="marca" class="text-xs">Brand:</Label>
<Input id="marca" class="h-7 text-xs" />
<Input id="marca" bind:value={descriptions.brand} class="h-7 text-xs" />
</div>
<div class="col-span-3 space-y-1">
<Label for="modelo" class="text-xs">Model:</Label>
<Input id="modelo" class="h-7 text-xs" />
<Input id="modelo" bind:value={descriptions.model} class="h-7 text-xs" />
</div>
<div class="col-span-3 space-y-1">
<Label for="orden_compra" class="text-xs">Purchase Order:</Label>
<Input id="orden_compra" class="h-7 text-xs" />
<Input id="orden_compra" bind:value={item.order} class="h-7 text-xs" />
</div>
</div>
</fieldset>

View File

@@ -1,4 +1,7 @@
<script lang="ts">
import type { LineFinancials, LineQuantities } from '$lib/api/dashboard/a76/items';
let { financials = $bindable(), quantities = $bindable() }: { financials: LineFinancials; quantities: LineQuantities } = $props();
</script>
<div>
@@ -7,19 +10,19 @@
<div class="text-xs font-semibold">RETURN QUANTITY SUB-ITEMS</div>
<div class="grid grid-cols-2 gap-2 text-xs">
<div>Temporary: <span class="text-blue-600">0.00000000</span></div>
<div>Temporary: <span class="text-blue-600">{quantities.quantity_temp_export?.toFixed(8) || '0.00000000'}</span></div>
<div>Replacement or Change: <span class="text-blue-600">0.00000000</span></div>
<div>Definitive: <span class="text-blue-600">0.00000000</span></div>
<div>Returned Values: <span class="text-blue-600">0.00000000</span></div>
<div class="col-span-2">Returned Values: <span class="text-blue-600">0.00000000</span></div>
<div>Definitive: <span class="text-blue-600">{quantities.quantity_returned?.toFixed(8) || '0.00000000'}</span></div>
<div>Returned Values: <span class="text-blue-600">{financials.value_returned_usd?.toFixed(8) || '0.00000000'}</span></div>
<div class="col-span-2">Returned Values: <span class="text-blue-600">{financials.value_returned_mxn?.toFixed(8) || '0.00000000'}</span></div>
</div>
<div class="grid grid-cols-2 gap-2 text-xs pt-2 border-t">
<div class="font-semibold">WEIGHTS (KILOS)</div>
<div class="font-semibold">WEIGHTS (Pounds)</div>
<div>Net: <span class="text-blue-600">0.00000000</span></div>
<div>Net: <span class="text-blue-600">{quantities.net_weight?.toFixed(8) || '0.00000000'}</span></div>
<div><span class="text-blue-600">0.00000000</span></div>
<div>Whole: <span class="text-blue-600">0.00000000</span></div>
<div>Whole: <span class="text-blue-600">{quantities.gross_weight?.toFixed(8) || '0.00000000'}</span></div>
<div><span class="text-blue-600">0.00000000</span></div>
</div>
</fieldset>
@@ -31,16 +34,16 @@
<div class="grid grid-cols-2 gap-2 text-xs">
<div class="font-semibold">(Dollars)</div>
<div class="font-semibold">(Pesos)</div>
<div>Cost: <span class="text-blue-600">0.00000000</span></div>
<div><span class="text-blue-600">0.00000000</span></div>
<div>Value: <span class="text-blue-600">0.00000000</span></div>
<div><span class="text-blue-600">0.00000000</span></div>
<div>Cost: <span class="text-blue-600">{financials.unit_cost_usd?.toFixed(8) || '0.00000000'}</span></div>
<div><span class="text-blue-600">{financials.unit_cost_mxn?.toFixed(8) || '0.00000000'}</span></div>
<div>Value: <span class="text-blue-600">{financials.value_usd?.toFixed(8) || '0.00000000'}</span></div>
<div><span class="text-blue-600">{financials.value_mxn?.toFixed(8) || '0.00000000'}</span></div>
</div>
<div class="space-y-1 pt-2 border-t">
<div class="text-xs">Capture Cost: <span class="text-blue-600">0.00000000</span> <span class="text-blue-600">USD</span></div>
<div class="text-xs">Capture Value: <span class="text-blue-600">0.00000000</span> <span class="text-blue-600">USD</span></div>
<div class="text-xs">Customs Value: <span class="text-blue-600">0.00000000</span> <span class="text-blue-600">USD</span></div>
<div class="text-xs">Capture Cost: <span class="text-blue-600">{financials.unit_cost_capture?.toFixed(8) || '0.00000000'}</span> <span class="text-blue-600">USD</span></div>
<div class="text-xs">Capture Value: <span class="text-blue-600">{financials.value_usd?.toFixed(8) || '0.00000000'}</span> <span class="text-blue-600">USD</span></div>
<div class="text-xs">Customs Value: <span class="text-blue-600">{financials.customs_value_usd?.toFixed(8) || '0.00000000'}</span> <span class="text-blue-600">USD</span></div>
</div>
</fieldset>
</div>

View File

@@ -3,6 +3,23 @@
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import { Checkbox } from '$lib/components/ui/checkbox';
import type { LineItem } from '$lib/api/dashboard/a76/items';
let {
lineItem = $bindable()
}: {
lineItem: LineItem;
} = $props();
let taxPaidValue = $derived(lineItem.tax_payment ? 'si' : 'no');
function setTaxPaid(val: string) {
lineItem.tax_payment = val === 'si';
}
let hasCertificateValue = $derived(lineItem.has_certificate ? 'si' : 'no');
function setHasCertificate(val: string) {
lineItem.has_certificate = val === 'si';
}
</script>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
@@ -12,7 +29,10 @@
<div class="grid grid-cols-4 gap-3">
<fieldset class="border rounded-md p-2 space-y-2">
<legend class="text-xs font-semibold px-2 bg-gray-200 dark:bg-gray-700">TAX PAID</legend>
<RadioGroup.Root value="no" class="flex gap-3">
<RadioGroup.Root
value={taxPaidValue}
onValueChange={setTaxPaid}
class="flex gap-3">
<div class="flex items-center space-x-2">
<RadioGroup.Item value="si" id="pago_impuesto_si" />
<Label for="pago_impuesto_si" class="text-xs font-normal">Yes</Label>
@@ -27,7 +47,7 @@
<div class="space-y-1">
<Label for="forma_pago" class="text-xs">Payment Method:</Label>
<div class="flex gap-1">
<Input id="forma_pago" value="21" class="h-7 text-xs" />
<Input id="forma_pago" bind:value={lineItem.payment_method} class="h-7 text-xs" />
</div>
</div>
<Label for="credito_iva" class="text-xs">VAT AND EXCISE TAX CREDITS.</Label>
@@ -37,7 +57,8 @@
<!-- IGI Amount -->
<div class="grid grid-cols-2 gap-2">
<div class="space-y-1">
<Label for="monto_igi" class="text-xs">IGI Amount: 0 <span class="text-xs">DOLLARS</span></Label>
<Label for="monto_igi" class="text-xs">IGI Amount: {lineItem.igi_amount || 0} <span class="text-xs">DOLLARS</span></Label>
<Input id="monto_igi" type="number" bind:value={lineItem.igi_amount} class="h-8 text-xs" />
</div>
</div>
@@ -45,7 +66,10 @@
<div class="grid grid-cols-4 gap-3">
<fieldset class="border rounded-md p-2 space-y-2">
<legend class="text-xs font-semibold px-2 bg-gray-200 dark:bg-gray-700">Has Certificate of Origin?</legend>
<RadioGroup.Root value="no" class="flex gap-3">
<RadioGroup.Root
value={hasCertificateValue}
onValueChange={setHasCertificate}
class="flex gap-3">
<div class="flex items-center space-x-2">
<RadioGroup.Item value="si" id="cert_origen_si" />
<Label for="cert_origen_si" class="text-xs font-normal">Yes</Label>

View File

@@ -1,6 +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';
let { lineItem = $bindable() }: { lineItem: LineItem } = $props();
</script>
<fieldset class="border rounded-md p-3 space-y-3">
@@ -9,7 +12,7 @@
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
<Label for="identificador1" class="text-xs">Identifier 1:</Label>
<Input id="identificador1" class="h-8 text-sm" />
<Input id="identificador1" bind:value={lineItem.identifier} class="h-8 text-sm" />
</div>
<div class="space-y-2">
<Label for="identificador2" class="text-xs">Identifier 2:</Label>

View File

@@ -1,6 +1,9 @@
<script lang="ts">
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import type { LineDescriptions } from '$lib/api/dashboard/a76/items';
let { descriptions = $bindable() }: { descriptions: LineDescriptions } = $props();
</script>
<fieldset class="border rounded-md p-3 space-y-3">
@@ -21,6 +24,7 @@
<Label for="observaciones_etiqueta" class="text-xs">Observations:</Label>
<textarea
id="observaciones_etiqueta"
bind:value={descriptions.additional_info_spanish}
class="flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
placeholder="Labeling observations..."
></textarea>

View File

@@ -1,5 +1,8 @@
<script lang="ts">
import { Label } from '$lib/components/ui/label';
import type { LineDescriptions } from '$lib/api/dashboard/a76/items';
let { descriptions = $bindable() }: { descriptions: LineDescriptions } = $props();
</script>
<fieldset class="border rounded-md p-3 space-y-3">
@@ -9,6 +12,7 @@
<Label for="series" class="text-xs">Serial Numbers:</Label>
<textarea
id="series"
bind:value={descriptions.extra_description}
class="flex min-h-[100px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
placeholder="Enter serial numbers, one per line..."
></textarea>

View File

@@ -112,23 +112,139 @@
isEditMode = false;
showItemSheet = true;
// Auto-asignar valores desde la factura
// Auto-asignar valores desde la factura con estructura completa
editingItem = {
invoice_id: invoice.id,
reference_number: '',
order: invoice.purchase_order || '',
warehouse: '',
location: ''
location: '',
lines: [{
line_number: 1,
// LineItem fields
part_number: undefined,
component_part_number: undefined,
class_code: undefined,
identifier: undefined,
unit_of_measure: undefined,
alternate_unit: undefined,
permit_number: undefined,
page_line: undefined,
has_certificate: false,
certificate_number: undefined,
is_subitem: false,
includes_subitems: false,
tax_payment: false,
payment_method: undefined,
igi_amount: 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_key: undefined,
package_quantity: undefined,
package_description: undefined,
},
customs: {
fraction: undefined,
fraction_type: 'GENERAL',
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,
},
reference: {
serie_id: undefined,
},
}]
};
}
function handleEdit(item: Item) {
isEditMode = true;
selectedItem = item;
editingItem = { ...item };
// Deep clone and normalize numeric values
editingItem = normalizeItemData({ ...item });
showItemSheet = true;
}
// 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 };
// 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 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,
};
}
return normalizedLine;
});
}
return item;
}
function handleDelete(item: Item) {
selectedItem = item;
showDeleteDialog = true;
@@ -144,7 +260,8 @@
reference_number: editingItem.reference_number,
order: editingItem.order,
warehouse: editingItem.warehouse,
location: editingItem.location
location: editingItem.location,
lines: editingItem.lines || []
});
// Recargar items
@@ -174,7 +291,8 @@
reference_number: editingItem.reference_number,
order: editingItem.order,
warehouse: editingItem.warehouse,
location: editingItem.location
location: editingItem.location,
lines: editingItem.lines || []
});
// Recargar items

View File

@@ -110,7 +110,7 @@ function buildInvoicePayload(formData: FormDataSet): CreateInvoiceData | UpdateI
// Solo agregar sub-recursos si tienen valores reales
// Compliance MX
const hasComplianceValue = InvoiceTopFieldsFormData?.pedimento || InvoiceTopFieldsFormData?.remesa || generalFormData?.aduana ||
const hasComplianceValue = InvoiceTopFieldsFormData?.pedimento_id || InvoiceTopFieldsFormData?.remesa || generalFormData?.aduana ||
generalFormData?.provider_id || generalFormData?.sold_to_id ||
generalFormData?.shipped_to_id || generalFormData?.customs_broker_id ||
observationFormData?.movement_type || observationFormData?.enclosure ||
@@ -158,7 +158,7 @@ function buildInvoicePayload(formData: FormDataSet): CreateInvoiceData | UpdateI
function buildComplianceMxData(InvoiceTopFieldsFormData: any, generalFormData: any, othersFormData: any, observationFormData: any) {
return {
// Pedimento fields - desde InvoiceTopFieldsFormData
pedimento: InvoiceTopFieldsFormData?.pedimento || null,
pedimento_id: InvoiceTopFieldsFormData?.pedimento_id ? Number(InvoiceTopFieldsFormData.pedimento_id) : null,
remesa: Number(InvoiceTopFieldsFormData?.remesa || null),
is_pedimento_pending: Boolean(InvoiceTopFieldsFormData?.is_pedimento_pending || false),
// Fields from generalFormData