🔍 Code Extractor

function close_approval_cycle

Maturity: 73

Closes a completed approval cycle and optionally updates the associated document's status, with permission checks, audit logging, and notifications.

File:
/tf/active/vicechatdev/CDocs/controllers/approval_controller.py
Lines:
992 - 1124
Complexity:
complex

Purpose

This function manages the closure of approval cycles in a document management system. It verifies user permissions, validates the approval cycle status, optionally transitions the document to a new status, logs the action in the audit trail, sends notifications to stakeholders, and updates document sharing permissions. It enforces business rules such as requiring the approval cycle to be completed before closure and validating status transitions.

Source Code

def close_approval_cycle(
    user: DocUser,
    approval_uid: str,
    update_document_status: bool = True,
    target_status: str = "DRAFT"
) -> Dict[str, Any]:
    """
    Close a approval cycle and optionally update document status.
    
    Args:
        user: User closing the approval cycle
        approval_uid: UID of approval cycle
        update_document_status: Whether to update document status
        target_status: Target document status if updating
        
    Returns:
        Dictionary with operation details
        
    Raises:
        ResourceNotFoundError: If approval cycle not found
        ValidationError: If target status is invalid
        PermissionError: If user doesn't have permission
        BusinessRuleError: If operation is not allowed
    """
    try:
        # Direct permission check at the beginning
        logger.info(f"Checking if user {user.uid} has MANAGE_APPROVALS or EDIT_DOCUMENT permission")
        if not permissions.user_has_permission(user, ["MANAGE_APPROVALS", "EDIT_DOCUMENT"]):
            logger.warning(f"Permission denied: User {user.uid} attempted to close approval cycle without required permissions")
            raise PermissionError("You do not have permission to close this approval cycle")
            
        logger.info(f"User {user.uid} has permission to close approval cycle {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 completed
        if approval_cycle.status != "COMPLETED":
            raise BusinessRuleError(f"Cannot close a approval cycle with status {approval_cycle.status}. Approval must be completed first.")
            
        # Get document
        document = None
        document_version = approval_cycle.document_version
        if document_version:
            document = document_version.document
        
        if not document:
            raise ResourceNotFoundError("Document not found for this approval cycle")
        
        # Check if user has permission to modify this document
        is_document_owner = document.owner_uid == user.uid
        is_approval_initiator = approval_cycle.initiated_by_uid == user.uid
        has_manage_permission = permissions.user_has_permission(user, "MANAGE_APPROVALS")
        
        if not (is_document_owner or is_approval_initiator or has_manage_permission):
            raise PermissionError("Only the document owner, approval initiator, or managers can close this approval cycle")
        
        # If requested, update document status
        if update_document_status:
            # Validate target status
            if not target_status in settings.DOCUMENT_STATUSES.values():
                raise ValidationError(f"Invalid target status: {target_status}")
                
            # Check if status transition is valid
            if not settings.is_valid_status_transition(document.status, target_status):
                raise BusinessRuleError(f"Invalid status transition from {document.status} to {target_status}")
                
            # Update document status
            from CDocs.controllers.document_controller import update_document
            update_result = update_document(
                user=user,
                document_uid=document.uid,
                status=target_status
            )
            
            if not update_result.get('success', False):
                raise BusinessRuleError(f"Failed to update document status: {update_result.get('message', 'Unknown error')}")
        
        # Log event
        audit_trail.log_approval_event(
            event_type="APPROVE_CYCLE_CLOSED",
            user=user,
            approval_uid=approval_uid,
            details={
                "document_uid": document.uid,
                "document_status_updated": update_document_status,
                "target_status": target_status if update_document_status else None
            }
        )
        
        # Notify document owner if different from current user
        if document.owner_uid and document.owner_uid != user.uid:
            notifications.send_notification(
                notification_type="APPROVE_CYCLE_CLOSED",
                users=[document.owner_uid],
                resource_uid=approval_cycle.uid,
                resource_type="ApprovalCycle",
                message=f"Approval cycle for {document.doc_number} has been closed",
                details={
                    "approval_uid": approval_uid,
                    "document_uid": document.uid,
                    "closed_by": user.name,
                    "doc_number": document.doc_number,
                    "title": document.title,
                    "status_updated": update_document_status,
                    "new_status": target_status if update_document_status else None
                },
                send_email=True,
                email_template="approval_cycle_closed"
            )
            
        # 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,
            "document_uid": document.uid,
            "document_status_updated": update_document_status,
            "new_status": target_status if update_document_status else None,
            "message": f"Approval cycle closed successfully"
        }
        
    except (ResourceNotFoundError, ValidationError, PermissionError, BusinessRuleError) as e:
        # Re-raise known errors
        raise
    except Exception as e:
        logger.error(f"Error closing approval cycle: {e}")
        raise BusinessRuleError(f"Failed to close approval cycle: {e}")

Parameters

Name Type Default Kind
user DocUser - positional_or_keyword
approval_uid str - positional_or_keyword
update_document_status bool True positional_or_keyword
target_status str 'DRAFT' positional_or_keyword

Parameter Details

user: DocUser object representing the user attempting to close the approval cycle. Must have MANAGE_APPROVALS or EDIT_DOCUMENT permission, or be the document owner or approval initiator.

approval_uid: String containing the unique identifier (UID) of the approval cycle to be closed. Must reference an existing approval cycle in the system.

update_document_status: Boolean flag indicating whether to update the associated document's status when closing the approval cycle. Defaults to True. When True, the document status will be changed to the target_status value.

target_status: String specifying the desired document status if update_document_status is True. Must be a valid status from settings.DOCUMENT_STATUSES and the transition must be valid according to business rules. Defaults to 'DRAFT'.

Return Value

Type: Dict[str, Any]

Returns a dictionary with keys: 'success' (bool, always True on successful execution), 'approval_uid' (str, the UID of the closed approval cycle), 'document_uid' (str, the UID of the associated document), 'document_status_updated' (bool, whether the document status was changed), 'new_status' (str or None, the new document status if updated), and 'message' (str, success message).

Dependencies

  • logging
  • uuid
  • os
  • typing
  • datetime
  • traceback
  • CDocs.db
  • CDocs.config.settings
  • CDocs.config.permissions
  • CDocs.models.document
  • CDocs.models.approval
  • CDocs.models.user_extensions
  • CDocs.utils.audit_trail
  • CDocs.utils.notifications
  • CDocs.controllers
  • CDocs.controllers.share_controller
  • CDocs.controllers.document_controller

Required Imports

from typing import Dict, Any
from CDocs.models.user_extensions import DocUser
from CDocs.models.approval import ApprovalCycle
from CDocs.models.document import ControlledDocument
from CDocs.config import permissions, settings
from CDocs.utils import audit_trail, notifications
from CDocs.controllers import ResourceNotFoundError, ValidationError, PermissionError, BusinessRuleError, log_controller_action
import logging

Conditional/Optional Imports

These imports are only needed under specific conditions:

from CDocs.controllers.document_controller import update_document

Condition: only when update_document_status is True

Required (conditional)
from CDocs.controllers.share_controller import manage_document_permissions

Condition: always executed at the end of the function to update document permissions

Required (conditional)

Usage Example

from CDocs.models.user_extensions import DocUser
from CDocs.controllers.approval_controller import close_approval_cycle

# Get the user object
user = DocUser.query.get(user_uid)

# Close approval cycle and update document to APPROVED status
try:
    result = close_approval_cycle(
        user=user,
        approval_uid='abc-123-def-456',
        update_document_status=True,
        target_status='APPROVED'
    )
    print(f"Success: {result['message']}")
    print(f"Document {result['document_uid']} status: {result['new_status']}")
except PermissionError as e:
    print(f"Permission denied: {e}")
except BusinessRuleError as e:
    print(f"Business rule violation: {e}")
except ResourceNotFoundError as e:
    print(f"Resource not found: {e}")

# Close approval cycle without updating document status
result = close_approval_cycle(
    user=user,
    approval_uid='abc-123-def-456',
    update_document_status=False
)

Best Practices

  • Always wrap calls in try-except blocks to handle ResourceNotFoundError, ValidationError, PermissionError, and BusinessRuleError exceptions
  • Ensure the approval cycle status is 'COMPLETED' before attempting to close it
  • Validate that target_status is a valid document status and that the transition is allowed before calling
  • The user must have MANAGE_APPROVALS or EDIT_DOCUMENT permission, or be the document owner or approval initiator
  • When update_document_status is True, ensure the target_status represents a valid state transition from the current document status
  • The function automatically sends email notifications to the document owner if they are different from the closing user
  • Document sharing permissions are automatically updated after closing the approval cycle
  • All actions are logged in the audit trail for compliance and tracking purposes
  • The function is decorated with @log_controller_action('close_approval_cycle') for additional logging
  • Consider the impact on document permissions when closing approval cycles as manage_document_permissions is called automatically

Similar Components

AI-powered semantic similarity - components with related functionality:

  • function close_approval_cycle_v1 88.4% similar

    Administratively closes an approval cycle by setting a final decision (APPROVED or REJECTED), updating the associated document status, and notifying relevant stakeholders.

    From: /tf/active/vicechatdev/CDocs/controllers/approval_controller_bis.py
  • function close_review_cycle 88.1% similar

    Closes a completed review cycle for a controlled document, with optional document status update, permission validation, and stakeholder notifications.

    From: /tf/active/vicechatdev/CDocs/controllers/review_controller.py
  • function cancel_approval_cycle 77.5% 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 complete_approval 77.0% similar

    Completes an approval cycle by recording a user's approval decision (APPROVED, REJECTED, etc.) and managing the approval workflow, including sequential approver activation and final cycle completion.

    From: /tf/active/vicechatdev/CDocs/controllers/approval_controller.py
  • function cancel_approval_cycle_v1 74.2% similar

    Cancels an active approval cycle, reverting the associated document to draft status, logging the cancellation, and notifying all pending approvers.

    From: /tf/active/vicechatdev/CDocs/controllers/approval_controller_bis.py
← Back to Browse