🔍 Code Extractor

class ApproverAssignment

Maturity: 50

Model class representing an approver assignment within an approval cycle, managing the relationship between an approver and their approval task including status, decisions, and lifecycle tracking.

File:
/tf/active/vicechatdev/CDocs/models/approval_bis.py
Lines:
808 - 1097
Complexity:
moderate

Purpose

ApproverAssignment manages individual approver assignments within approval cycles. It tracks the complete lifecycle of an approval task including status transitions (PENDING, COMPLETED, REMOVED), approver decisions (approve/reject), activity tracking, sequential ordering, and removal management. The class provides database persistence through automatic updates on property changes and supports both creation from existing data and loading from database by UID. It integrates with the approval workflow system to coordinate multi-approver processes.

Source Code

class ApproverAssignment(BaseModel):
    """Model representing an approver assignment within an approval cycle."""
    
    def __init__(self, data: Optional[Dict[str, Any]] = None, uid: Optional[str] = None):
        """
        Initialize an approver assignment.
        
        Args:
            data: Dictionary of assignment properties
            uid: Assignment UID to load from database (if data not provided)
        """
        if data is None and uid is not None:
            # Fetch assignment data from database
            data = db.get_node_by_uid(uid)
            
        super().__init__(data or {})
    
    @property
    def approver_uid(self) -> Optional[str]:
        """Get UID of assigned approver."""
        return self._data.get('approverUID')
    
    @property
    def approval_cycle_uid(self) -> Optional[str]:
        """Get UID of approval cycle."""
        return self._data.get('approvalCycleUID')
    
    @property
    def status(self) -> str:
        """Get assignment status."""
        return self._data.get('status', 'PENDING')
    
    @status.setter
    def status(self, new_status: str) -> None:
        """Set assignment status."""
        old_status = self.status
        
        if old_status == new_status:
            return
            
        self._data['status'] = new_status
        self._data['statusUpdatedAt'] = datetime.now()
        
        # Update in database
        db.update_node(self.uid, {
            'status': new_status,
            'statusUpdatedAt': self._data['statusUpdatedAt']
        })
        
    # Add a new property for approver-specific instructions
    @property
    def instructions(self) -> Optional[str]:
        """Get approver-specific instructions."""
        return self._data.get('instructions')
    
    @instructions.setter
    def instructions(self, text: str) -> None:
        """Set approver-specific instructions."""
        self._data['instructions'] = text
        
        # Update in database
        db.update_node(self.uid, {'instructions': text})
    
    @property
    def sequence_order(self) -> Optional[int]:
        """Get sequence order for sequential approval."""
        return self._data.get('sequenceOrder')
    
    @sequence_order.setter
    def sequence_order(self, order: int) -> None:
        """Set sequence order for sequential approval."""
        self._data['sequenceOrder'] = order
        
        # Update in database
        db.update_node(self.uid, {'sequenceOrder': order})
    
    @property
    def approver(self) -> Optional[DocUser]:
        """Get approver user."""
        approver_uid = self.approver_uid
        if approver_uid:
            return DocUser(uid=approver_uid)
        return None
    
    @property
    def first_activity_date(self) -> Optional[datetime]:
        """Get date when approver first interacted with assignment."""
        return self._data.get('firstActivityDate')
    
    @first_activity_date.setter
    def first_activity_date(self, date: datetime) -> None:
        """Set date when approver first interacted with assignment."""
        if not self._data.get('firstActivityDate'):
            self._data['firstActivityDate'] = date
            
            # Update in database
            db.update_node(self.uid, {'firstActivityDate': date})
    
    @property
    def decision(self) -> Optional[str]:
        """Get approver decision."""
        return self._data.get('decision')
    
    @decision.setter
    def decision(self, decision: str) -> None:
        """Set approver decision."""
        self._data['decision'] = decision
        self._data['decisionDate'] = datetime.now()
        
        # Update status to completed
        self.status = 'COMPLETED'
        
        # Update in database
        db.update_node(self.uid, {
            'decision': decision,
            'decisionDate': self._data['decisionDate']
        })
    
    @property
    def decision_date(self) -> Optional[datetime]:
        """Get date when approver made decision."""
        return self._data.get('decisionDate')
    
    @decision_date.setter
    def decision_date(self, date: datetime) -> None:
        """Set date when approver made decision."""
        self._data['decisionDate'] = date
        
        # Update in database
        db.update_node(self.uid, {'decisionDate': date})
    
    @property
    def decision_comments(self) -> Optional[str]:
        """Get comments for approver decision."""
        return self._data.get('decisionComments')
    
    @decision_comments.setter
    def decision_comments(self, text: str) -> None:
        """Set comments for approver decision."""
        self._data['decisionComments'] = text
        
        # Update in database
        db.update_node(self.uid, {'decisionComments': text})
    
    @property
    def removal_date(self) -> Optional[datetime]:
        """Get date when approver was removed from cycle."""
        return self._data.get('removalDate')
    
    @removal_date.setter
    def removal_date(self, date: datetime) -> None:
        """Set date when approver was removed from cycle."""
        self._data['removalDate'] = date
        self._data['status'] = 'REMOVED'
        
        # Update in database
        db.update_node(self.uid, {
            'removalDate': date,
            'status': 'REMOVED'
        })
    
    @property
    def removal_reason(self) -> Optional[str]:
        """Get reason why approver was removed from cycle."""
        return self._data.get('removalReason')
    
    @removal_reason.setter
    def removal_reason(self, reason: str) -> None:
        """Set reason why approver was removed from cycle."""
        self._data['removalReason'] = reason
        
        # Update in database
        db.update_node(self.uid, {'removalReason': reason})

    def save(self) -> bool:
        """Save changes to database."""
        try:
            # If node doesn't exist, create it
            if not db.node_exists(self.uid):
                return db.create_node_with_uid(
                    NodeLabels.APPROVER_ASSIGNMENT,
                    self._data,
                    self.uid
                )
                
            # Otherwise update existing node
            return db.update_node(self.uid, self._data)
        except Exception as e:
            logger.error(f"Error saving approver assignment: {e}")
            return False
    
    @classmethod
    def create(cls, approval_cycle_uid: str, approver_uid: str, 
              sequence_order: Optional[int] = None,
              instructions: Optional[str] = None) -> Optional['ApproverAssignment']:
        """
        Create a new approver assignment.
        
        Args:
            approval_cycle_uid: UID of approval cycle
            approver_uid: UID of approver
            sequence_order: Order in sequential approval
            instructions: Approver-specific instructions
            
        Returns:
            New ApproverAssignment instance or None if creation failed
        """
        try:
            # Prepare properties
            props = {
                'approverUID': approver_uid,
                'approvalCycleUID': approval_cycle_uid,
                'status': 'PENDING',
                'createdAt': datetime.now()
            }
            
            if sequence_order is not None:
                props['sequenceOrder'] = sequence_order
                
            if instructions:
                props['instructions'] = instructions
            
            # Create assignment node
            assignment_uid = str(uuid.uuid4())
            props['UID'] = assignment_uid
            
            # Create node in database
            success = db.create_node(NodeLabels.APPROVER_ASSIGNMENT, props)
            
            if not success:
                logger.error(f"Failed to create approver assignment for {approver_uid}")
                return None
            
            # Create relationship: Assignment -> ASSIGNMENT -> Approval Cycle
            rel_success = db.create_relationship(
                assignment_uid, 
                approval_cycle_uid,
                RelTypes.ASSIGNMENT
            )
            
            if not rel_success:
                logger.error(f"Failed to create relationship between assignment {assignment_uid} and approval cycle {approval_cycle_uid}")
                # Clean up the orphaned assignment node
                db.delete_node(assignment_uid)
                return None
                
            # Create approver assignment instance
            assignment = cls(props)
            
            return assignment
            
        except Exception as e:
            logger.error(f"Error creating approver assignment: {e}")
            return None
    
    @classmethod
    def get_assignments_for_approver(cls, approver_uid: str, include_completed: bool = False) -> List['ApproverAssignment']:
        """
        Get all approval assignments for a specific approver.
        
        Args:
            approver_uid: UID of approver
            include_completed: Whether to include completed assignments
            
        Returns:
            List of approver assignments
        """
        try:
            query = """
                MATCH (aa:ApproverAssignment {approverUID: $approver_uid})
                WHERE aa.removalDate IS NULL
                """
                
            if not include_completed:
                query += " AND aa.status = 'PENDING'"
                
            query += " RETURN aa ORDER BY aa.createdAt DESC"
            
            result = db.run_query(query, {"approver_uid": approver_uid})
            
            assignments = []
            for record in result:
                if 'aa' in record and record['aa']:
                    assignments.append(cls(record['aa']))
                    
            return assignments
            
        except Exception as e:
            logger.error(f"Error getting assignments for approver {approver_uid}: {e}")
            return []

Parameters

Name Type Default Kind
bases BaseModel -

Parameter Details

data: Optional dictionary containing assignment properties (approverUID, approvalCycleUID, status, instructions, sequenceOrder, etc.). If provided, initializes the assignment with this data. If None and uid is provided, data will be fetched from database.

uid: Optional string UID to load an existing assignment from the database. Used when data parameter is None to fetch assignment data by its unique identifier.

Return Value

Constructor returns an ApproverAssignment instance initialized with the provided data or loaded from database. Methods return various types: properties return Optional[str], Optional[int], Optional[datetime], or Optional[DocUser]; save() returns bool indicating success; create() returns Optional[ApproverAssignment]; get_assignments_for_approver() returns List[ApproverAssignment].

Class Interface

Methods

__init__(self, data: Optional[Dict[str, Any]] = None, uid: Optional[str] = None)

Purpose: Initialize an approver assignment from data dictionary or by loading from database using UID

Parameters:

  • data: Dictionary of assignment properties
  • uid: Assignment UID to load from database if data not provided

Returns: ApproverAssignment instance

@property approver_uid(self) -> Optional[str] property

Purpose: Get the UID of the assigned approver

Returns: String UID of approver or None

@property approval_cycle_uid(self) -> Optional[str] property

Purpose: Get the UID of the approval cycle this assignment belongs to

Returns: String UID of approval cycle or None

@property status(self) -> str property

Purpose: Get the current assignment status (PENDING, COMPLETED, REMOVED)

Returns: String status, defaults to 'PENDING'

@status.setter status(self, new_status: str) -> None property

Purpose: Set assignment status and update statusUpdatedAt timestamp, persists to database

Parameters:

  • new_status: New status value to set

Returns: None

@property instructions(self) -> Optional[str] property

Purpose: Get approver-specific instructions for this assignment

Returns: String instructions or None

@instructions.setter instructions(self, text: str) -> None property

Purpose: Set approver-specific instructions and persist to database

Parameters:

  • text: Instructions text to set

Returns: None

@property sequence_order(self) -> Optional[int] property

Purpose: Get the sequence order for sequential approval workflows

Returns: Integer order or None

@sequence_order.setter sequence_order(self, order: int) -> None property

Purpose: Set sequence order for sequential approval and persist to database

Parameters:

  • order: Integer order value

Returns: None

@property approver(self) -> Optional[DocUser] property

Purpose: Get the DocUser instance for the assigned approver

Returns: DocUser instance or None if no approver assigned

@property first_activity_date(self) -> Optional[datetime] property

Purpose: Get the date when approver first interacted with this assignment

Returns: datetime object or None

@first_activity_date.setter first_activity_date(self, date: datetime) -> None property

Purpose: Set first activity date (only if not already set) and persist to database

Parameters:

  • date: datetime of first activity

Returns: None

@property decision(self) -> Optional[str] property

Purpose: Get the approver's decision (e.g., APPROVED, REJECTED)

Returns: String decision or None

@decision.setter decision(self, decision: str) -> None property

Purpose: Set approver decision, automatically sets decisionDate, updates status to COMPLETED, and persists to database

Parameters:

  • decision: Decision value (e.g., 'APPROVED', 'REJECTED')

Returns: None

@property decision_date(self) -> Optional[datetime] property

Purpose: Get the date when approver made their decision

Returns: datetime object or None

@decision_date.setter decision_date(self, date: datetime) -> None property

Purpose: Set decision date and persist to database

Parameters:

  • date: datetime of decision

Returns: None

@property decision_comments(self) -> Optional[str] property

Purpose: Get comments provided by approver with their decision

Returns: String comments or None

@decision_comments.setter decision_comments(self, text: str) -> None property

Purpose: Set decision comments and persist to database

Parameters:

  • text: Comments text

Returns: None

@property removal_date(self) -> Optional[datetime] property

Purpose: Get the date when approver was removed from the cycle

Returns: datetime object or None

@removal_date.setter removal_date(self, date: datetime) -> None property

Purpose: Set removal date, automatically sets status to REMOVED, and persists to database

Parameters:

  • date: datetime of removal

Returns: None

@property removal_reason(self) -> Optional[str] property

Purpose: Get the reason why approver was removed from cycle

Returns: String reason or None

@removal_reason.setter removal_reason(self, reason: str) -> None property

Purpose: Set removal reason and persist to database

Parameters:

  • reason: Reason text

Returns: None

save(self) -> bool

Purpose: Save all changes to database, creates node if it doesn't exist or updates existing node

Returns: Boolean indicating success or failure

@classmethod create(cls, approval_cycle_uid: str, approver_uid: str, sequence_order: Optional[int] = None, instructions: Optional[str] = None) -> Optional['ApproverAssignment']

Purpose: Create a new approver assignment with database node and relationship to approval cycle

Parameters:

  • approval_cycle_uid: UID of the approval cycle
  • approver_uid: UID of the approver user
  • sequence_order: Optional order in sequential approval
  • instructions: Optional approver-specific instructions

Returns: New ApproverAssignment instance or None if creation failed

@classmethod get_assignments_for_approver(cls, approver_uid: str, include_completed: bool = False) -> List['ApproverAssignment']

Purpose: Retrieve all approval assignments for a specific approver, optionally filtering by status

Parameters:

  • approver_uid: UID of the approver
  • include_completed: Whether to include completed assignments (default False shows only PENDING)

Returns: List of ApproverAssignment instances, empty list if none found or error occurs

Attributes

Name Type Description Scope
_data Dict[str, Any] Internal dictionary storing all assignment properties, inherited from BaseModel instance
uid str Unique identifier for the assignment, inherited from BaseModel instance

Dependencies

  • logging
  • uuid
  • typing
  • datetime
  • CDocs.db
  • CDocs.config
  • CDocs.db.schema_manager
  • CDocs.models.base
  • CDocs.models.decorators
  • CDocs.models.user_extensions

Required Imports

import logging
import uuid
from typing import Dict, List, Any, Optional
from datetime import datetime
from CDocs import db
from CDocs.config import settings
from CDocs.db.schema_manager import NodeLabels, RelTypes
from CDocs.models.base import BaseModel
from CDocs.models.decorators import register_model
from CDocs.models.user_extensions import DocUser

Usage Example

# Create a new approver assignment
assignment = ApproverAssignment.create(
    approval_cycle_uid='cycle-123',
    approver_uid='user-456',
    sequence_order=1,
    instructions='Please review the technical specifications'
)

# Load existing assignment
existing = ApproverAssignment(uid='assignment-789')

# Check status and approver
if existing.status == 'PENDING':
    approver = existing.approver
    print(f'Waiting on {approver.name}')

# Record first activity
existing.first_activity_date = datetime.now()

# Make a decision
existing.decision_comments = 'Looks good, approved'
existing.decision = 'APPROVED'

# Get all pending assignments for an approver
pending = ApproverAssignment.get_assignments_for_approver(
    approver_uid='user-456',
    include_completed=False
)

# Remove an approver
assignment.removal_reason = 'No longer on team'
assignment.removal_date = datetime.now()

Best Practices

  • Always use the create() class method to instantiate new assignments rather than direct construction to ensure proper database relationships
  • Setting the decision property automatically updates status to COMPLETED and sets decisionDate, so avoid manually setting status after decision
  • Setting removal_date automatically sets status to REMOVED, maintaining data consistency
  • Properties automatically persist changes to database, so no need to call save() after property updates
  • Use save() method only when bulk updating _data dictionary directly
  • Check if assignment exists before loading by UID to avoid errors
  • When removing approvers, always set both removal_date and removal_reason for audit trail
  • Use first_activity_date to track when approver first engaged with the assignment
  • Sequential approval workflows should set sequence_order to control approval order
  • The status property setter prevents redundant updates if status hasn't changed
  • Get assignments using get_assignments_for_approver() to filter by approver and status efficiently

Similar Components

AI-powered semantic similarity - components with related functionality:

  • class ApproverAssignment_v1 96.3% similar

    Model class representing an approver assignment within an approval cycle, managing the relationship between an approver and their approval task including status, decisions, and timeline tracking.

    From: /tf/active/vicechatdev/CDocs/models/approval.py
  • class ReviewerAssignment 77.2% similar

    Model class representing a reviewer assignment within a review cycle, managing reviewer information, status, decisions, and lifecycle tracking for document review processes.

    From: /tf/active/vicechatdev/CDocs/models/review.py
  • function get_user_assigned_approvals_v1 72.4% similar

    Retrieves all approval assignments for a specific user with optional filtering by status, including associated approval cycles, documents, and version information.

    From: /tf/active/vicechatdev/CDocs/controllers/approval_controller.py
  • class ApprovalCycle 67.2% similar

    Model representing an approval cycle for a document version.

    From: /tf/active/vicechatdev/CDocs/models/approval_bis.py
  • class ApprovalComment 65.9% similar

    Model class representing a comment made during a document approval cycle, with support for resolution tracking and database persistence.

    From: /tf/active/vicechatdev/CDocs/models/approval_bis.py
← Back to Browse