🔍 Code Extractor

class ApproverAssignment_v1

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 timeline tracking.

File:
/tf/active/vicechatdev/CDocs/models/approval.py
Lines:
1225 - 1506
Complexity:
moderate

Purpose

ApproverAssignment manages individual approver assignments within approval cycles. It tracks the complete lifecycle of an approval task including assignment, activity, decision-making, and potential removal. The class handles database persistence, status transitions, and provides query methods for retrieving user-specific assignments. It supports sequential approval workflows through sequence ordering and maintains detailed audit trails with timestamps and comments.

Source Code

class ApproverAssignment(BaseModel):
    """Model representing a approver assignment within a approval cycle."""
    
    def __init__(self, data: Optional[Dict[str, Any]] = None, uid: Optional[str] = None):
        """
        Initialize a 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 approval_cycle_uid(self) -> Optional[str]:
        """Get the UID of the approval cycle this assignment belongs to."""
        return self._data.get('approval_cycle_uid')
    
    @property
    def approver_uid(self) -> Optional[str]:
        """Get UID of the assigned approver."""
        return self._data.get('approver_uid')
    
    @property
    def approver_name(self) -> Optional[str]:
        """Get name of the assigned approver."""
        name = self._data.get('approver_name')
        if not name and self.approver_uid:
            # Try to fetch from database
            result = db.run_query(
                "MATCH (u:User {UID: $uid}) RETURN u.Name as name",
                {"uid": self.approver_uid}
            )
            if result and 'name' in result[0]:
                name = result[0]['name']
                # Cache it
                self._data['approver_name'] = name
        return name
    
    @property
    def status(self) -> str:
        """Get assignment status (PENDING, ACTIVE, COMPLETED, CANCELED, REMOVED)."""
        return self._data.get('status', 'PENDING')
    
    @status.setter
    def status(self, value: str) -> None:
        """Set assignment status."""
        if value not in ['PENDING', 'ACTIVE', 'COMPLETED', 'CANCELED', 'REMOVED']:
            logger.warning(f"Invalid assignment status: {value}")
            return
            
        self._data['status'] = value
        db.update_node(self.uid, {'status': value})

        
    # 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
        db.update_node(self.uid, {'instructions': text})
    
    @property
    def sequence_order(self) -> Optional[int]:
        """Get sequence order for sequential approvals."""
        return self._data.get('sequence_order')
    
    @sequence_order.setter
    def sequence_order(self, value: Optional[int]) -> None:
        """Set sequence order."""
        self._data['sequence_order'] = value
        db.update_node(self.uid, {'sequence_order': value})
    
    @property
    def assigned_date(self) -> Optional[datetime]:
        """Get date when approver was assigned."""
        return self._data.get('assigned_date')
    
    @property
    def first_activity_date(self) -> Optional[datetime]:
        """Get date of first approver activity."""
        return self._data.get('first_activity_date')
    
    @first_activity_date.setter
    def first_activity_date(self, date: datetime) -> None:
        """Set first activity date."""
        self._data['first_activity_date'] = date
        db.update_node(self.uid, {'first_activity_date': date})
    
    @property
    def decision(self) -> Optional[str]:
        """Get approver's decision (APPROVED, REJECTED, etc.)."""
        return self._data.get('decision')
    
    @decision.setter
    def decision(self, value: str) -> None:
        """Set approver's decision."""
        if value not in settings.APPROVAL_DECISIONS:
            logger.warning(f"Invalid approval decision: {value}")
            return
            
        self._data['decision'] = value
        db.update_node(self.uid, {'decision': value})
    
    @property
    def decision_date(self) -> Optional[datetime]:
        """Get date when decision was made."""
        return self._data.get('decision_date')
    
    @decision_date.setter
    def decision_date(self, date: datetime) -> None:
        """Set decision date."""
        self._data['decision_date'] = date
        db.update_node(self.uid, {'decision_date': date})
    
    @property
    def decision_comments(self) -> Optional[str]:
        """Get comments about the decision."""
        return self._data.get('decision_comments')
    
    @decision_comments.setter
    def decision_comments(self, text: str) -> None:
        """Set decision comments."""
        self._data['decision_comments'] = text
        db.update_node(self.uid, {'decision_comments': text})
    
    @property
    def removal_date(self) -> Optional[datetime]:
        """Get date when approver was removed."""
        return self._data.get('removal_date')
    
    @removal_date.setter
    def removal_date(self, date: datetime) -> None:
        """Set removal date."""
        self._data['removal_date'] = date
        db.update_node(self.uid, {'removal_date': date})
    
    @property
    def removal_reason(self) -> Optional[str]:
        """Get reason for approver removal."""
        return self._data.get('removal_reason')
    
    @removal_reason.setter
    def removal_reason(self, text: str) -> None:
        """Set removal reason."""
        self._data['removal_reason'] = text
        db.update_node(self.uid, {'removal_reason': text})
    
        # Add this method to the ApproverAssignment class
    def save(self) -> bool:
        """Save changes to database."""
        try:
            # If node doesn't exist, create it
            if not db.node_exists(self.uid):
                created = db.create_node_with_uid(
                    NodeLabels.APPROVER_ASSIGNMENT,
                    self._data,
                    self.uid
                )
                
                if created and self._data.get('approval_cycle_uid'):
                    # Create ASSIGNMENT relationship from ApprovalCycle to ApproverAssignment
                    db.create_relationship(
                        self._data['approval_cycle_uid'],
                        self.uid,
                        RelTypes.ASSIGNMENT
                    )
                return created
            
            # Otherwise update existing node
            return db.update_node(self.uid, self._data)
        except Exception as e:
            logger.error(f"Error saving approver assignment: {e}")
            import traceback
            logger.error(traceback.format_exc())
            return False
    
    @classmethod
    def get_user_assignments(cls, user_uid: str, 
                           status_filter: Optional[List[str]] = None,
                           limit: int = 100, 
                           offset: int = 0) -> List['ApproverAssignment']:
        """
        Get all approval assignments for a user.
        
        Args:
            user_uid: UID of the user
            status_filter: Optional list of statuses to filter by
            limit: Maximum number of assignments to return
            offset: Number of assignments to skip
            
        Returns:
            List of ApproverAssignment instances
        """
        try:
            # Build query
            query = """
            MATCH (a:ApproverAssignment)
            WHERE a.approver_uid = $user_uid
            """
            
            params = {"user_uid": user_uid}
            
            # Add status filter if provided
            if status_filter:
                query += "AND a.status IN $statuses "
                params["statuses"] = status_filter
                
            # Add ordering and pagination
            query += """
            RETURN a
            ORDER BY a.assigned_date DESC
            SKIP $offset
            LIMIT $limit
            """
            
            params["offset"] = offset
            params["limit"] = limit
            
            # Execute query
            result = db.run_query(query, params)
            
            # Convert to assignment instances
            assignments = [cls(record['a']) for record in result if 'a' in record]
            
            return assignments
            
        except Exception as e:
            logger.error(f"Error getting user assignments: {e}")
            return []
    
    @classmethod
    def count_user_assignments(cls, user_uid: str, 
                             status_filter: Optional[List[str]] = None) -> int:
        """
        Count approval assignments for a user.
        
        Args:
            user_uid: UID of the user
            status_filter: Optional list of statuses to filter by
            
        Returns:
            Count of matching assignments
        """
        try:
            # Build query
            query = """
            MATCH (a:ApproverAssignment)
            WHERE a.approver_uid = $user_uid
            """
            
            params = {"user_uid": user_uid}
            
            # Add status filter if provided
            if status_filter:
                query += "AND a.status IN $statuses "
                params["statuses"] = status_filter
                
            # Add count
            query += "RETURN count(a) as count"
            
            # Execute query
            result = db.run_query(query, params)
            
            # Get count
            if result and 'count' in result[0]:
                return result[0]['count']
                
            return 0
            
        except Exception as e:
            logger.error(f"Error counting user assignments: {e}")
            return 0

Parameters

Name Type Default Kind
bases BaseModel -

Parameter Details

data: Optional dictionary containing assignment properties (approval_cycle_uid, approver_uid, status, instructions, sequence_order, etc.). If None and uid is provided, data will be fetched from database.

uid: Optional unique identifier string for the assignment. If provided without data, the assignment will be loaded from the database using this UID.

Return Value

Instantiation returns an ApproverAssignment object that provides access to assignment properties through properties and methods. The save() method returns a boolean indicating success/failure. Class methods get_user_assignments() returns a list of ApproverAssignment instances, and count_user_assignments() returns an integer count.

Class Interface

Methods

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

Purpose: Initialize an approver assignment, either from provided data 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 approval_cycle_uid(self) -> Optional[str] property

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

Returns: String UID of the parent approval cycle or None

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

Purpose: Get UID of the assigned approver

Returns: String UID of the approver user or None

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

Purpose: Get name of the assigned approver, fetching from database if not cached

Returns: String name of the approver or None

@property status(self) -> str property

Purpose: Get assignment status

Returns: String status value (PENDING, ACTIVE, COMPLETED, CANCELED, REMOVED), defaults to PENDING

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

Purpose: Set assignment status with validation and database persistence

Parameters:

  • value: Status string, must be one of: PENDING, ACTIVE, COMPLETED, CANCELED, REMOVED

Returns: None

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

Purpose: Get approver-specific instructions

Returns: String instructions or empty string

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

Purpose: Set approver-specific instructions with database persistence

Parameters:

  • text: Instructions text to set

Returns: None

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

Purpose: Get sequence order for sequential approvals

Returns: Integer sequence order or None

@sequence_order.setter sequence_order(self, value: Optional[int]) -> None property

Purpose: Set sequence order with database persistence

Parameters:

  • value: Integer sequence order or None

Returns: None

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

Purpose: Get date when approver was assigned

Returns: datetime object or None

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

Purpose: Get date of first approver activity

Returns: datetime object or None

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

Purpose: Set first activity date with database persistence

Parameters:

  • date: datetime object representing first activity

Returns: None

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

Purpose: Get approver's decision

Returns: String decision value (APPROVED, REJECTED, etc.) or None

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

Purpose: Set approver's decision with validation against settings.APPROVAL_DECISIONS

Parameters:

  • value: Decision string, must be in settings.APPROVAL_DECISIONS

Returns: None

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

Purpose: Get date when decision was made

Returns: datetime object or None

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

Purpose: Set decision date with database persistence

Parameters:

  • date: datetime object representing decision time

Returns: None

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

Purpose: Get comments about the decision

Returns: String comments or None

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

Purpose: Set decision comments with database persistence

Parameters:

  • text: Comments text to set

Returns: None

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

Purpose: Get date when approver was removed

Returns: datetime object or None

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

Purpose: Set removal date with database persistence

Parameters:

  • date: datetime object representing removal time

Returns: None

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

Purpose: Get reason for approver removal

Returns: String reason or None

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

Purpose: Set removal reason with database persistence

Parameters:

  • text: Reason text to set

Returns: None

save(self) -> bool

Purpose: Save assignment changes to database, creating node if it doesn't exist and establishing relationship with approval cycle

Returns: Boolean indicating success (True) or failure (False)

@classmethod get_user_assignments(cls, user_uid: str, status_filter: Optional[List[str]] = None, limit: int = 100, offset: int = 0) -> List['ApproverAssignment']

Purpose: Retrieve all approval assignments for a specific user with optional filtering and pagination

Parameters:

  • user_uid: UID of the user to get assignments for
  • status_filter: Optional list of status strings to filter by
  • limit: Maximum number of assignments to return (default 100)
  • offset: Number of assignments to skip for pagination (default 0)

Returns: List of ApproverAssignment instances ordered by assigned_date descending

@classmethod count_user_assignments(cls, user_uid: str, status_filter: Optional[List[str]] = None) -> int

Purpose: Count total approval assignments for a user with optional status filtering

Parameters:

  • user_uid: UID of the user to count assignments for
  • status_filter: Optional list of status strings to filter by

Returns: Integer count of matching assignments

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
  • traceback
  • CDocs.db
  • CDocs.config.settings
  • CDocs.db.schema_manager
  • CDocs.models.base
  • CDocs.models.user_extensions
  • CDocs.models.document

Required Imports

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
import logging

Conditional/Optional Imports

These imports are only needed under specific conditions:

import traceback

Condition: Used internally for error logging in save() method

Optional

Usage Example

# Create new assignment
assignment = ApproverAssignment(data={
    'approval_cycle_uid': 'cycle_123',
    'approver_uid': 'user_456',
    'approver_name': 'John Doe',
    'status': 'PENDING',
    'instructions': 'Please review by EOD',
    'sequence_order': 1,
    'assigned_date': datetime.now()
})
assignment.save()

# Load existing assignment
assignment = ApproverAssignment(uid='assignment_789')

# Update status and decision
assignment.status = 'ACTIVE'
assignment.first_activity_date = datetime.now()
assignment.decision = 'APPROVED'
assignment.decision_date = datetime.now()
assignment.decision_comments = 'Looks good'

# Get user assignments
user_assignments = ApproverAssignment.get_user_assignments(
    user_uid='user_456',
    status_filter=['ACTIVE', 'PENDING'],
    limit=50,
    offset=0
)

# Count assignments
count = ApproverAssignment.count_user_assignments(
    user_uid='user_456',
    status_filter=['PENDING']
)

Best Practices

  • Always call save() after modifying assignment properties to persist changes to the database
  • Use status setter to ensure valid status transitions (PENDING, ACTIVE, COMPLETED, CANCELED, REMOVED)
  • Set first_activity_date when approver first interacts with the assignment
  • Always set decision_date when setting a decision value
  • Use sequence_order for sequential approval workflows to control approval order
  • Validate decision values against settings.APPROVAL_DECISIONS before setting
  • When removing an approver, set both removal_date and removal_reason for audit trail
  • Use get_user_assignments() with status_filter for efficient querying of user tasks
  • The approver_name property auto-fetches from database if not cached, but caching improves performance
  • Handle save() return value to detect persistence failures
  • Use count_user_assignments() before get_user_assignments() for pagination implementation

Similar Components

AI-powered semantic similarity - components with related functionality:

  • class ApproverAssignment 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 lifecycle tracking.

    From: /tf/active/vicechatdev/CDocs/models/approval_bis.py
  • class ReviewerAssignment 75.8% 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 70.5% 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_v1 69.2% similar

    Model representing a approval cycle for a document version.

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

    Model representing an approval cycle for a document version.

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