function complete_review
Completes a document review cycle by submitting a reviewer's decision (APPROVED/REJECTED), updating review status, managing sequential review workflows, and triggering notifications.
/tf/active/vicechatdev/CDocs/controllers/review_controller.py
827 - 1040
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
logginguuidostypingdatetimetracebackCDocs.dbCDocs.config.settingsCDocs.config.permissionsCDocs.models.documentCDocs.models.reviewCDocs.models.user_extensionsCDocs.utils.audit_trailCDocs.utils.notificationsCDocs.controllersCDocs.controllers.document_controllerCDocs.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
Optionalfrom 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
Tags
Similar Components
AI-powered semantic similarity - components with related functionality:
-
function complete_approval 86.8% similar
-
function complete_approval_v1 79.3% similar
-
function close_review_cycle 79.3% similar
-
function cancel_review_cycle 73.3% similar
-
function remove_reviewer_from_active_review 73.0% similar