class ApproverAssignment
Model class representing an approver assignment within an approval cycle, managing the state and lifecycle of individual approval tasks assigned to users.
/tf/active/vicechatdev/CDocs single class/models/approval.py
1051 - 1334
complex
Purpose
ApproverAssignment tracks individual approver assignments within an approval cycle. It manages assignment status, decisions, sequencing for sequential approvals, and maintains audit trails of approver activities. The class handles creation, persistence, status transitions, and provides access to related approval cycle information. It supports both parallel and sequential approval workflows through sequence ordering.
Source Code
class ApproverAssignment(AssignmentBase, 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:
data = db.get_node_by_uid(uid)
BaseModel.__init__(self, data or {})
AssignmentBase.__init__(self, data or {})
# Ensure backward compatibility
if 'assigned_at' not in self._data and 'assignedDate' in self._data:
self._data['assigned_at'] = self._data['assignedDate']
@property
def approval_cycle_uid(self) -> Optional[str]:
"""Get 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 approver."""
return self._data.get('approver_uid') or self._data.get('user_uid')
@property
def status(self) -> str:
"""Get assignment status."""
return self._data.get('status', AssignmentStatus.PENDING.value)
@property
def sequence_order(self) -> int:
"""Get sequence order for sequential approvals."""
return self._data.get('sequence_order', 0)
@status.setter
def status(self, value: str) -> None:
"""Set assignment status."""
if value in [status.value for status in AssignmentStatus]:
old_status = self._data.get('status')
if old_status != value:
self._data['status'] = value
self._modified = True
# Record first activity if this is the first status change
if old_status == AssignmentStatus.PENDING.value and value != old_status:
self.first_activity_date = datetime.now()
@property
def sequence_order(self) -> int:
"""Get sequence order for sequential approvals."""
return self._data.get('sequence_order', 0)
@sequence_order.setter
def sequence_order(self, value: int) -> None:
"""Set sequence order for sequential approvals."""
self._data['sequence_order'] = value
self._modified = True
@property
def approval_cycle(self) -> Optional[ApprovalCycle]:
"""Get approval cycle this assignment belongs to."""
if not self.approval_cycle_uid:
return None
return ApprovalCycle(uid=self.approval_cycle_uid)
@property
def first_activity_date(self) -> Optional[datetime]:
"""Get date of first activity on this assignment."""
date_value = self._data.get('first_activity_date')
if date_value:
if isinstance(date_value, datetime):
return date_value
return datetime.fromisoformat(date_value)
return None
@first_activity_date.setter
def first_activity_date(self, value: datetime) -> None:
"""Set date of first activity on this assignment."""
self._data['first_activity_date'] = value.isoformat()
self._modified = True
@property
def decision(self) -> Optional[str]:
"""Get approver's decision."""
return self._data.get('decision')
@decision.setter
def decision(self, value: str) -> None:
"""Set approver's decision."""
self._data['decision'] = value
self._modified = True
@property
def decision_date(self) -> Optional[datetime]:
"""Get date when decision was made."""
date_value = self._data.get('decision_date')
if date_value:
if isinstance(date_value, datetime):
return date_value
return datetime.fromisoformat(date_value)
return None
@decision_date.setter
def decision_date(self, value: datetime) -> None:
"""Set date when decision was made."""
self._data['decision_date'] = value.isoformat()
self._modified = True
@property
def decision_comments(self) -> str:
"""Get comments on the decision."""
return self._data.get('decision_comments', '')
@decision_comments.setter
def decision_comments(self, value: str) -> None:
"""Set comments on the decision."""
self._data['decision_comments'] = value
self._modified = True
@property
def removal_date(self) -> Optional[datetime]:
"""Get date when approver was removed."""
date_value = self._data.get('removal_date')
if date_value:
if isinstance(date_value, datetime):
return date_value
return datetime.fromisoformat(date_value)
return None
@removal_date.setter
def removal_date(self, value: datetime) -> None:
"""Set date when approver was removed."""
self._data['removal_date'] = value.isoformat()
self._modified = True
@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, value: str) -> None:
"""Set reason for approver removal."""
self._data['removal_reason'] = value
self._modified = True
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(NodeLabels.APPROVER, self._data)
# 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
def _update_cycle_status(self) -> None:
"""Update the parent approval cycle status."""
cycle = self.approval_cycle
if cycle:
cycle.update_status()
@classmethod
def create(cls, approval_cycle_uid: str, approver: Union[DocUser, str], sequence_order: int = 0) -> Optional['ApproverAssignment']:
"""
Create a new approver assignment.
Args:
approval_cycle_uid: UID of the approval cycle
approver: User or UID to assign
sequence_order: Order in approval sequence (if sequential)
Returns:
New ApproverAssignment instance or None if creation failed
"""
try:
# Get approver info
approver_uid = approver.uid if isinstance(approver, DocUser) else approver
approver_name = None
if isinstance(approver, DocUser):
approver_name = approver.name
else:
user_info = db.run_query(
"""
MATCH (u:User {UID: $uid})
RETURN u.Name as name
""",
{"uid": approver_uid}
)
if user_info and 'name' in user_info[0]:
approver_name = user_info[0]['name']
# Prepare properties
props = {
'status': AssignmentStatus.PENDING.value,
'assigned_at': datetime.now().isoformat(),
'sequence_order': sequence_order,
'approver_uid': approver_uid,
'user_uid': approver_uid,
'user_name': approver_name,
'approval_cycle_uid': approval_cycle_uid
}
# Create assignment node
assignment_uid = str(uuid.uuid4())
props['UID'] = assignment_uid
success = db.create_node(NodeLabels.APPROVER, props)
if not success:
logger.error(f"Failed to create approver assignment node for {approver_uid}")
return None
# Create relationship from approval cycle to assignment
rel_success = db.create_relationship(
approval_cycle_uid,
assignment_uid,
RelTypes.ASSIGNMENT
)
if not rel_success:
logger.error(f"Failed to create relationship between approval cycle {approval_cycle_uid} and assignment {assignment_uid}")
# Clean up orphaned node
db.delete_node(assignment_uid)
return None
# Create the assignment instance
assignment = cls(props)
# If this is the first approver in a sequential approval, start the assignment
cycle = ApprovalCycle(uid=approval_cycle_uid)
if cycle.sequential and sequence_order == 1 and cycle.status == WorkflowStatus.PENDING.value:
assignment.status = AssignmentStatus.IN_PROGRESS.value
assignment.save()
# Also update cycle status to IN_PROGRESS
cycle.status = WorkflowStatus.IN_PROGRESS.value
cycle.started_at = datetime.now().isoformat()
cycle.save()
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) -> List['ApproverAssignment']:
"""
Get all assignments for an approver.
Args:
approver_uid: UID of the approver
Returns:
List of ApproverAssignment instances
"""
try:
result = db.run_query(
"""
MATCH (a:Approver)
WHERE a.approver_uid = $approver_uid
RETURN a
ORDER BY a.assigned_at DESC
""",
{"approver_uid": approver_uid}
)
return [cls(record['a']) for record in result if 'a' in record]
except Exception as e:
logger.error(f"Error getting approver assignments: {e}")
return []
Parameters
| Name | Type | Default | Kind |
|---|---|---|---|
bases |
AssignmentBase, BaseModel | - |
Parameter Details
data: Optional dictionary containing assignment properties. If provided, initializes the assignment with these properties. Properties include: approval_cycle_uid, approver_uid, status, sequence_order, decision, decision_date, decision_comments, first_activity_date, removal_date, removal_reason, assigned_at, user_name. If None, uid parameter must be provided.
uid: Optional string UID to load an existing assignment from the database. Used when data is None to fetch assignment data from the database by its unique identifier.
Return Value
Constructor returns an ApproverAssignment instance. The save() method returns a boolean indicating success/failure. The create() class method returns an Optional[ApproverAssignment] - a new instance if successful, None if creation failed. The get_assignments_for_approver() class method returns a List[ApproverAssignment] of all assignments for a given approver.
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: None - constructor initializes the 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 the UID of the approver assigned to this task
Returns: String UID of the approver user or None
@property status(self) -> str
property
Purpose: Get the current assignment status (PENDING, IN_PROGRESS, COMPLETED, etc.)
Returns: String status value from AssignmentStatus enum, defaults to PENDING
@status.setter status(self, value: str) -> None
property
Purpose: Set assignment status and automatically track first activity date on first status change from PENDING
Parameters:
value: New status value, must be valid AssignmentStatus enum value
Returns: None - updates internal state and marks as modified
@property sequence_order(self) -> int
property
Purpose: Get the sequence order for sequential approvals (determines approval order)
Returns: Integer sequence order, defaults to 0
@sequence_order.setter sequence_order(self, value: int) -> None
property
Purpose: Set the sequence order for sequential approvals
Parameters:
value: Integer sequence order value
Returns: None - updates internal state and marks as modified
@property approval_cycle(self) -> Optional[ApprovalCycle]
property
Purpose: Get the parent ApprovalCycle object this assignment belongs to
Returns: ApprovalCycle instance or None if no cycle UID is set
@property first_activity_date(self) -> Optional[datetime]
property
Purpose: Get the date when the approver first interacted with this assignment
Returns: datetime object of first activity or None if no activity yet
@first_activity_date.setter first_activity_date(self, value: datetime) -> None
property
Purpose: Set the date of first activity on this assignment
Parameters:
value: datetime object representing first activity time
Returns: None - stores as ISO format string and marks as modified
@property decision(self) -> Optional[str]
property
Purpose: Get the approver's decision (e.g., 'approved', 'rejected')
Returns: String decision value or None if no decision made
@decision.setter decision(self, value: str) -> None
property
Purpose: Set the approver's decision
Parameters:
value: String decision value
Returns: None - updates internal state and marks as modified
@property decision_date(self) -> Optional[datetime]
property
Purpose: Get the date when the decision was made
Returns: datetime object of decision or None if no decision made
@decision_date.setter decision_date(self, value: datetime) -> None
property
Purpose: Set the date when the decision was made
Parameters:
value: datetime object representing decision time
Returns: None - stores as ISO format string and marks as modified
@property decision_comments(self) -> str
property
Purpose: Get comments provided by the approver with their decision
Returns: String comments, defaults to empty string
@decision_comments.setter decision_comments(self, value: str) -> None
property
Purpose: Set comments on the decision
Parameters:
value: String comments text
Returns: None - updates internal state and marks as modified
@property removal_date(self) -> Optional[datetime]
property
Purpose: Get the date when the approver was removed from this assignment
Returns: datetime object of removal or None if not removed
@removal_date.setter removal_date(self, value: datetime) -> None
property
Purpose: Set the date when the approver was removed
Parameters:
value: datetime object representing removal time
Returns: None - stores as ISO format string and marks as modified
@property removal_reason(self) -> Optional[str]
property
Purpose: Get the reason why the approver was removed
Returns: String removal reason or None if not removed
@removal_reason.setter removal_reason(self, value: str) -> None
property
Purpose: Set the reason for approver removal
Parameters:
value: String reason text
Returns: None - updates internal state and marks as modified
save(self) -> bool
Purpose: Persist changes to the database, creating node if it doesn't exist or updating if it does
Returns: Boolean indicating success (True) or failure (False) of save operation
_update_cycle_status(self) -> None
Purpose: Internal method to update the parent approval cycle status after assignment changes
Returns: None - triggers status update on parent cycle
@classmethod create(cls, approval_cycle_uid: str, approver: Union[DocUser, str], sequence_order: int = 0) -> Optional['ApproverAssignment']
Purpose: Create a new approver assignment with proper database node and relationships, handling sequential workflow initialization
Parameters:
approval_cycle_uid: UID of the parent approval cycleapprover: DocUser object or string UID of the user to assignsequence_order: Order in approval sequence for sequential workflows, defaults to 0
Returns: New ApproverAssignment instance if successful, None if creation failed
@classmethod get_assignments_for_approver(cls, approver_uid: str) -> List['ApproverAssignment']
Purpose: Retrieve all assignments for a specific approver, ordered by assignment date descending
Parameters:
approver_uid: UID of the approver user
Returns: List of ApproverAssignment instances for the approver, empty list if none found or error occurs
Attributes
| Name | Type | Description | Scope |
|---|---|---|---|
_data |
Dict[str, Any] | Internal dictionary storing all assignment properties including status, dates, decisions, and relationships | instance |
_modified |
bool | Flag indicating whether the assignment has been modified since last save, inherited from BaseModel | instance |
uid |
str | Unique identifier for this assignment, stored in _data['UID'], inherited from BaseModel | instance |
Dependencies
logginguuidtypingdatetimeCDocsCDocs.configCDocs.db.schema_managerCDocs.models.user_extensionsCDocs.models.workflow_baseCDocs.models.document
Required Imports
from typing import Dict, List, Any, Optional, Union
from datetime import datetime
from CDocs import db
from CDocs.db.schema_manager import NodeLabels, RelTypes
from CDocs.models.user_extensions import DocUser
from CDocs.models.workflow_base import AssignmentBase, WorkflowStatus, AssignmentStatus
from CDocs.models import BaseModel, register_model
Usage Example
# Create a new approver assignment
approval_cycle_uid = 'cycle-123'
approver_uid = 'user-456'
assignment = ApproverAssignment.create(
approval_cycle_uid=approval_cycle_uid,
approver=approver_uid,
sequence_order=1
)
# Load existing assignment
existing = ApproverAssignment(uid='assignment-789')
# Update assignment status and decision
existing.status = AssignmentStatus.IN_PROGRESS.value
existing.decision = 'approved'
existing.decision_date = datetime.now()
existing.decision_comments = 'Looks good to me'
existing.save()
# Get all assignments for an approver
user_assignments = ApproverAssignment.get_assignments_for_approver('user-456')
# Access related approval cycle
cycle = assignment.approval_cycle
# Check assignment properties
if assignment.status == AssignmentStatus.PENDING.value:
print(f'Approver {assignment.approver_uid} has not yet acted')
print(f'Sequence order: {assignment.sequence_order}')
Best Practices
- Always use the create() class method to instantiate new assignments rather than direct instantiation to ensure proper database relationships
- Call save() after modifying any properties to persist changes to the database
- Status changes automatically track first_activity_date when transitioning from PENDING
- For sequential approvals, sequence_order determines the order of approval; only the current sequence approver should be IN_PROGRESS
- The class maintains backward compatibility with 'assignedDate' field by mapping it to 'assigned_at'
- When setting decision, also set decision_date and optionally decision_comments for complete audit trail
- Use removal_date and removal_reason when removing an approver to maintain audit history
- The _update_cycle_status() method should be called after status changes to keep parent cycle in sync
- Properties use lazy loading for related objects (approval_cycle) to avoid circular dependencies
- Date properties accept datetime objects but store as ISO format strings for database compatibility
- Check if assignment exists in database before calling save() - the method handles both create and update scenarios
- For sequential workflows, the create() method automatically sets the first approver to IN_PROGRESS and updates cycle status
Tags
Similar Components
AI-powered semantic similarity - components with related functionality:
-
class ApproverAssignment_v1 95.0% similar
-
class ApproverAssignment_v1 94.6% similar
-
class AssignmentBase 75.2% similar
-
class ReviewerAssignment 74.3% similar
-
class ReviewerAssignment_v1 74.1% similar