🔍 Code Extractor

function create_approval_cycle

Maturity: 86

Creates a new approval cycle for a document, assigning approvers with configurable workflow options (sequential/parallel), instructions, and due dates.

File:
/tf/active/vicechatdev/CDocs/controllers/approval_controller_bis.py
Lines:
34 - 207
Complexity:
complex

Purpose

This function orchestrates the creation of a document approval workflow by validating permissions, creating an approval cycle record, assigning approvers with optional individual instructions, updating document status, logging audit trails, and sending notifications. It supports both sequential and parallel approval workflows with configurable approval percentage thresholds. The function uses database transactions to ensure atomicity of the approval cycle creation process.

Source Code

def create_approval_cycle(
    user: DocUser,
    document_uid: str,
    approver_uids: List[str],
    approver_instructions: Optional[Dict[str, str]] = None,    
    due_date: Optional[datetime] = None,
    instructions: Optional[str] = None,
    approval_type: str = "STANDARD",
    sequential: bool = False,
    required_approval_percentage: int = 100
) -> Dict[str, Any]:
    """
    Create a new approval cycle for a document.
    
    Args:
        user: The user initiating the approval
        document_uid: UID of the document to approve
        approver_uids: List of user UIDs to assign as approvers
        approver_instructions: Dict mapping approver UID to specific instructions
        due_date: Date when approval should be completed
        instructions: General instructions for approvers
        approval_type: Type of approval workflow
        sequential: Whether approvals must happen in sequence
        required_approval_percentage: Percentage of approvers that must approve
        
    Returns:
        Result dictionary with success flag and approval cycle information
    """
    try:
        # Ensure user has permission to initiate approvals
        if not permissions.user_has_permission(user, "INITIATE_APPROVAL"):
            raise PermissionError("You do not have permission to initiate approval cycles")
            
        from CDocs.controllers.document_controller import get_document
        # Verify document exists
        document = get_document(document_uid=document_uid)
        if not document:
            raise ResourceNotFoundError("Document not found")
            
        # Can only initiate approval for documents in certain states
        allowed_statuses = getattr(settings, "APPROVAL_ALLOWED_STATUSES", ["DRAFT", "IN_REVIEW", "REVIEWED"])
        if document.get("status") not in allowed_statuses:
            raise BusinessRuleError(f"Document must be in one of these states to start approval: {', '.join(allowed_statuses)}")
            
        # Get current document version
        current_version = document.get("current_version")
        if not current_version:
            raise BusinessRuleError("Document must have a current version to initiate approval")
            
        # Ensure we have at least one approver
        if not approver_uids or len(approver_uids) == 0:
            raise ValidationError("At least one approver must be specified")
            
        # Validate approver UIDs
        for uid in approver_uids:
            approver = DocUser(uid=uid)
            if not approver or not approver.uid:
                raise ValidationError(f"Invalid approver UID: {uid}")
                
        from CDocs.db import get_driver
        approval_cycle = None

        # Get a driver and create a transaction
        driver = get_driver()
        with driver.session() as session:
            with session.begin_transaction() as tx:
                try:
                    # Create the approval cycle
                    approval_cycle = ApprovalCycle.create(
                        document_version_uid=current_version.get("UID"),
                        approvers=[],  # Start with no approvers, add them below
                        due_date=due_date,
                        instructions=instructions,
                        properties={
                            "approvalType": approval_type,
                            "sequential": sequential,
                            "requiredApprovalPercentage": required_approval_percentage,
                            "initiatedByUID": user.uid,
                            "initiatedByName": user.name
                        }
                    )
            
                    if not approval_cycle:
                        raise BusinessRuleError("Failed to create approval cycle")
                    
                    # Add approvers individually with proper sequence
                    for i, approver_uid in enumerate(approver_uids):
                        success = approval_cycle.add_approver(approver_uid)
                        if not success:
                            raise BusinessRuleError(f"Failed to add approver {approver_uid}")
                        
                        # Get the assignment and update sequence order separately
                        assignment = approval_cycle.get_approver_assignment(approver_uid)
                        if assignment:
                            if sequential:
                                # Set sequence order for sequential workflows
                                assignment.sequence_order = i + 1
                                
                            # Add specific instructions if provided
                            if approver_instructions and approver_uid in approver_instructions:
                                assignment.instructions = approver_instructions[approver_uid]
                    
                    # Update document status to in approval
                    from CDocs.controllers.document_controller import update_document
                    update_document(
                        document_uid=document_uid,
                        user=user,
                        status="IN_APPROVAL"
                    )
                    
                    # Commit the transaction if everything succeeds
                    tx.commit()
                except Exception as e:
                    # Roll back the transaction if anything fails
                    tx.rollback()
                    logger.error(f"Error in transaction creating approval cycle: {e}")
                    raise BusinessRuleError(f"Failed to create approval cycle: {e}")
        
        # If we got here, the transaction was successful
        # Create audit trail entry
        audit_trail.log_event(
            event_type="APPROVAL_CYCLE_CREATED",
            user=user,
            resource_uid=approval_cycle.uid,
            resource_type="ApprovalCycle",
            details={
                "document_uid": document_uid,
                "approval_type": approval_type,
                "approver_count": len(approver_uids),
                "sequential": sequential
            }
        )
        
        # Send notifications to approvers
        for approver_uid in approver_uids:
            approver = DocUser(uid=approver_uid)
            if approver:
                notifications.send_notification(
                    notification_type="APPROVAL_ASSIGNED",
                    users=approver.uid,
                    resource_uid=approval_cycle.uid,
                    resource_type="ApprovalCycle",
                    message=f"You have been assigned to approve {document.get('title')}",
                    details={
                        "document_uid": document_uid,
                        "document_number": document.get("docNumber"),
                        "document_title": document.get("title"),
                        "approval_cycle_uid": approval_cycle.uid,
                        "due_date": due_date.isoformat() if due_date else None
                    },
                    send_email=True,
                    email_template="approval_assigned"
                )
        
        return {
            "success": True,
            "message": "Approval cycle created successfully",
            "approval_cycle_uid": approval_cycle.uid,
            "document_uid": document_uid
        }
    
    except PermissionError as e:
        logger.warning(f"Permission error creating approval cycle: {e}")
        return {"success": False, "message": str(e)}
        
    except (ResourceNotFoundError, ValidationError, BusinessRuleError) as e:
        logger.warning(f"Error creating approval cycle: {e}")
        return {"success": False, "message": str(e)}
        
    except Exception as e:
        logger.error(f"Unexpected error creating approval cycle: {e}")
        import traceback
        logger.error(traceback.format_exc())
        return {"success": False, "message": "An unexpected error occurred"}

Parameters

Name Type Default Kind
user DocUser - positional_or_keyword
document_uid str - positional_or_keyword
approver_uids List[str] - positional_or_keyword
approver_instructions Optional[Dict[str, str]] None positional_or_keyword
due_date Optional[datetime] None positional_or_keyword
instructions Optional[str] None positional_or_keyword
approval_type str 'STANDARD' positional_or_keyword
sequential bool False positional_or_keyword
required_approval_percentage int 100 positional_or_keyword

Parameter Details

user: DocUser object representing the user initiating the approval cycle. Must have 'INITIATE_APPROVAL' permission. Used for permission checks, audit logging, and document status updates.

document_uid: Unique identifier (string) of the document for which the approval cycle is being created. The document must exist and be in an allowed status (DRAFT, IN_REVIEW, or REVIEWED by default).

approver_uids: List of user UID strings identifying the users who will be assigned as approvers. Must contain at least one valid user UID. Each UID is validated to ensure the user exists.

approver_instructions: Optional dictionary mapping approver UIDs (keys) to specific instruction strings (values). Allows customized instructions for individual approvers beyond the general instructions.

due_date: Optional datetime object specifying when the approval should be completed. Used for notifications and tracking. If provided, will be included in approver notifications.

instructions: Optional string containing general instructions for all approvers in the approval cycle. These are stored with the approval cycle and visible to all approvers.

approval_type: String indicating the type of approval workflow. Defaults to 'STANDARD'. Stored in approval cycle properties and can be used to differentiate workflow types.

sequential: Boolean flag indicating whether approvals must happen in sequence (True) or can happen in parallel (False). When True, approvers are assigned sequence_order values based on their position in approver_uids list.

required_approval_percentage: Integer (0-100) specifying the percentage of approvers that must approve for the cycle to be considered complete. Defaults to 100 (all approvers must approve).

Return Value

Type: Dict[str, Any]

Returns a dictionary with keys: 'success' (boolean indicating operation success), 'message' (string describing the result or error), 'approval_cycle_uid' (string UID of created approval cycle, only on success), and 'document_uid' (string UID of the document, only on success). On failure, only 'success' (False) and 'message' (error description) are returned.

Dependencies

  • CDocs
  • typing
  • datetime
  • logging
  • traceback

Required Imports

from typing import Dict, List, Any, Optional
from datetime import datetime
import logging
from CDocs.config import settings, permissions
from CDocs.models.approval import ApprovalCycle
from CDocs.models.user_extensions import DocUser
from CDocs.utils import audit_trail, notifications
from CDocs.controllers import log_controller_action, PermissionError, ResourceNotFoundError, ValidationError, BusinessRuleError
from CDocs.db import get_driver

Conditional/Optional Imports

These imports are only needed under specific conditions:

from CDocs.controllers.document_controller import get_document

Condition: imported inside function to verify document exists and retrieve document information

Required (conditional)
from CDocs.controllers.document_controller import update_document

Condition: imported inside transaction block to update document status to IN_APPROVAL

Required (conditional)
import traceback

Condition: imported in exception handler for detailed error logging

Required (conditional)

Usage Example

from datetime import datetime, timedelta
from CDocs.models.user_extensions import DocUser
from CDocs.controllers.approval_controller import create_approval_cycle

# Initialize the requesting user
initiating_user = DocUser(uid='user123')

# Define approvers
approver_list = ['approver1_uid', 'approver2_uid', 'approver3_uid']

# Optional: specific instructions per approver
approver_instructions = {
    'approver1_uid': 'Please review technical accuracy',
    'approver2_uid': 'Please verify compliance requirements'
}

# Set due date (7 days from now)
due_date = datetime.now() + timedelta(days=7)

# Create sequential approval cycle requiring 100% approval
result = create_approval_cycle(
    user=initiating_user,
    document_uid='doc_abc123',
    approver_uids=approver_list,
    approver_instructions=approver_instructions,
    due_date=due_date,
    instructions='Please review and approve this document by the due date',
    approval_type='STANDARD',
    sequential=True,
    required_approval_percentage=100
)

if result['success']:
    print(f"Approval cycle created: {result['approval_cycle_uid']}")
else:
    print(f"Error: {result['message']}")

Best Practices

  • Always ensure the initiating user has 'INITIATE_APPROVAL' permission before calling this function
  • Validate that all approver UIDs exist and are valid users before passing to the function
  • The function uses database transactions - ensure the database connection is properly configured and available
  • Handle the returned dictionary appropriately, checking the 'success' flag before accessing other keys
  • When using sequential=True, the order of approver_uids matters as it determines the approval sequence
  • Set required_approval_percentage appropriately based on business rules (100 for unanimous approval, lower values for majority approval)
  • Ensure the document is in an allowed status before attempting to create an approval cycle
  • The function automatically updates document status to 'IN_APPROVAL' and sends notifications to all approvers
  • Use approver_instructions parameter to provide context-specific guidance to individual approvers
  • The function is decorated with @log_controller_action, so all invocations are automatically logged

Similar Components

AI-powered semantic similarity - components with related functionality:

  • function create_approval_cycle_v1 85.7% similar

    Creates a new approval cycle for a controlled document, managing approver assignments, permissions, notifications, and audit logging.

    From: /tf/active/vicechatdev/CDocs/controllers/approval_controller.py
  • function add_approver_to_active_approval 79.9% similar

    Adds a new approver to an active approval cycle for a controlled document, with optional sequence ordering and custom instructions.

    From: /tf/active/vicechatdev/CDocs/controllers/approval_controller.py
  • function add_approver_to_active_approval_v1 77.7% similar

    Adds a new approver to an active approval cycle with permission checks, validation, audit logging, and email notifications.

    From: /tf/active/vicechatdev/CDocs/controllers/approval_controller_bis.py
  • function complete_approval_v1 76.3% similar

    Records a user's approval decision (APPROVED or REJECTED) for a document in an approval cycle, updating the approval status and document state accordingly.

    From: /tf/active/vicechatdev/CDocs/controllers/approval_controller_bis.py
  • function complete_approval 76.3% similar

    Completes an approval cycle by recording a user's approval decision (APPROVED, REJECTED, etc.) and managing the approval workflow, including sequential approver activation and final cycle completion.

    From: /tf/active/vicechatdev/CDocs/controllers/approval_controller.py
← Back to Browse