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 d14a4113..b14a9746 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
@@ -115,3 +115,21 @@ def validate_create(
solution="Proporciona una descripción del item en español",
code="REQUIRED",
)
+
+ # 8. Validar customs.origin_country
+ if not line.customs or not line.customs.origin_country:
+ errors.add_error(
+ field="customs.origin_country",
+ message="País de Origen es obligatorio",
+ solution="Selecciona el país de origen del item",
+ code="REQUIRED"
+ )
+
+ # 9. Validar customs.fraction_type
+ if not line.customs or not line.customs.fraction_type:
+ errors.add_error(
+ field="customs.fraction_type",
+ message="Tipo de Tarifa es obligatorio",
+ solution="Selecciona el tipo de tarifa (GENERAL, PROSEC, ALADI, TLCS)",
+ code="REQUIRED"
+ )
\ No newline at end of file
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 f5d183b9..78b044aa 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
@@ -130,15 +130,26 @@ def validate_update(
# 8. Validar datos aduanales si se proporcionan
if line.customs:
- # Validar país de origen
- if line.customs.origin_country is not None and not line.customs.origin_country:
- errors.add_error(
- field="customs.origin_country",
- message="Origin Country no puede estar vacío",
- solution="Selecciona el país de origen del item",
- code="REQUIRED",
- )
-
+ # Validar país de origen (OBLIGATORIO)
+ if line.customs.origin_country is not None:
+ if not line.customs.origin_country:
+ errors.add_error(
+ field="customs.origin_country",
+ message="País de Origen es obligatorio",
+ solution="Selecciona el país de origen del item",
+ code="REQUIRED"
+ )
+
+ # Validar tipo de tarifa (OBLIGATORIO)
+ if line.customs.fraction_type is not None:
+ if not line.customs.fraction_type:
+ errors.add_error(
+ field="customs.fraction_type",
+ message="Tipo de Tarifa es obligatorio",
+ solution="Selecciona el tipo de tarifa (GENERAL, PROSEC, ALADI, TLCS)",
+ code="REQUIRED"
+ )
+
# Validar preferencia arancelaria
if line.customs.preference is not None and not line.customs.preference:
errors.add_error(
@@ -181,20 +192,18 @@ def validate_update(
solution="Selecciona una forma de pago válida del catálogo",
code="INVALID_VALUE",
)
-
- # 9. Validar descripción en español si se proporciona
- if line.description and hasattr(line.description, "description_spanish"):
- if (
- line.description.description_spanish is not None
- and not line.description.description_spanish
- ):
- errors.add_error(
- field="description.description_spanish",
- message="La descripción en español no puede estar vacía",
- solution="Proporciona una descripción del item en español",
- code="REQUIRED",
- )
-
+
+ # 9. Validar descripción en español (OBLIGATORIA)
+ if line.description and hasattr(line.description, 'description_spanish'):
+ if line.description.description_spanish is not None:
+ if not line.description.description_spanish.strip():
+ errors.add_error(
+ field="description.description_spanish",
+ message="Descripción en Español es obligatoria",
+ solution="Proporciona una descripción del item en español",
+ code="REQUIRED"
+ )
+
# 10. Validar subpartidas si se actualizan
if line.fa_data and line.fa_data.is_subitem:
# Es subpartida, debe tener partida principal
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 6ac7bee0..a7603d11 100644
--- a/backend/api/v1/modules/a76/items/line_items/models.py
+++ b/backend/api/v1/modules/a76/items/line_items/models.py
@@ -40,11 +40,11 @@ class LineItem(Base, TenantScopedMixin, TimestampMixin):
line_number: Mapped[int] = mapped_column(Integer) # LINEAIMPO/LINEAEXPO/LINEA
# Part identification
- part_number: Mapped[Optional[str]] = mapped_column(
- ForeignKey("a76.parts.id")
+ part_number: Mapped[Optional[int]] = mapped_column(
+ Integer, ForeignKey("a76.parts.id")
) # NUMPARTE
- component_part_number: Mapped[Optional[str]] = mapped_column(
- ForeignKey("a76.parts.id")
+ component_part_number: Mapped[Optional[int]] = mapped_column(
+ Integer, ForeignKey("a76.parts.id")
) # NUMPARTECOM
class_id: Mapped[Optional[int]] = mapped_column(
ForeignKey("a76.classes.id")
diff --git a/backend/api/v1/modules/a76/items/line_items/schemas.py b/backend/api/v1/modules/a76/items/line_items/schemas.py
index bd3520b0..5c23910a 100644
--- a/backend/api/v1/modules/a76/items/line_items/schemas.py
+++ b/backend/api/v1/modules/a76/items/line_items/schemas.py
@@ -44,12 +44,16 @@ from api.v1.modules.a24.fa.fa_item_lines.dto import (
class LineItemBase(BaseModel):
"""Base schema for line items"""
+ model_config = ConfigDict(populate_by_name=True)
+
line_number: int = Field(..., description="Line number")
# Part identification
- part_number: Optional[int] = Field(None, description="Part number")
- component_part_number: Optional[int] = Field(
- None, description="Component part number"
+ part_number_id: Optional[int] = Field(
+ None, description="Part number", alias="part_number", serialization_alias="part_number_id"
+ )
+ component_part_number_id: Optional[int] = Field(
+ None, description="Component part number", alias="component_part_number", serialization_alias="component_part_number_id"
)
class_id: Optional[int] = Field(None, description="Class code")
@@ -253,6 +257,12 @@ class LineItemResponse(LineItemBase):
if hasattr(data, key):
result[key] = getattr(data, key)
+ # Map model field names to schema field names for aliased fields
+ if hasattr(data, "part_number"):
+ result["part_number_id"] = data.part_number
+ if hasattr(data, "component_part_number"):
+ result["component_part_number_id"] = data.component_part_number
+
# Extract class info
if hasattr(data, "class_info") and data.class_info is not None:
result["class_code"] = data.class_info.class_code
diff --git a/backend/api/v1/modules/a76/items/service.py b/backend/api/v1/modules/a76/items/service.py
index 7d8959f7..912a7b89 100644
--- a/backend/api/v1/modules/a76/items/service.py
+++ b/backend/api/v1/modules/a76/items/service.py
@@ -276,6 +276,12 @@ class ItemService:
line_dict["tenant_id"] = tenant_id
line_dict["company_id"] = company_id
+ # Map schema field names to model field names
+ if "part_number_id" in line_dict:
+ line_dict["part_number"] = line_dict.pop("part_number_id")
+ if "component_part_number_id" in line_dict:
+ line_dict["component_part_number"] = line_dict.pop("component_part_number_id")
+
# Create line item
db_line = LineItem(**line_dict)
db.add(db_line)
@@ -504,6 +510,12 @@ class ItemService:
line_dict["tenant_id"] = tenant_id
line_dict["company_id"] = company_id
+ # Map schema field names to model field names
+ if "part_number_id" in line_dict:
+ line_dict["part_number"] = line_dict.pop("part_number_id")
+ if "component_part_number_id" in line_dict:
+ line_dict["component_part_number"] = line_dict.pop("component_part_number_id")
+
db_line = LineItem(**line_dict)
db.add(db_line)
db.flush()
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 26da88e3..e71c6096 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
@@ -2,6 +2,9 @@
import * as RadioGroup from '$lib/components/ui/radio-group';
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
+ import { Button } from '$lib/components/ui/button';
+ import { Folder } from 'lucide-svelte';
+ import PartNumberDialog from './part-number-dialog.svelte';
import type { LineItem, LineDescriptions } from '$lib/api/dashboard/a76/items';
let {
@@ -12,6 +15,8 @@
descriptions: LineDescriptions;
} = $props();
+ let showPartDialog = $state(false);
+
// Initialize fa_data for fixed asset system
if (!lineItem.fa_data) {
lineItem.fa_data = {};
@@ -29,8 +34,18 @@
if (!lineItem.fa_data) lineItem.fa_data = {};
lineItem.fa_data.contains_subitems = val === 'si';
}
+
+ function handlePartSelect(part: any) {
+ lineItem.part_number_id = part.id;
+ // Store part number for display
+ (lineItem as any).part_number = part.part_number;
+ (lineItem as any).part_description_es = part.description_spanish;
+ (lineItem as any).part_description_en = part.description_english;
+ }
+