class ApproverAssignment_v1
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.
/tf/active/vicechatdev/CDocs/models/approval.py
1225 - 1506
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 propertiesuid: 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 forstatus_filter: Optional list of status strings to filter bylimit: 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 forstatus_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
logginguuidtypingdatetimetracebackCDocs.dbCDocs.config.settingsCDocs.db.schema_managerCDocs.models.baseCDocs.models.user_extensionsCDocs.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
OptionalUsage 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
Tags
Similar Components
AI-powered semantic similarity - components with related functionality:
-
class ApproverAssignment 96.3% similar
-
class ReviewerAssignment 75.8% similar
-
function get_user_assigned_approvals_v1 70.5% similar
-
class ApprovalCycle_v1 69.2% similar
-
class ApprovalCycle 67.7% similar