from decimal import Decimal from sqlalchemy import and_, func from sqlalchemy.orm import Session from ..accounts.models import Account from ..activities.models import Activity from ..contacts.models import Contact from ..leads.models import Lead from ..opportunities.models import Opportunity from ..pipelines.models import PipelineStage def _count(db: Session, model, tenant_id: int, company_id: int, *extra) -> int: query = db.query(func.count(model.id)).filter( model.tenant_id == tenant_id, model.company_id == company_id, model.deleted_at.is_(None), *extra, ) return int(query.scalar() or 0) def _sum_amount(db: Session, tenant_id: int, company_id: int, *extra) -> Decimal: total = ( db.query(func.coalesce(func.sum(Opportunity.amount), 0)) .filter( Opportunity.tenant_id == tenant_id, Opportunity.company_id == company_id, Opportunity.deleted_at.is_(None), *extra, ) .scalar() ) return Decimal(total or 0) def get_metrics(db: Session, tenant_id: int, company_id: int, pipeline_id: int | None = None) -> dict: """KPIs y embudo por etapa para el dashboard del CRM.""" open_opps = _count(db, Opportunity, tenant_id, company_id, Opportunity.status == "open") won_opps = _count(db, Opportunity, tenant_id, company_id, Opportunity.status == "won") # Embudo: oportunidades abiertas agrupadas por etapa stage_filters = [ PipelineStage.tenant_id == tenant_id, PipelineStage.company_id == company_id, PipelineStage.deleted_at.is_(None), ] if pipeline_id is not None: stage_filters.append(PipelineStage.pipeline_id == pipeline_id) rows = ( db.query( PipelineStage.id, PipelineStage.name, PipelineStage.position, func.count(Opportunity.id), func.coalesce(func.sum(Opportunity.amount), 0), ) .outerjoin( Opportunity, and_( Opportunity.stage_id == PipelineStage.id, Opportunity.status == "open", Opportunity.deleted_at.is_(None), Opportunity.tenant_id == tenant_id, Opportunity.company_id == company_id, ), ) .filter(*stage_filters) .group_by(PipelineStage.id, PipelineStage.name, PipelineStage.position) .order_by(PipelineStage.position.asc()) .all() ) by_stage = [ { "stage_id": row[0], "stage_name": row[1], "position": row[2], "count": int(row[3] or 0), "value": Decimal(row[4] or 0), } for row in rows ] return { "total_accounts": _count(db, Account, tenant_id, company_id), "total_contacts": _count(db, Contact, tenant_id, company_id), "total_leads": _count(db, Lead, tenant_id, company_id), "open_leads": _count(db, Lead, tenant_id, company_id, Lead.status != "converted"), "open_opportunities": open_opps, "open_pipeline_value": _sum_amount(db, tenant_id, company_id, Opportunity.status == "open"), "won_opportunities": won_opps, "won_value": _sum_amount(db, tenant_id, company_id, Opportunity.status == "won"), "pending_activities": _count(db, Activity, tenant_id, company_id, Activity.status == "pending"), "by_stage": by_stage, }