Files
CRM_AGENTES_CARGA/backend/api/v1/modules/crm/contacts/service.py
Aduanasoft b12af1a561 feat(crm): catálogos Clientes/Prospectos (enriquecido) y Proveedores + direcciones/documentos
Alinea el dominio al spec de catálogos:
- accounts (Clientes/Prospectos): tipo registro/persona, CURP, clasificación
  comercial, bloque fiscal (régimen, CFDI, pago, crédito), auditoría, notas internas
- suppliers (Proveedores): clasificación múltiple, cobertura, países/puertos/
  aeropuertos/aduanas (JSON), fiscal
- addresses y documents: tablas compartidas con FK a cliente o proveedor
- contacts enriquecidos (extensión, whatsapp, área, flags "recibe…", supplier_id)
- migración a7b8c9d0e1f2 (ALTER + CREATE) con downgrade completo
- permisos supplier/address/document; seed de datos actualizado
- 39 tests pytest en verde

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 16:19:13 -06:00

120 lines
3.8 KiB
Python

from datetime import datetime, timezone
from fastapi import HTTPException, status
from sqlalchemy.orm import Session
from ..accounts.models import Account
from ..suppliers.models import Supplier
from .dto import ContactCreate, ContactUpdate
from .models import Contact
def _validate_parent(db: Session, data: dict, tenant_id: int, company_id: int) -> None:
"""Verifica que el cliente/proveedor referenciado exista en el tenant/company."""
account_id = data.get("account_id")
if account_id is not None:
exists = (
db.query(Account.id)
.filter(
Account.id == account_id,
Account.tenant_id == tenant_id,
Account.company_id == company_id,
Account.deleted_at.is_(None),
)
.first()
)
if not exists:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail="El cliente asociado no existe",
)
supplier_id = data.get("supplier_id")
if supplier_id is not None:
exists = (
db.query(Supplier.id)
.filter(
Supplier.id == supplier_id,
Supplier.tenant_id == tenant_id,
Supplier.company_id == company_id,
Supplier.deleted_at.is_(None),
)
.first()
)
if not exists:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail="El proveedor asociado no existe",
)
def get_contacts(
db: Session,
tenant_id: int,
company_id: int,
search: str | None = None,
account_id: int | None = None,
supplier_id: int | None = None,
) -> list[Contact]:
query = db.query(Contact).filter(
Contact.tenant_id == tenant_id,
Contact.company_id == company_id,
Contact.deleted_at.is_(None),
)
if account_id is not None:
query = query.filter(Contact.account_id == account_id)
if supplier_id is not None:
query = query.filter(Contact.supplier_id == supplier_id)
if search:
pattern = f"%{search}%"
query = query.filter(
Contact.first_name.ilike(pattern)
| Contact.last_name.ilike(pattern)
| Contact.email.ilike(pattern)
)
return query.order_by(Contact.first_name.asc()).all()
def get_contact(db: Session, contact_id: int, tenant_id: int, company_id: int) -> Contact:
contact = (
db.query(Contact)
.filter(
Contact.id == contact_id,
Contact.tenant_id == tenant_id,
Contact.company_id == company_id,
Contact.deleted_at.is_(None),
)
.first()
)
if not contact:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Contacto no encontrado")
return contact
def create_contact(db: Session, payload: ContactCreate, tenant_id: int, company_id: int) -> Contact:
data = payload.model_dump()
_validate_parent(db, data, tenant_id, company_id)
contact = Contact(**data, tenant_id=tenant_id, company_id=company_id)
db.add(contact)
db.commit()
db.refresh(contact)
return contact
def update_contact(
db: Session, contact_id: int, payload: ContactUpdate, tenant_id: int, company_id: int
) -> Contact:
contact = get_contact(db, contact_id, tenant_id, company_id)
data = payload.model_dump(exclude_unset=True)
_validate_parent(db, data, tenant_id, company_id)
for field, value in data.items():
setattr(contact, field, value)
db.commit()
db.refresh(contact)
return contact
def delete_contact(db: Session, contact_id: int, tenant_id: int, company_id: int) -> None:
contact = get_contact(db, contact_id, tenant_id, company_id)
contact.deleted_at = datetime.now(timezone.utc)
db.commit()