520 lines
16 KiB
Python
520 lines
16 KiB
Python
"""
|
|
Workflow Integration Service
|
|
|
|
This service provides integration points for automatically logging events
|
|
and triggering workflows from existing system operations.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
from datetime import datetime, timezone
|
|
from typing import Dict, Any, Optional
|
|
import logging
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.services.workflow_engine import EventProcessor
|
|
from app.core.logging import get_logger
|
|
|
|
logger = get_logger("workflow_integration")
|
|
|
|
|
|
class WorkflowIntegration:
|
|
"""
|
|
Helper service for integrating workflow automation with existing systems
|
|
"""
|
|
|
|
def __init__(self, db: Session):
|
|
self.db = db
|
|
self.event_processor = EventProcessor(db)
|
|
|
|
async def log_file_status_change(
|
|
self,
|
|
file_no: str,
|
|
old_status: str,
|
|
new_status: str,
|
|
user_id: Optional[int] = None,
|
|
notes: Optional[str] = None
|
|
):
|
|
"""
|
|
Log a file status change event that may trigger workflows
|
|
"""
|
|
try:
|
|
event_data = {
|
|
'old_status': old_status,
|
|
'new_status': new_status,
|
|
'notes': notes
|
|
}
|
|
|
|
previous_state = {'status': old_status}
|
|
new_state = {'status': new_status}
|
|
|
|
await self.event_processor.log_event(
|
|
event_type="file_status_change",
|
|
event_source="file_management",
|
|
file_no=file_no,
|
|
user_id=user_id,
|
|
resource_type="file",
|
|
resource_id=file_no,
|
|
event_data=event_data,
|
|
previous_state=previous_state,
|
|
new_state=new_state
|
|
)
|
|
|
|
# Log specific status events
|
|
if new_status == "CLOSED":
|
|
await self.log_file_closed(file_no, user_id)
|
|
elif old_status in ["INACTIVE", "CLOSED"] and new_status == "ACTIVE":
|
|
await self.log_file_reopened(file_no, user_id)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error logging file status change for {file_no}: {str(e)}")
|
|
|
|
async def log_file_opened(
|
|
self,
|
|
file_no: str,
|
|
file_type: str,
|
|
client_id: str,
|
|
attorney: str,
|
|
user_id: Optional[int] = None
|
|
):
|
|
"""
|
|
Log a new file opening event
|
|
"""
|
|
try:
|
|
event_data = {
|
|
'file_type': file_type,
|
|
'client_id': client_id,
|
|
'attorney': attorney
|
|
}
|
|
|
|
await self.event_processor.log_event(
|
|
event_type="file_opened",
|
|
event_source="file_management",
|
|
file_no=file_no,
|
|
client_id=client_id,
|
|
user_id=user_id,
|
|
resource_type="file",
|
|
resource_id=file_no,
|
|
event_data=event_data
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error logging file opened for {file_no}: {str(e)}")
|
|
|
|
async def log_file_closed(
|
|
self,
|
|
file_no: str,
|
|
user_id: Optional[int] = None,
|
|
final_balance: Optional[float] = None
|
|
):
|
|
"""
|
|
Log a file closure event
|
|
"""
|
|
try:
|
|
event_data = {
|
|
'closed_by_user_id': user_id,
|
|
'final_balance': final_balance
|
|
}
|
|
|
|
await self.event_processor.log_event(
|
|
event_type="file_closed",
|
|
event_source="file_management",
|
|
file_no=file_no,
|
|
user_id=user_id,
|
|
resource_type="file",
|
|
resource_id=file_no,
|
|
event_data=event_data
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error logging file closed for {file_no}: {str(e)}")
|
|
|
|
async def log_file_reopened(
|
|
self,
|
|
file_no: str,
|
|
user_id: Optional[int] = None,
|
|
reason: Optional[str] = None
|
|
):
|
|
"""
|
|
Log a file reopening event
|
|
"""
|
|
try:
|
|
event_data = {
|
|
'reopened_by_user_id': user_id,
|
|
'reason': reason
|
|
}
|
|
|
|
await self.event_processor.log_event(
|
|
event_type="file_reopened",
|
|
event_source="file_management",
|
|
file_no=file_no,
|
|
user_id=user_id,
|
|
resource_type="file",
|
|
resource_id=file_no,
|
|
event_data=event_data
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error logging file reopened for {file_no}: {str(e)}")
|
|
|
|
async def log_deadline_approaching(
|
|
self,
|
|
deadline_id: int,
|
|
file_no: Optional[str] = None,
|
|
client_id: Optional[str] = None,
|
|
days_until_deadline: int = 0,
|
|
deadline_type: Optional[str] = None
|
|
):
|
|
"""
|
|
Log a deadline approaching event
|
|
"""
|
|
try:
|
|
event_data = {
|
|
'deadline_id': deadline_id,
|
|
'days_until_deadline': days_until_deadline,
|
|
'deadline_type': deadline_type
|
|
}
|
|
|
|
await self.event_processor.log_event(
|
|
event_type="deadline_approaching",
|
|
event_source="deadline_management",
|
|
file_no=file_no,
|
|
client_id=client_id,
|
|
resource_type="deadline",
|
|
resource_id=str(deadline_id),
|
|
event_data=event_data
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error logging deadline approaching for {deadline_id}: {str(e)}")
|
|
|
|
async def log_deadline_overdue(
|
|
self,
|
|
deadline_id: int,
|
|
file_no: Optional[str] = None,
|
|
client_id: Optional[str] = None,
|
|
days_overdue: int = 0,
|
|
deadline_type: Optional[str] = None
|
|
):
|
|
"""
|
|
Log a deadline overdue event
|
|
"""
|
|
try:
|
|
event_data = {
|
|
'deadline_id': deadline_id,
|
|
'days_overdue': days_overdue,
|
|
'deadline_type': deadline_type
|
|
}
|
|
|
|
await self.event_processor.log_event(
|
|
event_type="deadline_overdue",
|
|
event_source="deadline_management",
|
|
file_no=file_no,
|
|
client_id=client_id,
|
|
resource_type="deadline",
|
|
resource_id=str(deadline_id),
|
|
event_data=event_data
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error logging deadline overdue for {deadline_id}: {str(e)}")
|
|
|
|
async def log_deadline_completed(
|
|
self,
|
|
deadline_id: int,
|
|
file_no: Optional[str] = None,
|
|
client_id: Optional[str] = None,
|
|
completed_by_user_id: Optional[int] = None,
|
|
completion_notes: Optional[str] = None
|
|
):
|
|
"""
|
|
Log a deadline completion event
|
|
"""
|
|
try:
|
|
event_data = {
|
|
'deadline_id': deadline_id,
|
|
'completed_by_user_id': completed_by_user_id,
|
|
'completion_notes': completion_notes
|
|
}
|
|
|
|
await self.event_processor.log_event(
|
|
event_type="deadline_completed",
|
|
event_source="deadline_management",
|
|
file_no=file_no,
|
|
client_id=client_id,
|
|
user_id=completed_by_user_id,
|
|
resource_type="deadline",
|
|
resource_id=str(deadline_id),
|
|
event_data=event_data
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error logging deadline completed for {deadline_id}: {str(e)}")
|
|
|
|
async def log_payment_received(
|
|
self,
|
|
file_no: str,
|
|
amount: float,
|
|
payment_type: str,
|
|
payment_date: Optional[datetime] = None,
|
|
user_id: Optional[int] = None
|
|
):
|
|
"""
|
|
Log a payment received event
|
|
"""
|
|
try:
|
|
event_data = {
|
|
'amount': amount,
|
|
'payment_type': payment_type,
|
|
'payment_date': payment_date.isoformat() if payment_date else None
|
|
}
|
|
|
|
await self.event_processor.log_event(
|
|
event_type="payment_received",
|
|
event_source="billing",
|
|
file_no=file_no,
|
|
user_id=user_id,
|
|
resource_type="payment",
|
|
resource_id=f"{file_no}_{datetime.now().isoformat()}",
|
|
event_data=event_data
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error logging payment received for {file_no}: {str(e)}")
|
|
|
|
async def log_payment_overdue(
|
|
self,
|
|
file_no: str,
|
|
amount_due: float,
|
|
days_overdue: int,
|
|
invoice_date: Optional[datetime] = None
|
|
):
|
|
"""
|
|
Log a payment overdue event
|
|
"""
|
|
try:
|
|
event_data = {
|
|
'amount_due': amount_due,
|
|
'days_overdue': days_overdue,
|
|
'invoice_date': invoice_date.isoformat() if invoice_date else None
|
|
}
|
|
|
|
await self.event_processor.log_event(
|
|
event_type="payment_overdue",
|
|
event_source="billing",
|
|
file_no=file_no,
|
|
resource_type="invoice",
|
|
resource_id=f"{file_no}_overdue",
|
|
event_data=event_data
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error logging payment overdue for {file_no}: {str(e)}")
|
|
|
|
async def log_document_uploaded(
|
|
self,
|
|
file_no: str,
|
|
document_id: int,
|
|
filename: str,
|
|
document_type: Optional[str] = None,
|
|
user_id: Optional[int] = None
|
|
):
|
|
"""
|
|
Log a document upload event
|
|
"""
|
|
try:
|
|
event_data = {
|
|
'document_id': document_id,
|
|
'filename': filename,
|
|
'document_type': document_type,
|
|
'uploaded_by_user_id': user_id
|
|
}
|
|
|
|
await self.event_processor.log_event(
|
|
event_type="document_uploaded",
|
|
event_source="document_management",
|
|
file_no=file_no,
|
|
user_id=user_id,
|
|
resource_type="document",
|
|
resource_id=str(document_id),
|
|
event_data=event_data
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error logging document uploaded for {file_no}: {str(e)}")
|
|
|
|
async def log_qdro_status_change(
|
|
self,
|
|
qdro_id: int,
|
|
file_no: str,
|
|
old_status: str,
|
|
new_status: str,
|
|
user_id: Optional[int] = None
|
|
):
|
|
"""
|
|
Log a QDRO status change event
|
|
"""
|
|
try:
|
|
event_data = {
|
|
'qdro_id': qdro_id,
|
|
'old_status': old_status,
|
|
'new_status': new_status
|
|
}
|
|
|
|
previous_state = {'status': old_status}
|
|
new_state = {'status': new_status}
|
|
|
|
await self.event_processor.log_event(
|
|
event_type="qdro_status_change",
|
|
event_source="qdro_management",
|
|
file_no=file_no,
|
|
user_id=user_id,
|
|
resource_type="qdro",
|
|
resource_id=str(qdro_id),
|
|
event_data=event_data,
|
|
previous_state=previous_state,
|
|
new_state=new_state
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error logging QDRO status change for {qdro_id}: {str(e)}")
|
|
|
|
async def log_custom_event(
|
|
self,
|
|
event_type: str,
|
|
event_source: str,
|
|
file_no: Optional[str] = None,
|
|
client_id: Optional[str] = None,
|
|
user_id: Optional[int] = None,
|
|
resource_type: Optional[str] = None,
|
|
resource_id: Optional[str] = None,
|
|
event_data: Optional[Dict[str, Any]] = None
|
|
):
|
|
"""
|
|
Log a custom event
|
|
"""
|
|
try:
|
|
await self.event_processor.log_event(
|
|
event_type=event_type,
|
|
event_source=event_source,
|
|
file_no=file_no,
|
|
client_id=client_id,
|
|
user_id=user_id,
|
|
resource_type=resource_type,
|
|
resource_id=resource_id,
|
|
event_data=event_data or {}
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error logging custom event {event_type}: {str(e)}")
|
|
|
|
|
|
# Helper functions for easy integration
|
|
def create_workflow_integration(db: Session) -> WorkflowIntegration:
|
|
"""
|
|
Create a workflow integration instance
|
|
"""
|
|
return WorkflowIntegration(db)
|
|
|
|
|
|
def run_async_event_logging(coro):
|
|
"""
|
|
Helper to run async event logging in sync contexts
|
|
"""
|
|
try:
|
|
# Try to get the current event loop
|
|
loop = asyncio.get_event_loop()
|
|
if loop.is_running():
|
|
# If loop is running, schedule the coroutine
|
|
asyncio.create_task(coro)
|
|
else:
|
|
# If no loop is running, run the coroutine
|
|
loop.run_until_complete(coro)
|
|
except RuntimeError:
|
|
# No event loop, create a new one
|
|
asyncio.run(coro)
|
|
except Exception as e:
|
|
logger.error(f"Error running async event logging: {str(e)}")
|
|
|
|
|
|
# Sync wrapper functions for easy integration with existing code
|
|
def log_file_status_change_sync(
|
|
db: Session,
|
|
file_no: str,
|
|
old_status: str,
|
|
new_status: str,
|
|
user_id: Optional[int] = None,
|
|
notes: Optional[str] = None
|
|
):
|
|
"""
|
|
Synchronous wrapper for file status change logging
|
|
"""
|
|
integration = create_workflow_integration(db)
|
|
coro = integration.log_file_status_change(file_no, old_status, new_status, user_id, notes)
|
|
run_async_event_logging(coro)
|
|
|
|
|
|
def log_file_opened_sync(
|
|
db: Session,
|
|
file_no: str,
|
|
file_type: str,
|
|
client_id: str,
|
|
attorney: str,
|
|
user_id: Optional[int] = None
|
|
):
|
|
"""
|
|
Synchronous wrapper for file opened logging
|
|
"""
|
|
integration = create_workflow_integration(db)
|
|
coro = integration.log_file_opened(file_no, file_type, client_id, attorney, user_id)
|
|
run_async_event_logging(coro)
|
|
|
|
|
|
def log_deadline_approaching_sync(
|
|
db: Session,
|
|
deadline_id: int,
|
|
file_no: Optional[str] = None,
|
|
client_id: Optional[str] = None,
|
|
days_until_deadline: int = 0,
|
|
deadline_type: Optional[str] = None
|
|
):
|
|
"""
|
|
Synchronous wrapper for deadline approaching logging
|
|
"""
|
|
integration = create_workflow_integration(db)
|
|
coro = integration.log_deadline_approaching(deadline_id, file_no, client_id, days_until_deadline, deadline_type)
|
|
run_async_event_logging(coro)
|
|
|
|
|
|
def log_payment_received_sync(
|
|
db: Session,
|
|
file_no: str,
|
|
amount: float,
|
|
payment_type: str,
|
|
payment_date: Optional[datetime] = None,
|
|
user_id: Optional[int] = None
|
|
):
|
|
"""
|
|
Synchronous wrapper for payment received logging
|
|
"""
|
|
integration = create_workflow_integration(db)
|
|
coro = integration.log_payment_received(file_no, amount, payment_type, payment_date, user_id)
|
|
run_async_event_logging(coro)
|
|
|
|
|
|
def log_document_uploaded_sync(
|
|
db: Session,
|
|
file_no: str,
|
|
document_id: int,
|
|
filename: str,
|
|
document_type: Optional[str] = None,
|
|
user_id: Optional[int] = None
|
|
):
|
|
"""
|
|
Synchronous wrapper for document uploaded logging
|
|
"""
|
|
integration = create_workflow_integration(db)
|
|
coro = integration.log_document_uploaded(file_no, document_id, filename, document_type, user_id)
|
|
run_async_event_logging(coro)
|