kamau1's picture
feat: add field agent stats and global work queue to user overview endpoint
ac99973
"""
Analytics & Statistics Endpoints
"""
from fastapi import APIRouter, Depends, HTTPException, status, Query
from sqlalchemy.orm import Session
from sqlalchemy import func, and_
from typing import Dict, Any
from datetime import datetime, timedelta
import logging
from app.api.deps import get_db, get_current_user, get_current_active_user
from app.core.permissions import require_role
from app.models.user import User
from app.models.client import Client
from app.models.contractor import Contractor
from app.models.ticket import Ticket
from app.models.ticket_assignment import TicketAssignment
from app.models.project import Project
from app.models.timesheet import Timesheet
from app.models.audit_log import AuditLog
logger = logging.getLogger(__name__)
router = APIRouter()
@router.get("/platform-admin/dashboard")
@require_role(["platform_admin"])
def get_platform_admin_dashboard_stats(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
) -> Dict[str, Any]:
"""
Get comprehensive statistics for Platform Admin dashboard
Returns:
- User statistics (total, active, by role)
- Organization statistics (clients, contractors)
- Ticket statistics (total, by status, by type)
- Project statistics (total, active)
- Recent activity (latest audit logs)
"""
# User Statistics
total_users = db.query(func.count(User.id)).scalar()
active_users = db.query(func.count(User.id)).filter(User.is_active == True).scalar()
users_by_role = db.query(
User.role,
func.count(User.id)
).group_by(User.role).all()
user_stats = {
"total": total_users,
"active": active_users,
"inactive": total_users - active_users,
"by_role": {role: count for role, count in users_by_role}
}
# Organization Statistics
total_clients = db.query(func.count(Client.id)).scalar()
active_clients = db.query(func.count(Client.id)).filter(Client.is_active == True).scalar()
total_contractors = db.query(func.count(Contractor.id)).scalar()
active_contractors = db.query(func.count(Contractor.id)).filter(Contractor.is_active == True).scalar()
organization_stats = {
"clients": {
"total": total_clients,
"active": active_clients,
"inactive": total_clients - active_clients
},
"contractors": {
"total": total_contractors,
"active": active_contractors,
"inactive": total_contractors - active_contractors
}
}
# Ticket Statistics
total_tickets = db.query(func.count(Ticket.id)).scalar()
tickets_by_status = db.query(
Ticket.status,
func.count(Ticket.id)
).group_by(Ticket.status).all()
tickets_by_type = db.query(
Ticket.ticket_type,
func.count(Ticket.id)
).group_by(Ticket.ticket_type).all()
ticket_stats = {
"total": total_tickets,
"by_status": {status: count for status, count in tickets_by_status},
"by_type": {ticket_type: count for ticket_type, count in tickets_by_type}
}
# Project Statistics
total_projects = db.query(func.count(Project.id)).scalar()
active_projects = db.query(func.count(Project.id)).filter(
Project.status.in_(["planning", "active", "on_hold"])
).scalar()
project_stats = {
"total": total_projects,
"active": active_projects
}
# Assignment Statistics
total_assignments = db.query(func.count(TicketAssignment.id)).scalar()
active_assignments = db.query(func.count(TicketAssignment.id)).filter(
TicketAssignment.status.in_(["assigned", "en_route", "in_progress"])
).scalar()
assignment_stats = {
"total": total_assignments,
"active": active_assignments
}
# Recent Activity (last 10 audit logs)
recent_activity = db.query(AuditLog).order_by(
AuditLog.created_at.desc()
).limit(10).all()
activity_items = [{
"id": str(log.id),
"user_email": log.user_email,
"action": log.action,
"entity_type": log.entity_type,
"description": log.description,
"created_at": log.created_at
} for log in recent_activity]
# System Health
thirty_days_ago = (datetime.utcnow() - timedelta(days=30)).isoformat()
new_users_30d = db.query(func.count(User.id)).filter(
User.created_at >= thirty_days_ago
).scalar()
new_tickets_30d = db.query(func.count(Ticket.id)).filter(
Ticket.created_at >= thirty_days_ago
).scalar()
system_health = {
"new_users_last_30_days": new_users_30d,
"new_tickets_last_30_days": new_tickets_30d
}
return {
"users": user_stats,
"organizations": organization_stats,
"tickets": ticket_stats,
"projects": project_stats,
"assignments": assignment_stats,
"recent_activity": activity_items,
"system_health": system_health
}
@router.get("/user/overview")
def get_user_overview(
limit: int = Query(50, ge=1, le=100, description="Max items in work queue (for field agents)"),
db: Session = Depends(get_db),
current_user: User = Depends(get_current_active_user)
) -> Dict[str, Any]:
"""
Get user overview dashboard (cross-project aggregated stats)
Perfect for landing page / home dashboard.
Shows aggregated statistics across all projects the user has access to.
Returns different stats based on user role:
- Projects (total, active)
- Team members (for managers)
- Tickets (total, open, in_progress)
- Unread notifications
- Expenses (for managers/admins)
- Sales orders (for sales roles)
- Inventory (for managers/admins)
**For Field Agents/Sales Agents:**
- field_agent_stats: hours worked, pending expenses, inventory on hand, tickets completed this week
- work_queue: List of pending ticket assignments sorted by execution order
**Query Parameters:**
- limit: Max items in work queue (default 50, max 100)
**Authorization:** Any authenticated user
"""
try:
from app.services.dashboard_service import DashboardService
overview = DashboardService.get_user_overview(db, current_user, limit)
return overview
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to get user overview: {str(e)}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to get user overview: {str(e)}"
)