830 lines
26 KiB
Python
830 lines
26 KiB
Python
"""
|
|
Enhanced file management API endpoints
|
|
"""
|
|
from typing import List, Optional, Union, 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 (
|
|
File, FileStatus, FileType, Employee, User, FileStatusHistory,
|
|
FileTransferHistory, FileArchiveInfo, FileClosureChecklist, FileAlert
|
|
)
|
|
from app.services.file_management import FileManagementService, FileManagementError, FileStatusWorkflow
|
|
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 FileStatusChangeRequest(BaseModel):
|
|
"""Request to change file status"""
|
|
new_status: str = Field(..., description="New status code")
|
|
notes: Optional[str] = Field(None, description="Notes about the status change")
|
|
validate_transition: bool = Field(True, description="Whether to validate the status transition")
|
|
|
|
|
|
class FileClosureRequest(BaseModel):
|
|
"""Request to close a file"""
|
|
force_close: bool = Field(False, description="Force closure even if there are warnings")
|
|
final_payment_amount: Optional[float] = Field(None, gt=0, description="Final payment amount")
|
|
closing_notes: Optional[str] = Field(None, description="Notes about file closure")
|
|
|
|
|
|
class FileReopenRequest(BaseModel):
|
|
"""Request to reopen a closed file"""
|
|
new_status: str = Field("ACTIVE", description="Status to reopen file to")
|
|
notes: Optional[str] = Field(None, description="Notes about reopening")
|
|
|
|
|
|
class FileTransferRequest(BaseModel):
|
|
"""Request to transfer file to different attorney"""
|
|
new_attorney_id: str = Field(..., description="Employee ID of new attorney")
|
|
transfer_reason: Optional[str] = Field(None, description="Reason for transfer")
|
|
|
|
|
|
class FileArchiveRequest(BaseModel):
|
|
"""Request to archive a file"""
|
|
archive_location: Optional[str] = Field(None, description="Physical or digital archive location")
|
|
notes: Optional[str] = Field(None, description="Archive notes")
|
|
|
|
|
|
class BulkStatusUpdateRequest(BaseModel):
|
|
"""Request to update status for multiple files"""
|
|
file_numbers: List[str] = Field(..., max_length=100, description="List of file numbers")
|
|
new_status: str = Field(..., description="New status for all files")
|
|
notes: Optional[str] = Field(None, description="Notes for all status changes")
|
|
|
|
|
|
class FileStatusHistoryResponse(BaseModel):
|
|
"""Response for file status history"""
|
|
id: int
|
|
old_status: str
|
|
new_status: str
|
|
change_date: datetime
|
|
changed_by: str
|
|
notes: Optional[str] = None
|
|
system_generated: bool
|
|
|
|
|
|
class FileTransferHistoryResponse(BaseModel):
|
|
"""Response for file transfer history"""
|
|
id: int
|
|
old_attorney_id: str
|
|
new_attorney_id: str
|
|
transfer_date: datetime
|
|
authorized_by_name: str
|
|
reason: Optional[str] = None
|
|
old_hourly_rate: Optional[float] = None
|
|
new_hourly_rate: Optional[float] = None
|
|
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
|
|
class FileClosureSummaryResponse(BaseModel):
|
|
"""Response for file closure summary"""
|
|
file_no: str
|
|
closure_date: date
|
|
actions_taken: List[str]
|
|
warnings: List[str]
|
|
final_balance: float
|
|
trust_balance: float
|
|
|
|
|
|
class FileValidationResponse(BaseModel):
|
|
"""Response for file validation checks"""
|
|
file_no: str
|
|
current_status: str
|
|
valid_transitions: List[str]
|
|
can_close: bool
|
|
blocking_issues: List[str]
|
|
warnings: List[str]
|
|
|
|
|
|
class ClosureCandidateResponse(BaseModel):
|
|
"""Response for file closure candidates"""
|
|
file_no: str
|
|
client_name: str
|
|
attorney: str
|
|
opened_date: date
|
|
last_activity: Optional[date] = None
|
|
outstanding_balance: float
|
|
status: str
|
|
|
|
|
|
class BulkOperationResult(BaseModel):
|
|
"""Result of bulk operation"""
|
|
successful: List[str]
|
|
failed: List[Dict[str, str]]
|
|
total: int
|
|
|
|
|
|
# File status management endpoints
|
|
@router.post("/{file_no}/change-status")
|
|
async def change_file_status(
|
|
file_no: str,
|
|
request: FileStatusChangeRequest,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""Change file status with workflow validation"""
|
|
try:
|
|
service = FileManagementService(db)
|
|
# Get the old status before changing
|
|
old_file = db.query(File).filter(File.file_no == file_no).first()
|
|
old_status = old_file.status if old_file else None
|
|
|
|
file_obj = service.change_file_status(
|
|
file_no=file_no,
|
|
new_status=request.new_status,
|
|
user_id=current_user.id,
|
|
notes=request.notes,
|
|
validate_transition=request.validate_transition
|
|
)
|
|
|
|
# Log workflow event for file status change
|
|
try:
|
|
from app.services.workflow_integration import log_file_status_change_sync
|
|
log_file_status_change_sync(
|
|
db=db,
|
|
file_no=file_no,
|
|
old_status=old_status,
|
|
new_status=request.new_status,
|
|
user_id=current_user.id,
|
|
notes=request.notes
|
|
)
|
|
except Exception as e:
|
|
# Don't fail the operation if workflow logging fails
|
|
logger.warning(f"Failed to log workflow event for file {file_no}: {str(e)}")
|
|
|
|
return {
|
|
"message": f"File {file_no} status changed to {request.new_status}",
|
|
"file_no": file_obj.file_no,
|
|
"old_status": file_obj.status,
|
|
"new_status": request.new_status
|
|
}
|
|
except FileManagementError as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=str(e)
|
|
)
|
|
|
|
|
|
@router.get("/{file_no}/valid-transitions")
|
|
async def get_valid_status_transitions(
|
|
file_no: str,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""Get valid status transitions for a file"""
|
|
file_obj = db.query(File).filter(File.file_no == file_no).first()
|
|
if not file_obj:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="File not found"
|
|
)
|
|
|
|
workflow = FileStatusWorkflow()
|
|
valid_transitions = workflow.get_valid_transitions(file_obj.status)
|
|
|
|
return {
|
|
"file_no": file_no,
|
|
"current_status": file_obj.status,
|
|
"valid_transitions": valid_transitions
|
|
}
|
|
|
|
|
|
@router.get("/{file_no}/closure-validation", response_model=FileValidationResponse)
|
|
async def validate_file_closure(
|
|
file_no: str,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""Validate if file is ready for closure"""
|
|
try:
|
|
service = FileManagementService(db)
|
|
file_obj = db.query(File).filter(File.file_no == file_no).first()
|
|
if not file_obj:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="File not found"
|
|
)
|
|
|
|
validation_result = service._validate_file_closure(file_obj)
|
|
workflow = FileStatusWorkflow()
|
|
|
|
return FileValidationResponse(
|
|
file_no=file_no,
|
|
current_status=file_obj.status,
|
|
valid_transitions=workflow.get_valid_transitions(file_obj.status),
|
|
can_close=validation_result["can_close"],
|
|
blocking_issues=validation_result.get("blocking_issues", []),
|
|
warnings=validation_result.get("warnings", [])
|
|
)
|
|
except FileManagementError as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=str(e)
|
|
)
|
|
|
|
|
|
@router.post("/{file_no}/close", response_model=FileClosureSummaryResponse)
|
|
async def close_file(
|
|
file_no: str,
|
|
request: FileClosureRequest,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""Close a file with automated closure process"""
|
|
try:
|
|
service = FileManagementService(db)
|
|
closure_summary = service.close_file(
|
|
file_no=file_no,
|
|
user_id=current_user.id,
|
|
force_close=request.force_close,
|
|
final_payment_amount=request.final_payment_amount,
|
|
closing_notes=request.closing_notes
|
|
)
|
|
|
|
return FileClosureSummaryResponse(**closure_summary)
|
|
except FileManagementError as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=str(e)
|
|
)
|
|
|
|
|
|
@router.post("/{file_no}/reopen")
|
|
async def reopen_file(
|
|
file_no: str,
|
|
request: FileReopenRequest,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""Reopen a closed file"""
|
|
try:
|
|
service = FileManagementService(db)
|
|
file_obj = service.reopen_file(
|
|
file_no=file_no,
|
|
user_id=current_user.id,
|
|
new_status=request.new_status,
|
|
notes=request.notes
|
|
)
|
|
|
|
return {
|
|
"message": f"File {file_no} reopened with status {request.new_status}",
|
|
"file_no": file_obj.file_no,
|
|
"new_status": file_obj.status
|
|
}
|
|
except FileManagementError as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=str(e)
|
|
)
|
|
|
|
|
|
@router.post("/{file_no}/transfer")
|
|
async def transfer_file(
|
|
file_no: str,
|
|
request: FileTransferRequest,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""Transfer file to a different attorney"""
|
|
try:
|
|
service = FileManagementService(db)
|
|
file_obj = service.transfer_file(
|
|
file_no=file_no,
|
|
new_attorney_id=request.new_attorney_id,
|
|
user_id=current_user.id,
|
|
transfer_reason=request.transfer_reason
|
|
)
|
|
|
|
return {
|
|
"message": f"File {file_no} transferred to attorney {request.new_attorney_id}",
|
|
"file_no": file_obj.file_no,
|
|
"new_attorney": file_obj.empl_num,
|
|
"new_rate": file_obj.rate_per_hour
|
|
}
|
|
except FileManagementError as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=str(e)
|
|
)
|
|
|
|
|
|
@router.post("/{file_no}/archive")
|
|
async def archive_file(
|
|
file_no: str,
|
|
request: FileArchiveRequest,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""Archive a closed file"""
|
|
try:
|
|
service = FileManagementService(db)
|
|
file_obj = service.archive_file(
|
|
file_no=file_no,
|
|
user_id=current_user.id,
|
|
archive_location=request.archive_location,
|
|
notes=request.notes
|
|
)
|
|
|
|
return {
|
|
"message": f"File {file_no} has been archived",
|
|
"file_no": file_obj.file_no,
|
|
"status": file_obj.status,
|
|
"archive_location": request.archive_location
|
|
}
|
|
except FileManagementError as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=str(e)
|
|
)
|
|
|
|
|
|
# File history endpoints
|
|
@router.get("/{file_no}/status-history", response_model=List[FileStatusHistoryResponse])
|
|
async def get_file_status_history(
|
|
file_no: str,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""Get status change history for a file"""
|
|
# Verify file exists
|
|
file_obj = db.query(File).filter(File.file_no == file_no).first()
|
|
if not file_obj:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="File not found"
|
|
)
|
|
|
|
service = FileManagementService(db)
|
|
history = service.get_file_status_history(file_no)
|
|
|
|
return [FileStatusHistoryResponse(**item) for item in history]
|
|
|
|
|
|
@router.get("/{file_no}/transfer-history", response_model=List[FileTransferHistoryResponse])
|
|
async def get_file_transfer_history(
|
|
file_no: str,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""Get transfer history for a file"""
|
|
# Verify file exists
|
|
file_obj = db.query(File).filter(File.file_no == file_no).first()
|
|
if not file_obj:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="File not found"
|
|
)
|
|
|
|
transfers = db.query(FileTransferHistory).filter(
|
|
FileTransferHistory.file_no == file_no
|
|
).order_by(FileTransferHistory.transfer_date.desc()).all()
|
|
|
|
return transfers
|
|
|
|
|
|
# Bulk operations
|
|
@router.post("/bulk-status-update", response_model=BulkOperationResult)
|
|
async def bulk_status_update(
|
|
request: BulkStatusUpdateRequest,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""Update status for multiple files"""
|
|
try:
|
|
service = FileManagementService(db)
|
|
results = service.bulk_status_update(
|
|
file_numbers=request.file_numbers,
|
|
new_status=request.new_status,
|
|
user_id=current_user.id,
|
|
notes=request.notes
|
|
)
|
|
|
|
return BulkOperationResult(**results)
|
|
except FileManagementError as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=str(e)
|
|
)
|
|
|
|
|
|
# Checklist endpoints
|
|
|
|
class ChecklistItemRequest(BaseModel):
|
|
item_name: str
|
|
item_description: Optional[str] = None
|
|
is_required: bool = True
|
|
sort_order: int = 0
|
|
|
|
|
|
class ChecklistItemUpdateRequest(BaseModel):
|
|
item_name: Optional[str] = None
|
|
item_description: Optional[str] = None
|
|
is_required: Optional[bool] = None
|
|
is_completed: Optional[bool] = None
|
|
sort_order: Optional[int] = None
|
|
notes: Optional[str] = None
|
|
|
|
|
|
@router.get("/{file_no}/closure-checklist")
|
|
async def get_closure_checklist(
|
|
file_no: str,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
service = FileManagementService(db)
|
|
return service.get_closure_checklist(file_no)
|
|
|
|
|
|
@router.post("/{file_no}/closure-checklist")
|
|
async def add_checklist_item(
|
|
file_no: str,
|
|
request: ChecklistItemRequest,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
service = FileManagementService(db)
|
|
try:
|
|
item = service.add_checklist_item(
|
|
file_no=file_no,
|
|
item_name=request.item_name,
|
|
item_description=request.item_description,
|
|
is_required=request.is_required,
|
|
sort_order=request.sort_order,
|
|
)
|
|
return {
|
|
"id": item.id,
|
|
"file_no": item.file_no,
|
|
"item_name": item.item_name,
|
|
"item_description": item.item_description,
|
|
"is_required": item.is_required,
|
|
"is_completed": item.is_completed,
|
|
"sort_order": item.sort_order,
|
|
}
|
|
except FileManagementError as e:
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
|
|
|
|
|
@router.put("/closure-checklist/{item_id}")
|
|
async def update_checklist_item(
|
|
item_id: int,
|
|
request: ChecklistItemUpdateRequest,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
service = FileManagementService(db)
|
|
try:
|
|
item = service.update_checklist_item(
|
|
item_id=item_id,
|
|
item_name=request.item_name,
|
|
item_description=request.item_description,
|
|
is_required=request.is_required,
|
|
is_completed=request.is_completed,
|
|
sort_order=request.sort_order,
|
|
user_id=current_user.id,
|
|
notes=request.notes,
|
|
)
|
|
return {
|
|
"id": item.id,
|
|
"file_no": item.file_no,
|
|
"item_name": item.item_name,
|
|
"item_description": item.item_description,
|
|
"is_required": item.is_required,
|
|
"is_completed": item.is_completed,
|
|
"completed_date": item.completed_date,
|
|
"completed_by_name": item.completed_by_name,
|
|
"notes": item.notes,
|
|
"sort_order": item.sort_order,
|
|
}
|
|
except FileManagementError as e:
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
|
|
|
|
|
@router.delete("/closure-checklist/{item_id}")
|
|
async def delete_checklist_item(
|
|
item_id: int,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
service = FileManagementService(db)
|
|
try:
|
|
service.delete_checklist_item(item_id=item_id)
|
|
return {"message": "Checklist item deleted"}
|
|
except FileManagementError as e:
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
|
|
|
|
|
# Alerts endpoints
|
|
|
|
class AlertCreateRequest(BaseModel):
|
|
alert_type: str
|
|
title: str
|
|
message: str
|
|
alert_date: date
|
|
notify_attorney: bool = True
|
|
notify_admin: bool = False
|
|
notification_days_advance: int = 7
|
|
|
|
|
|
class AlertUpdateRequest(BaseModel):
|
|
title: Optional[str] = None
|
|
message: Optional[str] = None
|
|
alert_date: Optional[date] = None
|
|
is_active: Optional[bool] = None
|
|
|
|
|
|
@router.post("/{file_no}/alerts")
|
|
async def create_alert(
|
|
file_no: str,
|
|
request: AlertCreateRequest,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
service = FileManagementService(db)
|
|
try:
|
|
alert = service.create_alert(
|
|
file_no=file_no,
|
|
alert_type=request.alert_type,
|
|
title=request.title,
|
|
message=request.message,
|
|
alert_date=request.alert_date,
|
|
notify_attorney=request.notify_attorney,
|
|
notify_admin=request.notify_admin,
|
|
notification_days_advance=request.notification_days_advance,
|
|
)
|
|
return {
|
|
"id": alert.id,
|
|
"file_no": alert.file_no,
|
|
"alert_type": alert.alert_type,
|
|
"title": alert.title,
|
|
"message": alert.message,
|
|
"alert_date": alert.alert_date,
|
|
"is_active": alert.is_active,
|
|
}
|
|
except FileManagementError as e:
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
|
|
|
|
|
@router.get("/{file_no}/alerts")
|
|
async def get_alerts(
|
|
file_no: str,
|
|
active_only: bool = Query(True),
|
|
upcoming_only: bool = Query(False),
|
|
limit: int = Query(100, ge=1, le=500),
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
service = FileManagementService(db)
|
|
alerts = service.get_alerts(
|
|
file_no=file_no,
|
|
active_only=active_only,
|
|
upcoming_only=upcoming_only,
|
|
limit=limit,
|
|
)
|
|
return [
|
|
{
|
|
"id": a.id,
|
|
"file_no": a.file_no,
|
|
"alert_type": a.alert_type,
|
|
"title": a.title,
|
|
"message": a.message,
|
|
"alert_date": a.alert_date,
|
|
"is_active": a.is_active,
|
|
"is_acknowledged": a.is_acknowledged,
|
|
}
|
|
for a in alerts
|
|
]
|
|
|
|
|
|
@router.post("/alerts/{alert_id}/acknowledge")
|
|
async def acknowledge_alert(
|
|
alert_id: int,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
service = FileManagementService(db)
|
|
try:
|
|
alert = service.acknowledge_alert(alert_id=alert_id, user_id=current_user.id)
|
|
return {"message": "Alert acknowledged", "id": alert.id}
|
|
except FileManagementError as e:
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
|
|
|
|
|
@router.put("/alerts/{alert_id}")
|
|
async def update_alert(
|
|
alert_id: int,
|
|
request: AlertUpdateRequest,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
service = FileManagementService(db)
|
|
try:
|
|
alert = service.update_alert(
|
|
alert_id=alert_id,
|
|
title=request.title,
|
|
message=request.message,
|
|
alert_date=request.alert_date,
|
|
is_active=request.is_active,
|
|
)
|
|
return {"message": "Alert updated", "id": alert.id}
|
|
except FileManagementError as e:
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
|
|
|
|
|
@router.delete("/alerts/{alert_id}")
|
|
async def delete_alert(
|
|
alert_id: int,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
service = FileManagementService(db)
|
|
try:
|
|
service.delete_alert(alert_id=alert_id)
|
|
return {"message": "Alert deleted"}
|
|
except FileManagementError as e:
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
|
|
|
|
|
# Relationships endpoints
|
|
|
|
class RelationshipCreateRequest(BaseModel):
|
|
target_file_no: str
|
|
relationship_type: str
|
|
notes: Optional[str] = None
|
|
|
|
|
|
@router.post("/{file_no}/relationships")
|
|
async def create_relationship(
|
|
file_no: str,
|
|
request: RelationshipCreateRequest,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
service = FileManagementService(db)
|
|
try:
|
|
rel = service.create_relationship(
|
|
source_file_no=file_no,
|
|
target_file_no=request.target_file_no,
|
|
relationship_type=request.relationship_type,
|
|
user_id=current_user.id,
|
|
notes=request.notes,
|
|
)
|
|
return {
|
|
"id": rel.id,
|
|
"source_file_no": rel.source_file_no,
|
|
"target_file_no": rel.target_file_no,
|
|
"relationship_type": rel.relationship_type,
|
|
"notes": rel.notes,
|
|
}
|
|
except FileManagementError as e:
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
|
|
|
|
|
@router.get("/{file_no}/relationships")
|
|
async def get_relationships(
|
|
file_no: str,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
service = FileManagementService(db)
|
|
return service.get_relationships(file_no=file_no)
|
|
|
|
|
|
@router.delete("/relationships/{relationship_id}")
|
|
async def delete_relationship(
|
|
relationship_id: int,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
service = FileManagementService(db)
|
|
try:
|
|
service.delete_relationship(relationship_id=relationship_id)
|
|
return {"message": "Relationship deleted"}
|
|
except FileManagementError as e:
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
|
|
|
|
|
# File queries and reports
|
|
@router.get("/by-status/{status}")
|
|
async def get_files_by_status(
|
|
status: str,
|
|
attorney_id: Optional[str] = Query(None, description="Filter by attorney ID"),
|
|
limit: int = Query(100, ge=1, le=500, description="Maximum number of files to return"),
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""Get files by status with optional attorney filter"""
|
|
service = FileManagementService(db)
|
|
files = service.get_files_by_status(status, attorney_id, limit)
|
|
|
|
return [
|
|
{
|
|
"file_no": f.file_no,
|
|
"client_name": f"{f.owner.first or ''} {f.owner.last}".strip() if f.owner else "Unknown",
|
|
"regarding": f.regarding,
|
|
"attorney": f.empl_num,
|
|
"opened_date": f.opened,
|
|
"closed_date": f.closed,
|
|
"status": f.status
|
|
}
|
|
for f in files
|
|
]
|
|
|
|
|
|
@router.get("/closure-candidates", response_model=List[ClosureCandidateResponse])
|
|
async def get_closure_candidates(
|
|
days_inactive: int = Query(90, ge=30, le=365, description="Days since last activity"),
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""Get files that are candidates for closure"""
|
|
service = FileManagementService(db)
|
|
candidates = service.get_closure_candidates(days_inactive)
|
|
|
|
return [ClosureCandidateResponse(**candidate) for candidate in candidates]
|
|
|
|
|
|
# Lookup endpoints
|
|
@router.get("/statuses")
|
|
async def get_file_statuses(
|
|
active_only: bool = Query(True, description="Return only active statuses"),
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""Get available file statuses"""
|
|
query = db.query(FileStatus)
|
|
|
|
if active_only:
|
|
query = query.filter(FileStatus.active == True)
|
|
|
|
statuses = query.order_by(FileStatus.sort_order, FileStatus.status_code).all()
|
|
|
|
return [
|
|
{
|
|
"status_code": s.status_code,
|
|
"description": s.description,
|
|
"active": s.active,
|
|
"send": s.send,
|
|
"footer_code": s.footer_code
|
|
}
|
|
for s in statuses
|
|
]
|
|
|
|
|
|
@router.get("/types")
|
|
async def get_file_types(
|
|
active_only: bool = Query(True, description="Return only active file types"),
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""Get available file types"""
|
|
query = db.query(FileType)
|
|
|
|
if active_only:
|
|
query = query.filter(FileType.active == True)
|
|
|
|
types = query.order_by(FileType.type_code).all()
|
|
|
|
return [
|
|
{
|
|
"type_code": t.type_code,
|
|
"description": t.description,
|
|
"default_rate": t.default_rate,
|
|
"active": t.active
|
|
}
|
|
for t in types
|
|
]
|
|
|
|
|
|
@router.get("/attorneys")
|
|
async def get_attorneys(
|
|
active_only: bool = Query(True, description="Return only active attorneys"),
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""Get available attorneys for file assignment"""
|
|
query = db.query(Employee)
|
|
|
|
if active_only:
|
|
query = query.filter(Employee.active == True)
|
|
|
|
attorneys = query.order_by(Employee.last_name, Employee.first_name).all()
|
|
|
|
return [
|
|
{
|
|
"empl_num": a.empl_num,
|
|
"name": f"{a.first_name or ''} {a.last_name}".strip(),
|
|
"title": a.title,
|
|
"rate_per_hour": a.rate_per_hour,
|
|
"active": a.active
|
|
}
|
|
for a in attorneys
|
|
] |