function complete_approval_v1
Records a user's approval decision (APPROVED or REJECTED) for a document in an approval cycle, updating the approval status and document state accordingly.
/tf/active/vicechatdev/CDocs/controllers/approval_controller_bis.py
628 - 793
complex
Purpose
This function manages the approval workflow for controlled documents by recording individual approver decisions, validating permissions and workflow rules, updating approval cycle status, and triggering notifications. It handles both sequential and parallel approval workflows, calculates approval percentages, manages document status transitions, and maintains audit trails. The function ensures business rules are enforced (e.g., sequential turn-taking, required approval thresholds) and coordinates database transactions to maintain data consistency.
Source Code
def complete_approval(
user: DocUser,
approval_uid: str,
decision: str,
comments: Optional[str] = None
) -> Dict[str, Any]:
"""
Record a user's approval decision.
Args:
user: The user making the approval decision
approval_uid: UID of the approval cycle
decision: Decision (APPROVED or REJECTED)
comments: Optional comments with the decision
Returns:
Dictionary with success flag and approval status
"""
try:
# Get approval cycle
approval_cycle = ApprovalCycle(uid=approval_uid)
if not approval_cycle:
raise ResourceNotFoundError("Approval cycle not found")
# Verify user is an approver
if not approval_cycle.is_approver(user.uid):
raise PermissionError("You are not assigned as an approver for this document")
# Check if approval is still active
if not approval_cycle.is_active:
raise BusinessRuleError(f"This approval cycle is already {approval_cycle.status}")
# Check if user can approve in the current state
if not approval_cycle.can_approve(user.uid):
if approval_cycle._data.get('sequential', False):
raise BusinessRuleError("It is not your turn to approve in the sequential workflow")
else:
raise BusinessRuleError("You cannot approve this document in its current state")
# Get the approver assignment
assignment = approval_cycle.get_approver_assignment(user.uid)
if not assignment:
raise ResourceNotFoundError("Approver assignment not found")
document_uid = approval_cycle.document_uid
# Record the decision
from CDocs.db import get_driver
driver = get_driver()
with driver.session() as session:
with session.begin_transaction() as tx:
try:
# Update assignment with decision
assignment.decision = decision
assignment.decision_comments = comments
# Record first activity if not already set
if not assignment.first_activity_date:
assignment.first_activity_date = datetime.now()
# If decision is negative, the whole approval cycle is rejected
if decision == "REJECTED":
approval_cycle.complete_approval(approved=False)
# Update document status
if document_uid:
from CDocs.controllers.document_controller import update_document
update_document(
document_uid=document_uid,
user=user,
status="REJECTED"
)
else:
# Check if all required approvers have approved
assignments = approval_cycle.get_approver_assignments()
total_approvers = len(assignments)
approved_count = len([a for a in assignments if a.decision == "APPROVED"])
# Calculate if we've reached the required approval percentage
required_percentage = approval_cycle.required_approval_percentage
current_percentage = (approved_count / total_approvers) * 100
# If sequential, check if there are more approvers in sequence
is_sequential = approval_cycle._data.get('sequential', False)
next_approver = None
if is_sequential:
next_approver = approval_cycle.get_next_approver(assignment.sequence_order)
# If all required approvals received, or no more sequential approvers
all_approved = current_percentage >= required_percentage
if all_approved and (not is_sequential or not next_approver):
# Mark approval cycle as completed with approval
approval_cycle.complete_approval(approved=True)
# Update document status
if document_uid:
from CDocs.controllers.document_controller import update_document
update_document(
document_uid=document_uid,
user=user,
status="APPROVED"
)
# Commit the transaction if everything succeeds
tx.commit()
except Exception as e:
# Roll back the transaction if anything fails
tx.rollback()
logger.error(f"Error in transaction completing approval: {e}")
raise BusinessRuleError(f"Failed to complete approval: {e}")
# Create audit trail entry
if document_uid:
audit_trail.log_event(
event_type="APPROVAL_DECISION",
user=user,
resource_uid=approval_uid,
resource_type="ApprovalCycle",
details={
"document_uid": document_uid,
"decision": decision,
"has_comments": comments is not None and len(comments) > 0
}
)
# Send notification to document owner
from CDocs.controllers.document_controller import get_document
document = get_document(document_uid=document_uid)
if document and document.get("owner_uid"):
owner = DocUser(uid=document.get("owner_uid"))
if owner:
notifications.send_notification(
notification_type="APPROVAL_DECISION",
users=owner.uid,
resource_uid=approval_uid,
resource_type="ApprovalCycle",
message=f"{user.name} has {decision.lower()} your document",
details={
"document_uid": document_uid,
"document_number": document.get("docNumber"),
"document_title": document.get("title"),
"decision": decision,
"approver_name": user.name,
"approval_cycle_uid": approval_uid
},
send_email=True,
email_template="approval_decision"
)
return {
"success": True,
"message": f"Document {decision.lower()} recorded",
"status": approval_cycle.status,
"decision": decision
}
except (ResourceNotFoundError, PermissionError, BusinessRuleError) as e:
logger.warning(f"Error completing approval: {e}")
return {"success": False, "message": str(e)}
except Exception as e:
logger.error(f"Unexpected error completing approval: {e}")
import traceback
logger.error(traceback.format_exc())
return {"success": False, "message": "An unexpected error occurred"}
Parameters
| Name | Type | Default | Kind |
|---|---|---|---|
user |
DocUser | - | positional_or_keyword |
approval_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 making the approval decision. Must be an assigned approver for the approval cycle with appropriate permissions.
approval_uid: String UID (unique identifier) of the ApprovalCycle being acted upon. Must reference an existing, active approval cycle.
decision: String indicating the approval decision. Expected values are 'APPROVED' or 'REJECTED'. A REJECTED decision immediately fails the entire approval cycle.
comments: Optional string containing the approver's comments or justification for their decision. Can be None or empty string if no comments are provided.
Return Value
Type: Dict[str, Any]
Returns a dictionary with keys: 'success' (boolean indicating operation success), 'message' (string describing the outcome), 'status' (current approval cycle status), and 'decision' (the recorded decision). On failure, returns dictionary with 'success': False and 'message' containing error details.
Dependencies
CDocs.models.documentCDocs.models.approvalCDocs.models.user_extensionsCDocs.utils.audit_trailCDocs.utils.notificationsCDocs.controllersCDocs.controllers.document_controllerCDocs.dbCDocs.config.settingsCDocs.config.permissionsdatetimeloggingtypingtraceback
Required Imports
from typing import Dict, Any, Optional
from datetime import datetime
import logging
from CDocs.models.approval import ApprovalCycle, ApproverAssignment
from CDocs.models.user_extensions import DocUser
from CDocs.utils import audit_trail, notifications
from CDocs.controllers import log_controller_action, ResourceNotFoundError, PermissionError, BusinessRuleError
from CDocs.db import get_driver
Conditional/Optional Imports
These imports are only needed under specific conditions:
from CDocs.controllers.document_controller import update_document
Condition: imported inside transaction block when document status needs to be updated after approval decision
Required (conditional)from CDocs.controllers.document_controller import get_document
Condition: imported when creating audit trail and sending notifications to retrieve document details
Required (conditional)import traceback
Condition: imported for error logging when unexpected exceptions occur
Required (conditional)Usage Example
from CDocs.models.user_extensions import DocUser
from CDocs.controllers.approval_controller import complete_approval
# Get the user making the approval
approver = DocUser(uid='user_123')
# Record an approval decision
result = complete_approval(
user=approver,
approval_uid='approval_cycle_456',
decision='APPROVED',
comments='Document meets all quality standards and requirements.'
)
if result['success']:
print(f"Approval recorded: {result['message']}")
print(f"Current status: {result['status']}")
else:
print(f"Error: {result['message']}")
# Record a rejection
rejection_result = complete_approval(
user=approver,
approval_uid='approval_cycle_789',
decision='REJECTED',
comments='Technical specifications need revision.'
)
if rejection_result['success']:
print(f"Rejection recorded: {rejection_result['message']}")
Best Practices
- Always wrap calls in try-except blocks to handle returned error dictionaries gracefully
- Check the 'success' key in the returned dictionary before proceeding with dependent operations
- Ensure the user object has valid uid and name properties before calling
- Use 'APPROVED' or 'REJECTED' as decision values (case-sensitive)
- The function handles database transactions internally; do not wrap in additional transaction contexts
- For sequential workflows, only the current approver in sequence can approve
- A single REJECTED decision will fail the entire approval cycle regardless of other approvals
- The function automatically calculates approval percentages and determines cycle completion
- Audit trail entries and notifications are created automatically; no manual logging needed
- The function is decorated with @log_controller_action, so controller actions are logged automatically
- Document status is automatically updated to 'APPROVED' or 'REJECTED' based on the approval cycle outcome
- Email notifications are sent to document owners automatically when decisions are recorded
- Ensure database driver is properly configured before calling this function
- The function validates that the approval cycle is still active before accepting decisions
Tags
Similar Components
AI-powered semantic similarity - components with related functionality:
-
function complete_approval 90.5% similar
-
function close_approval_cycle_v1 80.4% similar
-
function update_status_after_approval 79.5% similar
-
function complete_review 79.3% similar
-
function create_approval_cycle 76.3% similar