🔍 Code Extractor

function create_review_cycle

Maturity: 75

Creates a new review cycle for a controlled document, assigning reviewers with optional sequential workflow, custom instructions, and approval requirements.

File:
/tf/active/vicechatdev/CDocs/controllers/review_controller.py
Lines:
37 - 213
Complexity:
complex

Purpose

This function initiates a formal review process for a document version by creating a ReviewCycle object, assigning reviewers with configurable workflow options (parallel or sequential), setting due dates, managing permissions, and notifying stakeholders. It enforces business rules around document status, validates inputs, handles audit logging, and manages document sharing permissions based on the review cycle.

Source Code

def create_review_cycle(
    user: DocUser,
    document_uid: str,
    reviewer_uids: List[str],
    reviewer_instructions: Optional[Dict[str, str]] = None,  # New parameter: Dict mapping reviewer UIDs to instructions
    due_date: Optional[datetime] = None,
    instructions: Optional[str] = None,
    review_type: str = "STANDARD",
    sequential: bool = False,
    required_approval_percentage: int = 100
) -> Dict[str, Any]:
    """
    Create a new review cycle for a document.
    """
    try:
        # Direct permission check at the beginning
        logger.info(f"Checking if user {user.uid} has CREATE_REVIEW, INITIATE_REVIEW permission")
        if not permissions.user_has_permission(user, ["CREATE_REVIEW", "INITIATE_REVIEW"]):
            logger.warning(f"Permission denied: User {user.uid} attempted to create and initiate review cycle without CREATE_REVIEW, INITIATE_REVIEW permission")
            raise PermissionError("You do not have permission to manage reviews")
            
        logger.info(f"User {user.uid} has permission to create and initiate review cycle")
        # Get document instance
        document = ControlledDocument(uid=document_uid)
        if not document:
            raise ResourceNotFoundError(f"Document not found: {document_uid}")
            
        # Check if document has a current version
        current_version = document.current_version
        if not current_version:
            raise BusinessRuleError("Cannot start review for document without a current version")
            
        # Check if document status allows review
        if document.status not in ["DRAFT", "IN_REVIEW"]:
            raise BusinessRuleError(f"Cannot start review for document with status {document.status}")
            
        # Validate review type
        if not review_type in settings.REVIEW_TYPES:
            raise ValidationError(f"Invalid review type: {review_type}")
            
        # Validate reviewer list
        if not reviewer_uids or len(reviewer_uids) == 0:
            raise ValidationError("At least one reviewer must be specified")
            
        # Validate approval percentage
        if required_approval_percentage < 1 or required_approval_percentage > 100:
            raise ValidationError("Required approval percentage must be between 1 and 100")
            
        # Set due date if not provided
        if not due_date:
            # Default to settings-defined review period (e.g., 14 days)
            review_period_days = getattr(settings, 'DEFAULT_REVIEW_PERIOD_DAYS', 14)
            due_date = datetime.now() + timedelta(days=review_period_days)
        
         # Create review cycle with proper document version instance
        review_cycle = ReviewCycle.create(
            document_version_uid=current_version.uid,
            reviewers=reviewer_uids,
            due_date=due_date,
            instructions=instructions or "",
            properties={
                "review_type": review_type,
                "sequential": sequential,
                "required_approval_percentage": required_approval_percentage,
                "initiated_by_uid": user.uid,
                "initiated_by_name": user.name
            }
        )

        # Add reviewers with proper sequence order for sequential reviews
        for i, reviewer_uid in enumerate(reviewer_uids):
            # Add the reviewer relationship
            review_cycle.add_reviewer(reviewer_uid)
            
            # Get assignment and set sequence order if sequential
            assignment = review_cycle.get_reviewer_assignment(reviewer_uid)
            if assignment:
                if sequential:
                    assignment.sequence_order = i + 1
                    if i==0:
                        assignment.status = "ACTIVE"  
                    
                # Set reviewer-specific instructions if provided
                if reviewer_instructions and reviewer_uid in reviewer_instructions:
                    assignment.instructions = reviewer_instructions[reviewer_uid]
                    
                assignment.save()

        # 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)
        
        if not review_cycle:
            raise BusinessRuleError("Failed to create review cycle")
            
        # Log review cycle creation - use a valid event type
        try:
            from CDocs.utils import audit_trail
            audit_trail.log_event(
                event_type="REVIEW_STARTED",  # Use a valid event type
                user=user,
                resource_uid=review_cycle.uid,
                resource_type="ReviewCycle",
                details={
                    "document_uid": document_uid,
                    "version_uid": current_version.uid,
                    "reviewer_uids": reviewer_uids,
                    "due_date": due_date.isoformat() if due_date else None,
                    "review_type": review_type,
                    "sequential": sequential
                }
            )
        except Exception as audit_err:
            logger.error(f"Error logging review cycle creation: {audit_err}")
            # Continue even if audit logging fails
        
        # Notify reviewers with error handling
        try:
            from CDocs.utils import notifications
            notifications.send_notification(
                notification_type="REVIEW_REQUESTED",
                users=reviewer_uids,
                resource_uid=review_cycle.uid,
                resource_type="ReviewCycle",
                message=f"You have been requested to review a document {document.doc_number} - {document.title}",
                details={
                    "doc_uid": document_uid,  # Use doc_uid for URL generation
                    "doc_number": document.doc_number,
                    "title": document.title,  # Use title instead of document_title
                    "review_uid": review_cycle.uid,
                    "review_type": review_type,
                    "due_date": due_date.isoformat() if due_date else None,
                    "instructions": instructions,
                    "owner_name": user.name,
                    "doc_type": document.doc_type,
                    "version_number": current_version.version_number if current_version else "N/A",
                },
                send_email=True,
                email_template="review_requested",
                email_data={
                    "app_url": settings.APP_URL,  # Add APP_URL for URL generation
                    "doc_uid": document_uid,
                    "review_uid": review_cycle.uid
                }
            )
        except Exception as notif_err:
            logger.error(f"Error sending review notifications: {notif_err}")
        
        # Make sure review_cycle.to_dict() returns a valid dict
        try:
            review_data = review_cycle.to_dict()
            if not isinstance(review_data, dict):
                review_data = {
                    "uid": review_cycle.uid,
                    "status": review_cycle.status,
                    "document_uid": document_uid
                }
        except Exception as dict_err:
            logger.error(f"Error converting review cycle to dict: {dict_err}")
            review_data = {
                "uid": review_cycle.uid,
                "status": "PENDING"
            }
        
        return {
            "success": True,
            "review_cycle": review_data,
            "message": "Review cycle created successfully"
        }
        
    except (ResourceNotFoundError, ValidationError, PermissionError, BusinessRuleError) as e:
        # Re-raise known errors
        raise
    except Exception as e:
        logger.error(f"Error creating review cycle: {e}")
        raise BusinessRuleError(f"Failed to create review cycle: {e}")

Parameters

Name Type Default Kind
user DocUser - positional_or_keyword
document_uid str - positional_or_keyword
reviewer_uids List[str] - positional_or_keyword
reviewer_instructions Optional[Dict[str, str]] None positional_or_keyword
due_date Optional[datetime] None positional_or_keyword
instructions Optional[str] None positional_or_keyword
review_type str 'STANDARD' positional_or_keyword
sequential bool False positional_or_keyword
required_approval_percentage int 100 positional_or_keyword

Parameter Details

user: DocUser object representing the authenticated user initiating the review cycle. Must have CREATE_REVIEW and INITIATE_REVIEW permissions.

document_uid: String unique identifier of the ControlledDocument to be reviewed. Document must exist and have a current version.

reviewer_uids: List of string UIDs identifying users who will review the document. Must contain at least one reviewer. Order matters for sequential reviews.

reviewer_instructions: Optional dictionary mapping reviewer UIDs (keys) to custom instruction strings (values). Allows per-reviewer specific guidance beyond general instructions.

due_date: Optional datetime object specifying when the review should be completed. If not provided, defaults to current time plus DEFAULT_REVIEW_PERIOD_DAYS (default 14 days) from settings.

instructions: Optional string containing general instructions for all reviewers. Applied to the entire review cycle.

review_type: String specifying the type of review. Must be one of the values defined in settings.REVIEW_TYPES. Defaults to 'STANDARD'.

sequential: Boolean flag indicating whether reviewers must complete reviews in order (True) or can review simultaneously (False). Defaults to False for parallel reviews.

required_approval_percentage: Integer between 1 and 100 specifying the percentage of reviewers who must approve for the review cycle to pass. Defaults to 100 (unanimous approval).

Return Value

Type: Dict[str, Any]

Returns a dictionary with three keys: 'success' (boolean indicating operation success), 'review_cycle' (dictionary representation of the created ReviewCycle object containing uid, status, and other cycle details), and 'message' (string confirmation message). On error, raises one of: PermissionError, ResourceNotFoundError, ValidationError, or BusinessRuleError.

Dependencies

  • logging
  • uuid
  • os
  • typing
  • datetime
  • traceback
  • CDocs

Required Imports

import logging
from typing import Dict, List, Any, Optional
from datetime import datetime, timedelta
from CDocs.config import settings, permissions
from CDocs.models.document import ControlledDocument, DocumentVersion
from CDocs.models.review import ReviewCycle, ReviewerAssignment
from CDocs.models.user_extensions import DocUser
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 manage_document_permissions

Condition: Required for updating document sharing permissions after review cycle creation

Required (conditional)
from CDocs.utils import audit_trail

Condition: Used for logging review cycle creation events. Wrapped in try-except, so failures won't break the function

Optional
from CDocs.utils import notifications

Condition: Used for sending email and in-app notifications to reviewers. Wrapped in try-except, so failures won't break the function

Optional

Usage Example

from datetime import datetime, timedelta
from CDocs.models.user_extensions import DocUser
from CDocs.controllers.review_controller import create_review_cycle

# Get authenticated user
user = DocUser(uid='user123')

# Define reviewers and custom instructions
reviewer_uids = ['reviewer1_uid', 'reviewer2_uid', 'reviewer3_uid']
reviewer_instructions = {
    'reviewer1_uid': 'Please focus on technical accuracy',
    'reviewer2_uid': 'Please review for compliance with regulations'
}

# Create sequential review cycle with 75% approval requirement
due_date = datetime.now() + timedelta(days=7)

try:
    result = create_review_cycle(
        user=user,
        document_uid='doc_abc123',
        reviewer_uids=reviewer_uids,
        reviewer_instructions=reviewer_instructions,
        due_date=due_date,
        instructions='Please complete review by end of week',
        review_type='STANDARD',
        sequential=True,
        required_approval_percentage=75
    )
    
    if result['success']:
        review_cycle_uid = result['review_cycle']['uid']
        print(f"Review cycle created: {review_cycle_uid}")
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 ensure the user has CREATE_REVIEW and INITIATE_REVIEW permissions before calling this function
  • Validate that the document exists and has a current version before initiating review
  • Only documents with status 'DRAFT' or 'IN_REVIEW' can have review cycles created
  • For sequential reviews, the order of reviewer_uids matters - first reviewer will be activated immediately
  • Handle all four exception types: PermissionError, ResourceNotFoundError, ValidationError, and BusinessRuleError
  • The function automatically sets due_date to 14 days from now if not provided, but explicit dates are recommended
  • Audit logging and notifications are non-blocking - failures in these subsystems won't prevent review cycle creation
  • Use reviewer_instructions parameter to provide specific guidance to individual reviewers beyond general instructions
  • Set required_approval_percentage appropriately - 100 requires unanimous approval, lower values allow partial approval
  • The function automatically manages document sharing permissions to grant reviewers access
  • Review type must be one of the values defined in settings.REVIEW_TYPES or a ValidationError will be raised

Similar Components

AI-powered semantic similarity - components with related functionality:

  • function create_approval_cycle_v1 84.4% similar

    Creates a new approval cycle for a controlled document, managing approver assignments, permissions, notifications, and audit logging.

    From: /tf/active/vicechatdev/CDocs/controllers/approval_controller.py
  • function create_approval_cycle 72.6% similar

    Creates a new approval cycle for a document, assigning approvers with configurable workflow options (sequential/parallel), instructions, and due dates.

    From: /tf/active/vicechatdev/CDocs/controllers/approval_controller_bis.py
  • class ReviewCycle 71.9% similar

    Model representing a review cycle for a document version.

    From: /tf/active/vicechatdev/CDocs/models/review.py
  • function update_reviewer_permissions 68.3% similar

    Updates file access permissions for reviewers associated with a review cycle by delegating to the main document permission management function.

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