function create_approval_cycle
Creates a new approval cycle for a document, assigning approvers with configurable workflow options (sequential/parallel), instructions, and due dates.
/tf/active/vicechatdev/CDocs/controllers/approval_controller_bis.py
34 - 207
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
CDocstypingdatetimeloggingtraceback
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
Tags
Similar Components
AI-powered semantic similarity - components with related functionality:
-
function create_approval_cycle_v1 85.7% similar
-
function add_approver_to_active_approval 79.9% similar
-
function add_approver_to_active_approval_v1 77.7% similar
-
function complete_approval_v1 76.3% similar
-
function complete_approval 76.3% similar