function create_review_cycle
Creates a new review cycle for a controlled document, assigning reviewers with optional sequential workflow, custom instructions, and approval requirements.
/tf/active/vicechatdev/CDocs/controllers/review_controller.py
37 - 213
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
logginguuidostypingdatetimetracebackCDocs
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
Optionalfrom 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
OptionalUsage 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
Tags
Similar Components
AI-powered semantic similarity - components with related functionality:
-
function create_approval_cycle_v1 84.4% similar
-
function create_approval_cycle 72.6% similar
-
class ReviewCycle 71.9% similar
-
function update_reviewer_permissions 68.3% similar
-
function close_review_cycle 68.2% similar