diff --git a/backend/api/v1/modules/a76/items/line_items/models.py b/backend/api/v1/modules/a76/items/line_items/models.py
index 8d9fefad..5669287c 100644
--- a/backend/api/v1/modules/a76/items/line_items/models.py
+++ b/backend/api/v1/modules/a76/items/line_items/models.py
@@ -1,6 +1,6 @@
from decimal import Decimal
from typing import Optional, TYPE_CHECKING
-from sqlalchemy import Boolean, String, Integer, Numeric, SmallInteger, ForeignKey
+from sqlalchemy import Boolean, String, Integer, Numeric, SmallInteger, ForeignKey, ForeignKeyConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship
from api.v1.common.base_models import TenantScopedMixin, TimestampMixin
from core.database import Base
@@ -25,9 +25,14 @@ class LineItem(Base, TenantScopedMixin, TimestampMixin):
"""
__tablename__ = "item_lines"
- __table_args__ = {
- "schema": "a76",
- }
+ __table_args__ = (
+ ForeignKeyConstraint(
+ ["class_code", "tenant_id", "company_id"],
+ ["a76.classes.class_code", "a76.classes.tenant_id", "a76.classes.company_id"],
+ name="fk_item_lines_class"
+ ),
+ {"schema": "a76"},
+ )
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
item_id: Mapped[int] = mapped_column(ForeignKey("a76.items.id"))
@@ -40,9 +45,7 @@ class LineItem(Base, TenantScopedMixin, TimestampMixin):
component_part_number: Mapped[Optional[str]] = mapped_column(
ForeignKey("a76.parts.id")
) # NUMPARTECOM
- class_code: Mapped[Optional[str]] = mapped_column(
- ForeignKey("a76.classes.class_code")
- ) # CLASE
+ class_code: Mapped[Optional[str]] = mapped_column(String(8)) # CLASE
# Unit of measure
unit_of_measure: Mapped[Optional[str]] = mapped_column(
diff --git a/frontend/src/lib/components/dashboard/goods/modales/client-selector-dialog.svelte b/frontend/src/lib/components/dashboard/goods/modales/client-selector-dialog.svelte
index fadd2e9a..ed216be2 100644
--- a/frontend/src/lib/components/dashboard/goods/modales/client-selector-dialog.svelte
+++ b/frontend/src/lib/components/dashboard/goods/modales/client-selector-dialog.svelte
@@ -109,12 +109,14 @@
RFC
Razón Social
Estado
- Acción
{#each filteredClients as client}
-
+ handleSelect(client)}
+ >
{client.id}
{client.rfc}
@@ -138,17 +140,6 @@
{/if}
-
- handleSelect(client)}
- >
- Usar
-
-
{/each}
diff --git a/frontend/src/lib/components/dashboard/goods/modales/country-selector-dialog.svelte b/frontend/src/lib/components/dashboard/goods/modales/country-selector-dialog.svelte
new file mode 100644
index 00000000..0eb36ab2
--- /dev/null
+++ b/frontend/src/lib/components/dashboard/goods/modales/country-selector-dialog.svelte
@@ -0,0 +1,145 @@
+
+
+
+
+
+ Seleccionar País
+
+ Seleccione el país de origen del catálogo.
+
+
+
+
+
+
+
+
+
+ {#if loading}
+
+
+
Cargando catálogo...
+
+ {:else if filteredItems.length === 0}
+
+
No se encontraron países.
+
+ {:else}
+
+
+
+ M3
+ MEX
+ AME
+ Descripción ES
+ Description EN
+
+
+
+ {#each filteredItems as item}
+ handleSelect(item)}
+ >
+
+
+
+
+ {item.m3_key}
+
+
+
+
+ {item.mex_key}
+
+
+ {item.ame_key}
+
+
+ {item.description_es}
+
+
+ {item.description_en}
+
+
+ {/each}
+
+
+ {/if}
+
+
+
+
+ {filteredItems.length} registros encontrados
+
+ open = false}>Cancelar
+
+
+
diff --git a/frontend/src/lib/components/dashboard/goods/modales/currency-selector-dialog.svelte b/frontend/src/lib/components/dashboard/goods/modales/currency-selector-dialog.svelte
new file mode 100644
index 00000000..2325fe34
--- /dev/null
+++ b/frontend/src/lib/components/dashboard/goods/modales/currency-selector-dialog.svelte
@@ -0,0 +1,138 @@
+
+
+
+
+
+ Seleccionar Moneda
+
+ Seleccione el tipo de moneda del catálogo.
+
+
+
+
+
+
+
+
+
+ {#if loading}
+
+
+
Cargando catálogo...
+
+ {:else if filteredItems.length === 0}
+
+
No se encontraron monedas.
+
+ {:else}
+
+
+
+ Código
+ País
+ Factor Conversión
+
+
+
+ {#each filteredItems as item}
+ handleSelect(item)}
+ >
+
+
+
+
+ {item.currency_type_code}
+
+
+
+
+ {item.country_key || '-'}
+
+
+ {item.conversion_factor?.toFixed(4) || '-'}
+
+
+ {/each}
+
+
+ {/if}
+
+
+
+
+ {filteredItems.length} registros encontrados
+
+ open = false}>Cancelar
+
+
+
diff --git a/frontend/src/lib/components/dashboard/goods/modales/fraction-selector-dialog.svelte b/frontend/src/lib/components/dashboard/goods/modales/fraction-selector-dialog.svelte
new file mode 100644
index 00000000..56db8e62
--- /dev/null
+++ b/frontend/src/lib/components/dashboard/goods/modales/fraction-selector-dialog.svelte
@@ -0,0 +1,155 @@
+
+
+
+
+
+ Seleccionar Fracción Arancelaria
+
+ Seleccione la fracción arancelaria del catálogo de clases.
+
+
+
+
+
+
+
+
+
+ {#if loading}
+
+
+
Cargando catálogo...
+
+ {:else if uniqueFractions.length === 0}
+
+
No se encontraron fracciones.
+
+ {:else}
+
+
+
+ Fracción
+ Descripción
+ Clase
+
+
+
+ {#each uniqueFractions as item}
+ handleSelect(item)}
+ >
+
+
+
+
+ {item.fraction}
+
+
+
+
+ {item.description || '-'}
+
+
+
+ {item.class_code}
+
+
+
+ {/each}
+
+
+ {/if}
+
+
+
+
+ {uniqueFractions.length} fracciones únicas encontradas
+
+ open = false}>Cancelar
+
+
+
diff --git a/frontend/src/lib/components/dashboard/goods/modales/unit-measure-dialog.svelte b/frontend/src/lib/components/dashboard/goods/modales/unit-measure-dialog.svelte
index 0deb485c..2520f8b6 100644
--- a/frontend/src/lib/components/dashboard/goods/modales/unit-measure-dialog.svelte
+++ b/frontend/src/lib/components/dashboard/goods/modales/unit-measure-dialog.svelte
@@ -116,7 +116,6 @@
Código
Descripción
-
@@ -129,7 +128,7 @@
{:else}
{#each items as item}
handleSelect(item)}
>
{item.code}
@@ -141,11 +140,6 @@
{/if}
-
-
-
-
-
{/each}
{/if}
diff --git a/frontend/src/lib/components/dashboard/goods/parts/class-selector-dialog.svelte b/frontend/src/lib/components/dashboard/goods/parts/class-selector-dialog.svelte
index 5d03286a..04a8ce34 100644
--- a/frontend/src/lib/components/dashboard/goods/parts/class-selector-dialog.svelte
+++ b/frontend/src/lib/components/dashboard/goods/parts/class-selector-dialog.svelte
@@ -110,12 +110,14 @@
Clave
Descripción
UM
- Acción
{#each filteredItems as item}
-
+ handleSelect(item)}
+ >
{item.class_code}
@@ -137,18 +139,6 @@
{item.unit_of_measure || '-'}
-
-
- handleSelect(item)}
- >
- Usar
-
-
{/each}
diff --git a/frontend/src/lib/components/dashboard/goods/parts/partForm.svelte b/frontend/src/lib/components/dashboard/goods/parts/partForm.svelte
index db5d7d67..185623d6 100644
--- a/frontend/src/lib/components/dashboard/goods/parts/partForm.svelte
+++ b/frontend/src/lib/components/dashboard/goods/parts/partForm.svelte
@@ -9,7 +9,9 @@
import * as Tabs from '$lib/components/ui/tabs';
import * as Card from '$lib/components/ui/card';
import * as Select from "$lib/components/ui/select";
- import { Switch } from "$lib/components/ui/switch";
+ import { Switch } from "$lib/components/ui/switch";
+ import { Separator } from '$lib/components/ui/separator';
+ import { Badge } from '$lib/components/ui/badge';
// Iconos
import {
@@ -30,6 +32,9 @@
import ClassSelectorDialog from '$lib/components/dashboard/goods/parts/class-selector-dialog.svelte';
import MaterialTypeSelectorDialog from '$lib/components/dashboard/goods/modales/material-type-selector-dialog.svelte';
import UnitMeasureSelectorDialog from '$lib/components/dashboard/goods/modales/unit-measure-dialog.svelte';
+ import CurrencySelectorDialog from '$lib/components/dashboard/goods/modales/currency-selector-dialog.svelte';
+ import CountrySelectorDialog from '$lib/components/dashboard/goods/modales/country-selector-dialog.svelte';
+ import FractionSelectorDialog from '$lib/components/dashboard/goods/modales/fraction-selector-dialog.svelte';
// --- PROPS ---
let { partId = null, formType = 'inv' }: { partId?: number | null, formType?: 'inv' | 'fa' } = $props();
@@ -40,6 +45,7 @@
let loading = $state(false);
let error = $state(null);
+ let activeTab = $state('general');
// Estado Modales
let showClientModal = $state(false);
@@ -47,11 +53,16 @@
let showMaterialModal = $state(false);
let showUOMModal = $state(false);
let showAltUOMModal = $state(false);
+ let showCurrencyModal = $state(false);
+ let showCountryModal = $state(false);
+ let showFractionModal = $state(false);
// Descripciones Visuales
let selectedClientName = $state("");
let selectedClientStatus = $state(true);
let selectedClassDesc = $state("");
+ let selectedCurrencyName = $state("");
+ let selectedCountryName = $state("");
let selectedMaterialDesc = $state("");
// Estado Formulario
@@ -60,13 +71,14 @@
part_number: '',
description_spanish: '',
description_english: '',
- part_class: '',
+ part_class: '',
material_type: '',
unit_of_measure: 'PZ',
unit_weight: 0,
weight_type: 'KG',
unit_cost: 0,
- currency_key: 'USD',
+ currency_type: '',
+ currency_key: null,
added_value: 0,
value_added_type: 'USD',
us_fraction: '',
@@ -180,6 +192,13 @@
function handleMaterialSelect(item: any) { formData.material_type = item.key; selectedMaterialDesc = item.description; }
function handleUOMSelect(item: any) { formData.unit_of_measure = item.code; }
function handleAltUOMSelect(item: any) { formData.alternate_unit_measure = item.code; }
+ function handleCurrencySelect(currency: any) {
+ formData.currency_type = ''; // Catálogo no usa currency_type
+ formData.currency_key = currency.currency_type_code;
+ selectedCurrencyName = currency.currency_type_code;
+ }
+ function handleCountrySelect(country: any) { formData.origin_country = country.m3_key; selectedCountryName = country.description_es; }
+ function handleFractionSelect(item: any) { formData.fraction = item.fraction; }
// --- SUBMIT ---
async function handleSubmit() {
@@ -192,51 +211,83 @@
loading = true;
try {
// Construimos el payload.
- // Si es FA, inyectamos el objeto fa_data anidado.
let commonData: any = { ...formData };
+ // Si currency_key está vacío o es el default, ponerlo en null
+ if (!commonData.currency_key || commonData.currency_key === 'USD') {
+ commonData.currency_key = null;
+ }
+
if (formType === 'fa') {
+ // Crear fa_data con los campos específicos de FA
commonData.fa_data = {
sector: formData.sector,
fraction_type: formData.fraction_type,
origin_country: formData.origin_country
};
+ // IMPORTANTE: Eliminar estos campos del nivel raíz para evitar duplicados
+ delete commonData.sector;
+ delete commonData.fraction_type;
+ delete commonData.origin_country;
}
+ // DEBUG: Ver qué se está enviando
+ console.log('Payload que se enviará:', JSON.stringify(commonData, null, 2));
+ console.log('client_id:', formData.client_id);
+ console.log('selectedClientName:', selectedClientName);
+
if (isEdit && partId) {
await partsApi.update(partId, commonData, activeCompanyId);
} else {
- await partsApi.create({ ...commonData, company_id: activeCompanyId }, activeCompanyId);
+ const result = await partsApi.create({ ...commonData, company_id: activeCompanyId }, activeCompanyId);
+ console.log('Resultado de creación:', result);
+ if (result.error) {
+ console.error('Error del backend:', result.error);
+ error = result.error;
+ return;
+ }
}
goto('/dashboard/goods/parts');
- } catch (e: any) { error = e.message || 'Error al guardar'; } finally { loading = false; }
+ } catch (e: any) {
+ console.error('Error completo:', e);
+ error = e.message || 'Error al guardar';
+ } finally { loading = false; }
}
-
-
-
-
-
-
-
{title}
+
+
+
+
+
+
+
+
{title}
+
+ {isEdit ? "Editar" : "Nueva"}
+
+
- {formType === 'fa' ? 'Gestión de Activos Fijos (Q-Partes)' : 'Gestión detallada de números de parte (S-Partes).'}
+ {formType === 'fa' ? 'Activo Fijo' : 'Inventario'}
+
+
{#if error}
⚠️ {error}
{/if}
+
+
{#if formType === 'fa'}
@@ -303,15 +356,23 @@
-
Moneda
-
- {formData.currency_key}
-
- Extranjera (USD)
- Nacional (MXN)
- Euros (EUR)
-
-
+
Moneda
+
Peso Unitario
@@ -334,7 +395,10 @@
Sector
@@ -408,32 +472,16 @@
-
- Generales
- Continuación 1
- Continuación 2
-
-
-
-
-
- Cancelar
-
- {#if loading} {:else} {/if}
- {isEdit ? 'Actualizar Activo' : 'Guardar Activo'}
-
-
-
{:else}