202 lines
6.3 KiB
Python
202 lines
6.3 KiB
Python
from datetime import datetime, timedelta
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from sqlalchemy import func
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.core.database import get_db
|
|
from app.core.security import get_current_user
|
|
from app.models.queue import Queue
|
|
from app.models.user import User, UserRole, UserStatus
|
|
from app.models.whatsapp import Conversation, ConversationStatus, Message
|
|
|
|
router = APIRouter(prefix="/api/supervisor", tags=["supervisor"])
|
|
|
|
|
|
def require_supervisor(current_user: User = Depends(get_current_user)) -> User:
|
|
if current_user.role not in [UserRole.ADMIN, UserRole.SUPERVISOR]:
|
|
raise HTTPException(status_code=403, detail="Supervisor or Admin required")
|
|
return current_user
|
|
|
|
|
|
@router.get("/dashboard")
|
|
def get_dashboard(
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_supervisor),
|
|
) -> dict:
|
|
"""Get supervisor dashboard data"""
|
|
now = datetime.utcnow()
|
|
today_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
|
|
status_counts = {}
|
|
for status in ConversationStatus:
|
|
count = db.query(Conversation).filter(Conversation.status == status).count()
|
|
status_counts[status.value] = count
|
|
|
|
waiting_conversations = db.query(Conversation).filter(
|
|
Conversation.status == ConversationStatus.WAITING
|
|
).count()
|
|
|
|
active_agents = db.query(User).filter(
|
|
User.status == UserStatus.ONLINE,
|
|
User.is_active == True,
|
|
).count()
|
|
|
|
total_agents = db.query(User).filter(
|
|
User.role.in_([UserRole.AGENT, UserRole.SUPERVISOR]),
|
|
User.is_active == True,
|
|
).count()
|
|
|
|
resolved_today = db.query(Conversation).filter(
|
|
Conversation.resolved_at >= today_start
|
|
).count()
|
|
|
|
messages_today = db.query(Message).filter(
|
|
Message.created_at >= today_start
|
|
).count()
|
|
|
|
csat_result = db.query(func.avg(Conversation.csat_score)).filter(
|
|
Conversation.csat_score != None,
|
|
Conversation.resolved_at >= today_start,
|
|
).scalar()
|
|
avg_csat = round(float(csat_result), 2) if csat_result else None
|
|
|
|
return {
|
|
"conversations": {
|
|
"by_status": status_counts,
|
|
"waiting": waiting_conversations,
|
|
"resolved_today": resolved_today,
|
|
},
|
|
"agents": {
|
|
"online": active_agents,
|
|
"total": total_agents,
|
|
},
|
|
"messages_today": messages_today,
|
|
"avg_csat": avg_csat,
|
|
}
|
|
|
|
|
|
@router.get("/agents")
|
|
def get_agents_status(
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_supervisor),
|
|
) -> list[dict]:
|
|
"""Get detailed agent status for supervisor view"""
|
|
agents = db.query(User).filter(
|
|
User.role.in_([UserRole.AGENT, UserRole.SUPERVISOR]),
|
|
User.is_active == True,
|
|
).all()
|
|
|
|
today_start = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)
|
|
result = []
|
|
|
|
for agent in agents:
|
|
active = db.query(Conversation).filter(
|
|
Conversation.assigned_to == agent.id,
|
|
Conversation.status == ConversationStatus.ACTIVE,
|
|
).count()
|
|
|
|
waiting = db.query(Conversation).filter(
|
|
Conversation.assigned_to == agent.id,
|
|
Conversation.status == ConversationStatus.WAITING,
|
|
).count()
|
|
|
|
resolved_today = db.query(Conversation).filter(
|
|
Conversation.assigned_to == agent.id,
|
|
Conversation.resolved_at >= today_start,
|
|
).count()
|
|
|
|
result.append({
|
|
"id": str(agent.id),
|
|
"name": agent.name,
|
|
"email": agent.email,
|
|
"role": agent.role.value,
|
|
"status": agent.status.value,
|
|
"active_conversations": active,
|
|
"waiting_conversations": waiting,
|
|
"resolved_today": resolved_today,
|
|
})
|
|
|
|
return result
|
|
|
|
|
|
@router.get("/queues")
|
|
def get_queues_status(
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_supervisor),
|
|
) -> list[dict]:
|
|
"""Get queue status for supervisor view"""
|
|
queues = db.query(Queue).filter(Queue.is_active == True).all()
|
|
|
|
result = []
|
|
for queue in queues:
|
|
waiting = db.query(Conversation).filter(
|
|
Conversation.queue_id == queue.id,
|
|
Conversation.status == ConversationStatus.WAITING,
|
|
).count()
|
|
|
|
active = db.query(Conversation).filter(
|
|
Conversation.queue_id == queue.id,
|
|
Conversation.status == ConversationStatus.ACTIVE,
|
|
).count()
|
|
|
|
online_agents = sum(
|
|
1 for qa in queue.agents
|
|
if qa.user and qa.user.status == UserStatus.ONLINE
|
|
)
|
|
|
|
result.append({
|
|
"id": str(queue.id),
|
|
"name": queue.name,
|
|
"waiting_conversations": waiting,
|
|
"active_conversations": active,
|
|
"online_agents": online_agents,
|
|
"total_agents": len(queue.agents),
|
|
"sla_first_response": queue.sla_first_response,
|
|
})
|
|
|
|
return result
|
|
|
|
|
|
@router.get("/conversations/critical")
|
|
def get_critical_conversations(
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_supervisor),
|
|
) -> list[dict]:
|
|
"""Get conversations that need attention"""
|
|
now = datetime.utcnow()
|
|
|
|
long_wait = db.query(Conversation).filter(
|
|
Conversation.status == ConversationStatus.WAITING,
|
|
Conversation.last_message_at < now - timedelta(minutes=5),
|
|
).all()
|
|
|
|
high_priority = db.query(Conversation).filter(
|
|
Conversation.priority.in_(["high", "urgent"]),
|
|
Conversation.status.in_([ConversationStatus.WAITING, ConversationStatus.ACTIVE]),
|
|
).all()
|
|
|
|
long_wait_ids = {conv.id for conv in long_wait}
|
|
result = []
|
|
seen_ids = set()
|
|
|
|
for conv in long_wait + high_priority:
|
|
if conv.id in seen_ids:
|
|
continue
|
|
seen_ids.add(conv.id)
|
|
|
|
reason = "long_wait" if conv.id in long_wait_ids else "high_priority"
|
|
|
|
result.append({
|
|
"id": str(conv.id),
|
|
"contact_name": conv.contact.name if conv.contact else "Unknown",
|
|
"contact_phone": conv.contact.phone_number if conv.contact else "",
|
|
"status": conv.status.value,
|
|
"priority": conv.priority,
|
|
"assigned_to": str(conv.assigned_to) if conv.assigned_to else None,
|
|
"last_message_at": conv.last_message_at.isoformat() if conv.last_message_at else None,
|
|
"reason": reason,
|
|
})
|
|
|
|
return result
|