🔍 Code Extractor

function add_approval_comment

Maturity: 83

Adds a comment to an approval cycle for a controlled document, with support for threaded comments, different comment types, and automatic notifications to relevant stakeholders.

File:
/tf/active/vicechatdev/CDocs/controllers/approval_controller.py
Lines:
444 - 629
Complexity:
complex

Purpose

This function enables users with appropriate permissions to add comments during the document approval process. It supports various comment types (GENERAL, QUESTION, ACTION_REQUIRED, etc.), threaded discussions via parent comments, and location-specific comments on document pages. The function handles permission checks, validates inputs, updates approval cycle status, logs audit events, and sends notifications to document owners or parent comment authors when appropriate.

Source Code

def add_approval_comment(
    user: DocUser,
    approval_uid: str,
    comment_text: str,
    comment_type: str = "GENERAL",
    page_number: Optional[int] = None,
    location_info: Optional[Dict[str, Any]] = None,
    parent_comment_uid: Optional[str] = None
) -> Dict[str, Any]:
    """
    Add a comment to a approval cycle.
    
    Args:
        user: User adding the comment
        approval_uid: UID of approval cycle
        comment_text: Text of the comment
        comment_type: Type of comment (GENERAL, QUESTION, SUGGESTION, etc.)
        page_number: Optional page number for document comments
        location_info: Optional location information for document comments
        parent_comment_uid: Optional parent comment UID for threaded comments
        
    Returns:
        Dictionary with created comment details
        
    Raises:
        ResourceNotFoundError: If approval 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 ADD_APPROVAL_COMMENT permission")
        if not permissions.user_has_permission(user, ["ADD_APPROVAL_COMMENT"]):
            logger.warning(f"Permission denied: User {user.uid} attempted to add a comment without ADD_APPROVAL_COMMENT permission")
            raise PermissionError("You do not have permission to addd comments")
            
        logger.info(f"User {user.uid} has permission to create and initiate approval cycle")
        # Get approval cycle instance
        approval_cycle = ApprovalCycle(uid=approval_uid)
        if not approval_cycle:
            raise ResourceNotFoundError(f"Approval cycle not found: {approval_uid}")
            
        # Check if approval status allows comments
        if approval_cycle.status not in ["PENDING", "IN_PROGRESS"]:
            raise BusinessRuleError(f"Cannot add comments to approval with status {approval_cycle.status}")
            
        # Check if this user is a approver for this cycle
        if not approval_cycle.is_approver(user.uid) and not permissions.user_has_permission(user, "MANAGE_APPROVALS"):
            raise PermissionError("User is not authorized to add comments to this approval")
            
        # Validate comment text
        if not comment_text or len(comment_text.strip()) == 0:
            raise ValidationError("Comment text cannot be empty")
            
        # Validate comment type
        if not comment_type in settings.APPROVAL_COMMENT_TYPES:
            raise ValidationError(f"Invalid comment type: {comment_type}")
            
        # Check parent comment if provided
        parent_comment = None
        if parent_comment_uid:
            parent_comment = ApprovalComment(uid=parent_comment_uid)
            if not parent_comment or parent_comment.approval_cycle_uid != approval_uid:
                raise ResourceNotFoundError(f"Parent comment not found: {parent_comment_uid}")
                
        # Create comment instance
         # Use the proper create method for ApprovalComment instead of direct instantiation
        comment = ApprovalComment.create(
            approval_cycle_uid=approval_uid,
            commenter=user,
            text=comment_text,
            requires_resolution=comment_type in ["QUESTION", "ACTION_REQUIRED"],
            properties={
                "comment_type": comment_type,
                "page_number": page_number,
                "location_info": location_info,
                "parent_comment_uid": parent_comment_uid,
                "status": "OPEN",
                "user_name": user.name,  # Include for backward compatibility
                "user_uid": user.uid      # Include for backward compatibility
            }
        )
        
        if not comment:
            raise BusinessRuleError("Failed to create approval comment")
        
        # If approval cycle is in PENDING status, update to IN_PROGRESS
        if approval_cycle.status == "PENDING":
            approval_cycle.status = "IN_PROGRESS"
            approval_cycle.save()
            
        # Update approver assignment status if this is the first activity
        assignment = approval_cycle.get_approver_assignment(user.uid)
        if assignment and assignment.status in ["PENDING", "ACTIVE"] and not assignment.first_activity_date:
            assignment.first_activity_date = datetime.now()
            assignment.status = "ACTIVE"
            assignment.save()
            
        # Log comment event
        audit_trail.log_approval_event(
            event_type="APPROVAL_COMMENT_ADDED",
            user=user,
            approval_uid=approval_uid,
            details={
                "comment_uid": comment.uid,
                "comment_type": comment_type,
                "page_number": page_number,
                "parent_comment_uid": parent_comment_uid
            }
        )
        
        # Notify about comment if it's a reply or directed to document owner
        if parent_comment_uid or comment_type in ["QUESTION", "ACTION_REQUIRED"]:
            # Get document and owner
            document = ControlledDocument(uid=approval_cycle.document_uid)
            
            # If it's a reply, notify the parent comment author
            if parent_comment and parent_comment.user_uid != user.uid:
                notifications.send_notification(
                    notification_type="APPROVAL_COMMENT_ADDED",
                    users=[parent_comment.user_uid],
                    resource_uid=approval_cycle.uid,
                    resource_type="ApprovalCycle",
                    message=f"New reply to your comment on {document.doc_number}",
                    details={
                        "approval_uid": approval_uid,
                        "comment_uid": comment.uid,
                        "doc_number": document.doc_number if document else "",
                        "title": document.title if document else ""
                    },
                    send_email=True,
                    email_template="approval_comment_reply",
                    email_data={
                        "app_url": settings.APP_URL,
                        "doc_uid": document.uid,
                        "approval_uid": approval_uid,
                        "comment_uid": comment.uid,
                        "commenter_name": user.name,
                        "recipient_name": parent_comment.user_name if hasattr(parent_comment, 'user_name') else "Reviewer",
                        "comment_text": comment_text[:100] + "..." if len(comment_text) > 100 else comment_text,
                        "comment_date": datetime.now().strftime("%Y-%m-%d %H:%M")
                    }
                )
                
            # If it's a question or action required, notify document owner
            elif comment_type in ["QUESTION", "ACTION_REQUIRED"] and document and document.owner_uid != user.uid:
                notifications.send_notification(
                    notification_type="APPROVAL_COMMENT_ADDED",
                    users=[document.owner_uid],
                    resource_uid=approval_cycle.uid,
                    resource_type="ApprovalCycle",
                    message=f"Action required on document {document.doc_number}",
                    details={
                        "approval_uid": approval_uid,
                        "comment_uid": comment.uid,
                        "doc_number": document.doc_number,
                        "title": document.title
                    },
                    send_email=True,
                    email_template="approval_action_required",
                    email_data={
                        "app_url": settings.APP_URL,
                        "doc_uid": document.uid,
                        "approval_uid": approval_uid,
                        "comment_uid": comment.uid,
                        "commenter_name": user.name,
                        "recipient_name": document.owner_name if hasattr(document, 'owner_name') else "Document Owner",
                        "comment_text": comment_text[:100] + "..." if len(comment_text) > 100 else comment_text,
                        "comment_date": datetime.now().strftime("%Y-%m-%d %H:%M"),
                        "comment_type": comment_type
                    }
                )
        
        return {
            "success": True,
            "comment": comment.to_dict(),
            "message": "Comment added successfully"
        }
        
    except (ResourceNotFoundError, ValidationError, PermissionError, BusinessRuleError) as e:
        # Re-raise known errors
        raise
    except Exception as e:
        logger.error(f"Error adding approval comment: {e}")
        raise BusinessRuleError(f"Failed to add approval comment: {e}")

Parameters

Name Type Default Kind
user DocUser - positional_or_keyword
approval_uid str - positional_or_keyword
comment_text str - positional_or_keyword
comment_type str 'GENERAL' positional_or_keyword
page_number Optional[int] None positional_or_keyword
location_info Optional[Dict[str, Any]] None positional_or_keyword
parent_comment_uid Optional[str] None positional_or_keyword

Parameter Details

user: DocUser object representing the user adding the comment. Must have ADD_APPROVAL_COMMENT permission and be an approver for the cycle (unless they have MANAGE_APPROVALS permission).

approval_uid: String UID (unique identifier) of the approval cycle to which the comment is being added. Must reference an existing approval cycle.

comment_text: String containing the text content of the comment. Cannot be empty or whitespace-only. Will be truncated to 100 characters in email notifications.

comment_type: String indicating the type of comment. Must be one of the values defined in settings.APPROVAL_COMMENT_TYPES (e.g., 'GENERAL', 'QUESTION', 'SUGGESTION', 'ACTION_REQUIRED'). Defaults to 'GENERAL'. QUESTION and ACTION_REQUIRED types trigger notifications and set requires_resolution flag.

page_number: Optional integer specifying the page number in the document where the comment applies. Used for location-specific comments on document pages.

location_info: Optional dictionary containing additional location information for the comment (e.g., coordinates, annotations). Structure depends on document viewer implementation.

parent_comment_uid: Optional string UID of a parent comment for creating threaded/nested comments. If provided, must reference an existing comment in the same approval cycle and triggers notification to the parent comment author.

Return Value

Type: Dict[str, Any]

Returns a dictionary with three keys: 'success' (boolean, always True on successful execution), 'comment' (dictionary containing the created comment's details via to_dict() method, including uid, text, commenter info, timestamps, and properties), and 'message' (string with success message 'Comment added successfully'). On error, raises one of the custom exceptions instead of returning.

Dependencies

  • logging
  • uuid
  • os
  • typing
  • datetime
  • traceback
  • CDocs.db
  • CDocs.config.settings
  • CDocs.config.permissions
  • CDocs.models.document
  • CDocs.models.approval
  • CDocs.models.user_extensions
  • CDocs.utils.audit_trail
  • CDocs.utils.notifications
  • CDocs.controllers

Required Imports

from typing import Dict, Any, Optional
from datetime import datetime
from CDocs.models.user_extensions import DocUser
from CDocs.models.approval import ApprovalCycle, ApprovalComment, ApproverAssignment
from CDocs.models.document import ControlledDocument
from CDocs.config import settings, permissions
from CDocs.utils import audit_trail, notifications
from CDocs.controllers import ResourceNotFoundError, ValidationError, PermissionError, BusinessRuleError
import logging

Usage Example

# Basic usage - add a general comment
from CDocs.models.user_extensions import DocUser
from add_approval_comment import add_approval_comment

user = DocUser(uid='user123')
approval_uid = 'approval456'

result = add_approval_comment(
    user=user,
    approval_uid=approval_uid,
    comment_text='This section looks good to me.'
)
print(f"Comment created: {result['comment']['uid']}")

# Add a question with page reference
result = add_approval_comment(
    user=user,
    approval_uid=approval_uid,
    comment_text='Can you clarify the requirements in section 3?',
    comment_type='QUESTION',
    page_number=5
)

# Reply to an existing comment
result = add_approval_comment(
    user=user,
    approval_uid=approval_uid,
    comment_text='I agree with your assessment.',
    parent_comment_uid='comment789'
)

# Add action required comment with location info
result = add_approval_comment(
    user=user,
    approval_uid=approval_uid,
    comment_text='This calculation needs to be updated.',
    comment_type='ACTION_REQUIRED',
    page_number=12,
    location_info={'x': 100, 'y': 200, 'width': 50, 'height': 20}
)

Best Practices

  • Always ensure the user object has the required ADD_APPROVAL_COMMENT permission before calling this function
  • Validate that the approval_uid exists and is in an appropriate status (PENDING or IN_PROGRESS) before attempting to add comments
  • Use appropriate comment_type values to trigger correct notification behavior (QUESTION and ACTION_REQUIRED types notify document owners)
  • When creating threaded comments, verify the parent_comment_uid belongs to the same approval cycle
  • Handle all four custom exceptions (ResourceNotFoundError, ValidationError, PermissionError, BusinessRuleError) in calling code
  • The function automatically transitions approval status from PENDING to IN_PROGRESS on first comment, so no manual status update is needed
  • Comment text should be meaningful and not empty; the function validates this but client-side validation is recommended
  • Be aware that email notifications are sent automatically for replies and action-required comments, so avoid spamming with excessive comments
  • The function logs audit events automatically, so no additional logging is needed in calling code
  • Use location_info parameter consistently with your document viewer implementation for proper comment positioning

Similar Components

AI-powered semantic similarity - components with related functionality:

  • function add_approval_comment_v1 92.5% similar

    Adds a comment to an approval cycle with optional location information, page references, and reply threading. Validates user permissions, logs audit trails, and sends notifications to other approvers for issue-type comments.

    From: /tf/active/vicechatdev/CDocs/controllers/approval_controller_bis.py
  • function add_review_comment 85.0% similar

    Adds a comment to a document review cycle with support for threaded comments, different comment types, and location-based annotations.

    From: /tf/active/vicechatdev/CDocs/controllers/review_controller.py
  • function update_approval_comment 81.3% similar

    Updates an approval comment's text and/or status with permission checks, audit logging, and notifications for resolved comments.

    From: /tf/active/vicechatdev/CDocs/controllers/approval_controller.py
  • function update_approval_comment_v1 77.1% similar

    Updates an existing approval comment in a document approval workflow, allowing modification of comment text and status (e.g., marking as resolved) with permission checks and audit logging.

    From: /tf/active/vicechatdev/CDocs/controllers/approval_controller_bis.py
  • function add_approver_to_active_approval 73.0% similar

    Adds a new approver to an active approval cycle for a controlled document, with optional sequence ordering and custom instructions.

    From: /tf/active/vicechatdev/CDocs/controllers/approval_controller.py
← Back to Browse