fix: update part_number and component_part_number fields in various schemas and components
This commit is contained in:
@@ -33,7 +33,7 @@ from api.v1.modules.a24.fa.fa_item_lines.models import FaLineItem
|
||||
from .models import Item
|
||||
from api.v1.modules.a76.invoices.models import InvoiceHeader
|
||||
from api.v1.modules.a76.general_catalogs.company.models import Company
|
||||
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -158,79 +158,86 @@ class ItemService:
|
||||
company_id: int,
|
||||
) -> Item:
|
||||
"""Create a new item with all related nested data (multiple lines)"""
|
||||
|
||||
|
||||
# Validaciones con ErrorCollector
|
||||
errors = ErrorCollector()
|
||||
|
||||
|
||||
# Validar que la factura exista y no esté actualizada (si viene invoice_id)
|
||||
invoice = None
|
||||
if item_data.invoice_id:
|
||||
invoice = db.query(InvoiceHeader).filter(
|
||||
InvoiceHeader.id == item_data.invoice_id,
|
||||
InvoiceHeader.tenant_id == tenant_id,
|
||||
InvoiceHeader.company_id == company_id
|
||||
).first()
|
||||
|
||||
invoice = (
|
||||
db.query(InvoiceHeader)
|
||||
.filter(
|
||||
InvoiceHeader.id == item_data.invoice_id,
|
||||
InvoiceHeader.tenant_id == tenant_id,
|
||||
InvoiceHeader.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
if not invoice:
|
||||
errors.add_error(
|
||||
field="invoice_id",
|
||||
message="La factura especificada no existe",
|
||||
code="NOT_FOUND",
|
||||
value=str(item_data.invoice_id)
|
||||
value=str(item_data.invoice_id),
|
||||
)
|
||||
|
||||
|
||||
# Validar cada line item que se va a crear
|
||||
if item_data.lines:
|
||||
for idx, line_data in enumerate(item_data.lines):
|
||||
# Convertir a LineItemCreate para validar
|
||||
line_create = LineItemCreate(**line_data.model_dump())
|
||||
|
||||
|
||||
validate_create(db, line_create, tenant_id, company_id, errors)
|
||||
|
||||
|
||||
# Validaciones adicionales específicas del negocio
|
||||
|
||||
|
||||
# Validar apóstrofes en número de parte
|
||||
if line_data.part_number_id and "'" in str(line_data.part_number_id):
|
||||
if line_data.part_number and "'" in str(line_data.part_number):
|
||||
errors.add_error(
|
||||
field=f"lines[{idx}].part_number_id",
|
||||
field=f"lines[{idx}].part_number",
|
||||
message=f"Advertencia: El Número de Parte contiene apóstrofes y serán omitidos",
|
||||
code="WARNING_APOSTROPHE"
|
||||
code="WARNING_APOSTROPHE",
|
||||
)
|
||||
|
||||
|
||||
# Validar tipo de partida
|
||||
if hasattr(line_data, 'item_type'):
|
||||
if hasattr(line_data, "item_type"):
|
||||
tipo_partida = line_data.item_type
|
||||
if tipo_partida and tipo_partida not in ['N', 'S']:
|
||||
if tipo_partida and tipo_partida not in ["N", "S"]:
|
||||
errors.add_error(
|
||||
field=f"lines[{idx}].item_type",
|
||||
message=f"Tipo de partida debe ser 'N' (Normal) o 'S' (Subpartida), recibido: '{tipo_partida}'",
|
||||
code="INVALID_ITEM_TYPE",
|
||||
value=str(tipo_partida)
|
||||
value=str(tipo_partida),
|
||||
)
|
||||
|
||||
|
||||
# Si es subpartida (S), debe tener partida principal
|
||||
if tipo_partida == 'S':
|
||||
if not hasattr(line_data, 'main_line_id') or not line_data.main_line_id:
|
||||
if tipo_partida == "S":
|
||||
if (
|
||||
not hasattr(line_data, "main_line_id")
|
||||
or not line_data.main_line_id
|
||||
):
|
||||
errors.add_error(
|
||||
field=f"lines[{idx}].main_line_id",
|
||||
message="Las subpartidas (tipo 'S') deben tener una partida principal",
|
||||
code="MISSING_MAIN_LINE"
|
||||
code="MISSING_MAIN_LINE",
|
||||
)
|
||||
|
||||
|
||||
# Validar que el line_number sea consecutivo (si se especifica)
|
||||
if hasattr(line_data, 'line_number') and line_data.line_number:
|
||||
if hasattr(line_data, "line_number") and line_data.line_number:
|
||||
expected_line = idx + 1
|
||||
if line_data.line_number != expected_line:
|
||||
errors.add_error(
|
||||
field=f"lines[{idx}].line_number",
|
||||
message=f"Número de línea esperado: {expected_line}, recibido: {line_data.line_number}",
|
||||
code="INVALID_LINE_SEQUENCE",
|
||||
value=str(line_data.line_number)
|
||||
value=str(line_data.line_number),
|
||||
)
|
||||
|
||||
|
||||
# Si hay errores, lanzar excepción ANTES de intentar crear
|
||||
errors.raise_if_errors("Error al crear el item")
|
||||
|
||||
|
||||
try:
|
||||
# Extract lines data
|
||||
lines_data = item_data.lines or []
|
||||
@@ -311,7 +318,9 @@ class ItemService:
|
||||
|
||||
# Create FA data if provided
|
||||
if fa_data:
|
||||
fa_dict = fa_data.model_dump(exclude={"line_item_id"}) # Exclude line_item_id from DTO
|
||||
fa_dict = fa_data.model_dump(
|
||||
exclude={"line_item_id"}
|
||||
) # Exclude line_item_id from DTO
|
||||
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
|
||||
@@ -343,47 +352,54 @@ class ItemService:
|
||||
company_id: int,
|
||||
) -> Item:
|
||||
"""Update an item and optionally its nested data (multiple lines)"""
|
||||
|
||||
|
||||
# Get existing item
|
||||
db_item = ItemService.get_by_id(db, item_id, tenant_id, company_id)
|
||||
if not db_item:
|
||||
raise HTTPException(status_code=404, detail="Item not found")
|
||||
|
||||
|
||||
# Validaciones con ErrorCollector
|
||||
errors = ErrorCollector()
|
||||
|
||||
|
||||
# Si se está actualizando el invoice_id, validar la factura
|
||||
invoice = None
|
||||
if item_data.invoice_id:
|
||||
invoice = db.query(InvoiceHeader).filter(
|
||||
InvoiceHeader.id == item_data.invoice_id,
|
||||
InvoiceHeader.tenant_id == tenant_id,
|
||||
InvoiceHeader.company_id == company_id
|
||||
).first()
|
||||
|
||||
invoice = (
|
||||
db.query(InvoiceHeader)
|
||||
.filter(
|
||||
InvoiceHeader.id == item_data.invoice_id,
|
||||
InvoiceHeader.tenant_id == tenant_id,
|
||||
InvoiceHeader.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
if not invoice:
|
||||
errors.add_error(
|
||||
field="invoice_id",
|
||||
message="La factura especificada no existe",
|
||||
code="NOT_FOUND",
|
||||
value=str(item_data.invoice_id)
|
||||
value=str(item_data.invoice_id),
|
||||
)
|
||||
else:
|
||||
# Si no se está actualizando invoice_id, obtener la factura actual por invoice_id
|
||||
from api.v1.modules.a76.invoices.models import InvoiceHeader
|
||||
invoice = db.query(InvoiceHeader).filter(
|
||||
InvoiceHeader.id == db_item.invoice_id
|
||||
).first()
|
||||
|
||||
|
||||
invoice = (
|
||||
db.query(InvoiceHeader)
|
||||
.filter(InvoiceHeader.id == db_item.invoice_id)
|
||||
.first()
|
||||
)
|
||||
|
||||
# Validar cada line item que se va a actualizar
|
||||
if item_data.lines:
|
||||
for idx, line_data in enumerate(item_data.lines):
|
||||
# Si el line tiene ID, es actualización; si no, es creación
|
||||
if hasattr(line_data, 'id') and line_data.id:
|
||||
if hasattr(line_data, "id") and line_data.id:
|
||||
# Buscar el line item existente
|
||||
existing_line = next(
|
||||
(line for line in db_item.lines if line.id == line_data.id),
|
||||
None
|
||||
None,
|
||||
)
|
||||
if existing_line:
|
||||
# Convertir a LineItemUpdate para validar
|
||||
@@ -393,43 +409,46 @@ class ItemService:
|
||||
# Es un nuevo line item, validar como creación
|
||||
line_create = LineItemCreate(**line_data.model_dump())
|
||||
validate_create(db, line_create, tenant_id, company_id, errors)
|
||||
|
||||
|
||||
# Validaciones adicionales específicas del negocio
|
||||
# (Aplican tanto para crear como actualizar)
|
||||
|
||||
|
||||
# Validar apóstrofes en número de parte
|
||||
if line_data.part_number_id and "'" in str(line_data.part_number_id):
|
||||
if line_data.part_number and "'" in str(line_data.part_number):
|
||||
errors.add_error(
|
||||
field=f"lines[{idx}].part_number_id",
|
||||
field=f"lines[{idx}].part_number",
|
||||
message=f"Advertencia: El Número de Parte contiene apóstrofes y serán omitidos",
|
||||
solution=None,
|
||||
code="WARNING_APOSTROPHE"
|
||||
code="WARNING_APOSTROPHE",
|
||||
)
|
||||
|
||||
|
||||
# Validar tipo de partida
|
||||
if hasattr(line_data, 'item_type'):
|
||||
if hasattr(line_data, "item_type"):
|
||||
tipo_partida = line_data.item_type
|
||||
if tipo_partida and tipo_partida not in ['N', 'S']:
|
||||
if tipo_partida and tipo_partida not in ["N", "S"]:
|
||||
errors.add_error(
|
||||
field=f"lines[{idx}].item_type",
|
||||
message=f"Tipo de partida debe ser 'N' (Normal) o 'S' (Subpartida), recibido: '{tipo_partida}'",
|
||||
solution=None,
|
||||
code="INVALID_ITEM_TYPE",
|
||||
value=str(tipo_partida)
|
||||
value=str(tipo_partida),
|
||||
)
|
||||
|
||||
|
||||
# Si es subpartida (S), debe tener partida principal
|
||||
if tipo_partida == 'S':
|
||||
if not hasattr(line_data, 'main_line_id') or not line_data.main_line_id:
|
||||
if tipo_partida == "S":
|
||||
if (
|
||||
not hasattr(line_data, "main_line_id")
|
||||
or not line_data.main_line_id
|
||||
):
|
||||
errors.add_error(
|
||||
field=f"lines[{idx}].main_line_id",
|
||||
message="Las subpartidas (tipo 'S') deben tener una partida principal",
|
||||
solution=None,
|
||||
code="MISSING_MAIN_LINE"
|
||||
code="MISSING_MAIN_LINE",
|
||||
)
|
||||
|
||||
|
||||
# Validar que el line_number sea consecutivo (si se especifica)
|
||||
if hasattr(line_data, 'line_number') and line_data.line_number:
|
||||
if hasattr(line_data, "line_number") and line_data.line_number:
|
||||
expected_line = idx + 1
|
||||
if line_data.line_number != expected_line:
|
||||
errors.add_error(
|
||||
@@ -437,12 +456,12 @@ class ItemService:
|
||||
message=f"Número de línea esperado: {expected_line}, recibido: {line_data.line_number}",
|
||||
solution=None,
|
||||
code="INVALID_LINE_SEQUENCE",
|
||||
value=str(line_data.line_number)
|
||||
value=str(line_data.line_number),
|
||||
)
|
||||
|
||||
|
||||
# Si hay errores, lanzar excepción ANTES de actualizar
|
||||
errors.raise_if_errors("Error al actualizar el item")
|
||||
|
||||
|
||||
try:
|
||||
|
||||
# Extract lines data
|
||||
@@ -519,7 +538,9 @@ class ItemService:
|
||||
|
||||
# Create FA data if provided
|
||||
if fa_data is not None:
|
||||
fa_dict = fa_data.model_dump(exclude_unset=True, exclude={"line_item_id"})
|
||||
fa_dict = fa_data.model_dump(
|
||||
exclude_unset=True, exclude={"line_item_id"}
|
||||
)
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user