function close_approval_cycle
Closes a completed approval cycle and optionally updates the associated document's status, with permission checks, audit logging, and notifications.
/tf/active/vicechatdev/CDocs/controllers/approval_controller.py
992 - 1124
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
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 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
Tags
Similar Components
AI-powered semantic similarity - components with related functionality:
-
function close_approval_cycle_v1 88.4% similar
-
function close_review_cycle 88.1% similar
-
function cancel_approval_cycle 77.5% similar
-
function complete_approval 77.0% similar
-
function cancel_approval_cycle_v1 74.2% similar