Files
plantillas-proyectos/backend/api/v1/modules/a76/parts/service.py

179 lines
6.1 KiB
Python

import logging
from typing import Any, Dict, List, Optional
from fastapi import HTTPException
from sqlalchemy import or_
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session
# Importamos el modelo PRINCIPAL
from .models import Part
# Importamos TODOS los DTOs necesarios
from .dto import (
PartCreateDTO,
PartUpdateDTO,
PartResponseDTO,
PartBasicDTO # ¡Importante tener este!
)
logger = logging.getLogger(__name__)
class PartService:
"""Servicio para gestión de Partes (Anexo 76 + Anexo 24)"""
@staticmethod
def get_all(
db: Session,
tenant_id: int,
company_id: int,
skip: int = 0,
limit: int = 100,
filters: Optional[Dict[str, Any]] = None,
) -> tuple[List[Part], int]:
query = db.query(Part).filter(
Part.tenant_id == tenant_id,
Part.company_id == company_id
)
if filters:
if filters.get("q"):
search = f"%{filters['q']}%"
query = query.filter(
or_(
Part.part_number.ilike(search),
Part.description_spanish.ilike(search),
Part.commercial_part_number.ilike(search)
)
)
# Otros filtros...
total = query.count()
items = query.offset(skip).limit(limit).all()
return items, total
@staticmethod
def get_by_id(db: Session, part_id: int, tenant_id: int, company_id: int) -> Optional[Part]:
return db.query(Part).filter(
Part.id == part_id,
Part.tenant_id == tenant_id,
Part.company_id == company_id
).first()
@staticmethod
def create(db: Session, part_data: PartCreateDTO, tenant_id: int, company_id: int) -> Part:
# 1. Preparar datos
data = part_data.model_dump()
# Separar datos anidados
fa_dict = data.pop('fa_data', None)
inv_dict = data.pop('inv_data', None)
# Inyectar IDs de contexto (Seguridad Multi-tenant)
data['company_id'] = company_id
data['tenant_id'] = tenant_id
# 2. Verificar duplicados (Usando la UniqueConstraint del modelo)
existing = db.query(Part).filter(
Part.tenant_id == tenant_id,
Part.company_id == company_id,
Part.part_number == data['part_number']
).first()
if existing:
raise HTTPException(
status_code=400,
detail=f"El número de parte '{data['part_number']}' ya existe."
)
# 3. Crear objeto Part
db_part = Part(**data)
# 4. Crear relaciones (Anexo 24)
# Importamos aquí para evitar ciclos, usando las rutas de tu modelo
if fa_dict:
from api.v1.modules.a24.fa.fa_parts.models import FaPart
# Importante: Pasar tenant/company también al hijo
db_part.fa_data = FaPart(**fa_dict, tenant_id=tenant_id, company_id=company_id)
if inv_dict:
from api.v1.modules.a24.inv.inv_parts.models import InvPart
db_part.inv_data = InvPart(**inv_dict, tenant_id=tenant_id, company_id=company_id)
try:
db.add(db_part)
db.commit()
db.refresh(db_part)
return db_part
except IntegrityError as e:
db.rollback()
err_msg = str(e.orig)
logger.error(f"Error DB creando parte: {err_msg}")
if "foreign key" in err_msg:
if "unit_of_measure" in err_msg:
raise HTTPException(400, "La Unidad de Medida no existe en el catálogo.")
if "currency_key" in err_msg:
raise HTTPException(400, "La Moneda no existe en el catálogo.")
if "part_class" in err_msg:
raise HTTPException(400, "La Clase no existe para este cliente.")
# El error genérico si falla Company/Client
raise HTTPException(400, "Error de referencia: Verifique Cliente, Compañía o Catálogos.")
raise HTTPException(400, "Error al guardar la parte.")
@staticmethod
def update(db: Session, part_id: int, tenant_id: int, part_data: PartUpdateDTO, company_id: int) -> Optional[Part]:
db_part = PartService.get_by_id(db, part_id, tenant_id, company_id)
if not db_part:
return None
data = part_data.model_dump(exclude_unset=True)
fa_dict = data.pop('fa_data', None)
inv_dict = data.pop('inv_data', None)
# Actualizar campos directos
for key, value in data.items():
setattr(db_part, key, value)
# Actualizar FA Data
if fa_dict is not None:
if db_part.fa_data:
for k, v in fa_dict.items():
setattr(db_part.fa_data, k, v)
else:
from api.v1.modules.a24.fa.fa_parts.models import FaPart
db_part.fa_data = FaPart(**fa_dict, tenant_id=tenant_id, company_id=company_id)
# Actualizar INV Data
if inv_dict is not None:
if db_part.inv_data:
for k, v in inv_dict.items():
setattr(db_part.inv_data, k, v)
else:
from api.v1.modules.a24.inv.inv_parts.models import InvPart
db_part.inv_data = InvPart(**inv_dict, tenant_id=tenant_id, company_id=company_id)
try:
db.commit()
db.refresh(db_part)
return db_part
except IntegrityError as e:
db.rollback()
raise HTTPException(400, f"Error actualizando: {str(e.orig)}")
@staticmethod
def delete(db: Session, part_id: int, tenant_id: int, company_id: int) -> bool:
db_part = PartService.get_by_id(db, part_id, tenant_id, company_id)
if not db_part: return False
try:
db.delete(db_part)
db.commit()
return True
except Exception:
db.rollback()
raise HTTPException(500, "Error eliminando parte")