class ApprovalCycle_v2
Model representing an approval cycle for a document version.
/tf/active/vicechatdev/CDocs single class/models/approval.py
269 - 1047
moderate
Purpose
Model representing an approval cycle for a document version.
Source Code
class ApprovalCycle(WorkflowCycleBase, DocumentVersionRelationMixin, BaseModel):
"""Model representing an approval cycle for a document version."""
def __init__(self, data: Optional[Dict[str, Any]] = None, uid: Optional[str] = None):
"""
Initialize an approval cycle.
Args:
data: Dictionary of approval cycle properties
uid: Approval cycle UID to load from database (if data not provided)
"""
if data is None and uid is not None:
# Fetch data from database
data = db.get_node_by_uid(uid)
BaseModel.__init__(self, data or {})
WorkflowCycleBase.__init__(self, data or {})
DocumentVersionRelationMixin.__init__(self)
# Set default workflow type
if 'workflow_type' not in self._data:
self._data['workflow_type'] = 'APPROVAL'
# Ensure backward compatibility - map old fields to new fields
if 'startDate' in self._data and 'started_at' not in self._data:
self._data['started_at'] = self._data['startDate']
if 'dueDate' in self._data and 'due_date' not in self._data:
self._data['due_date'] = self._data['dueDate']
if 'completionDate' in self._data and 'completed_at' not in self._data:
self._data['completed_at'] = self._data['completionDate']
self._comments_cache = None
self._approvers_cache = None
@classmethod
def create(cls, document_version_uid: str,
approvers: List[Union[DocUser, str]],
due_date: Optional[datetime] = None,
instructions: str = '',
sequential: bool = True,
properties: Optional[Dict[str, Any]] = None) -> Optional['ApprovalCycle']:
"""
Create a new approval cycle.
Args:
document_version_uid: UID of document version to approve
approvers: List of approvers or their UIDs
due_date: When approval is due
instructions: Instructions for approvers
sequential: Whether approvals should be sequential
properties: Additional properties for the approval cycle
Returns:
New ApprovalCycle instance or None if creation failed
"""
try:
# Prepare properties
props = properties or {}
props.update({
'instructions': instructions,
'status': WorkflowStatus.PENDING.value,
'created_at': datetime.now().isoformat(),
'sequential': sequential,
'current_sequence': 1 if sequential else 0,
'workflow_type': 'APPROVAL',
'document_version_uid': document_version_uid
})
if due_date:
props['due_date'] = due_date.isoformat()
# Create cycle node
cycle_uid = str(uuid.uuid4())
props['UID'] = cycle_uid
success = db.create_node(NodeLabels.APPROVAL, props)
if not success:
logger.error("Failed to create approval cycle node")
return None
# Create relationship to document version
rel_success = db.create_relationship(
document_version_uid,
cycle_uid,
RelTypes.FOR_APPROVAL
)
if not rel_success:
logger.error(f"Failed to create relationship between document version {document_version_uid} and approval cycle {cycle_uid}")
# Clean up orphaned node
db.delete_node(cycle_uid)
return None
# Create the approval cycle instance
cycle = cls(props)
# Add approvers
sequence = 1
for approver in approvers:
approver_success = cycle.add_approver(approver, sequence if sequential else 0)
if not approver_success:
logger.warning(f"Failed to add approver to approval cycle {cycle_uid}")
if sequential:
sequence += 1
return cycle
except Exception as e:
logger.error(f"Error creating approval cycle: {e}")
return None
@classmethod
def get_approvals_for_document(cls, document_uid: str) -> List['ApprovalCycle']:
"""
Get all approval cycles for a document.
Args:
document_uid: UID of the document
Returns:
List of ApprovalCycle instances
"""
result = db.run_query(
"""
MATCH (d:ControlledDocument {UID: $doc_uid})-[:HAS_VERSION]->(v:DocumentVersion)-[:FOR_APPROVAL]->(a:Approval)
RETURN a
ORDER BY a.created_at DESC
""",
{"doc_uid": document_uid}
)
return [cls(record['a']) for record in result if 'a' in record]
@property
def status(self) -> str:
"""Get approval cycle status."""
return self._data.get('status', WorkflowStatus.PENDING.value)
@status.setter
def status(self, value: str) -> None:
"""Set approval cycle status."""
if value in [status.value for status in WorkflowStatus]:
old_value = self._data.get('status')
if old_value != value:
self._data['status'] = value
self._data['updated_at'] = datetime.now().isoformat()
self._modified = True
@property
def initiated_by_uid(self) -> Optional[str]:
"""Get UID of user who initiated the approval cycle."""
return self._data.get('initiated_by_uid')
@initiated_by_uid.setter
def initiated_by_uid(self, uid: str) -> None:
"""Set UID of user who initiated the approval cycle."""
self._data['initiated_by_uid'] = uid
self._modified = True
@property
def required_approval_percentage(self) -> int:
"""Get percentage of approvers required for approval."""
return self._data.get('required_approval_percentage', 100)
@required_approval_percentage.setter
def required_approval_percentage(self, value: int) -> None:
"""Set percentage of approvers required for approval."""
if 0 <= value <= 100:
self._data['required_approval_percentage'] = value
self._modified = True
def save(self) -> bool:
"""Save changes to database."""
try:
# If the node doesn't exist, create it
if not db.node_exists(self.uid):
return db.create_node(NodeLabels.APPROVAL, self._data)
# Update the existing node
return db.update_node(self.uid, self._data)
except Exception as e:
logger.error(f"Error saving approval cycle: {e}")
return False
@property
def start_date(self) -> Optional[datetime]:
"""Get approval cycle start date."""
started_at = self._data.get('started_at') or self._data.get('startDate')
if started_at:
if isinstance(started_at, datetime):
return started_at
else:
return datetime.fromisoformat(started_at)
return None
@property
def due_date(self) -> Optional[datetime]:
"""Get approval cycle due date."""
due_date = self._data.get('due_date') or self._data.get('dueDate')
if due_date:
if isinstance(due_date, datetime):
return due_date
else:
return datetime.fromisoformat(due_date)
return None
@due_date.setter
def due_date(self, date: datetime) -> None:
"""Set approval cycle due date."""
self._data['due_date'] = date.isoformat()
self._modified = True
@property
def completion_date(self) -> Optional[datetime]:
"""Get approval cycle completion date."""
completed_at = self._data.get('completed_at') or self._data.get('completionDate')
if completed_at:
if isinstance(completed_at, datetime):
return completed_at
else:
return datetime.fromisoformat(completed_at)
return None
@completion_date.setter
def completion_date(self, date: datetime) -> None:
"""Set approval cycle completion date."""
self._data['completed_at'] = date.isoformat()
self._modified = True
@property
def instructions(self) -> str:
"""Get instructions for approvers."""
return self._data.get('instructions', '')
@property
def is_completed(self) -> bool:
"""Whether approval cycle is completed."""
return self.status in [WorkflowStatus.APPROVED.value, WorkflowStatus.REJECTED.value]
@property
def is_overdue(self) -> bool:
"""Whether approval cycle is overdue."""
if self.is_completed:
return False
if self.due_date:
return datetime.now() > self.due_date
return False
@property
def document_version_uid(self) -> Optional[str]:
"""Get UID of the document version for this approval cycle."""
return self._data.get('document_version_uid')
@property
def document_uid(self) -> Optional[str]:
"""Get UID of the document for this approval cycle."""
if not self.document_version_uid:
return None
result = db.run_query(
"""
MATCH (d:ControlledDocument)-[:HAS_VERSION]->(v:DocumentVersion {UID: $version_uid})
RETURN d.UID as doc_uid
""",
{"version_uid": self.document_version_uid}
)
if result and 'doc_uid' in result[0]:
return result[0]['doc_uid']
return None
@property
def document_version_number(self) -> Optional[str]:
"""Get version number of the document for this approval cycle."""
if not self.document_version_uid:
return None
result = db.run_query(
"""
MATCH (v:DocumentVersion {UID: $version_uid})
RETURN v.versionNumber as version_number
""",
{"version_uid": self.document_version_uid}
)
if result and 'version_number' in result[0]:
return result[0]['version_number']
return None
@property
def document_version(self) -> Optional[Any]:
"""Get the document version object for this approval cycle."""
if not self.document_version_uid:
return None
from CDocs.models.document import DocumentVersion
return DocumentVersion(uid=self.document_version_uid)
@property
def document(self) -> Optional[Any]:
"""Get the document object for this approval cycle."""
if not self.document_uid:
return None
from CDocs.models.document import ControlledDocument
return ControlledDocument(uid=self.document_uid)
@property
def approvers(self) -> List[DocUser]:
"""Get list of approvers for this cycle."""
if self._approvers_cache is not None:
return self._approvers_cache
result = db.run_query(
"""
MATCH (a:Approval {UID: $cycle_uid})-[:APPROVED_BY]->(u:User)
RETURN u.UID as uid, u.Name as name
""",
{"cycle_uid": self.uid}
)
self._approvers_cache = [DocUser(uid=record['uid']) for record in result]
return self._approvers_cache
@property
def approver_uids(self) -> List[str]:
"""Get UIDs of approvers for this cycle."""
return [approver.uid for approver in self.approvers]
def add_approver(self, approver: Union[DocUser, str], sequence_order: int = 0) -> bool:
"""
Add an approver to the approval cycle.
Args:
approver: User or UID to add as approver
sequence_order: Order in approval sequence (if sequential)
Returns:
Boolean indicating success
"""
try:
approver_uid = approver.uid if isinstance(approver, DocUser) else approver
# Check if already an approver
if approver_uid in self.approver_uids:
return True
# Create relationship to approver
rel_success = db.create_relationship(
self.uid,
approver_uid,
RelTypes.APPROVED_BY
)
if not rel_success:
logger.error(f"Failed to create relationship between approval cycle {self.uid} and approver {approver_uid}")
return False
# Create approver assignment
assignment = ApproverAssignment.create(self.uid, approver, sequence_order)
if not assignment:
logger.error(f"Failed to create approver assignment for {approver_uid}")
return False
# Clear approvers cache
self._approvers_cache = None
return True
except Exception as e:
logger.error(f"Error adding approver to approval cycle: {e}")
return False
def remove_approver(self, approver: Union[DocUser, str]) -> bool:
"""
Remove an approver from the approval cycle.
Args:
approver: User or UID to remove
Returns:
Boolean indicating success
"""
try:
approver_uid = approver.uid if isinstance(approver, DocUser) else approver
# Check if actually an approver
if approver_uid not in self.approver_uids:
return True
# Remove relationship to approver
rel_success = db.run_query(
"""
MATCH (a:Approval {UID: $cycle_uid})-[r:APPROVED_BY]->(u:User {UID: $approver_uid})
DELETE r
RETURN count(r) as deleted
""",
{
"cycle_uid": self.uid,
"approver_uid": approver_uid
}
)
# Remove approver assignment
assignment = self.get_approver_assignment(approver_uid)
if assignment:
# Record removal reason
assignment.removal_date = datetime.now()
assignment.removal_reason = "Removed from approval cycle"
assignment.save()
# Delete assignment node
db.run_query(
"""
MATCH (a:Approval {UID: $cycle_uid})-[r:ASSIGNMENT]->(aa:Approver {UID: $assignment_uid})
DELETE r
""",
{
"cycle_uid": self.uid,
"assignment_uid": assignment.uid
}
)
# Clear approvers cache
self._approvers_cache = None
return True
except Exception as e:
logger.error(f"Error removing approver from approval cycle: {e}")
return False
@property
def comments(self) -> List[ApprovalComment]:
"""Get all comments for this approval cycle."""
if self._comments_cache is not None:
return self._comments_cache
result = db.run_query(
"""
MATCH (a:Approval {UID: $cycle_uid})<-[:COMMENTED_ON]-(c:ReviewComment)
WHERE c.parent_comment_uid IS NULL
RETURN c
ORDER BY c.timestamp ASC
""",
{"cycle_uid": self.uid}
)
self._comments_cache = [ApprovalComment(record['c']) for record in result if 'c' in record]
return self._comments_cache
def add_comment(self, approver: Union[DocUser, str],
text: str,
requires_resolution: bool = False) -> Optional[ApprovalComment]:
"""
Add a comment to the approval cycle.
Args:
approver: User making the comment or their UID
text: Comment text
requires_resolution: Whether this comment requires resolution
Returns:
New ApprovalComment instance or None if creation failed
"""
comment = ApprovalComment.create(
self.uid,
approver,
text,
requires_resolution
)
if comment:
# Clear comment cache
self._comments_cache = None
return comment
def get_unresolved_comments(self) -> List[ApprovalComment]:
"""
Get all unresolved comments that require resolution.
Returns:
List of unresolved ApprovalComment instances
"""
# First get all comments using the updated method which handles relationships correctly
all_comments = self.comments
# Then filter for unresolved ones
return [
comment for comment in all_comments
if comment.requires_resolution and not comment.is_resolved
]
def complete_approval(self, approved: bool = True) -> bool:
"""
Complete the approval cycle.
Args:
approved: Whether the document was approved
Returns:
Boolean indicating success
"""
try:
# Set status based on approval result
new_status = WorkflowStatus.APPROVED if approved else WorkflowStatus.REJECTED
self.status = new_status.value
self.completion_date = datetime.now()
# Save changes
if self.save():
# Update document version status if approved
if approved:
doc_version = self.document_version
if doc_version:
doc_version.status = 'APPROVED'
doc_version.save()
return True
return False
except Exception as e:
logger.error(f"Error completing approval cycle: {e}")
return False
def get_approval_status(self, approver: Union[DocUser, str]) -> Dict[str, Any]:
"""
Get approval status for a specific approver.
Args:
approver: Approver to check
Returns:
Dictionary with approval status information
"""
approver_uid = approver.uid if isinstance(approver, DocUser) else approver
# Check if user is an approver
if approver_uid not in self.approver_uids:
return {"status": "NOT_APPROVER"}
# Get approval assignment
assignment = self.get_approver_assignment(approver_uid)
if assignment:
return {
"status": assignment.status,
"decision": assignment.decision,
"comments": assignment.decision_comments,
"date": assignment.decision_date.isoformat() if assignment.decision_date else None
}
# Check if approver has commented
result = db.run_query(
"""
MATCH (a:Approval {UID: $approval_uid})<-[:COMMENTED_ON]-(c:ReviewComment)
WHERE c.author_uid = $approver_uid
RETURN count(c) as comment_count
""",
{"approval_uid": self.uid, "approver_uid": approver_uid}
)
comment_count = result[0]['comment_count'] if result else 0
if comment_count > 0:
return {"status": "COMMENTED", "comment_count": comment_count}
else:
return {"status": "PENDING"}
def get_approver_assignments(self) -> List['ApproverAssignment']:
"""Get all approver assignments for this approval cycle."""
try:
result = db.run_query(
"""
MATCH (a:Approval {UID: $cycle_uid})-[:ASSIGNMENT]->(aa:Approver)
RETURN aa
ORDER BY aa.sequence_order
""",
{"cycle_uid": self.uid}
)
return [ApproverAssignment(record['aa']) for record in result if 'aa' in record]
except Exception as e:
logger.error(f"Error getting approver assignments: {e}")
return []
def get_approver_assignment(self, approver_uid: str) -> Optional['ApproverAssignment']:
"""
Get assignment for a specific approver.
Args:
approver_uid: UID of the approver
Returns:
ApproverAssignment instance or None if not found
"""
try:
result = db.run_query(
"""
MATCH (a:Approval {UID: $cycle_uid})-[:ASSIGNMENT]->(aa:Approver)
WHERE aa.approver_uid = $approver_uid
RETURN aa
""",
{"cycle_uid": self.uid, "approver_uid": approver_uid}
)
if result and len(result) > 0:
return ApproverAssignment(result[0]['aa'])
return None
except Exception as e:
logger.error(f"Error getting approver assignment: {e}")
return None
def is_approver(self, approver_uid: str) -> bool:
"""
Check if a user is an approver for this cycle.
Args:
approver_uid: UID of the user to check
Returns:
Boolean indicating if user is an approver
"""
try:
result = db.run_query(
"""
MATCH (a:Approval {UID: $cycle_uid})-[:APPROVED_BY]->(u:User {UID: $approver_uid})
RETURN count(u) as is_approver
""",
{"cycle_uid": self.uid, "approver_uid": approver_uid}
)
return result[0]['is_approver'] > 0 if result else False
except Exception as e:
logger.error(f"Error checking if user is approver: {e}")
return False
def get_next_approver(self, current_sequence: int) -> Optional['ApproverAssignment']:
"""
Get the next approver in sequence for sequential approvals.
Args:
current_sequence: Current sequence number
Returns:
Next ApproverAssignment in sequence or None
"""
if not self.sequential:
return None
assignments = self.get_approver_assignments()
next_assignments = [a for a in assignments if a.sequence_order > current_sequence]
if next_assignments:
return min(next_assignments, key=lambda a: a.sequence_order)
return None
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary representation."""
result = super().to_dict()
# Add approval cycle specific data
result.update({
"document_version": {
"UID": self.document_version_uid,
"version_number": self.document_version_number
} if self.document_version_uid else None,
"document": {
"UID": self.document_uid
} if self.document_uid else None,
"approvers": [
{
"UID": a.uid,
"name": a.name,
"email": a.email
}
for a in self.approvers
],
"approver_count": len(self.approvers),
"comment_count": len(self.comments),
"unresolved_comment_count": len(self.get_unresolved_comments())
})
return result
def update_status(self) -> None:
"""Update the approval cycle status based on approver assignments."""
# Skip if already completed
if self.status in [WorkflowStatus.APPROVED.value, WorkflowStatus.REJECTED.value]:
return
assignments = self.get_approver_assignments()
if not assignments:
return
# If sequential, check if all approvers up to current sequence have completed
if self.sequential:
current_seq = self.current_sequence
# Find all assignments up to current sequence
current_assignments = [a for a in assignments if a.sequence_order <= current_seq]
# If all current assignments complete, move to next sequence
if all(a.status in [AssignmentStatus.COMPLETED.value,
AssignmentStatus.REJECTED.value,
AssignmentStatus.SKIPPED.value]
for a in current_assignments):
# Find next approver
next_approver = self.get_next_approver(current_seq)
if next_approver:
# Move to next sequence
self.current_sequence = next_approver.sequence_order
self.save()
# Start next assignment
next_approver.status = AssignmentStatus.IN_PROGRESS.value
next_approver.save()
else:
# No more approvers, check if all approved
if any(a.status == AssignmentStatus.REJECTED.value for a in assignments):
self.status = WorkflowStatus.REJECTED.value
else:
self.status = WorkflowStatus.APPROVED.value
self.completion_date = datetime.now()
self.save()
# For parallel approval, check if all have completed
else:
if all(a.status in [AssignmentStatus.COMPLETED.value,
AssignmentStatus.REJECTED.value,
AssignmentStatus.SKIPPED.value]
for a in assignments):
# Check required approval percentage
total_approvers = len(assignments)
if total_approvers == 0:
return
completed = sum(1 for a in assignments if a.status == AssignmentStatus.COMPLETED.value)
approval_percentage = (completed / total_approvers) * 100
if approval_percentage >= self.required_approval_percentage:
self.status = WorkflowStatus.APPROVED.value
else:
self.status = WorkflowStatus.REJECTED.value
self.completion_date = datetime.now()
self.save()
def get_progress(self) -> Dict[str, int]:
"""Get progress statistics for the approval cycle."""
assignments = self.get_approver_assignments()
stats = {
"total": len(assignments),
"pending": sum(1 for a in assignments if a.status == AssignmentStatus.PENDING.value),
"in_progress": sum(1 for a in assignments if a.status == AssignmentStatus.IN_PROGRESS.value),
"completed": sum(1 for a in assignments if a.status == AssignmentStatus.COMPLETED.value),
"rejected": sum(1 for a in assignments if a.status == AssignmentStatus.REJECTED.value),
"skipped": sum(1 for a in assignments if a.status == AssignmentStatus.SKIPPED.value)
}
return stats
Parameters
| Name | Type | Default | Kind |
|---|---|---|---|
bases |
WorkflowCycleBase, DocumentVersionRelationMixin, BaseModel | - |
Parameter Details
bases: Parameter of type WorkflowCycleBase, DocumentVersionRelationMixin, BaseModel
Return Value
Returns unspecified type
Class Interface
Methods
__init__(self, data, uid)
Purpose: Initialize an approval cycle. Args: data: Dictionary of approval cycle properties uid: Approval cycle UID to load from database (if data not provided)
Parameters:
data: Type: Optional[Dict[str, Any]]uid: Type: Optional[str]
Returns: None
create(cls, document_version_uid, approvers, due_date, instructions, sequential, properties) -> Optional['ApprovalCycle']
Purpose: Create a new approval cycle. Args: document_version_uid: UID of document version to approve approvers: List of approvers or their UIDs due_date: When approval is due instructions: Instructions for approvers sequential: Whether approvals should be sequential properties: Additional properties for the approval cycle Returns: New ApprovalCycle instance or None if creation failed
Parameters:
cls: Parameterdocument_version_uid: Type: strapprovers: Type: List[Union[DocUser, str]]due_date: Type: Optional[datetime]instructions: Type: strsequential: Type: boolproperties: Type: Optional[Dict[str, Any]]
Returns: Returns Optional['ApprovalCycle']
get_approvals_for_document(cls, document_uid) -> List['ApprovalCycle']
Purpose: Get all approval cycles for a document. Args: document_uid: UID of the document Returns: List of ApprovalCycle instances
Parameters:
cls: Parameterdocument_uid: Type: str
Returns: Returns List['ApprovalCycle']
status(self) -> str
property
Purpose: Get approval cycle status.
Returns: Returns str
status(self, value) -> None
Purpose: Set approval cycle status.
Parameters:
value: Type: str
Returns: Returns None
initiated_by_uid(self) -> Optional[str]
property
Purpose: Get UID of user who initiated the approval cycle.
Returns: Returns Optional[str]
initiated_by_uid(self, uid) -> None
Purpose: Set UID of user who initiated the approval cycle.
Parameters:
uid: Type: str
Returns: Returns None
required_approval_percentage(self) -> int
property
Purpose: Get percentage of approvers required for approval.
Returns: Returns int
required_approval_percentage(self, value) -> None
Purpose: Set percentage of approvers required for approval.
Parameters:
value: Type: int
Returns: Returns None
save(self) -> bool
Purpose: Save changes to database.
Returns: Returns bool
start_date(self) -> Optional[datetime]
property
Purpose: Get approval cycle start date.
Returns: Returns Optional[datetime]
due_date(self) -> Optional[datetime]
property
Purpose: Get approval cycle due date.
Returns: Returns Optional[datetime]
due_date(self, date) -> None
Purpose: Set approval cycle due date.
Parameters:
date: Type: datetime
Returns: Returns None
completion_date(self) -> Optional[datetime]
property
Purpose: Get approval cycle completion date.
Returns: Returns Optional[datetime]
completion_date(self, date) -> None
Purpose: Set approval cycle completion date.
Parameters:
date: Type: datetime
Returns: Returns None
instructions(self) -> str
property
Purpose: Get instructions for approvers.
Returns: Returns str
is_completed(self) -> bool
property
Purpose: Whether approval cycle is completed.
Returns: Returns bool
is_overdue(self) -> bool
property
Purpose: Whether approval cycle is overdue.
Returns: Returns bool
document_version_uid(self) -> Optional[str]
property
Purpose: Get UID of the document version for this approval cycle.
Returns: Returns Optional[str]
document_uid(self) -> Optional[str]
property
Purpose: Get UID of the document for this approval cycle.
Returns: Returns Optional[str]
document_version_number(self) -> Optional[str]
property
Purpose: Get version number of the document for this approval cycle.
Returns: Returns Optional[str]
document_version(self) -> Optional[Any]
property
Purpose: Get the document version object for this approval cycle.
Returns: Returns Optional[Any]
document(self) -> Optional[Any]
property
Purpose: Get the document object for this approval cycle.
Returns: Returns Optional[Any]
approvers(self) -> List[DocUser]
property
Purpose: Get list of approvers for this cycle.
Returns: Returns List[DocUser]
approver_uids(self) -> List[str]
property
Purpose: Get UIDs of approvers for this cycle.
Returns: Returns List[str]
add_approver(self, approver, sequence_order) -> bool
Purpose: Add an approver to the approval cycle. Args: approver: User or UID to add as approver sequence_order: Order in approval sequence (if sequential) Returns: Boolean indicating success
Parameters:
approver: Type: Union[DocUser, str]sequence_order: Type: int
Returns: Returns bool
remove_approver(self, approver) -> bool
Purpose: Remove an approver from the approval cycle. Args: approver: User or UID to remove Returns: Boolean indicating success
Parameters:
approver: Type: Union[DocUser, str]
Returns: Returns bool
comments(self) -> List[ApprovalComment]
property
Purpose: Get all comments for this approval cycle.
Returns: Returns List[ApprovalComment]
add_comment(self, approver, text, requires_resolution) -> Optional[ApprovalComment]
Purpose: Add a comment to the approval cycle. Args: approver: User making the comment or their UID text: Comment text requires_resolution: Whether this comment requires resolution Returns: New ApprovalComment instance or None if creation failed
Parameters:
approver: Type: Union[DocUser, str]text: Type: strrequires_resolution: Type: bool
Returns: Returns Optional[ApprovalComment]
get_unresolved_comments(self) -> List[ApprovalComment]
Purpose: Get all unresolved comments that require resolution. Returns: List of unresolved ApprovalComment instances
Returns: Returns List[ApprovalComment]
complete_approval(self, approved) -> bool
Purpose: Complete the approval cycle. Args: approved: Whether the document was approved Returns: Boolean indicating success
Parameters:
approved: Type: bool
Returns: Returns bool
get_approval_status(self, approver) -> Dict[str, Any]
Purpose: Get approval status for a specific approver. Args: approver: Approver to check Returns: Dictionary with approval status information
Parameters:
approver: Type: Union[DocUser, str]
Returns: Returns Dict[str, Any]
get_approver_assignments(self) -> List['ApproverAssignment']
Purpose: Get all approver assignments for this approval cycle.
Returns: Returns List['ApproverAssignment']
get_approver_assignment(self, approver_uid) -> Optional['ApproverAssignment']
Purpose: Get assignment for a specific approver. Args: approver_uid: UID of the approver Returns: ApproverAssignment instance or None if not found
Parameters:
approver_uid: Type: str
Returns: Returns Optional['ApproverAssignment']
is_approver(self, approver_uid) -> bool
Purpose: Check if a user is an approver for this cycle. Args: approver_uid: UID of the user to check Returns: Boolean indicating if user is an approver
Parameters:
approver_uid: Type: str
Returns: Returns bool
get_next_approver(self, current_sequence) -> Optional['ApproverAssignment']
Purpose: Get the next approver in sequence for sequential approvals. Args: current_sequence: Current sequence number Returns: Next ApproverAssignment in sequence or None
Parameters:
current_sequence: Type: int
Returns: Returns Optional['ApproverAssignment']
to_dict(self) -> Dict[str, Any]
Purpose: Convert to dictionary representation.
Returns: Returns Dict[str, Any]
update_status(self) -> None
Purpose: Update the approval cycle status based on approver assignments.
Returns: Returns None
get_progress(self) -> Dict[str, int]
Purpose: Get progress statistics for the approval cycle.
Returns: Returns Dict[str, int]
Required Imports
import logging
import uuid
from typing import Dict
from typing import List
from typing import Any
Usage Example
# Example usage:
# result = ApprovalCycle(bases)
Tags
Similar Components
AI-powered semantic similarity - components with related functionality:
-
class ApprovalCycle_v1 97.5% similar
-
class ApprovalCycle 97.2% similar
-
class ReviewCycle_v1 78.9% similar
-
class ReviewCycle 78.8% similar
-
function create_approval_cycle_v2 76.0% similar