changes
This commit is contained in:
@@ -9,9 +9,10 @@ from sqlalchemy.orm import Session, joinedload
|
||||
from sqlalchemy import and_, func, or_, desc
|
||||
|
||||
from app.models import (
|
||||
File, Ledger, FileStatus, FileType, Rolodex, Employee,
|
||||
BillingStatement, Timer, TimeEntry, User, FileStatusHistory,
|
||||
FileTransferHistory, FileArchiveInfo
|
||||
File, Ledger, FileStatus, FileType, Rolodex, Employee,
|
||||
BillingStatement, Timer, TimeEntry, User, FileStatusHistory,
|
||||
FileTransferHistory, FileArchiveInfo, FileClosureChecklist, FileAlert,
|
||||
FileRelationship
|
||||
)
|
||||
from app.utils.logging import app_logger
|
||||
|
||||
@@ -432,6 +433,284 @@ class FileManagementService:
|
||||
|
||||
logger.info(f"Bulk status update: {len(results['successful'])} successful, {len(results['failed'])} failed")
|
||||
return results
|
||||
|
||||
# Checklist management
|
||||
|
||||
def get_closure_checklist(self, file_no: str) -> List[Dict[str, Any]]:
|
||||
"""Return the closure checklist items for a file."""
|
||||
items = self.db.query(FileClosureChecklist).filter(
|
||||
FileClosureChecklist.file_no == file_no
|
||||
).order_by(FileClosureChecklist.sort_order.asc(), FileClosureChecklist.id.asc()).all()
|
||||
|
||||
return [
|
||||
{
|
||||
"id": i.id,
|
||||
"file_no": i.file_no,
|
||||
"item_name": i.item_name,
|
||||
"item_description": i.item_description,
|
||||
"is_required": bool(i.is_required),
|
||||
"is_completed": bool(i.is_completed),
|
||||
"completed_date": i.completed_date,
|
||||
"completed_by_name": i.completed_by_name,
|
||||
"notes": i.notes,
|
||||
"sort_order": i.sort_order,
|
||||
}
|
||||
for i in items
|
||||
]
|
||||
|
||||
def add_checklist_item(
|
||||
self,
|
||||
*,
|
||||
file_no: str,
|
||||
item_name: str,
|
||||
item_description: Optional[str] = None,
|
||||
is_required: bool = True,
|
||||
sort_order: int = 0,
|
||||
) -> FileClosureChecklist:
|
||||
"""Add a checklist item to a file."""
|
||||
# Ensure file exists
|
||||
if not self.db.query(File).filter(File.file_no == file_no).first():
|
||||
raise FileManagementError(f"File {file_no} not found")
|
||||
|
||||
item = FileClosureChecklist(
|
||||
file_no=file_no,
|
||||
item_name=item_name,
|
||||
item_description=item_description,
|
||||
is_required=is_required,
|
||||
sort_order=sort_order,
|
||||
)
|
||||
self.db.add(item)
|
||||
self.db.commit()
|
||||
self.db.refresh(item)
|
||||
logger.info(f"Added checklist item '{item_name}' to file {file_no}")
|
||||
return item
|
||||
|
||||
def update_checklist_item(
|
||||
self,
|
||||
*,
|
||||
item_id: int,
|
||||
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,
|
||||
user_id: Optional[int] = None,
|
||||
notes: Optional[str] = None,
|
||||
) -> FileClosureChecklist:
|
||||
"""Update attributes of a checklist item; optionally mark complete/incomplete."""
|
||||
item = self.db.query(FileClosureChecklist).filter(FileClosureChecklist.id == item_id).first()
|
||||
if not item:
|
||||
raise FileManagementError("Checklist item not found")
|
||||
|
||||
if item_name is not None:
|
||||
item.item_name = item_name
|
||||
if item_description is not None:
|
||||
item.item_description = item_description
|
||||
if is_required is not None:
|
||||
item.is_required = bool(is_required)
|
||||
if sort_order is not None:
|
||||
item.sort_order = int(sort_order)
|
||||
if is_completed is not None:
|
||||
item.is_completed = bool(is_completed)
|
||||
if item.is_completed:
|
||||
item.completed_date = datetime.now(timezone.utc)
|
||||
if user_id:
|
||||
user = self.db.query(User).filter(User.id == user_id).first()
|
||||
item.completed_by_user_id = user_id
|
||||
item.completed_by_name = user.username if user else f"user_{user_id}"
|
||||
else:
|
||||
item.completed_date = None
|
||||
item.completed_by_user_id = None
|
||||
item.completed_by_name = None
|
||||
if notes is not None:
|
||||
item.notes = notes
|
||||
|
||||
self.db.commit()
|
||||
self.db.refresh(item)
|
||||
logger.info(f"Updated checklist item {item_id}")
|
||||
return item
|
||||
|
||||
def delete_checklist_item(self, *, item_id: int) -> None:
|
||||
item = self.db.query(FileClosureChecklist).filter(FileClosureChecklist.id == item_id).first()
|
||||
if not item:
|
||||
raise FileManagementError("Checklist item not found")
|
||||
self.db.delete(item)
|
||||
self.db.commit()
|
||||
logger.info(f"Deleted checklist item {item_id}")
|
||||
|
||||
# Alerts management
|
||||
|
||||
def create_alert(
|
||||
self,
|
||||
*,
|
||||
file_no: str,
|
||||
alert_type: str,
|
||||
title: str,
|
||||
message: str,
|
||||
alert_date: date,
|
||||
notify_attorney: bool = True,
|
||||
notify_admin: bool = False,
|
||||
notification_days_advance: int = 7,
|
||||
) -> FileAlert:
|
||||
if not self.db.query(File).filter(File.file_no == file_no).first():
|
||||
raise FileManagementError(f"File {file_no} not found")
|
||||
alert = FileAlert(
|
||||
file_no=file_no,
|
||||
alert_type=alert_type,
|
||||
title=title,
|
||||
message=message,
|
||||
alert_date=alert_date,
|
||||
notify_attorney=notify_attorney,
|
||||
notify_admin=notify_admin,
|
||||
notification_days_advance=notification_days_advance,
|
||||
)
|
||||
self.db.add(alert)
|
||||
self.db.commit()
|
||||
self.db.refresh(alert)
|
||||
logger.info(f"Created alert {alert.id} for file {file_no} on {alert_date}")
|
||||
return alert
|
||||
|
||||
def get_alerts(
|
||||
self,
|
||||
*,
|
||||
file_no: str,
|
||||
active_only: bool = True,
|
||||
upcoming_only: bool = False,
|
||||
limit: int = 100,
|
||||
) -> List[FileAlert]:
|
||||
query = self.db.query(FileAlert).filter(FileAlert.file_no == file_no)
|
||||
if active_only:
|
||||
query = query.filter(FileAlert.is_active == True)
|
||||
if upcoming_only:
|
||||
today = datetime.now(timezone.utc).date()
|
||||
query = query.filter(FileAlert.alert_date >= today)
|
||||
return query.order_by(FileAlert.alert_date.asc(), FileAlert.id.asc()).limit(limit).all()
|
||||
|
||||
def acknowledge_alert(self, *, alert_id: int, user_id: int) -> FileAlert:
|
||||
alert = self.db.query(FileAlert).filter(FileAlert.id == alert_id).first()
|
||||
if not alert:
|
||||
raise FileManagementError("Alert not found")
|
||||
if not alert.is_active:
|
||||
return alert
|
||||
alert.is_acknowledged = True
|
||||
alert.acknowledged_at = datetime.now(timezone.utc)
|
||||
alert.acknowledged_by_user_id = user_id
|
||||
self.db.commit()
|
||||
self.db.refresh(alert)
|
||||
logger.info(f"Acknowledged alert {alert_id} by user {user_id}")
|
||||
return alert
|
||||
|
||||
def update_alert(
|
||||
self,
|
||||
*,
|
||||
alert_id: int,
|
||||
title: Optional[str] = None,
|
||||
message: Optional[str] = None,
|
||||
alert_date: Optional[date] = None,
|
||||
is_active: Optional[bool] = None,
|
||||
) -> FileAlert:
|
||||
alert = self.db.query(FileAlert).filter(FileAlert.id == alert_id).first()
|
||||
if not alert:
|
||||
raise FileManagementError("Alert not found")
|
||||
if title is not None:
|
||||
alert.title = title
|
||||
if message is not None:
|
||||
alert.message = message
|
||||
if alert_date is not None:
|
||||
alert.alert_date = alert_date
|
||||
if is_active is not None:
|
||||
alert.is_active = bool(is_active)
|
||||
self.db.commit()
|
||||
self.db.refresh(alert)
|
||||
logger.info(f"Updated alert {alert_id}")
|
||||
return alert
|
||||
|
||||
def delete_alert(self, *, alert_id: int) -> None:
|
||||
alert = self.db.query(FileAlert).filter(FileAlert.id == alert_id).first()
|
||||
if not alert:
|
||||
raise FileManagementError("Alert not found")
|
||||
self.db.delete(alert)
|
||||
self.db.commit()
|
||||
logger.info(f"Deleted alert {alert_id}")
|
||||
|
||||
# Relationship management
|
||||
|
||||
def create_relationship(
|
||||
self,
|
||||
*,
|
||||
source_file_no: str,
|
||||
target_file_no: str,
|
||||
relationship_type: str,
|
||||
user_id: Optional[int] = None,
|
||||
notes: Optional[str] = None,
|
||||
) -> FileRelationship:
|
||||
if source_file_no == target_file_no:
|
||||
raise FileManagementError("Source and target file cannot be the same")
|
||||
source = self.db.query(File).filter(File.file_no == source_file_no).first()
|
||||
target = self.db.query(File).filter(File.file_no == target_file_no).first()
|
||||
if not source:
|
||||
raise FileManagementError(f"File {source_file_no} not found")
|
||||
if not target:
|
||||
raise FileManagementError(f"File {target_file_no} not found")
|
||||
user_name: Optional[str] = None
|
||||
if user_id is not None:
|
||||
user = self.db.query(User).filter(User.id == user_id).first()
|
||||
user_name = user.username if user else f"user_{user_id}"
|
||||
# Prevent duplicate exact relationship
|
||||
existing = self.db.query(FileRelationship).filter(
|
||||
FileRelationship.source_file_no == source_file_no,
|
||||
FileRelationship.target_file_no == target_file_no,
|
||||
FileRelationship.relationship_type == relationship_type,
|
||||
).first()
|
||||
if existing:
|
||||
return existing
|
||||
rel = FileRelationship(
|
||||
source_file_no=source_file_no,
|
||||
target_file_no=target_file_no,
|
||||
relationship_type=relationship_type,
|
||||
notes=notes,
|
||||
created_by_user_id=user_id,
|
||||
created_by_name=user_name,
|
||||
)
|
||||
self.db.add(rel)
|
||||
self.db.commit()
|
||||
self.db.refresh(rel)
|
||||
logger.info(
|
||||
f"Created relationship {relationship_type}: {source_file_no} -> {target_file_no}"
|
||||
)
|
||||
return rel
|
||||
|
||||
def get_relationships(self, *, file_no: str) -> List[Dict[str, Any]]:
|
||||
"""Return relationships where the given file is source or target."""
|
||||
rels = self.db.query(FileRelationship).filter(
|
||||
(FileRelationship.source_file_no == file_no) | (FileRelationship.target_file_no == file_no)
|
||||
).order_by(FileRelationship.id.desc()).all()
|
||||
results: List[Dict[str, Any]] = []
|
||||
for r in rels:
|
||||
direction = "outbound" if r.source_file_no == file_no else "inbound"
|
||||
other_file_no = r.target_file_no if direction == "outbound" else r.source_file_no
|
||||
results.append(
|
||||
{
|
||||
"id": r.id,
|
||||
"direction": direction,
|
||||
"relationship_type": r.relationship_type,
|
||||
"notes": r.notes,
|
||||
"source_file_no": r.source_file_no,
|
||||
"target_file_no": r.target_file_no,
|
||||
"other_file_no": other_file_no,
|
||||
"created_by_name": r.created_by_name,
|
||||
"created_at": getattr(r, "created_at", None),
|
||||
}
|
||||
)
|
||||
return results
|
||||
|
||||
def delete_relationship(self, *, relationship_id: int) -> None:
|
||||
rel = self.db.query(FileRelationship).filter(FileRelationship.id == relationship_id).first()
|
||||
if not rel:
|
||||
raise FileManagementError("Relationship not found")
|
||||
self.db.delete(rel)
|
||||
self.db.commit()
|
||||
logger.info(f"Deleted relationship {relationship_id}")
|
||||
|
||||
# Private helper methods
|
||||
|
||||
|
||||
Reference in New Issue
Block a user