changes
This commit is contained in:
519
app/services/workflow_integration.py
Normal file
519
app/services/workflow_integration.py
Normal file
@@ -0,0 +1,519 @@
|
||||
"""
|
||||
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)
|
||||
Reference in New Issue
Block a user