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} - - - {/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 +
+ +
+
+
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 +
+ +
+
+
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 +
+ +
+
+
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 || '-'} - - - - {/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'}
{ e.preventDefault(); handleSubmit(); }} class="space-y-6"> - +
@@ -250,10 +301,9 @@
- showClientModal = true} class="cursor-pointer font-mono" placeholder="Seleccione..."/> + showClientModal = true} class="cursor-pointer" placeholder="Seleccione..."/>
- {#if selectedClientName}
{selectedClientName}
{/if}
@@ -288,7 +338,10 @@
- +
+ showCountryModal = true} class="cursor-pointer" placeholder="Seleccione..."/> + +
@@ -303,15 +356,23 @@
- - - {formData.currency_key} - - Extranjera (USD) - Nacional (MXN) - Euros (EUR) - - + +
+
+ + +
+ +
@@ -334,7 +395,10 @@
- +
+ showFractionModal = true} class="cursor-pointer" placeholder="Seleccione..."/> + +
@@ -408,32 +472,16 @@
- - Generales - Continuación 1 - Continuación 2 - - - -
-
- - -
-
{:else}
{ e.preventDefault(); handleSubmit(); }} class="space-y-6"> - +
@@ -492,7 +540,10 @@
- +
+ showCountryModal = true} class="cursor-pointer" placeholder="Seleccione..."/> + +
@@ -513,15 +564,23 @@

Costos, valores y peso unitario

- - - {formData.currency_key} - - Dólares (USD) - Pesos (MXN) - Euros (EUR) - - + +
+
+ + +
+ +
@@ -617,14 +676,13 @@
- showClientModal = true}/> + showClientModal = true}/>
{#if selectedClientName} -
- {selectedClientName} - {#if selectedClientStatus} Activo{:else} Inactivo{/if} +
+ {#if selectedClientStatus} Activo{:else} Inactivo{/if}
{/if}
@@ -647,7 +705,10 @@
- +
+ showFractionModal = true} class="cursor-pointer" placeholder="Seleccione..."/> + +
@@ -674,27 +735,71 @@
- - - - - - - -
-
- - -
-
{/if} +
+
+ +
+
+ +
+ {#if formType === 'fa'} + + + + General + + + + Continuación 1 + + + + Continuación 2 + + + {:else} + + + + General + + + + Opciones + + + + Opcionales 2 + + + + Otros + + + {/if} +
+
+ +
+ + +
+
@@ -702,6 +807,9 @@ + + +