from datetime import UTC, datetime from decimal import Decimal from fastapi import APIRouter from sqlalchemy import func, select from app.core.security import CurrentUser, Database from app.models import Activity, Contact, Lead, LeadSource, Stage from app.schemas import ( DashboardResponse, DashboardSourceMetric, DashboardStageMetric, ) from app.services import activity_load_options, activity_to_out, decimal_or_zero router = APIRouter(tags=["Dashboard"]) @router.get("/dashboard", response_model=DashboardResponse) def get_dashboard(user: CurrentUser, db: Database) -> DashboardResponse: tenant_filter = Lead.tenant_id == user.tenant_id total_revenue = db.scalar( select(func.sum(Lead.value)).where(tenant_filter, Lead.status == "won"), ) open_pipeline_value = db.scalar( select(func.sum(Lead.value)).where(tenant_filter, Lead.status == "open"), ) total_leads = db.scalar(select(func.count(Lead.id)).where(tenant_filter)) or 0 open_leads = db.scalar( select(func.count(Lead.id)).where(tenant_filter, Lead.status == "open"), ) or 0 won_leads = db.scalar( select(func.count(Lead.id)).where(tenant_filter, Lead.status == "won"), ) or 0 lost_leads = db.scalar( select(func.count(Lead.id)).where(tenant_filter, Lead.status == "lost"), ) or 0 total_contacts = db.scalar( select(func.count(Contact.id)).where(Contact.tenant_id == user.tenant_id), ) or 0 overdue_activities = db.scalar( select(func.count(Activity.id)).where( Activity.tenant_id == user.tenant_id, Activity.is_done.is_(False), Activity.due_at < datetime.now(UTC), ), ) or 0 stage_rows = db.execute( select( Stage.id, Stage.name, Stage.color, func.count(Lead.id), func.coalesce(func.sum(Lead.value), 0), ) .outerjoin( Lead, (Lead.stage_id == Stage.id) & (Lead.tenant_id == user.tenant_id), ) .where(Stage.tenant_id == user.tenant_id) .group_by(Stage.id, Stage.name, Stage.color, Stage.position) .order_by(Stage.position), ).all() source_rows = db.execute( select( func.coalesce(LeadSource.name, "Direct"), func.count(Lead.id), ) .select_from(Lead) .outerjoin(LeadSource, Lead.source_id == LeadSource.id) .where(Lead.tenant_id == user.tenant_id) .group_by(LeadSource.name) .order_by(func.count(Lead.id).desc()), ).all() recent = db.scalars( select(Activity) .where(Activity.tenant_id == user.tenant_id) .options(*activity_load_options()) .order_by(Activity.created_at.desc()) .limit(5), ).all() return DashboardResponse( total_revenue=decimal_or_zero(total_revenue), open_pipeline_value=decimal_or_zero(open_pipeline_value), total_leads=total_leads, open_leads=open_leads, won_leads=won_leads, lost_leads=lost_leads, total_contacts=total_contacts, overdue_activities=overdue_activities, stages=[ DashboardStageMetric( stage_id=row[0], stage_name=row[1], color=row[2], lead_count=row[3], total_value=Decimal(row[4]), ) for row in stage_rows ], sources=[ DashboardSourceMetric(source=row[0], lead_count=row[1]) for row in source_rows ], recent_activities=[activity_to_out(item) for item in recent], )