""" Deadline management API endpoints """ from typing import List, Optional, Dict, Any from datetime import date, datetime from fastapi import APIRouter, Depends, HTTPException, status, Query from sqlalchemy.orm import Session from pydantic import BaseModel, Field, ConfigDict from app.database.base import get_db from app.models import ( Deadline, DeadlineTemplate, DeadlineHistory, User, DeadlineType, DeadlinePriority, DeadlineStatus, NotificationFrequency ) from app.services.deadlines import DeadlineService, DeadlineTemplateService, DeadlineManagementError from app.services.deadline_notifications import DeadlineNotificationService, DeadlineAlertManager from app.services.deadline_reports import DeadlineReportService, DeadlineDashboardService from app.services.deadline_calendar import DeadlineCalendarService, CalendarExportService from app.auth.security import get_current_user from app.utils.logging import app_logger router = APIRouter() logger = app_logger # Pydantic schemas for requests/responses class DeadlineCreateRequest(BaseModel): """Request to create a new deadline""" title: str = Field(..., min_length=1, max_length=200, description="Deadline title") description: Optional[str] = Field(None, description="Detailed description") deadline_date: date = Field(..., description="Deadline date") deadline_time: Optional[datetime] = Field(None, description="Specific deadline time") deadline_type: DeadlineType = Field(DeadlineType.OTHER, description="Type of deadline") priority: DeadlinePriority = Field(DeadlinePriority.MEDIUM, description="Priority level") file_no: Optional[str] = Field(None, description="Associated file number") client_id: Optional[str] = Field(None, description="Associated client ID") assigned_to_user_id: Optional[int] = Field(None, description="Assigned user ID") assigned_to_employee_id: Optional[str] = Field(None, description="Assigned employee ID") court_name: Optional[str] = Field(None, description="Court name if applicable") case_number: Optional[str] = Field(None, description="Case number if applicable") advance_notice_days: int = Field(7, ge=0, le=365, description="Days advance notice") notification_frequency: NotificationFrequency = Field(NotificationFrequency.WEEKLY, description="Notification frequency") class DeadlineUpdateRequest(BaseModel): """Request to update a deadline""" title: Optional[str] = Field(None, min_length=1, max_length=200) description: Optional[str] = None deadline_date: Optional[date] = None deadline_time: Optional[datetime] = None deadline_type: Optional[DeadlineType] = None priority: Optional[DeadlinePriority] = None assigned_to_user_id: Optional[int] = None assigned_to_employee_id: Optional[str] = None court_name: Optional[str] = None case_number: Optional[str] = None advance_notice_days: Optional[int] = Field(None, ge=0, le=365) notification_frequency: Optional[NotificationFrequency] = None class DeadlineCompleteRequest(BaseModel): """Request to complete a deadline""" completion_notes: Optional[str] = Field(None, description="Notes about completion") class DeadlineExtendRequest(BaseModel): """Request to extend a deadline""" new_deadline_date: date = Field(..., description="New deadline date") extension_reason: Optional[str] = Field(None, description="Reason for extension") extension_granted_by: Optional[str] = Field(None, description="Who granted the extension") class DeadlineCancelRequest(BaseModel): """Request to cancel a deadline""" cancellation_reason: Optional[str] = Field(None, description="Reason for cancellation") class DeadlineResponse(BaseModel): """Response for deadline data""" id: int title: str description: Optional[str] = None deadline_date: date deadline_time: Optional[datetime] = None deadline_type: DeadlineType priority: DeadlinePriority status: DeadlineStatus file_no: Optional[str] = None client_id: Optional[str] = None assigned_to_user_id: Optional[int] = None assigned_to_employee_id: Optional[str] = None court_name: Optional[str] = None case_number: Optional[str] = None advance_notice_days: int notification_frequency: NotificationFrequency completed_date: Optional[datetime] = None completion_notes: Optional[str] = None original_deadline_date: Optional[date] = None extension_reason: Optional[str] = None extension_granted_by: Optional[str] = None created_at: datetime updated_at: datetime is_overdue: bool = False days_until_deadline: int = 0 model_config = ConfigDict(from_attributes=True) class DeadlineTemplateCreateRequest(BaseModel): """Request to create a deadline template""" name: str = Field(..., min_length=1, max_length=200) description: Optional[str] = None deadline_type: DeadlineType = Field(..., description="Type of deadline") priority: DeadlinePriority = Field(DeadlinePriority.MEDIUM, description="Default priority") default_title_template: Optional[str] = Field(None, description="Title template with placeholders") default_description_template: Optional[str] = Field(None, description="Description template") default_advance_notice_days: int = Field(7, ge=0, le=365) default_notification_frequency: NotificationFrequency = Field(NotificationFrequency.WEEKLY) days_from_file_open: Optional[int] = Field(None, ge=0, description="Days from file open date") days_from_event: Optional[int] = Field(None, ge=0, description="Days from triggering event") class DeadlineTemplateResponse(BaseModel): """Response for deadline template data""" id: int name: str description: Optional[str] = None deadline_type: DeadlineType priority: DeadlinePriority default_title_template: Optional[str] = None default_description_template: Optional[str] = None default_advance_notice_days: int default_notification_frequency: NotificationFrequency days_from_file_open: Optional[int] = None days_from_event: Optional[int] = None active: bool created_at: datetime model_config = ConfigDict(from_attributes=True) class DeadlineStatisticsResponse(BaseModel): """Response for deadline statistics""" total_deadlines: int pending_deadlines: int completed_deadlines: int overdue_deadlines: int completion_rate: float priority_breakdown: Dict[str, int] type_breakdown: Dict[str, int] upcoming: Dict[str, int] class DeadlineFromTemplateRequest(BaseModel): """Request to create deadline from template""" template_id: int = Field(..., description="Template ID to use") file_no: Optional[str] = Field(None, description="File number for context") client_id: Optional[str] = Field(None, description="Client ID for context") deadline_date: Optional[date] = Field(None, description="Override calculated deadline date") title: Optional[str] = Field(None, description="Override template title") description: Optional[str] = Field(None, description="Override template description") priority: Optional[DeadlinePriority] = Field(None, description="Override template priority") assigned_to_user_id: Optional[int] = Field(None, description="Assign to specific user") assigned_to_employee_id: Optional[str] = Field(None, description="Assign to specific employee") # Deadline CRUD endpoints @router.post("/", response_model=DeadlineResponse) async def create_deadline( request: DeadlineCreateRequest, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Create a new deadline""" try: service = DeadlineService(db) deadline = service.create_deadline( title=request.title, description=request.description, deadline_date=request.deadline_date, deadline_time=request.deadline_time, deadline_type=request.deadline_type, priority=request.priority, file_no=request.file_no, client_id=request.client_id, assigned_to_user_id=request.assigned_to_user_id, assigned_to_employee_id=request.assigned_to_employee_id, court_name=request.court_name, case_number=request.case_number, advance_notice_days=request.advance_notice_days, notification_frequency=request.notification_frequency, created_by_user_id=current_user.id ) # Add computed properties response = DeadlineResponse.model_validate(deadline) response.is_overdue = deadline.is_overdue response.days_until_deadline = deadline.days_until_deadline return response except DeadlineManagementError as e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(e) ) @router.get("/", response_model=List[DeadlineResponse]) async def get_deadlines( file_no: Optional[str] = Query(None, description="Filter by file number"), client_id: Optional[str] = Query(None, description="Filter by client ID"), assigned_to_user_id: Optional[int] = Query(None, description="Filter by assigned user"), assigned_to_employee_id: Optional[str] = Query(None, description="Filter by assigned employee"), deadline_type: Optional[DeadlineType] = Query(None, description="Filter by deadline type"), priority: Optional[DeadlinePriority] = Query(None, description="Filter by priority"), status: Optional[DeadlineStatus] = Query(None, description="Filter by status"), upcoming_days: Optional[int] = Query(None, ge=1, le=365, description="Filter upcoming deadlines within N days"), overdue_only: bool = Query(False, description="Show only overdue deadlines"), limit: int = Query(100, ge=1, le=500, description="Maximum number of results"), db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Get deadlines with optional filtering""" service = DeadlineService(db) if overdue_only: deadlines = service.get_overdue_deadlines( user_id=assigned_to_user_id, employee_id=assigned_to_employee_id ) elif upcoming_days: deadlines = service.get_upcoming_deadlines( days_ahead=upcoming_days, user_id=assigned_to_user_id, employee_id=assigned_to_employee_id, priority=priority, deadline_type=deadline_type ) else: # Build custom query query = db.query(Deadline) if file_no: query = query.filter(Deadline.file_no == file_no) if client_id: query = query.filter(Deadline.client_id == client_id) if assigned_to_user_id: query = query.filter(Deadline.assigned_to_user_id == assigned_to_user_id) if assigned_to_employee_id: query = query.filter(Deadline.assigned_to_employee_id == assigned_to_employee_id) if deadline_type: query = query.filter(Deadline.deadline_type == deadline_type) if priority: query = query.filter(Deadline.priority == priority) if status: query = query.filter(Deadline.status == status) deadlines = query.order_by(Deadline.deadline_date.asc()).limit(limit).all() # Convert to response format with computed properties responses = [] for deadline in deadlines: response = DeadlineResponse.model_validate(deadline) response.is_overdue = deadline.is_overdue response.days_until_deadline = deadline.days_until_deadline responses.append(response) return responses @router.get("/{deadline_id}", response_model=DeadlineResponse) async def get_deadline( deadline_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Get a specific deadline by ID""" deadline = db.query(Deadline).filter(Deadline.id == deadline_id).first() if not deadline: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Deadline not found" ) response = DeadlineResponse.model_validate(deadline) response.is_overdue = deadline.is_overdue response.days_until_deadline = deadline.days_until_deadline return response @router.put("/{deadline_id}", response_model=DeadlineResponse) async def update_deadline( deadline_id: int, request: DeadlineUpdateRequest, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Update a deadline""" try: service = DeadlineService(db) # Only update provided fields updates = {k: v for k, v in request.model_dump(exclude_unset=True).items() if v is not None} deadline = service.update_deadline( deadline_id=deadline_id, user_id=current_user.id, **updates ) response = DeadlineResponse.model_validate(deadline) response.is_overdue = deadline.is_overdue response.days_until_deadline = deadline.days_until_deadline return response except DeadlineManagementError as e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(e) ) @router.delete("/{deadline_id}") async def delete_deadline( deadline_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Delete a deadline""" deadline = db.query(Deadline).filter(Deadline.id == deadline_id).first() if not deadline: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Deadline not found" ) db.delete(deadline) db.commit() return {"message": "Deadline deleted successfully"} # Deadline action endpoints @router.post("/{deadline_id}/complete", response_model=DeadlineResponse) async def complete_deadline( deadline_id: int, request: DeadlineCompleteRequest, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Mark a deadline as completed""" try: service = DeadlineService(db) deadline = service.complete_deadline( deadline_id=deadline_id, user_id=current_user.id, completion_notes=request.completion_notes ) response = DeadlineResponse.model_validate(deadline) response.is_overdue = deadline.is_overdue response.days_until_deadline = deadline.days_until_deadline return response except DeadlineManagementError as e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(e) ) @router.post("/{deadline_id}/extend", response_model=DeadlineResponse) async def extend_deadline( deadline_id: int, request: DeadlineExtendRequest, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Extend a deadline to a new date""" try: service = DeadlineService(db) deadline = service.extend_deadline( deadline_id=deadline_id, new_deadline_date=request.new_deadline_date, user_id=current_user.id, extension_reason=request.extension_reason, extension_granted_by=request.extension_granted_by ) response = DeadlineResponse.model_validate(deadline) response.is_overdue = deadline.is_overdue response.days_until_deadline = deadline.days_until_deadline return response except DeadlineManagementError as e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(e) ) @router.post("/{deadline_id}/cancel", response_model=DeadlineResponse) async def cancel_deadline( deadline_id: int, request: DeadlineCancelRequest, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Cancel a deadline""" try: service = DeadlineService(db) deadline = service.cancel_deadline( deadline_id=deadline_id, user_id=current_user.id, cancellation_reason=request.cancellation_reason ) response = DeadlineResponse.model_validate(deadline) response.is_overdue = deadline.is_overdue response.days_until_deadline = deadline.days_until_deadline return response except DeadlineManagementError as e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(e) ) # Deadline template endpoints @router.post("/templates/", response_model=DeadlineTemplateResponse) async def create_deadline_template( request: DeadlineTemplateCreateRequest, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Create a new deadline template""" try: service = DeadlineTemplateService(db) template = service.create_template( name=request.name, description=request.description, deadline_type=request.deadline_type, priority=request.priority, default_title_template=request.default_title_template, default_description_template=request.default_description_template, default_advance_notice_days=request.default_advance_notice_days, default_notification_frequency=request.default_notification_frequency, days_from_file_open=request.days_from_file_open, days_from_event=request.days_from_event, user_id=current_user.id ) return DeadlineTemplateResponse.model_validate(template) except DeadlineManagementError as e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(e) ) @router.get("/templates/", response_model=List[DeadlineTemplateResponse]) async def get_deadline_templates( deadline_type: Optional[DeadlineType] = Query(None, description="Filter by deadline type"), active_only: bool = Query(True, description="Return only active templates"), db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Get deadline templates""" service = DeadlineTemplateService(db) if active_only: templates = service.get_active_templates(deadline_type=deadline_type) else: query = db.query(DeadlineTemplate) if deadline_type: query = query.filter(DeadlineTemplate.deadline_type == deadline_type) templates = query.order_by(DeadlineTemplate.name).all() return [DeadlineTemplateResponse.model_validate(t) for t in templates] @router.post("/from-template/", response_model=DeadlineResponse) async def create_deadline_from_template( request: DeadlineFromTemplateRequest, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Create a deadline from a template""" try: service = DeadlineService(db) # Build overrides from request overrides = {} if request.title: overrides['title'] = request.title if request.description: overrides['description'] = request.description if request.priority: overrides['priority'] = request.priority if request.assigned_to_user_id: overrides['assigned_to_user_id'] = request.assigned_to_user_id if request.assigned_to_employee_id: overrides['assigned_to_employee_id'] = request.assigned_to_employee_id deadline = service.create_deadline_from_template( template_id=request.template_id, user_id=current_user.id, file_no=request.file_no, client_id=request.client_id, deadline_date=request.deadline_date, **overrides ) response = DeadlineResponse.model_validate(deadline) response.is_overdue = deadline.is_overdue response.days_until_deadline = deadline.days_until_deadline return response except DeadlineManagementError as e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(e) ) # Statistics and reporting endpoints @router.get("/statistics/", response_model=DeadlineStatisticsResponse) async def get_deadline_statistics( user_id: Optional[int] = Query(None, description="Filter by user ID"), employee_id: Optional[str] = Query(None, description="Filter by employee ID"), start_date: Optional[date] = Query(None, description="Start date for filtering"), end_date: Optional[date] = Query(None, description="End date for filtering"), db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Get deadline statistics for reporting""" service = DeadlineService(db) stats = service.get_deadline_statistics( user_id=user_id, employee_id=employee_id, start_date=start_date, end_date=end_date ) return DeadlineStatisticsResponse(**stats) @router.get("/{deadline_id}/history/") async def get_deadline_history( deadline_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Get history of changes for a deadline""" # Verify deadline exists deadline = db.query(Deadline).filter(Deadline.id == deadline_id).first() if not deadline: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Deadline not found" ) history = db.query(DeadlineHistory).filter( DeadlineHistory.deadline_id == deadline_id ).order_by(DeadlineHistory.change_date.desc()).all() return [ { "id": h.id, "change_type": h.change_type, "field_changed": h.field_changed, "old_value": h.old_value, "new_value": h.new_value, "change_reason": h.change_reason, "user_id": h.user_id, "change_date": h.change_date } for h in history ] # Utility endpoints @router.get("/types/") async def get_deadline_types(): """Get available deadline types""" return [{"value": dt.value, "name": dt.value.replace("_", " ").title()} for dt in DeadlineType] @router.get("/priorities/") async def get_deadline_priorities(): """Get available deadline priorities""" return [{"value": dp.value, "name": dp.value.replace("_", " ").title()} for dp in DeadlinePriority] @router.get("/statuses/") async def get_deadline_statuses(): """Get available deadline statuses""" return [{"value": ds.value, "name": ds.value.replace("_", " ").title()} for ds in DeadlineStatus] @router.get("/notification-frequencies/") async def get_notification_frequencies(): """Get available notification frequencies""" return [{"value": nf.value, "name": nf.value.replace("_", " ").title()} for nf in NotificationFrequency] # Notification and alert endpoints @router.get("/alerts/urgent/") async def get_urgent_alerts( user_id: Optional[int] = Query(None, description="Filter by user ID"), employee_id: Optional[str] = Query(None, description="Filter by employee ID"), db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Get urgent deadline alerts that need immediate attention""" notification_service = DeadlineNotificationService(db) # If no filters provided, default to current user if not user_id and not employee_id: user_id = current_user.id alerts = notification_service.get_urgent_alerts( user_id=user_id, employee_id=employee_id ) return alerts @router.get("/dashboard/summary/") async def get_dashboard_summary( user_id: Optional[int] = Query(None, description="Filter by user ID"), employee_id: Optional[str] = Query(None, description="Filter by employee ID"), db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Get deadline summary for dashboard display""" notification_service = DeadlineNotificationService(db) # If no filters provided, default to current user if not user_id and not employee_id: user_id = current_user.id summary = notification_service.get_dashboard_summary( user_id=user_id, employee_id=employee_id ) return summary class AdhocReminderRequest(BaseModel): """Request to create an ad-hoc reminder""" recipient_user_id: int = Field(..., description="User to receive the reminder") reminder_date: date = Field(..., description="When to send the reminder") custom_message: Optional[str] = Field(None, description="Custom reminder message") @router.post("/{deadline_id}/reminders/", response_model=dict) async def create_adhoc_reminder( deadline_id: int, request: AdhocReminderRequest, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Create an ad-hoc reminder for a deadline""" try: notification_service = DeadlineNotificationService(db) reminder = notification_service.create_adhoc_reminder( deadline_id=deadline_id, recipient_user_id=request.recipient_user_id, reminder_date=request.reminder_date, custom_message=request.custom_message ) return { "message": "Ad-hoc reminder created successfully", "reminder_id": reminder.id, "recipient_user_id": reminder.recipient_user_id, "reminder_date": reminder.reminder_date } except ValueError as e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(e) ) class CourtDateRemindersRequest(BaseModel): """Request to schedule court date reminders""" court_date: date = Field(..., description="Court appearance date") preparation_days: int = Field(7, ge=1, le=30, description="Days needed for preparation") @router.post("/{deadline_id}/court-reminders/") async def schedule_court_date_reminders( deadline_id: int, request: CourtDateRemindersRequest, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Schedule special reminders for court dates with preparation milestones""" try: notification_service = DeadlineNotificationService(db) notification_service.schedule_court_date_reminders( deadline_id=deadline_id, court_date=request.court_date, preparation_days=request.preparation_days ) return { "message": "Court date reminders scheduled successfully", "court_date": request.court_date, "preparation_days": request.preparation_days } except ValueError as e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(e) ) @router.get("/notifications/preferences/") async def get_notification_preferences( user_id: Optional[int] = Query(None, description="User ID (defaults to current user)"), db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Get user's notification preferences""" notification_service = DeadlineNotificationService(db) target_user_id = user_id or current_user.id preferences = notification_service.get_notification_preferences(target_user_id) return preferences @router.post("/alerts/process-daily/") async def process_daily_alerts( process_date: Optional[date] = Query(None, description="Date to process (defaults to today)"), db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Process daily deadline alerts and reminders (admin function)""" try: alert_manager = DeadlineAlertManager(db) results = alert_manager.run_daily_alert_processing(process_date) return results except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to process daily alerts: {str(e)}" ) @router.get("/alerts/overdue-escalations/") async def get_overdue_escalations( escalation_days: int = Query(1, ge=1, le=30, description="Days overdue before escalation"), db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Get deadlines that need escalation due to being overdue""" alert_manager = DeadlineAlertManager(db) escalations = alert_manager.escalate_overdue_deadlines(escalation_days) return { "escalation_days": escalation_days, "total_escalations": len(escalations), "escalations": escalations } # Reporting endpoints @router.get("/reports/upcoming/") async def get_upcoming_deadlines_report( start_date: Optional[date] = Query(None, description="Start date for report"), end_date: Optional[date] = Query(None, description="End date for report"), employee_id: Optional[str] = Query(None, description="Filter by employee ID"), user_id: Optional[int] = Query(None, description="Filter by user ID"), deadline_type: Optional[DeadlineType] = Query(None, description="Filter by deadline type"), priority: Optional[DeadlinePriority] = Query(None, description="Filter by priority"), db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Generate upcoming deadlines report""" report_service = DeadlineReportService(db) report = report_service.generate_upcoming_deadlines_report( start_date=start_date, end_date=end_date, employee_id=employee_id, user_id=user_id, deadline_type=deadline_type, priority=priority ) return report @router.get("/reports/overdue/") async def get_overdue_report( cutoff_date: Optional[date] = Query(None, description="Cutoff date (defaults to today)"), employee_id: Optional[str] = Query(None, description="Filter by employee ID"), user_id: Optional[int] = Query(None, description="Filter by user ID"), db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Generate overdue deadlines report""" report_service = DeadlineReportService(db) report = report_service.generate_overdue_report( cutoff_date=cutoff_date, employee_id=employee_id, user_id=user_id ) return report @router.get("/reports/completion/") async def get_completion_report( start_date: date = Query(..., description="Start date for report period"), end_date: date = Query(..., description="End date for report period"), employee_id: Optional[str] = Query(None, description="Filter by employee ID"), user_id: Optional[int] = Query(None, description="Filter by user ID"), db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Generate deadline completion performance report""" report_service = DeadlineReportService(db) report = report_service.generate_completion_report( start_date=start_date, end_date=end_date, employee_id=employee_id, user_id=user_id ) return report @router.get("/reports/workload/") async def get_workload_report( target_date: Optional[date] = Query(None, description="Target date (defaults to today)"), days_ahead: int = Query(30, ge=1, le=365, description="Number of days to look ahead"), db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Generate workload distribution report by assignee""" report_service = DeadlineReportService(db) report = report_service.generate_workload_report( target_date=target_date, days_ahead=days_ahead ) return report @router.get("/reports/trends/") async def get_trends_report( start_date: date = Query(..., description="Start date for trend analysis"), end_date: date = Query(..., description="End date for trend analysis"), granularity: str = Query("month", regex="^(week|month|quarter)$", description="Time granularity"), db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Generate deadline trends and analytics over time""" report_service = DeadlineReportService(db) report = report_service.generate_trends_report( start_date=start_date, end_date=end_date, granularity=granularity ) return report # Dashboard endpoints @router.get("/dashboard/widgets/") async def get_dashboard_widgets( user_id: Optional[int] = Query(None, description="Filter by user ID"), employee_id: Optional[str] = Query(None, description="Filter by employee ID"), db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Get all dashboard widgets for deadline management""" dashboard_service = DeadlineDashboardService(db) # Default to current user if no filters provided if not user_id and not employee_id: user_id = current_user.id widgets = dashboard_service.get_dashboard_widgets( user_id=user_id, employee_id=employee_id ) return widgets # Calendar endpoints @router.get("/calendar/monthly/{year}/{month}/") async def get_monthly_calendar( year: int, month: int, user_id: Optional[int] = Query(None, description="Filter by user ID"), employee_id: Optional[str] = Query(None, description="Filter by employee ID"), show_completed: bool = Query(False, description="Include completed deadlines"), db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Get monthly calendar view with deadlines""" calendar_service = DeadlineCalendarService(db) # Default to current user if no filters provided if not user_id and not employee_id: user_id = current_user.id calendar = calendar_service.get_monthly_calendar( year=year, month=month, user_id=user_id, employee_id=employee_id, show_completed=show_completed ) return calendar @router.get("/calendar/weekly/{year}/{week}/") async def get_weekly_calendar( year: int, week: int, user_id: Optional[int] = Query(None, description="Filter by user ID"), employee_id: Optional[str] = Query(None, description="Filter by employee ID"), show_completed: bool = Query(False, description="Include completed deadlines"), db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Get weekly calendar view with detailed scheduling""" calendar_service = DeadlineCalendarService(db) # Default to current user if no filters provided if not user_id and not employee_id: user_id = current_user.id calendar = calendar_service.get_weekly_calendar( year=year, week=week, user_id=user_id, employee_id=employee_id, show_completed=show_completed ) return calendar @router.get("/calendar/daily/{target_date}/") async def get_daily_schedule( target_date: date, user_id: Optional[int] = Query(None, description="Filter by user ID"), employee_id: Optional[str] = Query(None, description="Filter by employee ID"), show_completed: bool = Query(False, description="Include completed deadlines"), db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Get detailed daily schedule with time slots""" calendar_service = DeadlineCalendarService(db) # Default to current user if no filters provided if not user_id and not employee_id: user_id = current_user.id schedule = calendar_service.get_daily_schedule( target_date=target_date, user_id=user_id, employee_id=employee_id, show_completed=show_completed ) return schedule @router.get("/calendar/available-slots/") async def find_available_slots( start_date: date = Query(..., description="Start date for search"), end_date: date = Query(..., description="End date for search"), duration_minutes: int = Query(60, ge=15, le=480, description="Required duration in minutes"), user_id: Optional[int] = Query(None, description="Filter by user ID"), employee_id: Optional[str] = Query(None, description="Filter by employee ID"), business_hours_only: bool = Query(True, description="Limit to business hours"), db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Find available time slots for scheduling new deadlines""" calendar_service = DeadlineCalendarService(db) # Default to current user if no filters provided if not user_id and not employee_id: user_id = current_user.id slots = calendar_service.find_available_slots( start_date=start_date, end_date=end_date, duration_minutes=duration_minutes, user_id=user_id, employee_id=employee_id, business_hours_only=business_hours_only ) return { "search_criteria": { "start_date": start_date, "end_date": end_date, "duration_minutes": duration_minutes, "business_hours_only": business_hours_only }, "available_slots": slots, "total_slots": len(slots) } class ConflictAnalysisRequest(BaseModel): """Request for conflict analysis""" proposed_datetime: datetime = Field(..., description="Proposed deadline date and time") duration_minutes: int = Field(60, ge=15, le=480, description="Expected duration in minutes") user_id: Optional[int] = Field(None, description="Check conflicts for specific user") employee_id: Optional[str] = Field(None, description="Check conflicts for specific employee") @router.post("/calendar/conflict-analysis/") async def analyze_scheduling_conflicts( request: ConflictAnalysisRequest, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Analyze potential conflicts for a proposed deadline time""" calendar_service = DeadlineCalendarService(db) # Default to current user if no filters provided user_id = request.user_id or current_user.id analysis = calendar_service.get_conflict_analysis( proposed_datetime=request.proposed_datetime, duration_minutes=request.duration_minutes, user_id=user_id, employee_id=request.employee_id ) return analysis # Calendar export endpoints @router.get("/calendar/export/ical/") async def export_calendar_ical( start_date: date = Query(..., description="Start date for export"), end_date: date = Query(..., description="End date for export"), user_id: Optional[int] = Query(None, description="Filter by user ID"), employee_id: Optional[str] = Query(None, description="Filter by employee ID"), deadline_types: Optional[str] = Query(None, description="Comma-separated deadline types"), db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Export deadlines to iCalendar format""" from fastapi.responses import Response export_service = CalendarExportService(db) # Parse deadline types if provided parsed_types = None if deadline_types: type_names = [t.strip() for t in deadline_types.split(',')] parsed_types = [] for type_name in type_names: try: parsed_types.append(DeadlineType(type_name.lower())) except ValueError: pass # Skip invalid types # Default to current user if no filters provided if not user_id and not employee_id: user_id = current_user.id ical_content = export_service.export_to_ical( start_date=start_date, end_date=end_date, user_id=user_id, employee_id=employee_id, deadline_types=parsed_types ) filename = f"deadlines_{start_date}_{end_date}.ics" return Response( content=ical_content, media_type="text/calendar", headers={"Content-Disposition": f"attachment; filename={filename}"} )