from datetime import datetime, timezone from fastapi import HTTPException, status from sqlalchemy.orm import Session from ..accounts.models import Account from ..contacts.models import Contact from ..opportunities.models import Opportunity from .dto import LeadConvert, LeadCreate, LeadUpdate from .models import Lead def get_leads( db: Session, tenant_id: int, company_id: int, search: str | None = None, lead_status: str | None = None, ) -> list[Lead]: query = db.query(Lead).filter( Lead.tenant_id == tenant_id, Lead.company_id == company_id, Lead.deleted_at.is_(None), ) if lead_status: query = query.filter(Lead.status == lead_status) if search: pattern = f"%{search}%" query = query.filter( Lead.name.ilike(pattern) | Lead.company_name.ilike(pattern) | Lead.email.ilike(pattern) ) return query.order_by(Lead.created_at.desc()).all() def get_lead(db: Session, lead_id: int, tenant_id: int, company_id: int) -> Lead: lead = ( db.query(Lead) .filter( Lead.id == lead_id, Lead.tenant_id == tenant_id, Lead.company_id == company_id, Lead.deleted_at.is_(None), ) .first() ) if not lead: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Prospecto no encontrado") return lead def create_lead(db: Session, payload: LeadCreate, tenant_id: int, company_id: int) -> Lead: lead = Lead(**payload.model_dump(), tenant_id=tenant_id, company_id=company_id) db.add(lead) db.commit() db.refresh(lead) return lead def update_lead(db: Session, lead_id: int, payload: LeadUpdate, tenant_id: int, company_id: int) -> Lead: lead = get_lead(db, lead_id, tenant_id, company_id) for field, value in payload.model_dump(exclude_unset=True).items(): setattr(lead, field, value) db.commit() db.refresh(lead) return lead def delete_lead(db: Session, lead_id: int, tenant_id: int, company_id: int) -> None: lead = get_lead(db, lead_id, tenant_id, company_id) lead.deleted_at = datetime.now(timezone.utc) db.commit() def convert_lead( db: Session, lead_id: int, payload: LeadConvert, tenant_id: int, company_id: int ) -> dict: """Convierte un prospecto en cuenta (+ contacto y oportunidad opcionales). Es idempotente: si el prospecto ya fue convertido, retorna las referencias existentes. """ lead = get_lead(db, lead_id, tenant_id, company_id) if lead.status == "converted" and lead.converted_account_id: return { "lead": lead, "account_id": lead.converted_account_id, "contact_id": lead.converted_contact_id, "opportunity_id": lead.converted_opportunity_id, } # 1. Cuenta a partir de la empresa (o nombre) del prospecto account = Account( name=lead.company_name or lead.name, email=lead.email, phone=lead.phone, status="active", owner_user_id=lead.owner_user_id, tenant_id=tenant_id, company_id=company_id, ) db.add(account) db.flush() # necesitamos el id de la cuenta para enlazar contacto/oportunidad # 2. Contacto (si el prospecto trae nombre de contacto) contact = None if lead.contact_name: parts = lead.contact_name.split(" ", 1) contact = Contact( account_id=account.id, first_name=parts[0], last_name=parts[1] if len(parts) > 1 else None, email=lead.email, phone=lead.phone, is_primary=True, owner_user_id=lead.owner_user_id, tenant_id=tenant_id, company_id=company_id, ) db.add(contact) db.flush() # 3. Oportunidad (opcional) opportunity = None if payload.create_opportunity: opportunity = Opportunity( name=payload.opportunity_name or lead.name, account_id=account.id, contact_id=contact.id if contact else None, pipeline_id=payload.pipeline_id, stage_id=payload.stage_id, amount=payload.amount if payload.amount is not None else lead.estimated_value, source=lead.source, status="open", owner_user_id=lead.owner_user_id, tenant_id=tenant_id, company_id=company_id, ) db.add(opportunity) db.flush() # 4. Marcar el prospecto como convertido y enlazar lead.status = "converted" lead.converted_account_id = account.id lead.converted_contact_id = contact.id if contact else None lead.converted_opportunity_id = opportunity.id if opportunity else None db.commit() db.refresh(lead) return { "lead": lead, "account_id": account.id, "contact_id": contact.id if contact else None, "opportunity_id": opportunity.id if opportunity else None, }