🔍 Code Extractor

function remove_approver_from_active_approval

Maturity: 72

Removes an approver from an active approval cycle with permission checks, validation, and notification handling.

File:
/tf/active/vicechatdev/CDocs/controllers/approval_controller.py
Lines:
1532 - 1680
Complexity:
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

  • logging
  • uuid
  • os
  • typing
  • datetime
  • traceback
  • CDocs.db
  • CDocs.config
  • CDocs.models.document
  • CDocs.models.approval
  • CDocs.models.user_extensions
  • CDocs.utils.audit_trail
  • CDocs.utils.notifications
  • CDocs.controllers
  • CDocs.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

Similar Components

AI-powered semantic similarity - components with related functionality:

  • function remove_approver_from_active_approval_v1 95.2% similar

    Removes an approver from an active approval cycle with permission checks, audit logging, and notifications.

    From: /tf/active/vicechatdev/CDocs/controllers/approval_controller_bis.py
  • function cancel_approval_cycle 79.8% similar

    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.

    From: /tf/active/vicechatdev/CDocs/controllers/approval_controller.py
  • function remove_reviewer_from_active_review 78.8% similar

    Removes a reviewer from an active review cycle, updating assignment status, adjusting sequential review orders if needed, logging the event, and notifying the removed reviewer.

    From: /tf/active/vicechatdev/CDocs/controllers/review_controller.py
  • function add_approver_to_active_approval 77.6% 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 75.5% 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
← Back to Browse