function remove_approver_from_active_approval
Removes an approver from an active approval cycle with permission checks, validation, and notification handling.
/tf/active/vicechatdev/CDocs/controllers/approval_controller.py
1532 - 1680
complex
Purpose
This function manages the removal of an approver from an active document approval cycle. It performs comprehensive permission checks, validates the approval cycle state, updates approver assignments, adjusts sequential approval orders if necessary, logs audit events, sends notifications to the removed approver, and updates document sharing permissions. It ensures business rules are enforced (e.g., cannot remove approvers from completed approvals or non-active cycles).
Source Code
def remove_approver_from_active_approval(
user: DocUser,
approval_uid: str,
approver_uid: str,
reason: str
) -> Dict[str, Any]:
"""
Remove a approver from an active approval cycle.
Args:
user: User removing the approver
approval_uid: UID of approval cycle
approver_uid: UID of approver to remove
reason: Reason for removal
Returns:
Dictionary with removal details
Raises:
ResourceNotFoundError: If approval cycle or approver not found
ValidationError: If validation fails
PermissionError: If user doesn't have permission
BusinessRuleError: If removal 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 is still active
if approval_cycle.status not in ["PENDING", "IN_PROGRESS"]:
raise BusinessRuleError(f"Cannot remove approvers from approval with status {approval_cycle.status}")
# Check if approver exists and is a approver for this cycle
if not approval_cycle.is_approver(approver_uid):
raise ResourceNotFoundError(f"Approver {approver_uid} is not assigned to this approval cycle")
# Validate reason
if not reason or len(reason.strip()) == 0:
raise ValidationError("Removal reason is required")
# Get approver assignment
assignment = approval_cycle.get_approver_assignment(approver_uid)
if not assignment:
raise ResourceNotFoundError(f"Approver assignment not found for {approver_uid}")
# Check if approval is already completed
if assignment.status == "COMPLETED":
raise BusinessRuleError("Cannot remove a approver who has already completed their approval")
# Get approver name for notification
approver_name = assignment.approver_name
if not approver_name:
approver = DocUser(uid=approver_uid)
approver_name = approver.name if approver else "Unknown"
# Update assignment status
assignment.status = "REMOVED"
assignment.removal_date = datetime.now()
assignment.removal_reason = reason
assignment.save()
# Use the model method to remove the approver relationship
success = approval_cycle.remove_approver(approver_uid)
if not success:
logger.warning(f"Failed to remove relationship for approver {approver_uid}")
# Continue anyway since we've updated the assignment status
# If sequential approval, adjust sequence orders of other approvers
if approval_cycle.sequential and assignment.sequence_order:
# Get all other assignments
all_assignments = approval_cycle.get_approver_assignments()
# Update sequence for any assignments that come after this one
removed_sequence = assignment.sequence_order
for other_assignment in all_assignments:
if (other_assignment.uid != assignment.uid and
other_assignment.sequence_order and
other_assignment.sequence_order > removed_sequence):
# Decrement sequence order
other_assignment.sequence_order -= 1
other_assignment.save()
# Log removal event
audit_trail.log_approval_event(
event_type="APPROVEER_REMOVED",
user=user,
approval_uid=approval_uid,
details={
"approver_uid": approver_uid,
"approver_name": approver_name,
"reason": reason
}
)
# Notify the removed approver
document = None
document_version = approval_cycle.document_version
if document_version:
document = document_version.document
notifications.send_notification(
notification_type="APPROVEER_REMOVED",
users=[approver_uid],
resource_uid=approval_uid,
resource_type="ApprovalCycle",
message=f"You have been removed as a approver",
details={
"approval_uid": approval_uid,
"document_uid": document.uid if document else None,
"doc_number": document.doc_number if document else "Unknown",
"title": document.title if document else "Unknown Document",
"reason": reason
},
send_email=True,
email_template="approver_removed"
)
# 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,
"approver_uid": approver_uid,
"approver_name": approver_name,
"reason": reason,
"message": f"Approver {approver_name} removed successfully"
}
except (ResourceNotFoundError, ValidationError, PermissionError, BusinessRuleError) as e:
# Re-raise known errors
raise
except Exception as e:
logger.error(f"Error removing approver from approval cycle: {e}")
logger.error(traceback.format_exc())
raise BusinessRuleError(f"Failed to remove approver: {e}")
Parameters
| Name | Type | Default | Kind |
|---|---|---|---|
user |
DocUser | - | positional_or_keyword |
approval_uid |
str | - | positional_or_keyword |
approver_uid |
str | - | positional_or_keyword |
reason |
str | - | positional_or_keyword |
Parameter Details
user: DocUser object representing the user performing the removal action. Must have MANAGE_APPROVALS permission to execute this operation.
approval_uid: String containing the unique identifier (UID) of the approval cycle from which the approver should be removed. Must reference an existing approval cycle.
approver_uid: String containing the unique identifier (UID) of the approver to be removed from the approval cycle. Must be currently assigned to the specified approval cycle.
reason: String explaining why the approver is being removed. Cannot be empty or whitespace-only; used for audit trail and notifications.
Return Value
Type: Dict[str, Any]
Returns a dictionary with keys: 'success' (bool, always True on successful execution), 'approval_uid' (str, the approval cycle UID), 'approver_uid' (str, the removed approver's UID), 'approver_name' (str, the name of the removed approver), 'reason' (str, the removal reason), and 'message' (str, a confirmation message). On error, raises one of the documented exceptions instead of returning.
Dependencies
logginguuidostypingdatetimetracebackCDocs.dbCDocs.configCDocs.models.documentCDocs.models.approvalCDocs.models.user_extensionsCDocs.utils.audit_trailCDocs.utils.notificationsCDocs.controllersCDocs.controllers.share_controller
Required Imports
from typing import Dict, Any
from datetime import datetime
import logging
import traceback
from CDocs.models.user_extensions import DocUser
from CDocs.models.approval import ApprovalCycle
from CDocs.models.document import ControlledDocument
from CDocs.config import permissions
from CDocs.utils import audit_trail
from CDocs.utils import notifications
from CDocs.controllers import PermissionError, ResourceNotFoundError, ValidationError, BusinessRuleError
from CDocs.controllers import log_controller_action
from CDocs.controllers.share_controller import manage_document_permissions
Usage Example
from CDocs.models.user_extensions import DocUser
from CDocs.controllers.approval_controller import remove_approver_from_active_approval
# Get the user performing the action
manager = DocUser(uid='user_123')
# Remove an approver from an approval cycle
try:
result = remove_approver_from_active_approval(
user=manager,
approval_uid='approval_456',
approver_uid='approver_789',
reason='Approver is on extended leave and unavailable'
)
print(f"Success: {result['message']}")
print(f"Removed approver: {result['approver_name']}")
except PermissionError as e:
print(f"Permission denied: {e}")
except ResourceNotFoundError as e:
print(f"Resource not found: {e}")
except ValidationError as e:
print(f"Validation error: {e}")
except BusinessRuleError as e:
print(f"Business rule violation: {e}")
Best Practices
- Always ensure the calling user has MANAGE_APPROVALS permission before attempting to use this function
- Provide a meaningful, non-empty reason for removal as it will be logged in the audit trail and sent to the removed approver
- Handle all four exception types (PermissionError, ResourceNotFoundError, ValidationError, BusinessRuleError) when calling this function
- This function should only be called on approval cycles with status 'PENDING' or 'IN_PROGRESS'
- Be aware that removing an approver from a sequential approval cycle will automatically adjust the sequence order of remaining approvers
- The function sends email notifications to the removed approver, so ensure email configuration is properly set up
- Document sharing permissions are automatically updated after approver removal
- The function is decorated with log_controller_action, so all invocations are automatically logged
- Cannot remove approvers who have already completed their approval (status 'COMPLETED')
- The function performs database operations and should be called within an appropriate transaction context if needed
Tags
Similar Components
AI-powered semantic similarity - components with related functionality:
-
function remove_approver_from_active_approval_v1 95.2% similar
-
function cancel_approval_cycle 79.8% similar
-
function remove_reviewer_from_active_review 78.8% similar
-
function add_approver_to_active_approval 77.6% similar
-
function add_approver_to_active_approval_v1 75.5% similar