🔍 Code Extractor

class ApprovalCycle_v2

Maturity: 28

Model representing an approval cycle for a document version.

File:
/tf/active/vicechatdev/CDocs single class/models/approval.py
Lines:
269 - 1047
Complexity:
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: Parameter
  • document_version_uid: Type: str
  • approvers: Type: List[Union[DocUser, str]]
  • due_date: Type: Optional[datetime]
  • instructions: Type: str
  • sequential: Type: bool
  • properties: 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: Parameter
  • document_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: str
  • requires_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)

Similar Components

AI-powered semantic similarity - components with related functionality:

  • class ApprovalCycle_v1 97.5% similar

    Model representing a approval cycle for a document version.

    From: /tf/active/vicechatdev/CDocs/models/approval.py
  • class ApprovalCycle 97.2% similar

    Model representing an approval cycle for a document version.

    From: /tf/active/vicechatdev/CDocs/models/approval_bis.py
  • class ReviewCycle_v1 78.9% similar

    Model representing a review cycle for a document version.

    From: /tf/active/vicechatdev/CDocs single class/models/review.py
  • class ReviewCycle 78.8% similar

    Model representing a review cycle for a document version.

    From: /tf/active/vicechatdev/CDocs/models/review.py
  • function create_approval_cycle_v2 76.0% similar

    Creates an approval cycle for a specific document version, assigning approvers and configuring approval workflow parameters.

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