changes
This commit is contained in:
838
app/services/deadline_reports.py
Normal file
838
app/services/deadline_reports.py
Normal file
@@ -0,0 +1,838 @@
|
||||
"""
|
||||
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())
|
||||
}
|
||||
Reference in New Issue
Block a user