feat: Implement Fixed Asset (FA) line item management
- Added new schemas for FA line items in the backend, including creation, update, and response DTOs. - Updated existing line item schemas to include FA data. - Modified database models to reflect new table names for line quantities and references. - Enhanced ItemService to handle FA data during item creation and updates. - Introduced new routes and service layer for FA line items, including CRUD operations. - Updated frontend components to support FA line item data, including new fields and UI adjustments. - Implemented data flattening for improved item display in the dashboard.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.orm import Session
|
||||
from .... import schemas
|
||||
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
|
||||
@@ -206,7 +207,7 @@ def validate_common(
|
||||
exchange_rate_exists = (
|
||||
db.query(ExchangeRate)
|
||||
.filter(
|
||||
ExchangeRate.date == invoice.invoice_date,
|
||||
func.date(ExchangeRate.date) == invoice.invoice_date,
|
||||
ExchangeRate.tenant_id == tenant_id,
|
||||
ExchangeRate.company_id == company_id,
|
||||
)
|
||||
@@ -220,6 +221,8 @@ def validate_common(
|
||||
code="EXCHANGE_RATE_NOT_FOUND",
|
||||
value=invoice.financials.exchange_rate,
|
||||
)
|
||||
else:
|
||||
invoice.financials.exchange_rate = exchange_rate_exists.value
|
||||
|
||||
if invoice.compliance_mx.is_regime_change:
|
||||
if invoice.document_type in ["EXD", "ETE", "ETR"]:
|
||||
|
||||
@@ -12,7 +12,7 @@ class LineCustom(Base):
|
||||
Customs details for line items
|
||||
Consolidates all line-level data from Q and S tables
|
||||
"""
|
||||
__tablename__ = "line_customs"
|
||||
__tablename__ = "item_line_customs"
|
||||
__table_args__ = {
|
||||
"schema": "a76",
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ class LineDescription(Base):
|
||||
Description details for line items
|
||||
Consolidates all line-level data from Q and S tables
|
||||
"""
|
||||
__tablename__ = "line_descriptions"
|
||||
__tablename__ = "item_line_descriptions"
|
||||
__table_args__ = {
|
||||
"schema": "a76",
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ class LineFinancial(Base):
|
||||
Financial details for line items
|
||||
Consolidates all line-level data from Q and S tables
|
||||
"""
|
||||
__tablename__ = "line_financials"
|
||||
__tablename__ = "item_line_financials"
|
||||
__table_args__ = {
|
||||
"schema": "a76",
|
||||
}
|
||||
|
||||
@@ -12,6 +12,11 @@ if TYPE_CHECKING:
|
||||
from ..line_customs.models import LineCustom
|
||||
from ..line_descriptions.models import LineDescription
|
||||
from ..line_references.models import LineReference
|
||||
from api.v1.modules.a76.classes.models import Class
|
||||
from api.v1.modules.a76.general_catalogs.units_of_measure.models import (
|
||||
UnitOfMeasure,
|
||||
)
|
||||
from api.v1.modules.a24.fa.fa_item_lines.models import FaLineItem
|
||||
|
||||
|
||||
class LineItem(Base, TenantScopedMixin, TimestampMixin):
|
||||
@@ -19,6 +24,7 @@ class LineItem(Base, TenantScopedMixin, TimestampMixin):
|
||||
Unified line items for all items
|
||||
Consolidates all line-level data from Q and S tables
|
||||
"""
|
||||
|
||||
__tablename__ = "item_lines"
|
||||
__table_args__ = {
|
||||
"schema": "a76",
|
||||
@@ -26,154 +32,152 @@ class LineItem(Base, TenantScopedMixin, TimestampMixin):
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||
item_id: Mapped[int] = mapped_column(ForeignKey("a76.items.id"))
|
||||
line_number: Mapped[int] = mapped_column(
|
||||
Integer) # LINEAIMPO/LINEAEXPO/LINEA
|
||||
line_number: Mapped[int] = mapped_column(Integer) # LINEAIMPO/LINEAEXPO/LINEA
|
||||
|
||||
# Part identification
|
||||
part_number: Mapped[Optional[str]] = mapped_column(
|
||||
ForeignKey("a76.parts.id")) # NUMPARTE
|
||||
component_part_number: Mapped[Optional[str]] = mapped_column(
|
||||
ForeignKey("a76.parts.id")) # NUMPARTECOM
|
||||
class_code: Mapped[Optional[str]] = mapped_column(
|
||||
ForeignKey("a76.classes.id")) # CLASE
|
||||
part_number_id: Mapped[Optional[str]] = mapped_column(
|
||||
ForeignKey("a76.parts.id")
|
||||
) # NUMPARTE
|
||||
component_part_number_id: Mapped[Optional[str]] = mapped_column(
|
||||
ForeignKey("a76.parts.id")
|
||||
) # NUMPARTECOM
|
||||
class_id: Mapped[Optional[str]] = mapped_column(
|
||||
ForeignKey("a76.classes.id")
|
||||
) # CLASE
|
||||
|
||||
# Unit of measure
|
||||
unit_of_measure: Mapped[Optional[str]] = mapped_column(
|
||||
ForeignKey("a76.units_of_measure.id")) # UNIDADMEDIDA/UNIMED
|
||||
ForeignKey("a76.units_of_measure.id")
|
||||
) # UNIDADMEDIDA/UNIMED
|
||||
alternate_unit: Mapped[Optional[str]] = mapped_column(
|
||||
ForeignKey("a76.units_of_measure.id")) # UNIMEDALTERNA
|
||||
ForeignKey("a76.units_of_measure.id")
|
||||
) # UNIMEDALTERNA
|
||||
uma_key: Mapped[Optional[str]] = mapped_column(String(2)) # CLAVEUMA
|
||||
auxiliary_unit: Mapped[Optional[str]] = mapped_column(
|
||||
String(5)) # UNIMEDAUXILIAR
|
||||
auxiliary_unit: Mapped[Optional[str]] = mapped_column(String(5)) # UNIMEDAUXILIAR
|
||||
|
||||
# Permits and certificates
|
||||
permit_number: Mapped[Optional[str]] = mapped_column(
|
||||
String(20)) # NUMPERMISO
|
||||
permit_number: Mapped[Optional[str]] = mapped_column(String(20)) # NUMPERMISO
|
||||
page_line: Mapped[Optional[str]] = mapped_column(String(10)) # PAGRENGLON
|
||||
has_certificate: Mapped[Optional[bool]] = mapped_column(
|
||||
Boolean) # TIENECO/CERTORIGEN
|
||||
Boolean
|
||||
) # TIENECO/CERTORIGEN
|
||||
certificate_number: Mapped[Optional[str]] = mapped_column(
|
||||
String(10)) # NOCERTIFICADO
|
||||
octave_permit: Mapped[Optional[str]] = mapped_column(
|
||||
String(20)) # PERMISOROCTAVA
|
||||
permits_ped: Mapped[Optional[str]] = mapped_column(
|
||||
String(500)) # PERMISOSPED
|
||||
String(10)
|
||||
) # NOCERTIFICADO
|
||||
octave_permit: Mapped[Optional[str]] = mapped_column(String(20)) # PERMISOROCTAVA
|
||||
permits_ped: Mapped[Optional[str]] = mapped_column(String(500)) # PERMISOSPED
|
||||
|
||||
# FDA
|
||||
has_fda_code: Mapped[Optional[bool]] = mapped_column(
|
||||
Boolean) # LLEVACODFDA
|
||||
fda_key: Mapped[Optional[str]] = mapped_column(String(10)) # CLAVEFDA
|
||||
|
||||
# Subitem flags
|
||||
is_subitem: Mapped[Optional[bool]] = mapped_column(Boolean) # ESSUBPARTIDA
|
||||
contains_subitems: Mapped[Optional[bool]
|
||||
] = mapped_column(Boolean) # CONTIENESUBP
|
||||
includes_subitems: Mapped[Optional[bool]] = mapped_column(
|
||||
Boolean) # INCUYESUBPARTIDAS
|
||||
subitem_number: Mapped[Optional[bool]
|
||||
] = mapped_column(Boolean) # SUBPARTIDA
|
||||
has_fda_code: Mapped[Optional[bool]] = mapped_column(Boolean) # LLEVACODFDA
|
||||
fda_key: Mapped[Optional[str]] = mapped_column(String(10)) # CLAVEFDA
|
||||
|
||||
# Special flags
|
||||
is_military_mcia: Mapped[Optional[bool]
|
||||
] = mapped_column(Boolean) # ESMCIAMILITAR
|
||||
is_military_mcia: Mapped[Optional[bool]] = mapped_column(Boolean) # ESMCIAMILITAR
|
||||
|
||||
# IV32 (Tax identification)
|
||||
iv32_type_key: Mapped[Optional[str]] = mapped_column(
|
||||
String(5)) # CLAVETIPOIV32
|
||||
iv32_number: Mapped[Optional[str]] = mapped_column(
|
||||
String(35)) # NUMEROIV32
|
||||
iv32_type_key: Mapped[Optional[str]] = mapped_column(String(5)) # CLAVETIPOIV32
|
||||
iv32_number: Mapped[Optional[str]] = mapped_column(String(35)) # NUMEROIV32
|
||||
|
||||
# IN CASE OF EXPO
|
||||
scrap_invoice: Mapped[Optional[str]] = mapped_column(
|
||||
String(15)) # FACTURASCRAP
|
||||
consecutive_destination: Mapped[Optional[int]
|
||||
] = mapped_column(Integer) # CONSECUTIVODES
|
||||
ctm_section: Mapped[Optional[str]] = mapped_column(
|
||||
String(3)) # APARTADOCTM
|
||||
scrap_invoice: Mapped[Optional[str]] = mapped_column(String(15)) # FACTURASCRAP
|
||||
consecutive_destination: Mapped[Optional[int]] = mapped_column(
|
||||
Integer
|
||||
) # CONSECUTIVODES
|
||||
ctm_section: Mapped[Optional[str]] = mapped_column(String(3)) # APARTADOCTM
|
||||
|
||||
# Tax payment
|
||||
tax_payment: Mapped[Optional[bool]] = mapped_column(
|
||||
Boolean) # PAGOIMPUESTO
|
||||
tax_payment: Mapped[Optional[bool]] = mapped_column(Boolean) # PAGOIMPUESTO
|
||||
payment_method: Mapped[Optional[str]] = mapped_column(
|
||||
String(9)) # FORMAPAGO/FORMAPAGOTIGI
|
||||
igi_amount: Mapped[Optional[Decimal]] = mapped_column(
|
||||
Numeric(23, 8)) # MONTOIGI
|
||||
String(9)
|
||||
) # FORMAPAGO/FORMAPAGOTIGI
|
||||
igi_amount: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # MONTOIGI
|
||||
igi_payment_method: Mapped[Optional[str]] = mapped_column(
|
||||
String(9)) # FORMAPAGOTIGI
|
||||
String(9)
|
||||
) # FORMAPAGOTIGI
|
||||
|
||||
# FCC
|
||||
fcc_key: Mapped[Optional[str]] = mapped_column(String(30)) # CLAVEFCC
|
||||
|
||||
# Valuation method
|
||||
valuation_method: Mapped[Optional[str]
|
||||
] = mapped_column(String(2)) # METVALOR
|
||||
valuation_method: Mapped[Optional[str]] = mapped_column(String(2)) # METVALOR
|
||||
valuation_determined_value: Mapped[Optional[Decimal]] = mapped_column(
|
||||
Numeric(29, 8)) # METVALORVALORDETERMINADO/METVALORACIONVALORDETERMINADO
|
||||
Numeric(29, 8)
|
||||
) # METVALORVALORDETERMINADO/METVALORACIONVALORDETERMINADO
|
||||
valuation_reason: Mapped[Optional[str]] = mapped_column(
|
||||
String(500)) # METVALORMOTIVODEUSO/METVALORACIONMOTIVODEUSO
|
||||
String(500)
|
||||
) # METVALORMOTIVODEUSO/METVALORACIONMOTIVODEUSO
|
||||
|
||||
# Container rules
|
||||
container_rule: Mapped[Optional[str]] = mapped_column(
|
||||
String(50)) # CONTENEDORREGLA
|
||||
container_rule: Mapped[Optional[str]] = mapped_column(String(50)) # CONTENEDORREGLA
|
||||
container_parts_ii: Mapped[Optional[str]] = mapped_column(
|
||||
String(50)) # CONTENEDORPARTESII
|
||||
String(50)
|
||||
) # CONTENEDORPARTESII
|
||||
|
||||
# APHIS
|
||||
consecutive_aphis: Mapped[Optional[int]] = mapped_column(
|
||||
Integer) # CONSECUTIVOAPHIS
|
||||
Integer
|
||||
) # CONSECUTIVOAPHIS
|
||||
|
||||
# BOM/Commercial
|
||||
bom_version: Mapped[Optional[int]] = mapped_column(Integer) # VERSIONBOM
|
||||
bill_version: Mapped[Optional[int]] = mapped_column(Integer) # VERSIONBILL
|
||||
|
||||
# TLCAN value
|
||||
tlcan_value: Mapped[Optional[Decimal]] = mapped_column(
|
||||
Numeric(23, 8)) # VALORTLCAN
|
||||
tlcan_value: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORTLCAN
|
||||
|
||||
# Identifier
|
||||
identifier: Mapped[Optional[str]] = mapped_column(
|
||||
String(2)) # IDENTIFICADOR
|
||||
identifier: Mapped[Optional[str]] = mapped_column(String(2)) # IDENTIFICADOR
|
||||
|
||||
# Validation fields
|
||||
validation_zero: Mapped[Optional[int]] = mapped_column(
|
||||
Integer) # VALIDACIONZERO
|
||||
validation_one: Mapped[Optional[int]] = mapped_column(
|
||||
Integer) # VALIDACIONUNO
|
||||
validation_zero: Mapped[Optional[int]] = mapped_column(Integer) # VALIDACIONZERO
|
||||
validation_one: Mapped[Optional[int]] = mapped_column(Integer) # VALIDACIONUNO
|
||||
|
||||
# Material type
|
||||
material_type: Mapped[Optional[str]] = mapped_column(
|
||||
String(50)) # TIPOMAT/TIPODENUMPARTE
|
||||
String(50)
|
||||
) # TIPOMAT/TIPODENUMPARTE
|
||||
|
||||
# Order concept
|
||||
order_type: Mapped[Optional[str]] = mapped_column(
|
||||
String(50)) # TIPODEORDEN
|
||||
order_type: Mapped[Optional[str]] = mapped_column(String(50)) # TIPODEORDEN
|
||||
line_concept: Mapped[Optional[str]] = mapped_column(
|
||||
String(50)) # CONCEPTODELAPARTIDA
|
||||
String(50)
|
||||
) # CONCEPTODELAPARTIDA
|
||||
|
||||
# Review dispatch
|
||||
review_dispatch: Mapped[Optional[str]] = mapped_column(
|
||||
String(10)) # REVISARDESP
|
||||
review_dispatch: Mapped[Optional[str]] = mapped_column(String(10)) # REVISARDESP
|
||||
|
||||
# Take component from PT
|
||||
take_component_pt: Mapped[Optional[int]
|
||||
] = mapped_column(Integer) # TOMARCOMOPT
|
||||
take_component_pt: Mapped[Optional[int]] = mapped_column(Integer) # TOMARCOMOPT
|
||||
|
||||
# Pallet
|
||||
pallet2: Mapped[Optional[int]] = mapped_column(SmallInteger) # PALLET2
|
||||
|
||||
# Wildcard field
|
||||
wildcard_field: Mapped[Optional[str]] = mapped_column(
|
||||
String(100)) # CAMPOCOMODIN
|
||||
wildcard_field: Mapped[Optional[str]] = mapped_column(String(100)) # CAMPOCOMODIN
|
||||
|
||||
# Relationships
|
||||
item: Mapped["Item"] = relationship(back_populates="lines")
|
||||
financial: Mapped[Optional["LineFinancial"]] = relationship(
|
||||
back_populates="line", cascade="all, delete-orphan", uselist=False)
|
||||
back_populates="line", cascade="all, delete-orphan", uselist=False
|
||||
)
|
||||
quantity: Mapped[Optional["LineQuantity"]] = relationship(
|
||||
back_populates="line", cascade="all, delete-orphan", uselist=False)
|
||||
back_populates="line", cascade="all, delete-orphan", uselist=False
|
||||
)
|
||||
customs: Mapped[Optional["LineCustom"]] = relationship(
|
||||
back_populates="line", cascade="all, delete-orphan", uselist=False)
|
||||
back_populates="line", cascade="all, delete-orphan", uselist=False
|
||||
)
|
||||
description: Mapped[Optional["LineDescription"]] = relationship(
|
||||
back_populates="line", cascade="all, delete-orphan", uselist=False)
|
||||
back_populates="line", cascade="all, delete-orphan", uselist=False
|
||||
)
|
||||
reference: Mapped[Optional["LineReference"]] = relationship(
|
||||
back_populates="line", cascade="all, delete-orphan", uselist=False)
|
||||
back_populates="line", cascade="all, delete-orphan", uselist=False
|
||||
)
|
||||
class_info: Mapped[Optional["Class"]] = relationship(
|
||||
foreign_keys=[class_id], viewonly=True
|
||||
)
|
||||
unit_of_measure_info: Mapped[Optional["UnitOfMeasure"]] = relationship(
|
||||
foreign_keys=[unit_of_measure], viewonly=True
|
||||
)
|
||||
fa_data: Mapped[Optional["FaLineItem"]] = relationship(
|
||||
"FaLineItem", back_populates="master_info", uselist=False
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from decimal import Decimal
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel, Field, ConfigDict, field_validator
|
||||
from typing import Optional, Any
|
||||
from pydantic import BaseModel, Field, ConfigDict, field_validator, model_validator
|
||||
|
||||
# Import nested schemas
|
||||
from ..line_customs.schemas import (
|
||||
@@ -29,6 +29,13 @@ from ..line_references.schemas import (
|
||||
LineReferenceResponse,
|
||||
)
|
||||
|
||||
from api.v1.modules.a24.fa.fa_item_lines.dto import (
|
||||
FaLineItemCreateDTO,
|
||||
FaLineItemUpdateDTO,
|
||||
FaLineItemResponseDTO,
|
||||
)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# LINE ITEM SCHEMAS
|
||||
# ============================================================================
|
||||
@@ -40,16 +47,13 @@ class LineItemBase(BaseModel):
|
||||
line_number: int = Field(..., description="Line number")
|
||||
|
||||
# Part identification
|
||||
part_number: Optional[str] = Field(None, max_length=50, description="Part number")
|
||||
component_part_number: Optional[str] = Field(
|
||||
None, max_length=50, description="Component part number"
|
||||
part_number_id: Optional[int] = Field(None, description="Part number")
|
||||
component_part_number_id: Optional[int] = Field(
|
||||
None, description="Component part number"
|
||||
)
|
||||
class_code: Optional[str] = Field(None, max_length=20, description="Class code")
|
||||
class_id: Optional[int] = Field(None, description="Class code")
|
||||
|
||||
@field_validator(
|
||||
"class_code",
|
||||
"part_number",
|
||||
"component_part_number",
|
||||
"unit_of_measure",
|
||||
"alternate_unit",
|
||||
mode="before",
|
||||
@@ -91,12 +95,6 @@ class LineItemBase(BaseModel):
|
||||
has_fda_code: Optional[bool] = Field(None, description="Has FDA code")
|
||||
fda_key: Optional[str] = Field(None, max_length=10, description="FDA key")
|
||||
|
||||
# Subitem flags
|
||||
is_subitem: Optional[bool] = Field(None, description="Is subitem")
|
||||
contains_subitems: Optional[bool] = Field(None, description="Contains subitems")
|
||||
includes_subitems: Optional[bool] = Field(None, description="Includes subitems")
|
||||
subitem_number: Optional[bool] = Field(None, description="Subitem number")
|
||||
|
||||
# Special flags
|
||||
is_military_mcia: Optional[bool] = Field(
|
||||
None, description="Is military merchandise"
|
||||
@@ -210,6 +208,9 @@ class LineItemCreate(LineItemBase):
|
||||
reference: Optional[LineReferenceCreate] = Field(
|
||||
None, description="Reference data for this line"
|
||||
)
|
||||
fa_data: Optional[FaLineItemCreateDTO] = Field(
|
||||
None, description="Fixed Asset data for this line"
|
||||
)
|
||||
|
||||
|
||||
class LineItemUpdate(LineItemBase):
|
||||
@@ -231,6 +232,9 @@ class LineItemUpdate(LineItemBase):
|
||||
reference: Optional[LineReferenceUpdate] = Field(
|
||||
None, description="Reference data for this line"
|
||||
)
|
||||
fa_data: Optional[FaLineItemUpdateDTO] = Field(
|
||||
None, description="Fixed Asset data for this line"
|
||||
)
|
||||
|
||||
|
||||
class LineItemResponse(LineItemBase):
|
||||
@@ -243,5 +247,38 @@ class LineItemResponse(LineItemBase):
|
||||
customs: Optional[LineCustomResponse] = None
|
||||
description: Optional[LineDescriptionResponse] = None
|
||||
reference: Optional[LineReferenceResponse] = None
|
||||
fa_data: Optional[FaLineItemResponseDTO] = None
|
||||
|
||||
# Fields populated from relationships
|
||||
class_code: Optional[str] = None
|
||||
class_description: Optional[str] = None
|
||||
unit_of_measure_code: Optional[str] = None
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
def extract_relationship_info(cls, data: Any) -> Any:
|
||||
"""Extract class_code, class_description and unit_of_measure_code from relationships"""
|
||||
if isinstance(data, dict):
|
||||
return data
|
||||
|
||||
# It's an ORM object
|
||||
result = {}
|
||||
for key in cls.model_fields.keys():
|
||||
if hasattr(data, key):
|
||||
result[key] = getattr(data, key)
|
||||
|
||||
# Extract class info
|
||||
if hasattr(data, "class_info") and data.class_info is not None:
|
||||
result["class_code"] = data.class_info.class_code
|
||||
result["class_description"] = data.class_info.description_es
|
||||
|
||||
# Extract unit of measure code
|
||||
if (
|
||||
hasattr(data, "unit_of_measure_info")
|
||||
and data.unit_of_measure_info is not None
|
||||
):
|
||||
result["unit_of_measure_code"] = data.unit_of_measure_info.code
|
||||
|
||||
return result
|
||||
|
||||
@@ -12,7 +12,7 @@ class LineQuantity(Base):
|
||||
Quantity details for line items
|
||||
Consolidates all line-level data from Q and S tables
|
||||
"""
|
||||
__tablename__ = "line_quantities"
|
||||
__tablename__ = "item_line_quantities"
|
||||
__table_args__ = {
|
||||
"schema": "a76",
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ class LineReference(Base):
|
||||
Reference details for line items
|
||||
Consolidates all line-level data from Q and S tables
|
||||
"""
|
||||
__tablename__ = "line_references"
|
||||
__tablename__ = "item_line_references"
|
||||
__table_args__ = {
|
||||
"schema": "a76",
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ Item -> LineItem -> LineFinancial
|
||||
-> LineCustoms
|
||||
-> LineDescription
|
||||
-> LineReference
|
||||
-> FaLineItem (Fixed Assets - a24)
|
||||
"""
|
||||
|
||||
import logging
|
||||
@@ -24,6 +25,7 @@ from .line_quantities.models import LineQuantity
|
||||
from .line_customs.models import LineCustom
|
||||
from .line_descriptions.models import LineDescription
|
||||
from .line_references.models import LineReference
|
||||
from api.v1.modules.a24.fa.fa_item_lines.models import FaLineItem
|
||||
from .models import Item
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -47,6 +49,9 @@ class ItemService:
|
||||
joinedload(Item.lines).joinedload(LineItem.customs),
|
||||
joinedload(Item.lines).joinedload(LineItem.description),
|
||||
joinedload(Item.lines).joinedload(LineItem.reference),
|
||||
joinedload(Item.lines).joinedload(LineItem.class_info),
|
||||
joinedload(Item.lines).joinedload(LineItem.unit_of_measure_info),
|
||||
joinedload(Item.lines).joinedload(LineItem.fa_data),
|
||||
)
|
||||
.filter(
|
||||
Item.id == item_id,
|
||||
@@ -74,6 +79,9 @@ class ItemService:
|
||||
joinedload(Item.lines).joinedload(LineItem.customs),
|
||||
joinedload(Item.lines).joinedload(LineItem.description),
|
||||
joinedload(Item.lines).joinedload(LineItem.reference),
|
||||
joinedload(Item.lines).joinedload(LineItem.class_info),
|
||||
joinedload(Item.lines).joinedload(LineItem.unit_of_measure_info),
|
||||
joinedload(Item.lines).joinedload(LineItem.fa_data),
|
||||
)
|
||||
.filter(
|
||||
Item.tenant_id == tenant_id,
|
||||
@@ -122,6 +130,7 @@ class ItemService:
|
||||
joinedload(Item.lines).joinedload(LineItem.customs),
|
||||
joinedload(Item.lines).joinedload(LineItem.description),
|
||||
joinedload(Item.lines).joinedload(LineItem.reference),
|
||||
joinedload(Item.lines).joinedload(LineItem.fa_data),
|
||||
)
|
||||
.filter(
|
||||
Item.invoice_id == invoice_id,
|
||||
@@ -145,7 +154,7 @@ class ItemService:
|
||||
try:
|
||||
# Extract lines data
|
||||
lines_data = item_data.lines or []
|
||||
item_dict = item_data.model_dump(exclude={"lines"})
|
||||
item_dict = item_data.model_dump(exclude={"lines"})
|
||||
|
||||
# Add tenant and company
|
||||
item_dict["tenant_id"] = tenant_id
|
||||
@@ -154,16 +163,17 @@ class ItemService:
|
||||
# Create the item
|
||||
db_item = Item(**item_dict)
|
||||
db.add(db_item)
|
||||
db.flush() # Get the item ID
|
||||
db.flush() # Get the item ID
|
||||
|
||||
# Create line items if provided
|
||||
for idx, line_data in enumerate(lines_data):
|
||||
for idx, line_data in enumerate(lines_data):
|
||||
# Extract nested data from line
|
||||
financial_data = line_data.financial
|
||||
quantity_data = line_data.quantity
|
||||
customs_data = line_data.customs
|
||||
description_data = line_data.description
|
||||
reference_data = line_data.reference
|
||||
reference_data = line_data.reference
|
||||
fa_data = line_data.fa_data
|
||||
|
||||
line_dict = line_data.model_dump(
|
||||
exclude={
|
||||
@@ -172,6 +182,7 @@ class ItemService:
|
||||
"customs",
|
||||
"description",
|
||||
"reference",
|
||||
"fa_data",
|
||||
}
|
||||
)
|
||||
line_dict["item_id"] = db_item.id
|
||||
@@ -181,45 +192,54 @@ class ItemService:
|
||||
# Create line item
|
||||
db_line = LineItem(**line_dict)
|
||||
db.add(db_line)
|
||||
db.flush() # Get the line ID
|
||||
db.flush() # Get the line ID
|
||||
|
||||
# Create financial data if provided
|
||||
if financial_data:
|
||||
financial_dict = financial_data.model_dump()
|
||||
financial_dict["item_line_id"] = db_line.id
|
||||
db_financial = LineFinancial(**financial_dict)
|
||||
db.add(db_financial)
|
||||
db.add(db_financial)
|
||||
|
||||
# Create quantity data if provided
|
||||
if quantity_data:
|
||||
quantity_dict = quantity_data.model_dump()
|
||||
quantity_dict["item_line_id"] = db_line.id
|
||||
db_quantity = LineQuantity(**quantity_dict)
|
||||
db.add(db_quantity)
|
||||
db.add(db_quantity)
|
||||
|
||||
# Create customs data if provided
|
||||
if customs_data:
|
||||
customs_dict = customs_data.model_dump()
|
||||
customs_dict["item_line_id"] = db_line.id
|
||||
db_customs = LineCustom(**customs_dict)
|
||||
db.add(db_customs)
|
||||
db.add(db_customs)
|
||||
|
||||
# Create description data if provided
|
||||
if description_data:
|
||||
description_dict = description_data.model_dump()
|
||||
description_dict["item_line_id"] = db_line.id
|
||||
db_description = LineDescription(**description_dict)
|
||||
db.add(db_description)
|
||||
db.add(db_description)
|
||||
|
||||
# Create reference data if provided
|
||||
if reference_data:
|
||||
reference_dict = reference_data.model_dump()
|
||||
reference_dict["item_line_id"] = db_line.id
|
||||
db_reference = LineReference(**reference_dict)
|
||||
db.add(db_reference)
|
||||
|
||||
db.add(db_reference)
|
||||
|
||||
# Create FA data if provided
|
||||
if fa_data:
|
||||
fa_dict = fa_data.model_dump()
|
||||
fa_dict["id"] = db_line.id # FA table uses same ID as line item
|
||||
fa_dict["tenant_id"] = tenant_id
|
||||
fa_dict["company_id"] = company_id
|
||||
db_fa = FaLineItem(**fa_dict)
|
||||
db.add(db_fa)
|
||||
|
||||
db.commit()
|
||||
db.refresh(db_item)
|
||||
db.refresh(db_item)
|
||||
return db_item
|
||||
|
||||
except IntegrityError as e:
|
||||
@@ -272,6 +292,7 @@ class ItemService:
|
||||
customs_data = line_data.customs
|
||||
description_data = line_data.description
|
||||
reference_data = line_data.reference
|
||||
fa_data = line_data.fa_data
|
||||
|
||||
line_dict = line_data.model_dump(
|
||||
exclude={
|
||||
@@ -280,6 +301,7 @@ class ItemService:
|
||||
"customs",
|
||||
"description",
|
||||
"reference",
|
||||
"fa_data",
|
||||
},
|
||||
exclude_unset=True,
|
||||
)
|
||||
@@ -319,6 +341,14 @@ class ItemService:
|
||||
reference_dict["item_line_id"] = db_line.id
|
||||
db.add(LineReference(**reference_dict))
|
||||
|
||||
# Create FA data if provided
|
||||
if fa_data is not None:
|
||||
fa_dict = fa_data.model_dump(exclude_unset=True)
|
||||
fa_dict["id"] = db_line.id # FA table uses same ID as line item
|
||||
fa_dict["tenant_id"] = tenant_id
|
||||
fa_dict["company_id"] = company_id
|
||||
db.add(FaLineItem(**fa_dict))
|
||||
|
||||
db.commit()
|
||||
db.refresh(db_item)
|
||||
return db_item
|
||||
|
||||
Reference in New Issue
Block a user