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; + } + +
@@ -72,11 +87,32 @@
- +
- + (showPartDialog = true)} + /> +
-

ID de número de parte existente en catálogo

+ {#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 a091ac75..5bf87a1c 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 @@ -23,6 +23,7 @@ editingItem = $bindable(), invoice, onSave, + onCancel, isSaving = false }: { open: boolean; @@ -30,6 +31,7 @@ editingItem: Partial; invoice: Invoice | null; onSave: () => void; + onCancel?: () => void; isSaving?: boolean; } = $props(); @@ -55,7 +57,7 @@

-
@@ -164,7 +166,7 @@