Merge branch 'development' into feature/items-calculations
This commit is contained in:
@@ -188,6 +188,23 @@ def validate_create(
|
||||
line.financial.unit_cost_mxn = unit_cost_capture
|
||||
# Si es otro tipo de moneda, dejamos el costo como está
|
||||
|
||||
# Calcular valores totales basados en cantidad y costo unitario
|
||||
quantity = line.quantity.quantity or Decimal("0")
|
||||
|
||||
# Valor Comercial
|
||||
if line.financial.unit_cost_usd is not None:
|
||||
line.financial.value_usd = line.financial.unit_cost_usd * quantity
|
||||
if line.financial.unit_cost_mxn is not None:
|
||||
line.financial.value_mxn = line.financial.unit_cost_mxn * quantity
|
||||
|
||||
# Valor Aduanas (asumiendo que es igual al Valor Comercial por defecto)
|
||||
line.financial.customs_value_usd = line.financial.value_usd
|
||||
line.financial.customs_value_mxn = line.financial.value_mxn
|
||||
|
||||
# Valor MP Temp (Materia Prima Temporal)
|
||||
line.financial.value_temp_material_usd = line.financial.value_usd
|
||||
line.financial.value_temp_material_mxn = line.financial.value_mxn
|
||||
|
||||
# ==========================================
|
||||
# VALIDAR Y CONVERTIR PESOS NETOS
|
||||
# ==========================================
|
||||
|
||||
@@ -64,6 +64,30 @@ def validate_update(
|
||||
# Costo unitario
|
||||
if line.financial.unit_cost_capture is None:
|
||||
line.financial.unit_cost_capture = existing_line.financial.unit_cost_capture
|
||||
|
||||
# Recalcular valores monetarios si el costo o la cantidad cambian
|
||||
currency_type = invoice.financials.currency_type
|
||||
unit_cost_capture = line.financial.unit_cost_capture or Decimal("0")
|
||||
|
||||
if currency_type in ["USD", "ME"]:
|
||||
line.financial.unit_cost_usd = unit_cost_capture
|
||||
line.financial.unit_cost_mxn = unit_cost_capture * exchange_rate
|
||||
elif currency_type in ["MXN", "MN"]:
|
||||
line.financial.unit_cost_usd = (unit_cost_capture / exchange_rate) if exchange_rate else Decimal("0")
|
||||
line.financial.unit_cost_mxn = unit_cost_capture
|
||||
|
||||
quantity = line.quantity.quantity if line.quantity.quantity is not None else existing_line.quantity.quantity
|
||||
|
||||
if line.financial.unit_cost_usd is not None:
|
||||
line.financial.value_usd = line.financial.unit_cost_usd * quantity
|
||||
if line.financial.unit_cost_mxn is not None:
|
||||
line.financial.value_mxn = line.financial.unit_cost_mxn * quantity
|
||||
|
||||
line.financial.customs_value_usd = line.financial.value_usd
|
||||
line.financial.customs_value_mxn = line.financial.value_mxn
|
||||
|
||||
line.financial.value_temp_material_usd = line.financial.value_usd
|
||||
line.financial.value_temp_material_mxn = line.financial.value_mxn
|
||||
|
||||
# Convertir peso neto si se proporcionó
|
||||
invoice_weight_type = invoice.logistics.weight_type
|
||||
|
||||
@@ -4,7 +4,7 @@ Complete nested one-to-one structure:
|
||||
LineItem -> LineFinancial -> LineQuantity -> LineCustoms -> LineDescription -> LineReference
|
||||
"""
|
||||
|
||||
from typing import Any, Optional
|
||||
from typing import Any, Optional, Union
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from pydantic import BaseModel, Field, ConfigDict, model_validator
|
||||
@@ -58,13 +58,13 @@ class LineItemBase(BaseModel):
|
||||
line_number: int = Field(..., description="Line number")
|
||||
|
||||
# Part identification
|
||||
part_number_id: Optional[int] = Field(
|
||||
part_number_id: Union[int, str, None] = Field(
|
||||
None,
|
||||
description="Part number",
|
||||
alias="part_number",
|
||||
serialization_alias="part_number_id",
|
||||
)
|
||||
component_part_number_id: Optional[int] = Field(
|
||||
component_part_number_id: Union[int, str, None] = Field(
|
||||
None,
|
||||
description="Component part number",
|
||||
alias="component_part_number",
|
||||
|
||||
@@ -36,6 +36,7 @@ from .line_references.models import LineReference
|
||||
from api.v1.modules.a24.fa.fa_item_lines.models import FaLineItem
|
||||
from .models import LineItem
|
||||
from api.v1.modules.a76.invoices.models import InvoiceHeader
|
||||
from api.v1.modules.a76.parts.models import Part
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -45,6 +46,33 @@ class ItemService:
|
||||
Service for managing Items and related entities with tenant/company isolation
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def _resolve_part_number(
|
||||
db: Session,
|
||||
part_number: Optional[str],
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
) -> Optional[int]:
|
||||
"""Try to resolve a part number string to its database ID."""
|
||||
if not part_number:
|
||||
return None
|
||||
|
||||
# If it's already an integer (or a string representing an integer), it might be the ID
|
||||
try:
|
||||
return int(part_number)
|
||||
except (ValueError, TypeError):
|
||||
# It's a string part number (e.g., "MAQ-001"), look it up
|
||||
part = (
|
||||
db.query(Part)
|
||||
.filter(
|
||||
Part.part_number == part_number,
|
||||
Part.tenant_id == tenant_id,
|
||||
Part.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
return part.id if part else None
|
||||
|
||||
@staticmethod
|
||||
def _get_next_line_number(db: Session, invoice_id: int) -> int:
|
||||
"""Calculate the next line_number for a given invoice based on database."""
|
||||
@@ -282,6 +310,24 @@ class ItemService:
|
||||
# Calculate the next line number for this single item
|
||||
line_number = ItemService._get_next_line_number(db, item_data.invoice_id)
|
||||
|
||||
# Resolve part ID if a string is provided in part_number (alias for part_number_id)
|
||||
if item_data.part_number_id and not isinstance(item_data.part_number_id, int):
|
||||
resolved_id = ItemService._resolve_part_number(
|
||||
db, str(item_data.part_number_id), tenant_id, company_id
|
||||
)
|
||||
if resolved_id:
|
||||
item_data.part_number_id = resolved_id
|
||||
|
||||
# Resolve component part ID
|
||||
if item_data.component_part_number_id and not isinstance(
|
||||
item_data.component_part_number_id, int
|
||||
):
|
||||
resolved_id = ItemService._resolve_part_number(
|
||||
db, str(item_data.component_part_number_id), tenant_id, company_id
|
||||
)
|
||||
if resolved_id:
|
||||
item_data.component_part_number_id = resolved_id
|
||||
|
||||
# Validar el item
|
||||
validate_create(
|
||||
db,
|
||||
@@ -378,6 +424,24 @@ class ItemService:
|
||||
):
|
||||
errors.raise_if_errors("Error al actualizar el item")
|
||||
|
||||
# Resolve part ID if a string is provided in part_number (alias for part_number_id)
|
||||
if hasattr(item_data, 'part_number_id') and item_data.part_number_id and not isinstance(item_data.part_number_id, int):
|
||||
resolved_id = ItemService._resolve_part_number(
|
||||
db, str(item_data.part_number_id), tenant_id, company_id
|
||||
)
|
||||
if resolved_id:
|
||||
item_data.part_number_id = resolved_id
|
||||
|
||||
# Resolve component part ID
|
||||
if hasattr(item_data, 'component_part_number_id') and item_data.component_part_number_id and not isinstance(
|
||||
item_data.component_part_number_id, int
|
||||
):
|
||||
resolved_id = ItemService._resolve_part_number(
|
||||
db, str(item_data.component_part_number_id), tenant_id, company_id
|
||||
)
|
||||
if resolved_id:
|
||||
item_data.component_part_number_id = resolved_id
|
||||
|
||||
# Validar el item que se va a actualizar
|
||||
validate_update(
|
||||
db,
|
||||
|
||||
Reference in New Issue
Block a user