feat: Enhance fixed asset class management with new fields, refactor forms, and update invoice-related services.

This commit is contained in:
Galindo97
2026-02-24 08:17:38 -06:00
parent f9f6a17c4a
commit 3898d3a1e0
25 changed files with 910 additions and 769 deletions

View File

@@ -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
# ==========================================

View File

@@ -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

View File

@@ -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",

View File

@@ -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,