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:
@@ -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;
|
||||
|
||||
@@ -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[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user