🔍 Code Extractor

function cancel_review_cycle

Maturity: 69

Cancels an active review cycle for a controlled document, updating the review status, notifying reviewers, and reverting the document status if necessary.

File:
/tf/active/vicechatdev/CDocs/controllers/review_controller.py
Lines:
1187 - 1344
Complexity:
complex

Purpose

This function manages the cancellation workflow for document review cycles in a controlled document management system. It validates user permissions, checks if the review can be canceled based on its current status, updates all related entities (review cycle, reviewer assignments, document status), logs audit events, and sends notifications to affected reviewers. It ensures business rules are enforced (only PENDING or IN_PROGRESS reviews can be canceled) and maintains data integrity across the document lifecycle.

Source Code

def cancel_review_cycle(
    user: DocUser,
    review_uid: str,
    reason: str
) -> Dict[str, Any]:
    """
    Cancel an active review cycle.
    
    Args:
        user: User canceling the review
        review_uid: UID of review cycle to cancel
        reason: Reason for cancellation
        
    Returns:
        Dictionary with cancellation details
        
    Raises:
        ResourceNotFoundError: If review 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_REVIEWS permission")
        if not permissions.user_has_permission(user, "MANAGE_REVIEWS"):
            logger.warning(f"Permission denied: User {user.uid} attempted to manage reviews without MANAGE_REVIEWS permission")
            raise PermissionError("You do not have permission to manage reviews")
            
        logger.info(f"User {user.uid} has permission to manage reviews {review_uid}")

        # Get review cycle instance
        review_cycle = ReviewCycle(uid=review_uid)
        if not review_cycle:
            raise ResourceNotFoundError(f"Review cycle not found: {review_uid}")
            
        # Check if review can be canceled
        if review_cycle.status not in ["PENDING", "IN_PROGRESS"]:
            raise BusinessRuleError(f"Cannot cancel review with status {review_cycle.status}")
            
        # Check if user has permission
        if not permissions.user_has_permission(user, "MANAGE_REVIEWS"):
            # Check if user is the document owner or review initiator
            document = ControlledDocument(uid=review_cycle.document_uid)
            if not (document and document.owner_uid == user.uid) and review_cycle.initiated_by_uid != user.uid:
                raise PermissionError("User is not authorized to cancel this review cycle")
                
        # Validate reason
        if not reason or len(reason.strip()) == 0:
            raise ValidationError("Cancellation reason is required")
            
        # Update review cycle
        review_cycle.status = "CANCELED"
        review_cycle.cancellation_date = datetime.now()
        review_cycle.cancellation_reason = reason
        review_cycle.save()
        
        # Update all active reviewer assignments to canceled
        assignments = review_cycle.get_reviewer_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=review_cycle.document_uid)
        if document and document.status == "IN_REVIEW":
            # 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_REVIEW",
                    "new_status": "DRAFT",
                    "reason": f"Review cycle {review_uid} was canceled"
                }
            )
                
        # Log cancellation event
        audit_trail.log_review_event(
            event_type="REVIEW_CYCLE_CANCELED",
            user=user,
            review_uid=review_uid,
            details={
                "reason": reason
            }
        )
        
        # Get document
        document = ControlledDocument(uid=review_cycle.document_uid)
        
        # Notify reviewers
        for reviewer_uid in review_cycle.reviewer_uids:
            notifications.send_notification(
                notification_type="REVIEW_CANCELED",
                users=[reviewer_uid],
                resource_uid=review_cycle.uid,
                resource_type="ReviewCycle",
                message=f"Review for {document.doc_number if document else 'document'} has been canceled",
                details={
                    "review_uid": review_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="review_canceled",
                email_data={
                    "app_url": settings.APP_URL,
                    "doc_uid": document.uid if document else None,
                    "review_uid": review_uid,
                    "reason": reason,
                    "canceled_by": user.name
                }
            )
            
        # Check if document status should be updated
        if document and document.status == "IN_REVIEW":
            # Check if there are any other active reviews
            other_active_reviews = ReviewCycle.get_reviews_for_document(
                document_uid=document.uid,
                status_filter=["PENDING", "IN_PROGRESS"]
            )
            
            # If no other active reviews, update document status back to DRAFT
            if not other_active_reviews:
                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 review cycle
        from CDocs.controllers.share_controller import manage_document_permissions
        document = ControlledDocument(uid=document.uid)
        permission_result = manage_document_permissions(document)

        return {
            "success": True,
            "review_uid": review_uid,
            "reason": reason,
            "message": "Review cycle canceled successfully"
        }
        
    except (ResourceNotFoundError, ValidationError, PermissionError, BusinessRuleError) as e:
        # Re-raise known errors
        raise
    except Exception as e:
        logger.error(f"Error canceling review cycle: {e}")
        raise BusinessRuleError(f"Failed to cancel review cycle: {e}")

Parameters

Name Type Default Kind
user DocUser - positional_or_keyword
review_uid str - positional_or_keyword
reason str - positional_or_keyword

Parameter Details

user: DocUser object representing the user attempting to cancel the review. Must have MANAGE_REVIEWS permission, or be the document owner or review initiator. Used for permission checks, audit logging, and notification attribution.

review_uid: String containing the unique identifier (UID) of the review cycle to be canceled. Must correspond to an existing ReviewCycle record in the database.

reason: String explaining why the review is being canceled. Required field that cannot be empty or whitespace-only. Used for audit trail and included in notifications to reviewers.

Return Value

Type: Dict[str, Any]

Returns a dictionary with keys: 'success' (boolean, always True on successful execution), 'review_uid' (string, the UID of the canceled review), 'reason' (string, the cancellation reason provided), and 'message' (string, confirmation message 'Review cycle canceled successfully'). On error, raises one of the documented exceptions instead of returning.

Dependencies

  • logging
  • uuid
  • os
  • typing
  • datetime
  • traceback
  • CDocs
  • CDocs.config
  • CDocs.models.document
  • CDocs.models.review
  • CDocs.models.user_extensions
  • CDocs.utils
  • CDocs.controllers
  • CDocs.controllers.document_controller
  • CDocs.controllers.share_controller
  • CDocs.db

Required Imports

from typing import Dict, Any
from datetime import datetime
from CDocs.models.user_extensions import DocUser
from CDocs.models.review import ReviewCycle, ReviewerAssignment
from CDocs.models.document import ControlledDocument
from CDocs.config import permissions, settings
from CDocs.utils import audit_trail, notifications
from CDocs.controllers import PermissionError, ResourceNotFoundError, ValidationError, BusinessRuleError, log_controller_action
from CDocs.controllers.document_controller import update_document
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 reverted to DRAFT after canceling the last active review

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

Condition: called at the end to update document sharing permissions based on the new review state

Required (conditional)

Usage Example

from CDocs.models.user_extensions import DocUser
from CDocs.controllers.review_controller import cancel_review_cycle
from CDocs.controllers import PermissionError, ResourceNotFoundError, ValidationError, BusinessRuleError

# Assume user and review_uid are already obtained
user = DocUser(uid='user-123')
review_uid = 'review-456'
cancellation_reason = 'Document requirements changed, review no longer needed'

try:
    result = cancel_review_cycle(
        user=user,
        review_uid=review_uid,
        reason=cancellation_reason
    )
    print(f"Success: {result['message']}")
    print(f"Canceled review: {result['review_uid']}")
except PermissionError as e:
    print(f"Permission denied: {e}")
except ResourceNotFoundError as e:
    print(f"Review 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's logged in the audit trail and sent to reviewers
  • Ensure the user has MANAGE_REVIEWS permission or is the document owner/review initiator before calling
  • Only reviews with status PENDING or IN_PROGRESS can be canceled; check status before attempting cancellation
  • Handle all four exception types (ResourceNotFoundError, ValidationError, PermissionError, BusinessRuleError) in calling code
  • The function automatically handles document status reversion, reviewer notifications, and permission updates
  • Be aware that canceling a review may trigger document status changes from IN_REVIEW back to DRAFT
  • The function is decorated with @log_controller_action('cancel_review_cycle') for automatic action logging
  • All reviewer assignments associated with the review are automatically set to CANCELED status
  • Email notifications are sent to all reviewers with details about the cancellation
  • The function checks for other active reviews before reverting document status to avoid premature status changes

Similar Components

AI-powered semantic similarity - components with related functionality:

  • function cancel_approval_cycle 85.9% 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 close_review_cycle 79.7% 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_v1 79.5% 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
  • function remove_reviewer_from_active_review 76.9% 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 complete_review 73.3% similar

    Completes a document review cycle by submitting a reviewer's decision (APPROVED/REJECTED), updating review status, managing sequential review workflows, and triggering notifications.

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