feat: Enhance invoice item management with detailed line item structure

- Introduced nested interfaces for line items including customs, financials, quantities, descriptions, and references.
- Updated Item interface to include lines as an array of LineItem.
- Modified invoice top fields to handle pedimento ID and auto-assign values from the invoice.
- Enhanced item configuration component to manage line item properties and descriptions.
- Updated main data, packages section, summary section, and other components to bind new line item properties.
- Implemented normalization of numeric values when editing items to ensure consistent data types.
- Adjusted save invoice logic to accommodate new line item structure and compliance data.
This commit is contained in:
AlexeerCT
2026-01-08 16:24:34 -06:00
parent 09bee94ae6
commit c040679fc5
17 changed files with 466 additions and 75 deletions

View File

@@ -136,9 +136,9 @@ class InvoiceComplianceMx(Base, TenantScopedMixin, TimestampMixin):
invoice_id: Mapped[int] = mapped_column(ForeignKey("a76.invoice_header.id"), primary_key=True)
# Core Customs Data
pedimento_id: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.pedimentos.id")) # PEDIMENTO/PEDIMENTOIMPO/EXPO
pedimento_r1: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.pedimentos.id")) # PEDIMENTOR1
pedimento_k1: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.pedimentos.id")) # PEDIMENTOK1
pedimento_id: Mapped[Optional[int]] = mapped_column(ForeignKey("a76.pedimentos.id")) # PEDIMENTO/PEDIMENTOIMPO/EXPO
pedimento_r1: Mapped[Optional[int]] = mapped_column(ForeignKey("a76.pedimentos.id")) # PEDIMENTOR1
pedimento_k1: Mapped[Optional[int]] = mapped_column(ForeignKey("a76.pedimentos.id")) # PEDIMENTOK1
remesa: Mapped[Optional[int]] = mapped_column(Integer) # REMESA
aduana: Mapped[Optional[str]] = mapped_column(ForeignKey("public.customs_sections.customs_code")) # ADUANA_CRUCE
port_of_entry: Mapped[Optional[str]] = mapped_column(String(6)) # PUERTOENTRADA / Puerto de entrada

View File

@@ -1,6 +1,6 @@
from decimal import Decimal
from typing import Optional
from pydantic import BaseModel, Field, ConfigDict
from pydantic import BaseModel, Field, ConfigDict, field_validator
# Import nested schemas
from ..line_customs.schemas import (
@@ -42,6 +42,14 @@ class LineItemBase(BaseModel):
component_part_number: Optional[str] = Field(None, max_length=50, description="Component part number")
class_code: Optional[str] = Field(None, max_length=20, description="Class code")
@field_validator('class_code', 'part_number', 'component_part_number', 'unit_of_measure', 'alternate_unit', mode='before')
@classmethod
def convert_to_string(cls, v):
"""Convert integers to strings for FK fields"""
if v is not None and not isinstance(v, str):
return str(v)
return v
# Unit of measure
unit_of_measure: Optional[str] = Field(None, max_length=10, description="Unit of measure")
alternate_unit: Optional[str] = Field(None, max_length=10, description="Alternate unit")

View File

@@ -151,6 +151,12 @@ class ItemService:
lines_data = item_data.lines or []
item_dict = item_data.model_dump(exclude={"lines"})
# DEBUG: Log incoming data
print(f"\n🔍 DEBUG CREATE ITEM:")
print(f" Item data: {item_dict}")
print(f" Lines count: {len(lines_data)}")
print(f" Tenant ID: {tenant_id}, Company ID: {company_id}")
# Add tenant and company
item_dict["tenant_id"] = tenant_id
item_dict["company_id"] = company_id
@@ -160,8 +166,11 @@ class ItemService:
db.add(db_item)
db.flush() # Get the item ID
print(f" ✅ Item created with ID: {db_item.id}")
# Create line items if provided
for line_data in lines_data:
for idx, line_data in enumerate(lines_data):
print(f"\n 📝 Processing line {idx + 1}/{len(lines_data)}")
# Extract nested data from line
financial_data = line_data.financial
quantity_data = line_data.quantity
@@ -169,16 +178,26 @@ class ItemService:
description_data = line_data.description
reference_data = line_data.reference
print(f" Line data: {line_data.model_dump()}")
print(f" Has financial: {financial_data is not None}")
print(f" Has quantity: {quantity_data is not None}")
print(f" Has customs: {customs_data is not None}")
print(f" Has description: {description_data is not None}")
print(f" Has reference: {reference_data is not None}")
line_dict = line_data.model_dump(
exclude={"financial", "quantity",
"customs", "description", "reference"}
)
line_dict["item_id"] = db_item.id
line_dict["tenant_id"] = tenant_id
line_dict["company_id"] = company_id
# Create line item
db_line = LineItem(**line_dict)
db.add(db_line)
db.flush() # Get the line ID
print(f" ✅ Line created with ID: {db_line.id}")
# Create financial data if provided
if financial_data:
@@ -186,6 +205,7 @@ class ItemService:
financial_dict["item_line_id"] = db_line.id
db_financial = LineFinancial(**financial_dict)
db.add(db_financial)
print(f" ✅ Financial data added")
# Create quantity data if provided
if quantity_data:
@@ -193,6 +213,7 @@ class ItemService:
quantity_dict["item_line_id"] = db_line.id
db_quantity = LineQuantity(**quantity_dict)
db.add(db_quantity)
print(f" ✅ Quantity data added")
# Create customs data if provided
if customs_data:
@@ -200,6 +221,7 @@ class ItemService:
customs_dict["item_line_id"] = db_line.id
db_customs = LineCustom(**customs_dict)
db.add(db_customs)
print(f" ✅ Customs data added")
# Create description data if provided
if description_data:
@@ -207,6 +229,7 @@ class ItemService:
description_dict["item_line_id"] = db_line.id
db_description = LineDescription(**description_dict)
db.add(db_description)
print(f" ✅ Description data added")
# Create reference data if provided
if reference_data:
@@ -214,9 +237,12 @@ class ItemService:
reference_dict["item_line_id"] = db_line.id
db_reference = LineReference(**reference_dict)
db.add(db_reference)
print(f" ✅ Reference data added")
print(f"\n 💾 Committing transaction...")
db.commit()
db.refresh(db_item)
print(f" ✅ Transaction committed successfully!")
return db_item
except IntegrityError as e:
@@ -277,6 +303,9 @@ class ItemService:
exclude_unset=True
)
line_dict["item_id"] = db_item.id
line_dict["tenant_id"] = tenant_id
line_dict["company_id"] = company_id
db_line = LineItem(**line_dict)
db.add(db_line)
db.flush()