feat: restore source parity and harden agent runtime
This commit is contained in:
@@ -0,0 +1,730 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
流程进度聚合服务
|
||||
负责聚合流程执行路径、节点状态、处理人信息等
|
||||
"""
|
||||
import logging
|
||||
from dataclasses import dataclass, field, asdict
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class HandlerInfo:
|
||||
"""处理人信息"""
|
||||
user_id: str
|
||||
user_name: str
|
||||
status: str # pending/approved/rejected/transferred/delegated/waiting
|
||||
action: str = '' # approve/reject/transfer/delegate/add_sign
|
||||
comment: str = ''
|
||||
handled_at: str = ''
|
||||
signature_file_id: str = '' # 签名文件ID
|
||||
|
||||
def to_dict(self):
|
||||
return asdict(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ExtraAction:
|
||||
"""额外操作(加签、转交等)"""
|
||||
type: str # add_sign/transfer/delegate/return
|
||||
from_user_id: str = ''
|
||||
from_user_name: str = ''
|
||||
to_user_id: str = ''
|
||||
to_user_name: str = ''
|
||||
sign_type: str = '' # before/after/parallel(加签类型)
|
||||
status: str = ''
|
||||
time: str = ''
|
||||
comment: str = '' # 操作说明/驳回原因
|
||||
|
||||
def to_dict(self):
|
||||
return asdict(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ProgressNode:
|
||||
"""进度节点"""
|
||||
id: str
|
||||
name: str
|
||||
type: str # start/approval/handle/copy/condition/parallel/delay/notify/service/subflow/end
|
||||
status: str # completed/active/pending/skipped/rejected
|
||||
completed_at: str = ''
|
||||
handlers: List[HandlerInfo] = field(default_factory=list)
|
||||
extra_actions: List[ExtraAction] = field(default_factory=list)
|
||||
# 条件分支信息
|
||||
condition_result: str = '' # 条件分支选中的分支名称
|
||||
# 并行分支信息
|
||||
branches: List[Dict] = field(default_factory=list)
|
||||
# 延时节点信息
|
||||
delay_until: str = '' # 延时到期时间
|
||||
|
||||
def to_dict(self):
|
||||
result = {
|
||||
'id': self.id,
|
||||
'name': self.name,
|
||||
'type': self.type,
|
||||
'status': self.status,
|
||||
'completed_at': self.completed_at,
|
||||
'handlers': [h.to_dict() for h in self.handlers],
|
||||
'extra_actions': [a.to_dict() for a in self.extra_actions],
|
||||
}
|
||||
if self.condition_result:
|
||||
result['condition_result'] = self.condition_result
|
||||
if self.branches:
|
||||
result['branches'] = self.branches
|
||||
if self.delay_until:
|
||||
result['delay_until'] = self.delay_until
|
||||
return result
|
||||
|
||||
|
||||
@dataclass
|
||||
class ReturnRecord:
|
||||
"""驳回记录"""
|
||||
from_node_id: str
|
||||
from_node_name: str
|
||||
to_node_id: str
|
||||
to_node_name: str
|
||||
operator_name: str
|
||||
reason: str
|
||||
time: str
|
||||
|
||||
def to_dict(self):
|
||||
return asdict(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class FlowProgress:
|
||||
"""流程进度"""
|
||||
instance_id: str
|
||||
instance_status: str
|
||||
current_node_id: str
|
||||
nodes: List[ProgressNode] = field(default_factory=list)
|
||||
returns: List[ReturnRecord] = field(default_factory=list)
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
'instance_id': self.instance_id,
|
||||
'instance_status': self.instance_status,
|
||||
'current_node_id': self.current_node_id,
|
||||
'nodes': [n.to_dict() for n in self.nodes],
|
||||
'returns': [r.to_dict() for r in self.returns],
|
||||
}
|
||||
|
||||
|
||||
class FlowProgressService:
|
||||
"""流程进度服务"""
|
||||
|
||||
@staticmethod
|
||||
async def get_progress(db: AsyncSession, instance_id: str) -> Optional[Dict]:
|
||||
"""
|
||||
获取流程实例的执行进度
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
instance_id: 流程实例ID
|
||||
|
||||
Returns:
|
||||
FlowProgress 字典
|
||||
"""
|
||||
from online_dev.workflow.model import WorkflowInstance, WorkflowDefinition, WorkflowTask, WorkflowLog
|
||||
from core.user.model import User
|
||||
|
||||
# 获取实例
|
||||
stmt = select(WorkflowInstance).where(
|
||||
WorkflowInstance.id == instance_id,
|
||||
WorkflowInstance.is_deleted == False,
|
||||
)
|
||||
result = await db.execute(stmt)
|
||||
instance = result.scalar_one_or_none()
|
||||
|
||||
if not instance:
|
||||
return None
|
||||
|
||||
# 获取流程定义
|
||||
stmt = select(WorkflowDefinition).where(WorkflowDefinition.id == instance.workflow_id)
|
||||
result = await db.execute(stmt)
|
||||
workflow = result.scalar_one_or_none()
|
||||
|
||||
if not workflow or not workflow.flow_definition:
|
||||
return None
|
||||
|
||||
flow_definition = workflow.flow_definition
|
||||
|
||||
# 获取所有日志
|
||||
stmt = select(WorkflowLog).where(
|
||||
WorkflowLog.instance_id == instance_id,
|
||||
WorkflowLog.is_deleted == False,
|
||||
).order_by(WorkflowLog.sys_create_datetime)
|
||||
result = await db.execute(stmt)
|
||||
logs = list(result.scalars().all())
|
||||
|
||||
# 获取所有任务
|
||||
stmt = select(WorkflowTask).where(
|
||||
WorkflowTask.instance_id == instance_id,
|
||||
WorkflowTask.is_deleted == False,
|
||||
).order_by(WorkflowTask.sys_create_datetime)
|
||||
result = await db.execute(stmt)
|
||||
tasks = list(result.scalars().all())
|
||||
|
||||
# 获取用户信息
|
||||
user_ids = set()
|
||||
if instance.initiator_id:
|
||||
user_ids.add(instance.initiator_id)
|
||||
for log in logs:
|
||||
if log.operator_id:
|
||||
user_ids.add(log.operator_id)
|
||||
for task in tasks:
|
||||
if task.assignee_id:
|
||||
user_ids.add(task.assignee_id)
|
||||
if task.transferred_to_id:
|
||||
user_ids.add(task.transferred_to_id)
|
||||
|
||||
users_map = {}
|
||||
if user_ids:
|
||||
stmt = select(User).where(User.id.in_(list(user_ids)))
|
||||
result = await db.execute(stmt)
|
||||
for user in result.scalars().all():
|
||||
users_map[str(user.id)] = user
|
||||
|
||||
# 加载表单数据(用于解析表单字段类型的审批人)
|
||||
from online_dev.workflow.engine.utils import FormDataUtils
|
||||
form_data = {}
|
||||
if instance.form_code and instance.form_data_id:
|
||||
try:
|
||||
form_data = await FormDataUtils.load_form_data(db, instance.form_code, instance.form_data_id)
|
||||
except Exception as e:
|
||||
logger.warning(f"加载表单数据失败: {e}")
|
||||
|
||||
# 构建进度
|
||||
progress = FlowProgress(
|
||||
instance_id=str(instance.id),
|
||||
instance_status=instance.status,
|
||||
current_node_id=instance.current_node_id or '',
|
||||
)
|
||||
|
||||
# 构建日志索引(按节点ID分组)
|
||||
logs_by_node = {}
|
||||
for log in logs:
|
||||
node_id = log.node_id or 'start'
|
||||
if node_id not in logs_by_node:
|
||||
logs_by_node[node_id] = []
|
||||
logs_by_node[node_id].append(log)
|
||||
|
||||
# 构建任务索引(按节点ID分组)
|
||||
tasks_by_node = {}
|
||||
for task in tasks:
|
||||
node_id = task.node_id
|
||||
if node_id not in tasks_by_node:
|
||||
tasks_by_node[node_id] = []
|
||||
tasks_by_node[node_id].append(task)
|
||||
|
||||
# 确定每个节点的状态
|
||||
executed_nodes = set()
|
||||
for log in logs:
|
||||
if log.node_id:
|
||||
executed_nodes.add(log.node_id)
|
||||
# 有任务的节点也算已触达
|
||||
touched_nodes = executed_nodes | set(tasks_by_node.keys())
|
||||
|
||||
# 解析流程定义,提取节点列表(根据表单数据评估条件分支,展示完整审批路径)
|
||||
nodes_list = FlowProgressService._extract_nodes(flow_definition, form_data)
|
||||
|
||||
# 提取驳回记录
|
||||
progress.returns = FlowProgressService._extract_returns(logs, users_map, flow_definition)
|
||||
|
||||
# 处理每个节点
|
||||
for node_info in nodes_list:
|
||||
node_id = node_info['id']
|
||||
node_type = node_info['type']
|
||||
node_name = node_info['name']
|
||||
node_config = node_info.get('config', {})
|
||||
|
||||
# 确定节点状态
|
||||
status = FlowProgressService._determine_node_status(
|
||||
node_id=node_id,
|
||||
node_type=node_type,
|
||||
instance=instance,
|
||||
executed_nodes=executed_nodes,
|
||||
logs_by_node=logs_by_node,
|
||||
tasks_by_node=tasks_by_node,
|
||||
)
|
||||
|
||||
progress_node = ProgressNode(
|
||||
id=node_id,
|
||||
name=node_name,
|
||||
type=node_type,
|
||||
status=status,
|
||||
)
|
||||
|
||||
# 开始节点显示发起人
|
||||
if node_type == 'start':
|
||||
initiator = users_map.get(str(instance.initiator_id)) if instance.initiator_id else None
|
||||
progress_node.handlers = [HandlerInfo(
|
||||
user_id=str(instance.initiator_id) if instance.initiator_id else '',
|
||||
user_name=initiator.name if initiator else '',
|
||||
status='completed',
|
||||
action='start',
|
||||
handled_at=instance.sys_create_datetime.strftime('%Y-%m-%d %H:%M:%S') if instance.sys_create_datetime else '',
|
||||
)]
|
||||
|
||||
# 获取节点的处理人信息
|
||||
if node_type in ['approval', 'handle', 'copy']:
|
||||
if status in ['completed', 'active', 'rejected']:
|
||||
progress_node.handlers = FlowProgressService._get_handlers(
|
||||
node_id, tasks_by_node, logs_by_node, users_map, status
|
||||
)
|
||||
progress_node.extra_actions = FlowProgressService._get_extra_actions(
|
||||
node_id, tasks_by_node, logs_by_node, users_map, flow_definition
|
||||
)
|
||||
elif status == 'pending':
|
||||
progress_node.handlers = await FlowProgressService._get_pending_handlers(
|
||||
db, node_config, instance, form_data, users_map
|
||||
)
|
||||
|
||||
# 获取完成时间
|
||||
if status == 'completed':
|
||||
progress_node.completed_at = FlowProgressService._get_completed_time(
|
||||
node_id, logs_by_node
|
||||
)
|
||||
|
||||
# 延时节点信息
|
||||
if node_type == 'delay' and status == 'active':
|
||||
if instance.delay_node_id == node_id and instance.delay_until:
|
||||
progress_node.delay_until = instance.delay_until.strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
# 条件分支信息
|
||||
if node_type == 'condition' and node_id in executed_nodes:
|
||||
progress_node.condition_result = FlowProgressService._get_condition_result(
|
||||
node_id, node_info, logs_by_node
|
||||
)
|
||||
|
||||
progress.nodes.append(progress_node)
|
||||
|
||||
return progress.to_dict()
|
||||
|
||||
@staticmethod
|
||||
def _extract_nodes(flow_definition: Dict, form_data: Dict = None) -> List[Dict]:
|
||||
"""
|
||||
从流程定义中提取线性化的节点列表
|
||||
|
||||
对于条件分支:
|
||||
- 根据表单数据评估条件,展示将要执行的分支
|
||||
- 这样可以在审批前就显示完整的审批路径
|
||||
"""
|
||||
from online_dev.workflow.engine.condition_evaluator import condition_evaluator
|
||||
|
||||
nodes = []
|
||||
visited = set()
|
||||
form = form_data or {}
|
||||
root = flow_definition.get('nodes')
|
||||
if not root:
|
||||
return nodes
|
||||
|
||||
def _evaluate_branch(branch: Dict) -> bool:
|
||||
"""评估分支条件是否满足"""
|
||||
config = branch.get('config', {})
|
||||
# 默认分支总是作为兜底
|
||||
if config.get('isDefault'):
|
||||
return False # 默认分支不主动匹配,只在没有其他分支匹配时使用
|
||||
|
||||
# 获取条件组(字段名是 groups,与 condition_handler.py 保持一致)
|
||||
groups = config.get('groups', [])
|
||||
if not groups:
|
||||
# 没有条件组,视为不匹配
|
||||
return False
|
||||
|
||||
# 评估条件组(组之间是 OR 关系)
|
||||
return condition_evaluator.evaluate_groups(groups, form)
|
||||
|
||||
def _find_matching_branch(branches: List[Dict]) -> Dict:
|
||||
"""找到匹配的分支,如果没有匹配则返回默认分支"""
|
||||
default_branch = None
|
||||
for branch in branches:
|
||||
config = branch.get('config', {})
|
||||
if config.get('isDefault'):
|
||||
default_branch = branch
|
||||
elif _evaluate_branch(branch):
|
||||
return branch
|
||||
# 没有匹配的分支,返回默认分支
|
||||
return default_branch
|
||||
|
||||
def traverse(node: Dict, depth: int = 0, in_branch: bool = False):
|
||||
if not node or not isinstance(node, dict):
|
||||
return
|
||||
|
||||
node_id = node.get('id', '')
|
||||
node_type = node.get('type', '')
|
||||
node_name = node.get('name', '')
|
||||
|
||||
if node_id and node_id not in visited:
|
||||
visited.add(node_id)
|
||||
nodes.append({
|
||||
'id': node_id,
|
||||
'type': node_type,
|
||||
'name': node_name,
|
||||
'depth': depth,
|
||||
'config': node.get('config', {}),
|
||||
'branches': node.get('branches', []),
|
||||
'in_branch': in_branch,
|
||||
})
|
||||
|
||||
if node_type == 'condition':
|
||||
branches = node.get('branches', [])
|
||||
# 根据表单数据评估条件,找到将要执行的分支
|
||||
matching_branch = _find_matching_branch(branches)
|
||||
|
||||
if matching_branch:
|
||||
branch_children = matching_branch.get('children')
|
||||
if branch_children:
|
||||
traverse(branch_children, depth, in_branch=in_branch)
|
||||
|
||||
if node_type == 'parallel':
|
||||
branches = node.get('branches', [])
|
||||
for branch in branches:
|
||||
branch_children = branch.get('children')
|
||||
if branch_children:
|
||||
traverse(branch_children, depth + 1, in_branch=True)
|
||||
|
||||
children = node.get('children')
|
||||
if children:
|
||||
traverse(children, depth, in_branch=in_branch)
|
||||
|
||||
traverse(root)
|
||||
return nodes
|
||||
|
||||
@staticmethod
|
||||
def _determine_node_status(
|
||||
node_id: str,
|
||||
node_type: str,
|
||||
instance,
|
||||
executed_nodes: set,
|
||||
logs_by_node: Dict,
|
||||
tasks_by_node: Dict,
|
||||
) -> str:
|
||||
"""确定节点状态"""
|
||||
if node_type == 'start':
|
||||
return 'completed'
|
||||
|
||||
if instance.status == 'approved':
|
||||
if node_type == 'end':
|
||||
return 'completed'
|
||||
if node_id in executed_nodes:
|
||||
return 'completed'
|
||||
return 'skipped'
|
||||
|
||||
if instance.status == 'rejected':
|
||||
node_logs = logs_by_node.get(node_id, [])
|
||||
for log in node_logs:
|
||||
if log.action == 'reject':
|
||||
return 'rejected'
|
||||
if node_id in executed_nodes:
|
||||
return 'completed'
|
||||
return 'skipped'
|
||||
|
||||
if instance.status == 'canceled':
|
||||
if node_id in executed_nodes:
|
||||
return 'completed'
|
||||
return 'skipped'
|
||||
|
||||
if node_id == instance.current_node_id:
|
||||
# 检查该节点是否还有未完成的任务
|
||||
node_tasks = tasks_by_node.get(node_id, [])
|
||||
has_pending = any(t.status in ['pending', 'waiting'] for t in node_tasks)
|
||||
if has_pending:
|
||||
return 'active'
|
||||
# 所有任务已完成但 current_node_id 未更新(条件分支推进 bug 的遗留数据)
|
||||
if node_tasks and not has_pending:
|
||||
return 'completed'
|
||||
return 'active'
|
||||
|
||||
if node_id in executed_nodes:
|
||||
return 'completed'
|
||||
|
||||
node_tasks = tasks_by_node.get(node_id, [])
|
||||
for task in node_tasks:
|
||||
if task.status in ['pending', 'waiting']:
|
||||
return 'active'
|
||||
|
||||
return 'pending'
|
||||
|
||||
@staticmethod
|
||||
def _get_handlers(
|
||||
node_id: str,
|
||||
tasks_by_node: Dict,
|
||||
logs_by_node: Dict,
|
||||
users_map: Dict,
|
||||
node_status: str = '',
|
||||
) -> List[HandlerInfo]:
|
||||
"""获取节点的处理人信息"""
|
||||
handlers = []
|
||||
tasks = tasks_by_node.get(node_id, [])
|
||||
|
||||
before_sign_tasks = []
|
||||
normal_tasks = []
|
||||
after_sign_tasks = []
|
||||
delegate_tasks = []
|
||||
transfer_tasks = []
|
||||
parallel_sign_tasks = []
|
||||
|
||||
for task in tasks:
|
||||
if task.sign_type == 'before':
|
||||
before_sign_tasks.append(task)
|
||||
elif task.sign_type == 'after':
|
||||
after_sign_tasks.append(task)
|
||||
elif task.sign_type == 'delegate':
|
||||
delegate_tasks.append(task)
|
||||
elif task.sign_type == 'transfer':
|
||||
transfer_tasks.append(task)
|
||||
elif task.sign_type == 'parallel':
|
||||
parallel_sign_tasks.append(task)
|
||||
else:
|
||||
normal_tasks.append(task)
|
||||
|
||||
# 节点已完成时,将未处理的任务标记为已跳过(或签场景)
|
||||
node_completed = node_status == 'completed'
|
||||
|
||||
def task_to_handler(task, sign_label: str = '') -> HandlerInfo:
|
||||
user = users_map.get(str(task.assignee_id)) if task.assignee_id else None
|
||||
task_status = task.status
|
||||
# 抄送任务显示"已抄送"而非"已跳过"
|
||||
if task.task_type == 'copy':
|
||||
task_status = 'copied'
|
||||
elif task_status == 'canceled' or (node_completed and task_status in ['pending', 'waiting']):
|
||||
task_status = 'skipped'
|
||||
handler = HandlerInfo(
|
||||
user_id=str(task.assignee_id) if task.assignee_id else '',
|
||||
user_name=user.name if user else '',
|
||||
status=task_status,
|
||||
)
|
||||
|
||||
if sign_label and task_status == 'pending':
|
||||
handler.user_name = f"[{sign_label}] {handler.user_name}"
|
||||
|
||||
if task.status == 'approved':
|
||||
handler.action = 'approve'
|
||||
handler.comment = task.comment or ''
|
||||
handler.handled_at = task.handled_at.strftime('%Y-%m-%d %H:%M:%S') if task.handled_at else ''
|
||||
handler.signature_file_id = task.signature_file_id or ''
|
||||
elif task.status == 'rejected':
|
||||
handler.action = 'reject'
|
||||
handler.comment = task.comment or ''
|
||||
handler.handled_at = task.handled_at.strftime('%Y-%m-%d %H:%M:%S') if task.handled_at else ''
|
||||
elif task.status == 'returned':
|
||||
handler.action = 'return'
|
||||
handler.comment = task.comment or ''
|
||||
handler.handled_at = task.handled_at.strftime('%Y-%m-%d %H:%M:%S') if task.handled_at else ''
|
||||
elif task.status == 'transferred':
|
||||
handler.action = 'transfer'
|
||||
handler.comment = task.comment or ''
|
||||
handler.handled_at = task.handled_at.strftime('%Y-%m-%d %H:%M:%S') if task.handled_at else ''
|
||||
elif task.status == 'delegated':
|
||||
handler.action = 'delegate'
|
||||
handler.comment = task.comment or ''
|
||||
handler.handled_at = task.handled_at.strftime('%Y-%m-%d %H:%M:%S') if task.handled_at else ''
|
||||
elif task.status == 'handled':
|
||||
handler.action = 'handle'
|
||||
handler.comment = task.comment or ''
|
||||
handler.handled_at = task.handled_at.strftime('%Y-%m-%d %H:%M:%S') if task.handled_at else ''
|
||||
|
||||
return handler
|
||||
|
||||
for task in before_sign_tasks:
|
||||
handlers.append(task_to_handler(task, '前加签'))
|
||||
for task in normal_tasks:
|
||||
handlers.append(task_to_handler(task))
|
||||
for task in delegate_tasks:
|
||||
handlers.append(task_to_handler(task, '委托'))
|
||||
for task in transfer_tasks:
|
||||
handlers.append(task_to_handler(task, '转交'))
|
||||
for task in after_sign_tasks:
|
||||
handlers.append(task_to_handler(task, '后加签'))
|
||||
for task in parallel_sign_tasks:
|
||||
handlers.append(task_to_handler(task, '并行加签'))
|
||||
|
||||
return handlers
|
||||
|
||||
@staticmethod
|
||||
async def _get_pending_handlers(
|
||||
db: AsyncSession,
|
||||
node_config: Dict,
|
||||
instance,
|
||||
form_data: Dict,
|
||||
users_map: Dict,
|
||||
) -> List[HandlerInfo]:
|
||||
"""获取待执行节点的预设处理人(通过 AssigneeResolver 解析具体用户)"""
|
||||
from online_dev.workflow.engine.assignee_resolver import assignee_resolver
|
||||
from core.user.model import User
|
||||
|
||||
handlers = []
|
||||
assignee_type = node_config.get('assigneeType', 'user')
|
||||
|
||||
try:
|
||||
user_ids = await assignee_resolver.resolve(db, node_config, instance, form_data)
|
||||
except Exception as e:
|
||||
logger.warning(f"解析待执行节点审批人失败: {e}")
|
||||
user_ids = []
|
||||
|
||||
if user_ids:
|
||||
# 查询不在 users_map 中的用户
|
||||
missing_ids = [uid for uid in user_ids if uid not in users_map]
|
||||
if missing_ids:
|
||||
stmt = select(User).where(User.id.in_(missing_ids))
|
||||
result = await db.execute(stmt)
|
||||
for user in result.scalars().all():
|
||||
users_map[str(user.id)] = user
|
||||
|
||||
for uid in user_ids:
|
||||
user = users_map.get(uid)
|
||||
handlers.append(HandlerInfo(
|
||||
user_id=uid,
|
||||
user_name=user.name if user else '',
|
||||
status='pending',
|
||||
))
|
||||
else:
|
||||
# 解析失败时的降级显示
|
||||
fallback_labels = {
|
||||
'role': '角色',
|
||||
'department': '部门',
|
||||
'superior': '上级主管',
|
||||
'manager': '直属经理',
|
||||
'form_field': '表单字段',
|
||||
'initiator': '发起人',
|
||||
}
|
||||
if assignee_type in fallback_labels:
|
||||
handlers.append(HandlerInfo(
|
||||
user_id='',
|
||||
user_name=f'[{fallback_labels[assignee_type]}]',
|
||||
status='pending',
|
||||
))
|
||||
|
||||
return handlers
|
||||
|
||||
@staticmethod
|
||||
def _get_extra_actions(
|
||||
node_id: str,
|
||||
tasks_by_node: Dict,
|
||||
logs_by_node: Dict,
|
||||
users_map: Dict,
|
||||
flow_definition: Dict = None,
|
||||
) -> List[ExtraAction]:
|
||||
"""获取节点的额外操作"""
|
||||
actions = []
|
||||
logs = logs_by_node.get(node_id, [])
|
||||
|
||||
for log in logs:
|
||||
extra_data = log.extra_data or {}
|
||||
time_str = log.sys_create_datetime.strftime('%Y-%m-%d %H:%M:%S') if log.sys_create_datetime else ''
|
||||
operator = users_map.get(str(log.operator_id)) if log.operator_id else None
|
||||
|
||||
if log.action == 'transfer':
|
||||
action = ExtraAction(
|
||||
type='transfer',
|
||||
from_user_id=str(log.operator_id) if log.operator_id else '',
|
||||
from_user_name=operator.name if operator else '',
|
||||
to_user_id=str(extra_data.get('to_user_id', '')),
|
||||
to_user_name=extra_data.get('to_user_name', ''),
|
||||
time=time_str,
|
||||
comment=log.comment or '',
|
||||
)
|
||||
actions.append(action)
|
||||
elif log.action == 'delegate':
|
||||
action = ExtraAction(
|
||||
type='delegate',
|
||||
from_user_id=str(log.operator_id) if log.operator_id else '',
|
||||
from_user_name=operator.name if operator else '',
|
||||
to_user_id=str(extra_data.get('to_user_id', '')),
|
||||
to_user_name=extra_data.get('to_user_name', ''),
|
||||
time=time_str,
|
||||
comment=log.comment or '',
|
||||
)
|
||||
actions.append(action)
|
||||
elif log.action == 'add_sign':
|
||||
action = ExtraAction(
|
||||
type='add_sign',
|
||||
from_user_id=str(log.operator_id) if log.operator_id else '',
|
||||
from_user_name=operator.name if operator else '',
|
||||
to_user_id=str(extra_data.get('to_user_id', '')),
|
||||
to_user_name=extra_data.get('to_user_name', ''),
|
||||
sign_type=extra_data.get('sign_type', ''),
|
||||
time=time_str,
|
||||
comment=log.comment or '',
|
||||
)
|
||||
actions.append(action)
|
||||
elif log.action == 'return':
|
||||
# 解析驳回目标节点名称
|
||||
to_name = extra_data.get('return_to_name', '')
|
||||
if not to_name:
|
||||
return_to = extra_data.get('return_to', 'initiator')
|
||||
if return_to == 'initiator' or not return_to:
|
||||
to_name = '发起人'
|
||||
elif flow_definition:
|
||||
from online_dev.workflow.engine.utils import FlowUtils
|
||||
target_node = FlowUtils.find_node_by_id(flow_definition, return_to)
|
||||
to_name = target_node.get('name', return_to) if target_node else return_to
|
||||
else:
|
||||
to_name = return_to
|
||||
action = ExtraAction(
|
||||
type='return',
|
||||
from_user_id=str(log.operator_id) if log.operator_id else '',
|
||||
from_user_name=operator.name if operator else '',
|
||||
to_user_id='',
|
||||
to_user_name=to_name,
|
||||
time=time_str,
|
||||
comment=log.comment or '',
|
||||
)
|
||||
actions.append(action)
|
||||
|
||||
return actions
|
||||
|
||||
@staticmethod
|
||||
def _get_completed_time(node_id: str, logs_by_node: Dict) -> str:
|
||||
"""获取节点完成时间"""
|
||||
logs = logs_by_node.get(node_id, [])
|
||||
for log in reversed(logs):
|
||||
if log.action in ['approve', 'handle', 'start', 'parallel_complete', 'delay_complete', 'notify',
|
||||
'service_call', 'subflow_complete', 'condition', 'copy']:
|
||||
return log.sys_create_datetime.strftime('%Y-%m-%d %H:%M:%S') if log.sys_create_datetime else ''
|
||||
return ''
|
||||
|
||||
@staticmethod
|
||||
def _get_condition_result(node_id: str, node_info: Dict, logs_by_node: Dict) -> str:
|
||||
"""获取条件分支的选择结果"""
|
||||
return ''
|
||||
|
||||
@staticmethod
|
||||
def _extract_returns(logs: List, users_map: Dict, flow_definition: Dict = None) -> List[ReturnRecord]:
|
||||
"""提取驳回记录"""
|
||||
from online_dev.workflow.engine.utils import FlowUtils
|
||||
|
||||
returns = []
|
||||
for log in logs:
|
||||
if log.action == 'return':
|
||||
extra_data = log.extra_data or {}
|
||||
operator = users_map.get(str(log.operator_id)) if log.operator_id else None
|
||||
# 解析驳回目标节点名称
|
||||
to_node_name = extra_data.get('return_to_name', '')
|
||||
if not to_node_name:
|
||||
return_to = extra_data.get('return_to', 'initiator')
|
||||
if return_to == 'initiator' or not return_to:
|
||||
to_node_name = '发起人'
|
||||
elif flow_definition:
|
||||
target_node = FlowUtils.find_node_by_id(flow_definition, return_to)
|
||||
to_node_name = target_node.get('name', return_to) if target_node else return_to
|
||||
else:
|
||||
to_node_name = return_to
|
||||
record = ReturnRecord(
|
||||
from_node_id=log.node_id or '',
|
||||
from_node_name=log.node_name or '',
|
||||
to_node_id=extra_data.get('return_to', ''),
|
||||
to_node_name=to_node_name,
|
||||
operator_name=operator.name if operator else '',
|
||||
reason=log.comment or '',
|
||||
time=log.sys_create_datetime.strftime('%Y-%m-%d %H:%M:%S') if log.sys_create_datetime else '',
|
||||
)
|
||||
returns.append(record)
|
||||
return returns
|
||||
Reference in New Issue
Block a user