function cancel_approval_cycle
Cancels an active approval cycle for a controlled document, updating the approval status, notifying approvers, and reverting the document to DRAFT status if no other active approvals exist.
/tf/active/vicechatdev/CDocs/controllers/approval_controller.py
1127 - 1277
complex
Purpose
This function manages the cancellation workflow for document approval cycles in a controlled document management system. It enforces permission checks, validates cancellation reasons, updates approval cycle and approver assignment statuses, logs audit events, sends notifications to all approvers, and manages document status transitions. It ensures that only users with MANAGE_APPROVALS permission, document owners, or approval initiators can cancel approval cycles, and only when the approval is in PENDING or IN_PROGRESS status.
Source Code
def cancel_approval_cycle(
user: DocUser,
approval_uid: str,
reason: str
) -> Dict[str, Any]:
"""
Cancel an active approval cycle.
Args:
user: User canceling the approval
approval_uid: UID of approval cycle to cancel
reason: Reason for cancellation
Returns:
Dictionary with cancellation details
Raises:
ResourceNotFoundError: If approval cycle not found
ValidationError: If validation fails
PermissionError: If user doesn't have permission
BusinessRuleError: If cancellation is not allowed
"""
try:
# Direct permission check at the beginning
logger.info(f"Checking if user {user.uid} has MANAGE_APPROVALS permission")
if not permissions.user_has_permission(user, "MANAGE_APPROVALS"):
logger.warning(f"Permission denied: User {user.uid} attempted to manage approvals without MANAGE_APPROVALS permission")
raise PermissionError("You do not have permission to manage approvals")
logger.info(f"User {user.uid} has permission to manage approvals {approval_uid}")
# Get approval cycle instance
approval_cycle = ApprovalCycle(uid=approval_uid)
if not approval_cycle:
raise ResourceNotFoundError(f"Approval cycle not found: {approval_uid}")
# Check if approval can be canceled
if approval_cycle.status not in ["PENDING", "IN_PROGRESS"]:
raise BusinessRuleError(f"Cannot cancel approval with status {approval_cycle.status}")
# Check if user has permission
if not permissions.user_has_permission(user, "MANAGE_APPROVALS"):
# Check if user is the document owner or approval initiator
document = ControlledDocument(uid=approval_cycle.document_uid)
if not (document and document.owner_uid == user.uid) and approval_cycle.initiated_by_uid != user.uid:
raise PermissionError("User is not authorized to cancel this approval cycle")
# Validate reason
if not reason or len(reason.strip()) == 0:
raise ValidationError("Cancellation reason is required")
# Update approval cycle
approval_cycle.status = "CANCELED"
approval_cycle.cancellation_date = datetime.now()
approval_cycle.cancellation_reason = reason
approval_cycle.save()
# Update all active approver assignments to canceled
assignments = approval_cycle.get_approver_assignments()
for assignment in assignments:
if assignment.status in ["PENDING", "ACTIVE"]:
assignment.status = "CANCELED"
assignment.save()
# Check if document status should be updated
document = ControlledDocument(uid=approval_cycle.document_uid)
if document and document.status == "IN_APPROVAL":
# Update the document status back to DRAFT
document.status = "DRAFT"
document.save()
# Add document status update to the event details for audit trail
audit_trail.log_event(
event_type="DOCUMENT_STATUS_CHANGED",
user=user,
resource_uid=document.uid,
resource_type="ControlledDocument",
details={
"old_status": "IN_APPROVAL",
"new_status": "DRAFT",
"reason": f"Approval cycle {approval_uid} was canceled"
}
)
# Log cancellation event
audit_trail.log_approval_event(
event_type="APPROVE_CYCLE_CANCELED",
user=user,
approval_uid=approval_uid,
details={
"reason": reason
}
)
# Get document
document = ControlledDocument(uid=approval_cycle.document_uid)
# Notify approvers
for approver_uid in approval_cycle.approver_uids:
notifications.send_notification(
notification_type="APPROVE_CANCELED",
users=[approver_uid],
resource_uid=approval_cycle.uid,
resource_type="ApprovalCycle",
message=f"Approval for {document.doc_number if document else 'document'} has been canceled",
details={
"approval_uid": approval_uid,
"document_uid": document.uid if document else None,
"doc_number": document.doc_number if document else None,
"title": document.title if document else None,
"reason": reason
},
send_email=True,
email_template="approval_canceled"
)
# Check if document status should be updated
if document and document.status == "IN_APPROVAL":
# Check if there are any other active approvals
other_active_approvals = ApprovalCycle.get_approvals_for_document(
document_uid=document.uid,
status_filter=["PENDING", "IN_PROGRESS"]
)
# If no other active approvals, update document status back to DRAFT
if not other_active_approvals:
from CDocs.controllers.document_controller import update_document
update_document(
user=user,
document_uid=document.uid,
status="DRAFT"
)
# 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)
return {
"success": True,
"approval_uid": approval_uid,
"reason": reason,
"message": "Approval cycle canceled successfully"
}
except (ResourceNotFoundError, ValidationError, PermissionError, BusinessRuleError) as e:
# Re-raise known errors
raise
except Exception as e:
logger.error(f"Error canceling approval cycle: {e}")
raise BusinessRuleError(f"Failed to cancel approval cycle: {e}")
Parameters
| Name | Type | Default | Kind |
|---|---|---|---|
user |
DocUser | - | positional_or_keyword |
approval_uid |
str | - | positional_or_keyword |
reason |
str | - | positional_or_keyword |
Parameter Details
user: DocUser object representing the user requesting the cancellation. Must have MANAGE_APPROVALS permission, be the document owner, or be the approval initiator. Used for permission checks and audit logging.
approval_uid: String containing the unique identifier (UID) of the approval cycle to cancel. Must correspond to an existing ApprovalCycle record in the system.
reason: String explaining why the approval cycle is being canceled. Cannot be empty or whitespace-only. This reason is stored in the approval cycle record and included in notifications to approvers.
Return Value
Type: Dict[str, Any]
Returns a dictionary with keys: 'success' (boolean, always True on successful execution), 'approval_uid' (string, the UID of the canceled approval), 'reason' (string, the cancellation reason provided), and 'message' (string, confirmation message 'Approval cycle canceled successfully'). On error, raises one of the documented exceptions instead of returning.
Dependencies
logginguuidostypingdatetimetracebackCDocs.dbCDocs.config.settingsCDocs.config.permissionsCDocs.models.documentCDocs.models.approvalCDocs.models.user_extensionsCDocs.utils.audit_trailCDocs.utils.notificationsCDocs.controllersCDocs.controllers.share_controllerCDocs.controllers.document_controller
Required Imports
from typing import Dict, Any
from datetime import datetime
from CDocs.config import permissions
from CDocs.models.document import ControlledDocument
from CDocs.models.approval import ApprovalCycle
from CDocs.models.user_extensions import DocUser
from CDocs.utils import audit_trail, notifications
from CDocs.controllers import PermissionError, ResourceNotFoundError, ValidationError, BusinessRuleError, log_controller_action
from CDocs.controllers.share_controller import manage_document_permissions
import logging
Conditional/Optional Imports
These imports are only needed under specific conditions:
from CDocs.controllers.document_controller import update_document
Condition: only when document status needs to be updated back to DRAFT and no other active approvals exist
Required (conditional)Usage Example
from CDocs.models.user_extensions import DocUser
from CDocs.controllers.approval_controller import cancel_approval_cycle
from CDocs.controllers import PermissionError, ResourceNotFoundError
# Get the user who is canceling the approval
user = DocUser.get_by_uid('user_123')
# Cancel an approval cycle
try:
result = cancel_approval_cycle(
user=user,
approval_uid='approval_456',
reason='Requirements have changed and document needs major revision'
)
print(f"Success: {result['message']}")
print(f"Canceled approval: {result['approval_uid']}")
except PermissionError as e:
print(f"Permission denied: {e}")
except ResourceNotFoundError as e:
print(f"Approval not found: {e}")
except ValidationError as e:
print(f"Invalid input: {e}")
except BusinessRuleError as e:
print(f"Cannot cancel: {e}")
Best Practices
- Always provide a meaningful cancellation reason as it will be logged in audit trail and sent to all approvers
- Ensure the user has appropriate permissions (MANAGE_APPROVALS, document ownership, or approval initiator) before calling this function
- Handle all four exception types (ResourceNotFoundError, ValidationError, PermissionError, BusinessRuleError) when calling this function
- Be aware that canceling an approval cycle will revert the document to DRAFT status if no other active approvals exist
- This function sends email notifications to all approvers, so ensure email service is properly configured
- The function is decorated with @log_controller_action which provides automatic logging of the action
- Cancellation is only allowed for approval cycles with status PENDING or IN_PROGRESS
- The function updates multiple related entities (approval cycle, approver assignments, document status) in a coordinated manner
- All status changes and cancellations are logged to the audit trail for compliance and tracking
- Document permissions are automatically recalculated after cancellation via manage_document_permissions
Tags
Similar Components
AI-powered semantic similarity - components with related functionality:
-
function cancel_approval_cycle_v1 93.6% similar
-
function cancel_review_cycle 85.9% similar
-
function remove_approver_from_active_approval 79.8% similar
-
function close_approval_cycle 77.5% similar
-
function remove_approver_from_active_approval_v1 75.1% similar