🔍 Code Extractor

function complete_approval_v1

Maturity: 76

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

File:
/tf/active/vicechatdev/CDocs/controllers/approval_controller_bis.py
Lines:
628 - 793
Complexity:
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.document
  • CDocs.models.approval
  • CDocs.models.user_extensions
  • CDocs.utils.audit_trail
  • CDocs.utils.notifications
  • CDocs.controllers
  • CDocs.controllers.document_controller
  • CDocs.db
  • CDocs.config.settings
  • CDocs.config.permissions
  • datetime
  • logging
  • typing
  • traceback

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

Similar Components

AI-powered semantic similarity - components with related functionality:

  • function complete_approval 90.5% 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 close_approval_cycle_v1 80.4% similar

    Administratively closes an approval cycle by setting a final decision (APPROVED or REJECTED), updating the associated document status, and notifying relevant stakeholders.

    From: /tf/active/vicechatdev/CDocs/controllers/approval_controller_bis.py
  • function update_status_after_approval 79.5% similar

    Updates a controlled document's status after an approval workflow completes, determining the next status based on the approval decision and logging the change to the audit trail.

    From: /tf/active/vicechatdev/CDocs/controllers/document_controller.py
  • function complete_review 79.3% similar

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

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