diff --git a/backend/api/v1/modules/a76/invoices/imports/temporary/validators/update.py b/backend/api/v1/modules/a76/invoices/imports/temporary/validators/update.py
index d30069a4..9c254a9b 100644
--- a/backend/api/v1/modules/a76/invoices/imports/temporary/validators/update.py
+++ b/backend/api/v1/modules/a76/invoices/imports/temporary/validators/update.py
@@ -64,7 +64,7 @@ def validate_update(
# Columna B: Remesa
if invoice_data.compliance_mx.remesa:
- invoice_data.compliance_mx.remesa = clean_str(invoice_data.compliance_mx.remesa)
+ invoice_data.compliance_mx.remesa = invoice_data.compliance_mx.remesa
else:
invoice_data.compliance_mx.remesa = existing_invoice.compliance_mx.remesa if existing_invoice.compliance_mx else None
diff --git a/backend/api/v1/modules/a76/items/imports/temporary/validators/common.py b/backend/api/v1/modules/a76/items/imports/temporary/validators/common.py
index d10c95fd..3cbe5958 100644
--- a/backend/api/v1/modules/a76/items/imports/temporary/validators/common.py
+++ b/backend/api/v1/modules/a76/items/imports/temporary/validators/common.py
@@ -164,7 +164,7 @@ def validate_common(
code="PACKAGE_QUANTITY_MUST_BE_GREATER_THAN_ZERO",
)
else:
- if line.quantity.package_quantity and line.quantity.package_quantity > 0:
+ if (line.quantity.package_quantity or line.quantity.package_quantity > 0) and not line.quantity.package_id:
errors.add_error(
field=f"line[{line_number}].quantity.package_id",
message="El paquete es obligatorio cuando se proporciona la cantidad de paquetes.",
diff --git a/backend/api/v1/modules/a76/items/imports/temporary/validators/create.py b/backend/api/v1/modules/a76/items/imports/temporary/validators/create.py
index 146a4278..66024a5e 100644
--- a/backend/api/v1/modules/a76/items/imports/temporary/validators/create.py
+++ b/backend/api/v1/modules/a76/items/imports/temporary/validators/create.py
@@ -211,18 +211,18 @@ def validate_create(
# Calcular peso neto en kilogramos (estándar interno)
if unit_is_kgs:
- if invoice_weight_type == "kgs":
+ if invoice_weight_type == "KGS":
line.quantity.net_weight = quantity
else: # invoice en libras
line.quantity.net_weight = quantity * Decimal("2.204624")
elif unit_is_lbs:
- if invoice_weight_type == "kgs":
+ if invoice_weight_type == "KGS":
line.quantity.net_weight = quantity / Decimal("2.204624")
else: # invoice en libras
line.quantity.net_weight = quantity
else:
# Otra unidad de medida - usar peso capturado y convertir si es necesario
- if invoice_weight_type == "kgs":
+ if invoice_weight_type == "KGS":
# El peso capturado está en kilos
line.quantity.net_weight = net_weight_input
else:
@@ -252,7 +252,7 @@ def validate_create(
# Si no se proporcionó peso bruto, calcularlo
if not gross_weight_input or gross_weight_input == 0:
- if invoice_weight_type == "kgs":
+ if invoice_weight_type == "KGS":
line.quantity.gross_weight = line.quantity.net_weight + (
package_weight_unit * package_quantity
)
@@ -262,7 +262,7 @@ def validate_create(
)
else:
# Convertir peso bruto capturado según tipo de factura
- if invoice_weight_type == "kgs":
+ if invoice_weight_type == "KGS":
line.quantity.gross_weight = gross_weight_input
else: # libras
line.quantity.gross_weight = gross_weight_input / Decimal("2.204624")
diff --git a/backend/api/v1/modules/a76/items/imports/temporary/validators/update.py b/backend/api/v1/modules/a76/items/imports/temporary/validators/update.py
index 338aac13..eec6f443 100644
--- a/backend/api/v1/modules/a76/items/imports/temporary/validators/update.py
+++ b/backend/api/v1/modules/a76/items/imports/temporary/validators/update.py
@@ -73,7 +73,7 @@ def validate_update(
# Se proporcionó nuevo peso neto, convertir según tipo
net_weight_input = line.quantity.net_weight
- if invoice_weight_type == "kgs":
+ if invoice_weight_type == "KGS":
line.quantity.net_weight = net_weight_input
else: # libras, convertir a kilos
line.quantity.net_weight = net_weight_input / Decimal("2.204624")
@@ -85,7 +85,7 @@ def validate_update(
if line.quantity.gross_weight is not None:
gross_weight_input = line.quantity.gross_weight
- if invoice_weight_type == "kgs":
+ if invoice_weight_type == "KGS":
line.quantity.gross_weight = gross_weight_input
else: # libras, convertir a kilos
line.quantity.gross_weight = gross_weight_input / Decimal("2.204624")
@@ -98,8 +98,8 @@ def validate_update(
line.quantity.package_quantity = existing_line.quantity.package_quantity
# Clave de bultos
- if not line.quantity.package_key:
- line.quantity.package_key = existing_line.quantity.package_key
+ if not line.quantity.package_id:
+ line.quantity.package_id = existing_line.quantity.package_id
# País de origen
if not line.customs.origin_country:
diff --git a/frontend/messages/en.json b/frontend/messages/en.json
index cf838b4f..fc56fcdf 100644
--- a/frontend/messages/en.json
+++ b/frontend/messages/en.json
@@ -63,6 +63,16 @@
"back_flush": "Back Flush",
"crossing_notice": "Crossing Notice"
},
+ "fractions": {
+ "title": "Fractions",
+ "sitar": "Fraction Sitar",
+ "sitar_seventh_amendment": "Fraction Sitar - Seventh Amendment",
+ "sitar_us": "Fraction Sitar US",
+ "american": "Fraction American",
+ "canadian": "Fraction Canadian",
+ "historical": "Fraction Historical",
+ "sectors": "Sectors"
+ },
"goods": {
"title": "Goods",
"classes": "Classes",
diff --git a/frontend/messages/es.json b/frontend/messages/es.json
index 1ea855b2..413726cd 100644
--- a/frontend/messages/es.json
+++ b/frontend/messages/es.json
@@ -62,6 +62,16 @@
"electronic_notices": "Avisos electrónicos",
"back_flush": "Back Flush",
"crossing_notice": "Aviso de cruce"
+ },
+ "fractions": {
+ "title": "Fracciones",
+ "sitar": "Fracciones Sitar",
+ "sitar_seventh_amendment": "Fracciones Sitar - 7ma enmienda",
+ "sitar_us": "Fracciones Sitar US",
+ "american": "Fracciones Americana",
+ "canadian": "Fracciones Canadiense",
+ "historical": "Fracciones Historicas",
+ "sectors": "Sectores"
},
"goods": {
"title": "Mercancías",
diff --git a/frontend/src/lib/api/dashboard/a76/items.ts b/frontend/src/lib/api/dashboard/a76/items.ts
index 353ddcfa..1ec5e69d 100644
--- a/frontend/src/lib/api/dashboard/a76/items.ts
+++ b/frontend/src/lib/api/dashboard/a76/items.ts
@@ -53,9 +53,9 @@ export interface LineQuantities {
gross_weight?: number;
// Packaging
- package_key?: string;
+ package_id?: number; // Changed from string to number - this is the ID from packages catalog
package_quantity?: number;
- package_description?: string;
+ package_description?: string; // This is for display purposes only
}
export interface LineDescriptions {
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/item-configuration.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/item-configuration.svelte
index e71c6096..f399c4f5 100644
--- a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/item-configuration.svelte
+++ b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/item-configuration.svelte
@@ -36,7 +36,7 @@
}
function handlePartSelect(part: any) {
- lineItem.part_number_id = part.id;
+ lineItem.part_number = part.id;
// Store part number for display
(lineItem as any).part_number = part.part_number;
(lineItem as any).part_description_es = part.description_spanish;
@@ -110,9 +110,6 @@
{#if (lineItem as any).part_description_es}
{(lineItem as any).part_description_es}
{/if}
- {#if lineItem.part_number_id}
- ID: {lineItem.part_number_id}
- {/if}
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/item-sheet-fa.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/item-sheet-fa.svelte
index ea64e1f0..be1ec0f3 100644
--- a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/item-sheet-fa.svelte
+++ b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/item-sheet-fa.svelte
@@ -65,7 +65,7 @@
-
+
{#if line}
@@ -129,6 +129,7 @@
bind:descriptions={editingItem.lines![0].description!}
bind:customs={editingItem.lines![0].customs!}
bind:quantities={editingItem.lines![0].quantity!}
+ invoice={invoice}
/>
{(lineItem as any).class_description}
{/if}
- {#if lineItem.class_id}
- ID: {lineItem.class_id}
- {/if}
@@ -183,9 +180,6 @@
- {#if (lineItem as any).unit_description}
-
{(lineItem as any).unit_description}
- {/if}
@@ -217,9 +211,6 @@
- {#if (customs as any).fraction_description}
- {(customs as any).fraction_description}
- {/if}
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/package-dialog.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/package-dialog.svelte
new file mode 100644
index 00000000..85e20201
--- /dev/null
+++ b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/package-dialog.svelte
@@ -0,0 +1,163 @@
+
+
+
+
+
+ CATALOGO DE BULTOS
+
+
+
+
+
+ {#if loading}
+
+
+
+ {:else if error}
+
+ {:else}
+
+
+
+
+ | Clave |
+ Descripción Español |
+ Descripción Inglés |
+ Peso Unitario |
+ Código ACE |
+
+
+
+ {#each filteredPackages as pkg, i}
+ handleSelect(pkg)}
+ >
+ | {pkg.key || ''} |
+ {pkg.description_es || ''} |
+ {pkg.description_en || ''} |
+ {pkg.weight_unit || ''} |
+ {pkg.code_ace || ''} |
+
+ {/each}
+ {#if filteredPackages.length === 0}
+
+ |
+ No se encontraron resultados
+ |
+
+ {/if}
+
+
+
+ {/if}
+
+
+
+
+
+
+
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/packages-section.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/packages-section.svelte
index d3c6f785..d0ba2383 100644
--- a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/packages-section.svelte
+++ b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/packages-section.svelte
@@ -1,21 +1,95 @@
+
+
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/payment-method-dialog.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/payment-method-dialog.svelte
new file mode 100644
index 00000000..fe65a75a
--- /dev/null
+++ b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/payment-method-dialog.svelte
@@ -0,0 +1,151 @@
+
+
+
+
+
+ CATALOGO DE FORMAS DE PAGO
+
+
+
+
+
+ {#if loading}
+
+
+
+ {:else if error}
+
+ {:else}
+
+
+
+
+ | Clave |
+ Descripción |
+
+
+
+ {#each filteredMethods as method, i}
+ handleSelect(method)}
+ >
+ | {method.key || ''} |
+ {method.description || ''} |
+
+ {/each}
+ {#if filteredMethods.length === 0}
+
+ |
+ No se encontraron resultados
+ |
+
+ {/if}
+
+
+
+ {/if}
+
+
+
+
+
+
+
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tab-continuation.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tab-continuation.svelte
index 6381f4c9..e3111bde 100644
--- a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tab-continuation.svelte
+++ b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tab-continuation.svelte
@@ -3,7 +3,10 @@
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
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 PaymentMethodDialog from './payment-method-dialog.svelte';
let {
lineItem = $bindable(),
@@ -22,6 +25,47 @@
function setHasCertificate(val: string) {
lineItem.has_certificate = val === 'si';
}
+
+ let paymentMethodDialogOpen = $state(false);
+ let payment_method_description = $state('');
+
+ // Load payment method description when payment_method exists
+ $effect(() => {
+ async function loadPaymentMethodData() {
+ // Si ya tiene la descripción cargada por enrichItemData, usarla
+ if ((lineItem as any).payment_method_description) {
+ payment_method_description = (lineItem as any).payment_method_description;
+ return;
+ }
+
+ // Si tiene payment_method pero no descripción, cargarla
+ if (lineItem.payment_method && !payment_method_description) {
+ try {
+ const response = await fetch('/api-sveltekit/payment-methods', {
+ credentials: 'include'
+ });
+ if (response.ok) {
+ const data = await response.json();
+ const methods = data.items || data.data || data;
+ if (Array.isArray(methods)) {
+ const method = methods.find((m: any) => m.key === lineItem.payment_method);
+ if (method) {
+ payment_method_description = method.description;
+ }
+ }
+ }
+ } catch (error) {
+ console.error('Error loading payment method data:', error);
+ }
+ }
+ }
+ loadPaymentMethodData();
+ });
+
+ function handlePaymentMethodSelect(method: any) {
+ lineItem.payment_method = method.key;
+ payment_method_description = method.description;
+ }
@@ -49,10 +93,19 @@
-
+
@@ -158,3 +211,4 @@
+
\ No newline at end of file
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/items-tab-form.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/items-tab-form.svelte
index 4da902cc..82d25695 100644
--- a/frontend/src/lib/components/dashboard/invoices/edit/items/items-tab-form.svelte
+++ b/frontend/src/lib/components/dashboard/invoices/edit/items/items-tab-form.svelte
@@ -290,7 +290,7 @@
quantity_returned: undefined,
net_weight: undefined,
gross_weight: undefined,
- package_key: undefined,
+ package_id: undefined,
package_quantity: undefined,
package_description: undefined
},
@@ -664,6 +664,53 @@
console.error('Error loading fraction data:', error);
}
}
+
+ // Load package data (if needed)
+ const packageId = line.quantity?.package_id;
+ if (packageId && line.quantity) {
+ try {
+ const response = await fetch('/api-sveltekit/packages', {
+ method: 'GET',
+ headers: { 'Content-Type': 'application/json' }
+ });
+ if (response.ok) {
+ const data = await response.json();
+ const packages = data.items || data.data || data;
+ 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;
+ }
+ }
+ }
+ } catch (error) {
+ console.error('Error loading package data:', error);
+ }
+ }
+
+ // Load payment method description (if needed)
+ if (line.payment_method) {
+ try {
+ const response = await fetch('/api-sveltekit/payment-methods', {
+ method: 'GET',
+ headers: { 'Content-Type': 'application/json' }
+ });
+ if (response.ok) {
+ 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);
+ if (method) {
+ (line as any).payment_method_description = method.description;
+ }
+ }
+ }
+ } catch (error) {
+ console.error('Error loading payment method data:', error);
+ }
+ }
}
// Normalize numeric values from strings to numbers
diff --git a/frontend/src/lib/components/sidebar/modules.ts b/frontend/src/lib/components/sidebar/modules.ts
index ee0a8d9a..264c7683 100644
--- a/frontend/src/lib/components/sidebar/modules.ts
+++ b/frontend/src/lib/components/sidebar/modules.ts
@@ -8,6 +8,7 @@ import {
FileText,
Frame,
GalleryVerticalEnd,
+ Hash,
LayoutDashboard,
Package,
Settings2,
@@ -296,6 +297,41 @@ export function getSidebarData(): SidebarData {
},
],
},
+ {
+ title: m["sidebar.fractions.title"](),
+ url: "#",
+ icon: Hash,
+ items: [
+ {
+ title: m["sidebar.fractions.sitar"](),
+ url: "#",
+ },
+ {
+ title: m["sidebar.fractions.sitar_seventh_amendment"](),
+ url: "#",
+ },
+ {
+ title: m["sidebar.fractions.sitar_us"](),
+ url: "#",
+ },
+ {
+ title: m["sidebar.fractions.american"](),
+ url: "#",
+ },
+ {
+ title: m["sidebar.fractions.canadian"](),
+ url: "#",
+ },
+ {
+ title: m["sidebar.fractions.historical"](),
+ url: "#",
+ },
+ {
+ title: m["sidebar.fractions.sectors"](),
+ url: "#",
+ },
+ ],
+ },
{
title: m["sidebar.goods.title"](),
url: "#",
diff --git a/frontend/src/routes/api-sveltekit/packages/+server.ts b/frontend/src/routes/api-sveltekit/packages/+server.ts
new file mode 100644
index 00000000..a78d61e0
--- /dev/null
+++ b/frontend/src/routes/api-sveltekit/packages/+server.ts
@@ -0,0 +1,80 @@
+import type { RequestHandler } from './$types';
+
+export const GET: RequestHandler = async ({ cookies, url }) => {
+ const token = cookies.get('access_token');
+
+ // Obtener company_id de la cookie
+ const companyId = cookies.get('active_company_id');
+
+ if (!companyId) {
+ return new Response(
+ JSON.stringify({ error: 'No company selected' }),
+ {
+ status: 400,
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ }
+ );
+ }
+
+ // 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 and add company_id
+ const searchParams = new URLSearchParams(url.search);
+ searchParams.set('company_id', companyId);
+ const queryString = searchParams.toString();
+
+ try {
+ const fetchUrl = `${baseUrl}v1/a76/packages?${queryString}`;
+
+ const response = await fetch(
+ fetchUrl,
+ {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${token}`
+ }
+ }
+ );
+
+ const data = await response.json();
+
+ if (!response.ok) {
+ return new Response(JSON.stringify(data), {
+ status: response.status,
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ });
+ }
+
+ return new Response(JSON.stringify(data), {
+ status: 200,
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ });
+ } catch (error) {
+ console.error('Error fetching packages:', error);
+ return new Response(
+ JSON.stringify({ error: 'Failed to fetch packages' }),
+ {
+ status: 500,
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ }
+ );
+ }
+};
diff --git a/frontend/src/routes/api-sveltekit/payment-methods/+server.ts b/frontend/src/routes/api-sveltekit/payment-methods/+server.ts
new file mode 100644
index 00000000..688259a0
--- /dev/null
+++ b/frontend/src/routes/api-sveltekit/payment-methods/+server.ts
@@ -0,0 +1,64 @@
+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
+ const searchParams = new URLSearchParams(url.search);
+ const queryString = searchParams.toString();
+
+ try {
+ const fetchUrl = `${baseUrl}v1/public/reference_data/payment-methods${queryString ? `?${queryString}` : ''}`;
+
+ const response = await fetch(
+ fetchUrl,
+ {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${token}`
+ }
+ }
+ );
+
+ const data = await response.json();
+
+ if (!response.ok) {
+ return new Response(JSON.stringify(data), {
+ status: response.status,
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ });
+ }
+
+ return new Response(JSON.stringify(data), {
+ status: 200,
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ });
+ } catch (error) {
+ console.error('Error fetching payment methods:', error);
+ return new Response(
+ JSON.stringify({ error: 'Failed to fetch payment methods' }),
+ {
+ status: 500,
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ }
+ );
+ }
+};