1103 lines
40 KiB
Python
1103 lines
40 KiB
Python
"""
|
|
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}"}
|
|
) |