🔍 Code Extractor

function complete_review

Maturity: 71

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

File:
/tf/active/vicechatdev/CDocs/controllers/review_controller.py
Lines:
827 - 1040
Complexity:
complex

Purpose

This function handles the completion of a review assignment within a document review cycle. It validates permissions, checks business rules, records the reviewer's decision, manages sequential review progression (activating the next reviewer if applicable), determines overall review cycle completion status, updates document permissions, and sends notifications to relevant stakeholders. It's a critical component in document management workflows where multiple reviewers must approve or reject documents.

Source Code

def complete_review(
    user: DocUser,
    review_uid: str,
    decision: str,
    comments: Optional[str] = None
) -> Dict[str, Any]:
    """
    Complete a review by submitting a decision.
    
    Args:
        user: User completing the review
        review_uid: UID of review cycle
        decision: Decision (APPROVED, REJECTED, etc.)
        comments: Optional comments about the decision
        
    Returns:
        Dictionary with review completion details
        
    Raises:
        ResourceNotFoundError: If review cycle not found
        ValidationError: If validation fails
        PermissionError: If user doesn't have permission
        BusinessRuleError: If a business rule is violated
    """
    try:
        # Direct permission check at the beginning
        logger.info(f"Checking if user {user.uid} has COMPLETE_REVIEW permission")
        if not permissions.user_has_permission(user, "COMPLETE_REVIEW"):
            logger.warning(f"Permission denied: User {user.uid} attempted to complete review without COMPLETE_REVIEW permission")
            raise PermissionError("You do not have permission to complete reviews")
            
        logger.info(f"User {user.uid} has permission to complete review {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 status allows completion
        if review_cycle.status not in ["PENDING", "IN_PROGRESS"]:
            raise BusinessRuleError(f"Cannot complete review with status {review_cycle.status}")
            
        # Validate decision
        if decision not in settings.REVIEW_DECISIONS:
            raise ValidationError(f"Invalid review decision: {decision}")
            
        # Check if user is a reviewer for this cycle
        if not review_cycle.is_reviewer(user.uid):
            raise PermissionError("User is not a reviewer for this review cycle")
            
        # Get reviewer assignment
        assignment = review_cycle.get_reviewer_assignment(user.uid)
        if not assignment:
            raise ResourceNotFoundError(f"Reviewer assignment not found for user {user.uid}")
            
        # Check if already completed
        if assignment.status == "COMPLETED":
            raise BusinessRuleError("This reviewer has already completed their review")
            
        # Update assignment
        assignment.status = "COMPLETED"
        assignment.decision = decision
        assignment.decision_date = datetime.now()
        assignment.decision_comments = comments
        
        # Update first activity date if not already set
        if not assignment.first_activity_date:
            assignment.first_activity_date = datetime.now()
            
        # Save assignment to database
        assignment.save()
        
        # Get document to update permissions after status change
        document = review_cycle.document
        document_version = review_cycle.document_version
        
        # Check for sequential review progression
        if review_cycle.sequential:
            # Find the next reviewer in sequence
            next_reviewer = review_cycle.get_next_reviewer(assignment.sequence_order)
            
            if next_reviewer:
                # Activate next reviewer
                next_reviewer.status = "ACTIVE"
                next_reviewer.save()
                
                # Notify next reviewer
                notifications.send_notification(
                    notification_type="REVIEW_ACTIVATED",
                    users=[next_reviewer.reviewer_uid],
                    resource_uid=review_cycle.uid,
                    resource_type="ReviewCycle",
                    message="Your turn to review a document",
                    details={
                        "review_uid": review_uid,
                        "previous_reviewer": user.name
                    },
                    send_email=True,
                    email_template="review_activated",
                    email_data={
                        "app_url": settings.APP_URL,
                        "review_uid": review_uid,
                        "previous_reviewer": user.name
                    }
                )
                
                # Update permission management for the new active reviewer
                if document and document_version:
                    from CDocs.controllers.share_controller import update_reviewer_permissions
                    update_reviewer_permissions(review_cycle)
                
        # Check if all reviewers have completed their review
        assignments = review_cycle.get_reviewer_assignments()
        all_completed = all(a.status == "COMPLETED" for a in assignments)
        # Get document
        document = ControlledDocument(uid=review_cycle.document_uid)
        # If all completed, update review cycle status
        if all_completed:
            # Calculate approval statistics
            approved_count = sum(1 for a in assignments if a.status == "COMPLETED" and a.decision == "APPROVED")
            total_count = len(assignments)
            approval_percentage = (approved_count / total_count) * 100 if total_count > 0 else 0
            
            # Determine overall result
            overall_decision = "APPROVED" if approval_percentage >= review_cycle.required_approval_percentage else "REJECTED"
            
            # Update review cycle
            review_cycle.status = "COMPLETED"
            review_cycle.completion_date = datetime.now()
            review_cycle.decision = overall_decision
            review_cycle.save()
            
            # Get document
            document = ControlledDocument(uid=review_cycle.document_uid)
            
            # Log review completion event
            audit_trail.log_review_event(
                event_type="REVIEW_CYCLE_COMPLETED",
                user=user,
                review_uid=review_uid,
                details={
                    "decision": overall_decision,
                    "approval_percentage": approval_percentage,
                    "approved_count": approved_count,
                    "total_reviewers": total_count
                }
            )

            # Update document sharing permissions when review is completed
            if document:
                from CDocs.controllers.share_controller import manage_document_permissions
                document = ControlledDocument(uid=document.uid)
                manage_document_permissions(document)
            
            # Notify document owner
            if document and document.owner_uid:
                notifications.send_notification(
                    notification_type="REVIEW_COMPLETED",
                    users=[document.owner_uid],
                    resource_uid=review_cycle.uid,
                    resource_type="ReviewCycle",
                    message=f"Review cycle for {document.doc_number} completed with result: {overall_decision}",
                    details={
                        "review_uid": review_uid,
                        "document_uid": document.uid,
                        "doc_number": document.doc_number,
                        "title": document.title,
                        "decision": overall_decision,
                        "approval_percentage": approval_percentage
                    },
                    send_email=True,
                    email_template="review_completed",
                    email_data={
                        "app_url": settings.APP_URL,
                        "doc_uid": document.uid,
                        "review_uid": review_uid,
                        "decision": overall_decision,
                        "approval_percentage": approval_percentage
                    }
                )
            
        else:
            # Log individual review decision
            audit_trail.log_review_event(
                event_type="REVIEWER_DECISION_SUBMITTED",
                user=user,
                review_uid=review_uid,
                details={
                    "decision": decision,
                    "reviewer_uid": user.uid,
                    "comments": comments
                }
            )
        
        # 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,
            "assignment": assignment.to_dict(),
            "decision": decision,
            "review_completed": all_completed,
            "message": "Review decision submitted successfully"
        }
        
    except (ResourceNotFoundError, ValidationError, PermissionError, BusinessRuleError) as e:
        # Re-raise known errors
        raise
    except Exception as e:
        logger.error(f"Error completing review: {e}")
        raise BusinessRuleError(f"Failed to complete review: {e}")

Parameters

Name Type Default Kind
user DocUser - positional_or_keyword
review_uid str - positional_or_keyword
decision str - positional_or_keyword
comments Optional[str] None positional_or_keyword

Parameter Details

user: DocUser object representing the user completing the review. Must have COMPLETE_REVIEW permission and be assigned as a reviewer for the specified review cycle.

review_uid: String UID (unique identifier) of the review cycle being completed. Must correspond to an existing ReviewCycle with status PENDING or IN_PROGRESS.

decision: String representing the reviewer's decision. Must be one of the valid values defined in settings.REVIEW_DECISIONS (typically 'APPROVED', 'REJECTED', etc.).

comments: Optional string containing the reviewer's comments or justification for their decision. Can be None if no comments are provided.

Return Value

Type: Dict[str, Any]

Returns a dictionary with keys: 'success' (bool, always True on success), 'review_uid' (str, the review cycle UID), 'assignment' (dict, the reviewer assignment details converted to dictionary), 'decision' (str, the submitted decision), 'review_completed' (bool, True if all reviewers have completed their reviews), and 'message' (str, success message). On error, raises one of the documented exceptions instead of returning.

Dependencies

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

Required Imports

from typing import Dict, Any, Optional
from datetime import datetime
import logging
from CDocs.config import settings, permissions
from CDocs.models.review import ReviewCycle
from CDocs.models.document import ControlledDocument
from CDocs.models.user_extensions import DocUser
from CDocs.utils import audit_trail, notifications
from CDocs.controllers import PermissionError, ResourceNotFoundError, ValidationError, BusinessRuleError

Conditional/Optional Imports

These imports are only needed under specific conditions:

from CDocs.controllers.share_controller import update_reviewer_permissions

Condition: only when sequential review is enabled and next reviewer needs to be activated

Optional
from CDocs.controllers.share_controller import manage_document_permissions

Condition: called multiple times to update document permissions after review status changes

Required (conditional)

Usage Example

from CDocs.models.user_extensions import DocUser
from CDocs.controllers.review_controller import complete_review

# Get the user completing the review
reviewer = DocUser.get_by_uid('user-123')

# Complete the review with approval
try:
    result = complete_review(
        user=reviewer,
        review_uid='review-cycle-456',
        decision='APPROVED',
        comments='All requirements met. Document is well-structured.'
    )
    
    if result['success']:
        print(f"Review completed: {result['message']}")
        print(f"All reviews completed: {result['review_completed']}")
        print(f"Decision: {result['decision']}")
except PermissionError as e:
    print(f"Permission denied: {e}")
except ValidationError as e:
    print(f"Invalid input: {e}")
except BusinessRuleError as e:
    print(f"Business rule violation: {e}")

Best Practices

  • Always wrap calls in try-except blocks to handle PermissionError, ResourceNotFoundError, ValidationError, and BusinessRuleError exceptions
  • Ensure the user has COMPLETE_REVIEW permission before calling this function
  • Verify the user is assigned as a reviewer for the review cycle
  • Use valid decision values from settings.REVIEW_DECISIONS
  • The function is decorated with @log_controller_action('complete_review') for automatic logging
  • This function handles both sequential and parallel review workflows automatically
  • Document permissions are automatically updated after review completion
  • Email notifications are sent automatically to next reviewers (sequential) or document owners (on completion)
  • The function is transactional - if any step fails, changes should be rolled back
  • Audit trail events are logged for both individual decisions and overall review completion
  • For sequential reviews, only the active reviewer can complete their assignment
  • Once a reviewer completes their review, they cannot submit another decision for the same cycle

Similar Components

AI-powered semantic similarity - components with related functionality:

  • function complete_approval 86.8% 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 complete_approval_v1 79.3% similar

    Records a user's approval decision (APPROVED or REJECTED) for a document in an approval cycle, updating the approval status and document state accordingly.

    From: /tf/active/vicechatdev/CDocs/controllers/approval_controller_bis.py
  • function close_review_cycle 79.3% 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_review_cycle 73.3% similar

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

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