function create_approval_cycle_v1
Creates a new approval cycle for a controlled document, managing approver assignments, permissions, notifications, and audit logging.
/tf/active/vicechatdev/CDocs/controllers/approval_controller.py
37 - 192
complex
Purpose
This function initiates a formal approval workflow for a document version. It validates user permissions, document state, and approval parameters before creating an ApprovalCycle with specified approvers. It supports both parallel and sequential approval workflows, manages document sharing permissions, sends notifications to approvers, and logs the approval initiation in the audit trail. The function is designed for document management systems requiring formal review and approval processes.
Source Code
def create_approval_cycle(
user: DocUser,
document_uid: str,
approver_uids: List[str],
approver_instructions: Optional[Dict[str, str]] = None, # New parameter: Dict mapping approver UIDs to instructions
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.
"""
try:
# Direct permission check at the beginning
logger.info(f"Checking if user {user.uid} has CREATE_APPROVE, INITIATE_APPROVE permission")
if not permissions.user_has_permission(user, ["CREATE_APPROVE", "INITIATE_APPROVE"]):
logger.warning(f"Permission denied: User {user.uid} attempted to create and initiate approval cycle without CREATE_APPROVE, INITIATE_APPROVE permission")
raise PermissionError("You do not have permission to manage approvals")
logger.info(f"User {user.uid} has permission to create and initiate approval cycle")
# Get document instance
document = ControlledDocument(uid=document_uid)
if not document:
raise ResourceNotFoundError(f"Document not found: {document_uid}")
# Check if document has a current version
current_version = document.current_version
if not current_version:
raise BusinessRuleError("Cannot start approval for document without a current version")
# Check if document status allows approval
if document.status not in ["DRAFT", "IN_APPROVE"]:
raise BusinessRuleError(f"Cannot start approval for document with status {document.status}")
# Validate approval type
if not approval_type in settings.APPROVAL_TYPES:
raise ValidationError(f"Invalid approval type: {approval_type}")
# Validate approver list
if not approver_uids or len(approver_uids) == 0:
raise ValidationError("At least one approver must be specified")
# Validate approval percentage
if required_approval_percentage < 1 or required_approval_percentage > 100:
raise ValidationError("Required approval percentage must be between 1 and 100")
# Set due date if not provided
if not due_date:
# Default to settings-defined approval period (e.g., 14 days)
approval_period_days = getattr(settings, 'DEFAULT_APPROVE_PERIOD_DAYS', 14)
due_date = datetime.now() + timedelta(days=approval_period_days)
# Create approval cycle with proper document version instance
approval_cycle = ApprovalCycle.create(
document_version_uid=current_version.uid,
approvers=approver_uids,
due_date=due_date,
instructions=instructions or "",
properties={
"approval_type": approval_type,
"sequential": sequential,
"required_approval_percentage": required_approval_percentage,
"initiated_by_uid": user.uid,
"initiated_by_name": user.name
}
)
# Add approvers with proper sequence order for sequential approvals
for i, approver_uid in enumerate(approver_uids):
# Add the approver relationship
approval_cycle.add_approver(approver_uid)
# Get assignment and set sequence order if sequential
assignment = approval_cycle.get_approver_assignment(approver_uid)
if assignment:
if sequential:
assignment.sequence_order = i + 1
if i==0:
assignment.status = "ACTIVE"
# Set approver-specific instructions if provided
if approver_instructions and approver_uid in approver_instructions:
assignment.instructions = approver_instructions[approver_uid]
assignment.save()
# Update sharing permissions for document based on new approval cycle
from CDocs.controllers.share_controller import manage_document_permissions
document = ControlledDocument(uid=document.uid)
permission_result = manage_document_permissions(document)
if not approval_cycle:
raise BusinessRuleError("Failed to create approval cycle")
# Log approval cycle creation - use a valid event type
try:
from CDocs.utils import audit_trail
audit_trail.log_event(
event_type="APPROVE_STARTED", # Use a valid event type
user=user,
resource_uid=approval_cycle.uid,
resource_type="ApprovalCycle",
details={
"document_uid": document_uid,
"version_uid": current_version.uid,
"approver_uids": approver_uids,
"due_date": due_date.isoformat() if due_date else None,
"approval_type": approval_type,
"sequential": sequential
}
)
except Exception as audit_err:
logger.error(f"Error logging approval cycle creation: {audit_err}")
# Continue even if audit logging fails
# Notify approvers with error handling
try:
from CDocs.utils.notifications import notify_approval
notify_approval(
approval=approval_cycle,
notification_type="APPROVAL_REQUESTED",
approver_uids=approver_uids
)
except Exception as notif_err:
logger.error(f"Error sending approval notifications: {notif_err}")
# Make sure approval_cycle.to_dict() returns a valid dict
try:
approval_data = approval_cycle.to_dict()
if not isinstance(approval_data, dict):
approval_data = {
"uid": approval_cycle.uid,
"status": approval_cycle.status,
"document_uid": document_uid
}
except Exception as dict_err:
logger.error(f"Error converting approval cycle to dict: {dict_err}")
approval_data = {
"uid": approval_cycle.uid,
"status": "PENDING"
}
return {
"success": True,
"approval_cycle": approval_data,
"message": "Approval cycle created successfully"
}
except (ResourceNotFoundError, ValidationError, PermissionError, BusinessRuleError) as e:
# Re-raise known errors
raise
except Exception as e:
logger.error(f"Error creating approval cycle: {e}")
raise BusinessRuleError(f"Failed to create approval cycle: {e}")
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 instance representing the user initiating the approval cycle. Must have CREATE_APPROVE and INITIATE_APPROVE permissions.
document_uid: String unique identifier of the ControlledDocument for which the approval cycle is being created. Document must exist and have a current version.
approver_uids: List of string UIDs identifying users who will approve the document. Must contain at least one approver. Order matters for sequential approvals.
approver_instructions: Optional dictionary mapping approver UIDs (strings) to specific instructions (strings) for each approver. Allows customized guidance per approver.
due_date: Optional datetime object specifying when the approval cycle should be completed. If not provided, defaults to current time plus DEFAULT_APPROVE_PERIOD_DAYS (default 14 days) from settings.
instructions: Optional string containing general instructions for all approvers in the approval cycle.
approval_type: String specifying the type of approval workflow. Must be one of the values defined in settings.APPROVAL_TYPES. Defaults to 'STANDARD'.
sequential: Boolean flag indicating whether approvers must approve in sequence (True) or can approve in parallel (False). Defaults to False.
required_approval_percentage: Integer between 1 and 100 specifying the percentage of approvers who must approve for the cycle to succeed. Defaults to 100 (unanimous approval).
Return Value
Type: Dict[str, Any]
Returns a dictionary with three keys: 'success' (boolean indicating operation success), 'approval_cycle' (dictionary representation of the created ApprovalCycle object containing uid, status, and other cycle details), and 'message' (string with success message). On error, raises one of the custom exceptions (PermissionError, ResourceNotFoundError, ValidationError, BusinessRuleError).
Dependencies
logginguuidostypingdatetimetracebackCDocs
Required Imports
from typing import Dict, List, Any, Optional
from datetime import datetime, timedelta
import logging
from CDocs.config import settings, permissions
from CDocs.models.document import ControlledDocument, DocumentVersion
from CDocs.models.approval import ApprovalCycle, ApprovalComment, ApproverAssignment
from CDocs.models.user_extensions import DocUser
from CDocs.controllers import log_controller_action, PermissionError, ResourceNotFoundError, ValidationError, BusinessRuleError
Conditional/Optional Imports
These imports are only needed under specific conditions:
from CDocs.utils import audit_trail
Condition: imported inside try block for logging approval cycle creation event
Required (conditional)from CDocs.utils.notifications import notify_approval
Condition: imported inside try block for sending approval notifications to approvers
Required (conditional)from CDocs.controllers.share_controller import manage_document_permissions
Condition: imported for updating document sharing permissions based on approval cycle
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
# Get the user initiating the approval
initiating_user = DocUser(uid='user123')
# Define approvers and their specific instructions
approver_uids = ['approver1_uid', 'approver2_uid', 'approver3_uid']
approver_instructions = {
'approver1_uid': 'Please review technical accuracy',
'approver2_uid': 'Please verify compliance requirements'
}
# Set due date to 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='doc456',
approver_uids=approver_uids,
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']:
approval_cycle_uid = result['approval_cycle']['uid']
print(f"Approval cycle created: {approval_cycle_uid}")
else:
print(f"Error: {result['message']}")
Best Practices
- Always ensure the user has CREATE_APPROVE and INITIATE_APPROVE permissions before calling this function
- Verify the document exists and has a current version before initiating approval
- Only initiate approval for documents in DRAFT or IN_APPROVE status
- Provide at least one approver UID in the approver_uids list
- Set required_approval_percentage between 1 and 100 based on your approval policy
- Use sequential=True when approvers must review in a specific order (e.g., technical review before management approval)
- Provide approver-specific instructions using approver_instructions dictionary for clarity
- Handle all custom exceptions (PermissionError, ResourceNotFoundError, ValidationError, BusinessRuleError) appropriately
- The function automatically sends notifications and logs audit events, but continues execution if these fail
- Document permissions are automatically updated based on the approval cycle creation
- For sequential approvals, only the first approver will have ACTIVE status initially
- Ensure settings.APPROVAL_TYPES contains the approval_type value you intend to use
Tags
Similar Components
AI-powered semantic similarity - components with related functionality:
-
function create_approval_cycle 85.7% similar
-
function create_review_cycle 84.4% similar
-
class ApprovalCycle 75.4% similar
-
class ApprovalCycle_v1 74.0% similar
-
function update_approver_permissions 71.1% similar