""" Document Workflow Automation Models This module provides automated document generation workflows triggered by case events, deadlines, file status changes, and other system events. """ from sqlalchemy import Column, Integer, String, Text, ForeignKey, Boolean, JSON, DateTime, Date, Enum from sqlalchemy.orm import relationship from sqlalchemy.sql import func from enum import Enum as PyEnum from typing import Dict, Any, List, Optional import json from app.models.base import BaseModel class WorkflowTriggerType(PyEnum): """Types of events that can trigger workflows""" FILE_STATUS_CHANGE = "file_status_change" DEADLINE_APPROACHING = "deadline_approaching" DEADLINE_OVERDUE = "deadline_overdue" DEADLINE_COMPLETED = "deadline_completed" PAYMENT_RECEIVED = "payment_received" PAYMENT_OVERDUE = "payment_overdue" FILE_OPENED = "file_opened" FILE_CLOSED = "file_closed" DOCUMENT_UPLOADED = "document_uploaded" QDRO_STATUS_CHANGE = "qdro_status_change" TIME_BASED = "time_based" MANUAL_TRIGGER = "manual_trigger" CUSTOM_EVENT = "custom_event" class WorkflowStatus(PyEnum): """Workflow execution status""" ACTIVE = "active" INACTIVE = "inactive" PAUSED = "paused" ARCHIVED = "archived" class WorkflowActionType(PyEnum): """Types of actions a workflow can perform""" GENERATE_DOCUMENT = "generate_document" SEND_EMAIL = "send_email" CREATE_DEADLINE = "create_deadline" UPDATE_FILE_STATUS = "update_file_status" CREATE_LEDGER_ENTRY = "create_ledger_entry" SEND_NOTIFICATION = "send_notification" EXECUTE_CUSTOM = "execute_custom" class ExecutionStatus(PyEnum): """Status of workflow execution""" PENDING = "pending" RUNNING = "running" COMPLETED = "completed" FAILED = "failed" CANCELLED = "cancelled" RETRYING = "retrying" class DocumentWorkflow(BaseModel): """ Defines automated workflows for document generation and case management """ __tablename__ = "document_workflows" id = Column(Integer, primary_key=True, autoincrement=True, index=True) # Basic workflow information name = Column(String(200), nullable=False, index=True) description = Column(Text, nullable=True) status = Column(Enum(WorkflowStatus), default=WorkflowStatus.ACTIVE, nullable=False) # Trigger configuration trigger_type = Column(Enum(WorkflowTriggerType), nullable=False, index=True) trigger_conditions = Column(JSON, nullable=True) # JSON conditions for when to trigger # Execution settings delay_minutes = Column(Integer, default=0) # Delay before execution max_retries = Column(Integer, default=3) retry_delay_minutes = Column(Integer, default=30) timeout_minutes = Column(Integer, default=60) # Filtering conditions file_type_filter = Column(JSON, nullable=True) # Array of file types to include status_filter = Column(JSON, nullable=True) # Array of file statuses to include attorney_filter = Column(JSON, nullable=True) # Array of attorney IDs to include client_filter = Column(JSON, nullable=True) # Array of client IDs to include # Schedule settings (for time-based triggers) schedule_cron = Column(String(100), nullable=True) # Cron expression for scheduling schedule_timezone = Column(String(50), default="UTC") next_run_time = Column(DateTime(timezone=True), nullable=True) # Priority and organization priority = Column(Integer, default=5) # 1-10, higher = more important category = Column(String(100), nullable=True, index=True) tags = Column(JSON, nullable=True) # Array of tags for organization # Metadata created_by = Column(String(150), ForeignKey("users.username"), nullable=True) created_at = Column(DateTime(timezone=True), default=func.now(), nullable=False) updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) last_triggered_at = Column(DateTime(timezone=True), nullable=True) # Statistics execution_count = Column(Integer, default=0) success_count = Column(Integer, default=0) failure_count = Column(Integer, default=0) # Relationships actions = relationship("WorkflowAction", back_populates="workflow", cascade="all, delete-orphan") executions = relationship("WorkflowExecution", back_populates="workflow", cascade="all, delete-orphan") def __repr__(self): return f"" class WorkflowAction(BaseModel): """ Individual actions within a workflow """ __tablename__ = "workflow_actions" id = Column(Integer, primary_key=True, autoincrement=True, index=True) workflow_id = Column(Integer, ForeignKey("document_workflows.id"), nullable=False, index=True) # Action configuration action_type = Column(Enum(WorkflowActionType), nullable=False) action_order = Column(Integer, default=1) # Order of execution within workflow action_name = Column(String(200), nullable=True) # Optional descriptive name # Action parameters (specific to action type) parameters = Column(JSON, nullable=True) # Document generation specific fields template_id = Column(Integer, ForeignKey("document_templates.id"), nullable=True) output_format = Column(String(50), default="DOCX") # DOCX, PDF, HTML custom_filename_template = Column(String(500), nullable=True) # Template for filename # Email action specific fields email_template_id = Column(Integer, nullable=True) # Reference to email template email_recipients = Column(JSON, nullable=True) # Array of recipient types/addresses email_subject_template = Column(String(500), nullable=True) # Conditional execution condition = Column(JSON, nullable=True) # Conditions for this action to execute continue_on_failure = Column(Boolean, default=False) # Whether to continue if this action fails # Relationships workflow = relationship("DocumentWorkflow", back_populates="actions") template = relationship("DocumentTemplate") def __repr__(self): return f"" class WorkflowExecution(BaseModel): """ Tracks individual workflow executions """ __tablename__ = "workflow_executions" id = Column(Integer, primary_key=True, autoincrement=True, index=True) workflow_id = Column(Integer, ForeignKey("document_workflows.id"), nullable=False, index=True) # Execution context triggered_by_event_id = Column(String(100), nullable=True, index=True) # Reference to triggering event triggered_by_event_type = Column(String(50), nullable=True) context_file_no = Column(String(45), nullable=True, index=True) context_client_id = Column(String(80), nullable=True) context_user_id = Column(Integer, ForeignKey("users.id"), nullable=True) # Execution details status = Column(Enum(ExecutionStatus), default=ExecutionStatus.PENDING, nullable=False) started_at = Column(DateTime(timezone=True), nullable=True) completed_at = Column(DateTime(timezone=True), nullable=True) # Input data and context trigger_data = Column(JSON, nullable=True) # Data from the triggering event execution_context = Column(JSON, nullable=True) # Variables and context for execution # Results and outputs generated_documents = Column(JSON, nullable=True) # Array of generated document info action_results = Column(JSON, nullable=True) # Results from each action error_message = Column(Text, nullable=True) error_details = Column(JSON, nullable=True) # Performance metrics execution_duration_seconds = Column(Integer, nullable=True) retry_count = Column(Integer, default=0) next_retry_at = Column(DateTime(timezone=True), nullable=True) # Relationships workflow = relationship("DocumentWorkflow", back_populates="executions") user = relationship("User") def __repr__(self): return f"" class WorkflowTemplate(BaseModel): """ Pre-defined workflow templates for common scenarios """ __tablename__ = "workflow_templates" id = Column(Integer, primary_key=True, autoincrement=True, index=True) # Template information name = Column(String(200), nullable=False, unique=True) description = Column(Text, nullable=True) category = Column(String(100), nullable=True, index=True) # Workflow definition workflow_definition = Column(JSON, nullable=False) # Complete workflow configuration # Metadata created_by = Column(String(150), ForeignKey("users.username"), nullable=True) created_at = Column(DateTime(timezone=True), default=func.now(), nullable=False) is_system_template = Column(Boolean, default=False) # Built-in vs user-created usage_count = Column(Integer, default=0) # How many times this template has been used # Version control version = Column(String(20), default="1.0.0") template_tags = Column(JSON, nullable=True) # Tags for categorization def __repr__(self): return f"" class EventLog(BaseModel): """ Unified event log for workflow triggering """ __tablename__ = "event_log" id = Column(Integer, primary_key=True, autoincrement=True, index=True) # Event identification event_id = Column(String(100), nullable=False, unique=True, index=True) # UUID event_type = Column(String(50), nullable=False, index=True) event_source = Column(String(100), nullable=False) # Which system/module generated the event # Event context file_no = Column(String(45), nullable=True, index=True) client_id = Column(String(80), nullable=True, index=True) user_id = Column(Integer, ForeignKey("users.id"), nullable=True) resource_type = Column(String(50), nullable=True) # deadline, file, payment, etc. resource_id = Column(String(100), nullable=True) # Event data event_data = Column(JSON, nullable=True) # Event-specific data previous_state = Column(JSON, nullable=True) # Previous state before event new_state = Column(JSON, nullable=True) # New state after event # Workflow processing processed = Column(Boolean, default=False, index=True) processed_at = Column(DateTime(timezone=True), nullable=True) triggered_workflows = Column(JSON, nullable=True) # Array of workflow IDs triggered processing_errors = Column(JSON, nullable=True) # Timing occurred_at = Column(DateTime(timezone=True), default=func.now(), nullable=False, index=True) # Relationships user = relationship("User") def __repr__(self): return f"" class WorkflowSchedule(BaseModel): """ Scheduled workflow executions (for time-based triggers) """ __tablename__ = "workflow_schedules" id = Column(Integer, primary_key=True, autoincrement=True, index=True) workflow_id = Column(Integer, ForeignKey("document_workflows.id"), nullable=False, index=True) # Schedule configuration schedule_name = Column(String(200), nullable=True) cron_expression = Column(String(100), nullable=False) timezone = Column(String(50), default="UTC") # Execution tracking next_run_time = Column(DateTime(timezone=True), nullable=False, index=True) last_run_time = Column(DateTime(timezone=True), nullable=True) # Status active = Column(Boolean, default=True, nullable=False) # Metadata created_at = Column(DateTime(timezone=True), default=func.now(), nullable=False) # Relationships workflow = relationship("DocumentWorkflow") def __repr__(self): return f""