838 lines
30 KiB
Python
838 lines
30 KiB
Python
"""
|
|
Deadline reporting and dashboard services
|
|
Provides comprehensive reporting and analytics for deadline management
|
|
"""
|
|
from typing import List, Dict, Any, Optional, Tuple
|
|
from datetime import datetime, date, timezone, timedelta
|
|
from sqlalchemy.orm import Session, joinedload
|
|
from sqlalchemy import and_, func, or_, desc, case, extract
|
|
from decimal import Decimal
|
|
|
|
from app.models import (
|
|
Deadline, DeadlineHistory, User, Employee, File, Rolodex,
|
|
DeadlineType, DeadlinePriority, DeadlineStatus, NotificationFrequency
|
|
)
|
|
from app.utils.logging import app_logger
|
|
|
|
logger = app_logger
|
|
|
|
|
|
class DeadlineReportService:
|
|
"""Service for deadline reporting and analytics"""
|
|
|
|
def __init__(self, db: Session):
|
|
self.db = db
|
|
|
|
def generate_upcoming_deadlines_report(
|
|
self,
|
|
start_date: date = None,
|
|
end_date: date = None,
|
|
employee_id: Optional[str] = None,
|
|
user_id: Optional[int] = None,
|
|
deadline_type: Optional[DeadlineType] = None,
|
|
priority: Optional[DeadlinePriority] = None
|
|
) -> Dict[str, Any]:
|
|
"""Generate comprehensive upcoming deadlines report"""
|
|
|
|
if start_date is None:
|
|
start_date = date.today()
|
|
|
|
if end_date is None:
|
|
end_date = start_date + timedelta(days=30)
|
|
|
|
# Build query
|
|
query = self.db.query(Deadline).filter(
|
|
Deadline.status == DeadlineStatus.PENDING,
|
|
Deadline.deadline_date.between(start_date, end_date)
|
|
)
|
|
|
|
if employee_id:
|
|
query = query.filter(Deadline.assigned_to_employee_id == employee_id)
|
|
|
|
if user_id:
|
|
query = query.filter(Deadline.assigned_to_user_id == user_id)
|
|
|
|
if deadline_type:
|
|
query = query.filter(Deadline.deadline_type == deadline_type)
|
|
|
|
if priority:
|
|
query = query.filter(Deadline.priority == priority)
|
|
|
|
deadlines = query.options(
|
|
joinedload(Deadline.file),
|
|
joinedload(Deadline.client),
|
|
joinedload(Deadline.assigned_to_user),
|
|
joinedload(Deadline.assigned_to_employee)
|
|
).order_by(
|
|
Deadline.deadline_date.asc(),
|
|
Deadline.priority.desc()
|
|
).all()
|
|
|
|
# Group deadlines by week
|
|
weeks = {}
|
|
for deadline in deadlines:
|
|
# Calculate week start (Monday)
|
|
days_since_monday = deadline.deadline_date.weekday()
|
|
week_start = deadline.deadline_date - timedelta(days=days_since_monday)
|
|
week_key = week_start.strftime("%Y-%m-%d")
|
|
|
|
if week_key not in weeks:
|
|
weeks[week_key] = {
|
|
"week_start": week_start,
|
|
"week_end": week_start + timedelta(days=6),
|
|
"deadlines": [],
|
|
"counts": {
|
|
"total": 0,
|
|
"critical": 0,
|
|
"high": 0,
|
|
"medium": 0,
|
|
"low": 0
|
|
}
|
|
}
|
|
|
|
deadline_data = {
|
|
"id": deadline.id,
|
|
"title": deadline.title,
|
|
"deadline_date": deadline.deadline_date,
|
|
"deadline_time": deadline.deadline_time,
|
|
"priority": deadline.priority.value,
|
|
"deadline_type": deadline.deadline_type.value,
|
|
"file_no": deadline.file_no,
|
|
"client_name": self._get_client_name(deadline),
|
|
"assigned_to": self._get_assigned_to(deadline),
|
|
"court_name": deadline.court_name,
|
|
"case_number": deadline.case_number,
|
|
"days_until": (deadline.deadline_date - date.today()).days
|
|
}
|
|
|
|
weeks[week_key]["deadlines"].append(deadline_data)
|
|
weeks[week_key]["counts"]["total"] += 1
|
|
weeks[week_key]["counts"][deadline.priority.value] += 1
|
|
|
|
# Sort weeks by date
|
|
sorted_weeks = sorted(weeks.values(), key=lambda x: x["week_start"])
|
|
|
|
# Calculate summary statistics
|
|
total_deadlines = len(deadlines)
|
|
priority_breakdown = {}
|
|
type_breakdown = {}
|
|
|
|
for priority in DeadlinePriority:
|
|
count = sum(1 for d in deadlines if d.priority == priority)
|
|
priority_breakdown[priority.value] = count
|
|
|
|
for deadline_type in DeadlineType:
|
|
count = sum(1 for d in deadlines if d.deadline_type == deadline_type)
|
|
type_breakdown[deadline_type.value] = count
|
|
|
|
return {
|
|
"report_period": {
|
|
"start_date": start_date,
|
|
"end_date": end_date,
|
|
"days": (end_date - start_date).days + 1
|
|
},
|
|
"filters": {
|
|
"employee_id": employee_id,
|
|
"user_id": user_id,
|
|
"deadline_type": deadline_type.value if deadline_type else None,
|
|
"priority": priority.value if priority else None
|
|
},
|
|
"summary": {
|
|
"total_deadlines": total_deadlines,
|
|
"priority_breakdown": priority_breakdown,
|
|
"type_breakdown": type_breakdown
|
|
},
|
|
"weeks": sorted_weeks
|
|
}
|
|
|
|
def generate_overdue_report(
|
|
self,
|
|
cutoff_date: date = None,
|
|
employee_id: Optional[str] = None,
|
|
user_id: Optional[int] = None
|
|
) -> Dict[str, Any]:
|
|
"""Generate report of overdue deadlines"""
|
|
|
|
if cutoff_date is None:
|
|
cutoff_date = date.today()
|
|
|
|
query = self.db.query(Deadline).filter(
|
|
Deadline.status == DeadlineStatus.PENDING,
|
|
Deadline.deadline_date < cutoff_date
|
|
)
|
|
|
|
if employee_id:
|
|
query = query.filter(Deadline.assigned_to_employee_id == employee_id)
|
|
|
|
if user_id:
|
|
query = query.filter(Deadline.assigned_to_user_id == user_id)
|
|
|
|
overdue_deadlines = query.options(
|
|
joinedload(Deadline.file),
|
|
joinedload(Deadline.client),
|
|
joinedload(Deadline.assigned_to_user),
|
|
joinedload(Deadline.assigned_to_employee)
|
|
).order_by(
|
|
Deadline.deadline_date.asc()
|
|
).all()
|
|
|
|
# Group by days overdue
|
|
overdue_groups = {
|
|
"1-3_days": [],
|
|
"4-7_days": [],
|
|
"8-30_days": [],
|
|
"over_30_days": []
|
|
}
|
|
|
|
for deadline in overdue_deadlines:
|
|
days_overdue = (cutoff_date - deadline.deadline_date).days
|
|
|
|
deadline_data = {
|
|
"id": deadline.id,
|
|
"title": deadline.title,
|
|
"deadline_date": deadline.deadline_date,
|
|
"priority": deadline.priority.value,
|
|
"deadline_type": deadline.deadline_type.value,
|
|
"file_no": deadline.file_no,
|
|
"client_name": self._get_client_name(deadline),
|
|
"assigned_to": self._get_assigned_to(deadline),
|
|
"days_overdue": days_overdue
|
|
}
|
|
|
|
if days_overdue <= 3:
|
|
overdue_groups["1-3_days"].append(deadline_data)
|
|
elif days_overdue <= 7:
|
|
overdue_groups["4-7_days"].append(deadline_data)
|
|
elif days_overdue <= 30:
|
|
overdue_groups["8-30_days"].append(deadline_data)
|
|
else:
|
|
overdue_groups["over_30_days"].append(deadline_data)
|
|
|
|
return {
|
|
"report_date": cutoff_date,
|
|
"filters": {
|
|
"employee_id": employee_id,
|
|
"user_id": user_id
|
|
},
|
|
"summary": {
|
|
"total_overdue": len(overdue_deadlines),
|
|
"by_timeframe": {
|
|
"1-3_days": len(overdue_groups["1-3_days"]),
|
|
"4-7_days": len(overdue_groups["4-7_days"]),
|
|
"8-30_days": len(overdue_groups["8-30_days"]),
|
|
"over_30_days": len(overdue_groups["over_30_days"])
|
|
}
|
|
},
|
|
"overdue_groups": overdue_groups
|
|
}
|
|
|
|
def generate_completion_report(
|
|
self,
|
|
start_date: date,
|
|
end_date: date,
|
|
employee_id: Optional[str] = None,
|
|
user_id: Optional[int] = None
|
|
) -> Dict[str, Any]:
|
|
"""Generate deadline completion performance report"""
|
|
|
|
# Get all deadlines that were due within the period
|
|
query = self.db.query(Deadline).filter(
|
|
Deadline.deadline_date.between(start_date, end_date)
|
|
)
|
|
|
|
if employee_id:
|
|
query = query.filter(Deadline.assigned_to_employee_id == employee_id)
|
|
|
|
if user_id:
|
|
query = query.filter(Deadline.assigned_to_user_id == user_id)
|
|
|
|
deadlines = query.options(
|
|
joinedload(Deadline.assigned_to_user),
|
|
joinedload(Deadline.assigned_to_employee)
|
|
).all()
|
|
|
|
# Calculate completion statistics
|
|
total_deadlines = len(deadlines)
|
|
completed_on_time = 0
|
|
completed_late = 0
|
|
still_pending = 0
|
|
missed = 0
|
|
|
|
completion_by_priority = {}
|
|
completion_by_type = {}
|
|
completion_by_assignee = {}
|
|
|
|
for deadline in deadlines:
|
|
# Determine completion status
|
|
if deadline.status == DeadlineStatus.COMPLETED:
|
|
if deadline.completed_date and deadline.completed_date.date() <= deadline.deadline_date:
|
|
completed_on_time += 1
|
|
status = "on_time"
|
|
else:
|
|
completed_late += 1
|
|
status = "late"
|
|
elif deadline.status == DeadlineStatus.PENDING:
|
|
if deadline.deadline_date < date.today():
|
|
missed += 1
|
|
status = "missed"
|
|
else:
|
|
still_pending += 1
|
|
status = "pending"
|
|
elif deadline.status == DeadlineStatus.CANCELLED:
|
|
status = "cancelled"
|
|
else:
|
|
status = "other"
|
|
|
|
# Track by priority
|
|
priority_key = deadline.priority.value
|
|
if priority_key not in completion_by_priority:
|
|
completion_by_priority[priority_key] = {
|
|
"total": 0, "on_time": 0, "late": 0, "missed": 0, "pending": 0, "cancelled": 0
|
|
}
|
|
completion_by_priority[priority_key]["total"] += 1
|
|
completion_by_priority[priority_key][status] += 1
|
|
|
|
# Track by type
|
|
type_key = deadline.deadline_type.value
|
|
if type_key not in completion_by_type:
|
|
completion_by_type[type_key] = {
|
|
"total": 0, "on_time": 0, "late": 0, "missed": 0, "pending": 0, "cancelled": 0
|
|
}
|
|
completion_by_type[type_key]["total"] += 1
|
|
completion_by_type[type_key][status] += 1
|
|
|
|
# Track by assignee
|
|
assignee = self._get_assigned_to(deadline) or "Unassigned"
|
|
if assignee not in completion_by_assignee:
|
|
completion_by_assignee[assignee] = {
|
|
"total": 0, "on_time": 0, "late": 0, "missed": 0, "pending": 0, "cancelled": 0
|
|
}
|
|
completion_by_assignee[assignee]["total"] += 1
|
|
completion_by_assignee[assignee][status] += 1
|
|
|
|
# Calculate completion rates
|
|
completed_total = completed_on_time + completed_late
|
|
on_time_rate = (completed_on_time / completed_total * 100) if completed_total > 0 else 0
|
|
completion_rate = (completed_total / total_deadlines * 100) if total_deadlines > 0 else 0
|
|
|
|
return {
|
|
"report_period": {
|
|
"start_date": start_date,
|
|
"end_date": end_date
|
|
},
|
|
"filters": {
|
|
"employee_id": employee_id,
|
|
"user_id": user_id
|
|
},
|
|
"summary": {
|
|
"total_deadlines": total_deadlines,
|
|
"completed_on_time": completed_on_time,
|
|
"completed_late": completed_late,
|
|
"still_pending": still_pending,
|
|
"missed": missed,
|
|
"on_time_rate": round(on_time_rate, 2),
|
|
"completion_rate": round(completion_rate, 2)
|
|
},
|
|
"breakdown": {
|
|
"by_priority": completion_by_priority,
|
|
"by_type": completion_by_type,
|
|
"by_assignee": completion_by_assignee
|
|
}
|
|
}
|
|
|
|
def generate_workload_report(
|
|
self,
|
|
target_date: date = None,
|
|
days_ahead: int = 30
|
|
) -> Dict[str, Any]:
|
|
"""Generate workload distribution report by assignee"""
|
|
|
|
if target_date is None:
|
|
target_date = date.today()
|
|
|
|
end_date = target_date + timedelta(days=days_ahead)
|
|
|
|
# Get pending deadlines in the timeframe
|
|
deadlines = self.db.query(Deadline).filter(
|
|
Deadline.status == DeadlineStatus.PENDING,
|
|
Deadline.deadline_date.between(target_date, end_date)
|
|
).options(
|
|
joinedload(Deadline.assigned_to_user),
|
|
joinedload(Deadline.assigned_to_employee),
|
|
joinedload(Deadline.file),
|
|
joinedload(Deadline.client)
|
|
).all()
|
|
|
|
# Group by assignee
|
|
workload_by_assignee = {}
|
|
unassigned_deadlines = []
|
|
|
|
for deadline in deadlines:
|
|
assignee = self._get_assigned_to(deadline)
|
|
|
|
if not assignee:
|
|
unassigned_deadlines.append({
|
|
"id": deadline.id,
|
|
"title": deadline.title,
|
|
"deadline_date": deadline.deadline_date,
|
|
"priority": deadline.priority.value,
|
|
"deadline_type": deadline.deadline_type.value,
|
|
"file_no": deadline.file_no
|
|
})
|
|
continue
|
|
|
|
if assignee not in workload_by_assignee:
|
|
workload_by_assignee[assignee] = {
|
|
"total_deadlines": 0,
|
|
"critical": 0,
|
|
"high": 0,
|
|
"medium": 0,
|
|
"low": 0,
|
|
"overdue": 0,
|
|
"due_this_week": 0,
|
|
"due_next_week": 0,
|
|
"deadlines": []
|
|
}
|
|
|
|
# Count by priority
|
|
workload_by_assignee[assignee]["total_deadlines"] += 1
|
|
workload_by_assignee[assignee][deadline.priority.value] += 1
|
|
|
|
# Count by timeframe
|
|
days_until = (deadline.deadline_date - target_date).days
|
|
if days_until < 0:
|
|
workload_by_assignee[assignee]["overdue"] += 1
|
|
elif days_until <= 7:
|
|
workload_by_assignee[assignee]["due_this_week"] += 1
|
|
elif days_until <= 14:
|
|
workload_by_assignee[assignee]["due_next_week"] += 1
|
|
|
|
workload_by_assignee[assignee]["deadlines"].append({
|
|
"id": deadline.id,
|
|
"title": deadline.title,
|
|
"deadline_date": deadline.deadline_date,
|
|
"priority": deadline.priority.value,
|
|
"deadline_type": deadline.deadline_type.value,
|
|
"file_no": deadline.file_no,
|
|
"days_until": days_until
|
|
})
|
|
|
|
# Sort assignees by workload
|
|
sorted_assignees = sorted(
|
|
workload_by_assignee.items(),
|
|
key=lambda x: (x[1]["critical"] + x[1]["high"], x[1]["total_deadlines"]),
|
|
reverse=True
|
|
)
|
|
|
|
return {
|
|
"report_date": target_date,
|
|
"timeframe_days": days_ahead,
|
|
"summary": {
|
|
"total_assignees": len(workload_by_assignee),
|
|
"total_deadlines": len(deadlines),
|
|
"unassigned_deadlines": len(unassigned_deadlines)
|
|
},
|
|
"workload_by_assignee": dict(sorted_assignees),
|
|
"unassigned_deadlines": unassigned_deadlines
|
|
}
|
|
|
|
def generate_trends_report(
|
|
self,
|
|
start_date: date,
|
|
end_date: date,
|
|
granularity: str = "month" # "week", "month", "quarter"
|
|
) -> Dict[str, Any]:
|
|
"""Generate deadline trends and analytics over time"""
|
|
|
|
# Get all deadlines created within the period
|
|
deadlines = self.db.query(Deadline).filter(
|
|
func.date(Deadline.created_at) >= start_date,
|
|
func.date(Deadline.created_at) <= end_date
|
|
).all()
|
|
|
|
# Group by time periods
|
|
periods = {}
|
|
|
|
for deadline in deadlines:
|
|
created_date = deadline.created_at.date()
|
|
|
|
if granularity == "week":
|
|
# Get Monday of the week
|
|
days_since_monday = created_date.weekday()
|
|
period_start = created_date - timedelta(days=days_since_monday)
|
|
period_key = period_start.strftime("%Y-W%U")
|
|
elif granularity == "month":
|
|
period_key = created_date.strftime("%Y-%m")
|
|
elif granularity == "quarter":
|
|
quarter = (created_date.month - 1) // 3 + 1
|
|
period_key = f"{created_date.year}-Q{quarter}"
|
|
else:
|
|
period_key = created_date.strftime("%Y-%m-%d")
|
|
|
|
if period_key not in periods:
|
|
periods[period_key] = {
|
|
"total_created": 0,
|
|
"completed": 0,
|
|
"missed": 0,
|
|
"pending": 0,
|
|
"by_type": {},
|
|
"by_priority": {},
|
|
"avg_completion_days": 0
|
|
}
|
|
|
|
periods[period_key]["total_created"] += 1
|
|
|
|
# Track completion status
|
|
if deadline.status == DeadlineStatus.COMPLETED:
|
|
periods[period_key]["completed"] += 1
|
|
elif deadline.status == DeadlineStatus.PENDING and deadline.deadline_date < date.today():
|
|
periods[period_key]["missed"] += 1
|
|
else:
|
|
periods[period_key]["pending"] += 1
|
|
|
|
# Track by type and priority
|
|
type_key = deadline.deadline_type.value
|
|
priority_key = deadline.priority.value
|
|
|
|
if type_key not in periods[period_key]["by_type"]:
|
|
periods[period_key]["by_type"][type_key] = 0
|
|
periods[period_key]["by_type"][type_key] += 1
|
|
|
|
if priority_key not in periods[period_key]["by_priority"]:
|
|
periods[period_key]["by_priority"][priority_key] = 0
|
|
periods[period_key]["by_priority"][priority_key] += 1
|
|
|
|
# Calculate trends
|
|
sorted_periods = sorted(periods.items())
|
|
|
|
return {
|
|
"report_period": {
|
|
"start_date": start_date,
|
|
"end_date": end_date,
|
|
"granularity": granularity
|
|
},
|
|
"summary": {
|
|
"total_periods": len(periods),
|
|
"total_deadlines": len(deadlines)
|
|
},
|
|
"trends": {
|
|
"by_period": sorted_periods
|
|
}
|
|
}
|
|
|
|
# Private helper methods
|
|
|
|
def _get_client_name(self, deadline: Deadline) -> Optional[str]:
|
|
"""Get formatted client name from deadline"""
|
|
|
|
if deadline.client:
|
|
return f"{deadline.client.first or ''} {deadline.client.last or ''}".strip()
|
|
elif deadline.file and deadline.file.owner:
|
|
return f"{deadline.file.owner.first or ''} {deadline.file.owner.last or ''}".strip()
|
|
return None
|
|
|
|
def _get_assigned_to(self, deadline: Deadline) -> Optional[str]:
|
|
"""Get assigned person name from deadline"""
|
|
|
|
if deadline.assigned_to_user:
|
|
return deadline.assigned_to_user.username
|
|
elif deadline.assigned_to_employee:
|
|
employee = deadline.assigned_to_employee
|
|
return f"{employee.first_name or ''} {employee.last_name or ''}".strip()
|
|
return None
|
|
|
|
|
|
class DeadlineDashboardService:
|
|
"""Service for deadline dashboard widgets and summaries"""
|
|
|
|
def __init__(self, db: Session):
|
|
self.db = db
|
|
self.report_service = DeadlineReportService(db)
|
|
|
|
def get_dashboard_widgets(
|
|
self,
|
|
user_id: Optional[int] = None,
|
|
employee_id: Optional[str] = None
|
|
) -> Dict[str, Any]:
|
|
"""Get all dashboard widgets for deadline management"""
|
|
|
|
today = date.today()
|
|
|
|
return {
|
|
"summary_cards": self._get_summary_cards(user_id, employee_id),
|
|
"upcoming_deadlines": self._get_upcoming_deadlines_widget(user_id, employee_id),
|
|
"overdue_alerts": self._get_overdue_alerts_widget(user_id, employee_id),
|
|
"priority_breakdown": self._get_priority_breakdown_widget(user_id, employee_id),
|
|
"recent_completions": self._get_recent_completions_widget(user_id, employee_id),
|
|
"weekly_calendar": self._get_weekly_calendar_widget(today, user_id, employee_id)
|
|
}
|
|
|
|
def _get_summary_cards(
|
|
self,
|
|
user_id: Optional[int] = None,
|
|
employee_id: Optional[str] = None
|
|
) -> List[Dict[str, Any]]:
|
|
"""Get summary cards for dashboard"""
|
|
|
|
base_query = self.db.query(Deadline).filter(Deadline.status == DeadlineStatus.PENDING)
|
|
|
|
if user_id:
|
|
base_query = base_query.filter(Deadline.assigned_to_user_id == user_id)
|
|
|
|
if employee_id:
|
|
base_query = base_query.filter(Deadline.assigned_to_employee_id == employee_id)
|
|
|
|
today = date.today()
|
|
|
|
# Calculate counts
|
|
total_pending = base_query.count()
|
|
overdue = base_query.filter(Deadline.deadline_date < today).count()
|
|
due_today = base_query.filter(Deadline.deadline_date == today).count()
|
|
due_this_week = base_query.filter(
|
|
Deadline.deadline_date.between(today, today + timedelta(days=7))
|
|
).count()
|
|
|
|
return [
|
|
{
|
|
"title": "Total Pending",
|
|
"value": total_pending,
|
|
"icon": "calendar",
|
|
"color": "blue"
|
|
},
|
|
{
|
|
"title": "Overdue",
|
|
"value": overdue,
|
|
"icon": "exclamation-triangle",
|
|
"color": "red"
|
|
},
|
|
{
|
|
"title": "Due Today",
|
|
"value": due_today,
|
|
"icon": "clock",
|
|
"color": "orange"
|
|
},
|
|
{
|
|
"title": "Due This Week",
|
|
"value": due_this_week,
|
|
"icon": "calendar-week",
|
|
"color": "green"
|
|
}
|
|
]
|
|
|
|
def _get_upcoming_deadlines_widget(
|
|
self,
|
|
user_id: Optional[int] = None,
|
|
employee_id: Optional[str] = None,
|
|
limit: int = 5
|
|
) -> Dict[str, Any]:
|
|
"""Get upcoming deadlines widget"""
|
|
|
|
query = self.db.query(Deadline).filter(
|
|
Deadline.status == DeadlineStatus.PENDING,
|
|
Deadline.deadline_date >= date.today()
|
|
)
|
|
|
|
if user_id:
|
|
query = query.filter(Deadline.assigned_to_user_id == user_id)
|
|
|
|
if employee_id:
|
|
query = query.filter(Deadline.assigned_to_employee_id == employee_id)
|
|
|
|
deadlines = query.options(
|
|
joinedload(Deadline.file),
|
|
joinedload(Deadline.client)
|
|
).order_by(
|
|
Deadline.deadline_date.asc(),
|
|
Deadline.priority.desc()
|
|
).limit(limit).all()
|
|
|
|
return {
|
|
"title": "Upcoming Deadlines",
|
|
"deadlines": [
|
|
{
|
|
"id": d.id,
|
|
"title": d.title,
|
|
"deadline_date": d.deadline_date,
|
|
"priority": d.priority.value,
|
|
"deadline_type": d.deadline_type.value,
|
|
"file_no": d.file_no,
|
|
"client_name": self.report_service._get_client_name(d),
|
|
"days_until": (d.deadline_date - date.today()).days
|
|
}
|
|
for d in deadlines
|
|
]
|
|
}
|
|
|
|
def _get_overdue_alerts_widget(
|
|
self,
|
|
user_id: Optional[int] = None,
|
|
employee_id: Optional[str] = None
|
|
) -> Dict[str, Any]:
|
|
"""Get overdue alerts widget"""
|
|
|
|
query = self.db.query(Deadline).filter(
|
|
Deadline.status == DeadlineStatus.PENDING,
|
|
Deadline.deadline_date < date.today()
|
|
)
|
|
|
|
if user_id:
|
|
query = query.filter(Deadline.assigned_to_user_id == user_id)
|
|
|
|
if employee_id:
|
|
query = query.filter(Deadline.assigned_to_employee_id == employee_id)
|
|
|
|
overdue_deadlines = query.options(
|
|
joinedload(Deadline.file),
|
|
joinedload(Deadline.client)
|
|
).order_by(
|
|
Deadline.deadline_date.asc()
|
|
).limit(10).all()
|
|
|
|
return {
|
|
"title": "Overdue Deadlines",
|
|
"count": len(overdue_deadlines),
|
|
"deadlines": [
|
|
{
|
|
"id": d.id,
|
|
"title": d.title,
|
|
"deadline_date": d.deadline_date,
|
|
"priority": d.priority.value,
|
|
"file_no": d.file_no,
|
|
"client_name": self.report_service._get_client_name(d),
|
|
"days_overdue": (date.today() - d.deadline_date).days
|
|
}
|
|
for d in overdue_deadlines
|
|
]
|
|
}
|
|
|
|
def _get_priority_breakdown_widget(
|
|
self,
|
|
user_id: Optional[int] = None,
|
|
employee_id: Optional[str] = None
|
|
) -> Dict[str, Any]:
|
|
"""Get priority breakdown widget"""
|
|
|
|
base_query = self.db.query(Deadline).filter(Deadline.status == DeadlineStatus.PENDING)
|
|
|
|
if user_id:
|
|
base_query = base_query.filter(Deadline.assigned_to_user_id == user_id)
|
|
|
|
if employee_id:
|
|
base_query = base_query.filter(Deadline.assigned_to_employee_id == employee_id)
|
|
|
|
breakdown = {}
|
|
for priority in DeadlinePriority:
|
|
count = base_query.filter(Deadline.priority == priority).count()
|
|
breakdown[priority.value] = count
|
|
|
|
return {
|
|
"title": "Priority Breakdown",
|
|
"breakdown": breakdown
|
|
}
|
|
|
|
def _get_recent_completions_widget(
|
|
self,
|
|
user_id: Optional[int] = None,
|
|
employee_id: Optional[str] = None,
|
|
days_back: int = 7
|
|
) -> Dict[str, Any]:
|
|
"""Get recent completions widget"""
|
|
|
|
cutoff_date = date.today() - timedelta(days=days_back)
|
|
|
|
query = self.db.query(Deadline).filter(
|
|
Deadline.status == DeadlineStatus.COMPLETED,
|
|
func.date(Deadline.completed_date) >= cutoff_date
|
|
)
|
|
|
|
if user_id:
|
|
query = query.filter(Deadline.assigned_to_user_id == user_id)
|
|
|
|
if employee_id:
|
|
query = query.filter(Deadline.assigned_to_employee_id == employee_id)
|
|
|
|
completed = query.options(
|
|
joinedload(Deadline.file),
|
|
joinedload(Deadline.client)
|
|
).order_by(
|
|
Deadline.completed_date.desc()
|
|
).limit(5).all()
|
|
|
|
return {
|
|
"title": "Recently Completed",
|
|
"count": len(completed),
|
|
"deadlines": [
|
|
{
|
|
"id": d.id,
|
|
"title": d.title,
|
|
"deadline_date": d.deadline_date,
|
|
"completed_date": d.completed_date.date() if d.completed_date else None,
|
|
"priority": d.priority.value,
|
|
"file_no": d.file_no,
|
|
"client_name": self.report_service._get_client_name(d),
|
|
"on_time": d.completed_date.date() <= d.deadline_date if d.completed_date else False
|
|
}
|
|
for d in completed
|
|
]
|
|
}
|
|
|
|
def _get_weekly_calendar_widget(
|
|
self,
|
|
week_start: date,
|
|
user_id: Optional[int] = None,
|
|
employee_id: Optional[str] = None
|
|
) -> Dict[str, Any]:
|
|
"""Get weekly calendar widget"""
|
|
|
|
# Adjust to Monday
|
|
days_since_monday = week_start.weekday()
|
|
monday = week_start - timedelta(days=days_since_monday)
|
|
sunday = monday + timedelta(days=6)
|
|
|
|
query = self.db.query(Deadline).filter(
|
|
Deadline.status == DeadlineStatus.PENDING,
|
|
Deadline.deadline_date.between(monday, sunday)
|
|
)
|
|
|
|
if user_id:
|
|
query = query.filter(Deadline.assigned_to_user_id == user_id)
|
|
|
|
if employee_id:
|
|
query = query.filter(Deadline.assigned_to_employee_id == employee_id)
|
|
|
|
deadlines = query.options(
|
|
joinedload(Deadline.file),
|
|
joinedload(Deadline.client)
|
|
).order_by(
|
|
Deadline.deadline_date.asc(),
|
|
Deadline.deadline_time.asc()
|
|
).all()
|
|
|
|
# Group by day
|
|
calendar_days = {}
|
|
for i in range(7):
|
|
day = monday + timedelta(days=i)
|
|
calendar_days[day.strftime("%Y-%m-%d")] = {
|
|
"date": day,
|
|
"day_name": day.strftime("%A"),
|
|
"deadlines": []
|
|
}
|
|
|
|
for deadline in deadlines:
|
|
day_key = deadline.deadline_date.strftime("%Y-%m-%d")
|
|
if day_key in calendar_days:
|
|
calendar_days[day_key]["deadlines"].append({
|
|
"id": deadline.id,
|
|
"title": deadline.title,
|
|
"deadline_time": deadline.deadline_time,
|
|
"priority": deadline.priority.value,
|
|
"deadline_type": deadline.deadline_type.value,
|
|
"file_no": deadline.file_no
|
|
})
|
|
|
|
return {
|
|
"title": "This Week",
|
|
"week_start": monday,
|
|
"week_end": sunday,
|
|
"days": list(calendar_days.values())
|
|
} |