function add_approval_comment
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.
/tf/active/vicechatdev/CDocs/controllers/approval_controller.py
444 - 629
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
logginguuidostypingdatetimetracebackCDocs.dbCDocs.config.settingsCDocs.config.permissionsCDocs.models.documentCDocs.models.approvalCDocs.models.user_extensionsCDocs.utils.audit_trailCDocs.utils.notificationsCDocs.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
Tags
Similar Components
AI-powered semantic similarity - components with related functionality:
-
function add_approval_comment_v1 92.5% similar
-
function add_review_comment 85.0% similar
-
function update_approval_comment 81.3% similar
-
function update_approval_comment_v1 77.1% similar
-
function add_approver_to_active_approval 73.0% similar