class ApproverAssignment
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.
/tf/active/vicechatdev/CDocs/models/approval_bis.py
808 - 1097
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 propertiesuid: 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 cycleapprover_uid: UID of the approver usersequence_order: Optional order in sequential approvalinstructions: 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 approverinclude_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
logginguuidtypingdatetimeCDocs.dbCDocs.configCDocs.db.schema_managerCDocs.models.baseCDocs.models.decoratorsCDocs.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
Tags
Similar Components
AI-powered semantic similarity - components with related functionality:
-
class ApproverAssignment_v1 96.3% similar
-
class ReviewerAssignment 77.2% similar
-
function get_user_assigned_approvals_v1 72.4% similar
-
class ApprovalCycle 67.2% similar
-
class ApprovalComment 65.9% similar