Files

450 lines
16 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
工作流引擎辅助工具
包含节点查找、表单数据处理等通用方法
"""
import logging
from typing import Dict, Optional, Tuple
logger = logging.getLogger(__name__)
class FlowUtils:
"""流程工具类"""
@staticmethod
def find_node_by_id(flow_def: Dict, node_id: str) -> Optional[Dict]:
"""
在流程定义中查找节点
Args:
flow_def: 流程定义
node_id: 节点ID
Returns:
节点配置字典,未找到返回 None
"""
nodes = flow_def.get('nodes')
if not nodes:
return None
return FlowUtils._find_node_recursive(nodes, node_id)
@staticmethod
def _find_node_recursive(node: Dict, target_id: str) -> Optional[Dict]:
"""递归查找节点"""
if node.get('id') == target_id:
return node
# 在子节点中查找
children = node.get('children')
if children:
result = FlowUtils._find_node_recursive(children, target_id)
if result:
return result
# 在分支中查找
branches = node.get('branches', [])
for branch in branches:
branch_children = branch.get('children')
if branch_children:
result = FlowUtils._find_node_recursive(branch_children, target_id)
if result:
return result
return None
@staticmethod
def find_parallel_branch_for_node(flow_def: Dict, node_id: str) -> Optional[Tuple[Dict, Dict]]:
"""
查找节点所在的并行分支
Args:
flow_def: 流程定义
node_id: 节点ID
Returns:
(parallel_node, branch) 元组,如果节点不在并行分支内则返回 None
"""
nodes = flow_def.get('nodes')
if not nodes:
return None
return FlowUtils._find_parallel_branch_recursive(nodes, node_id, None, None)
@staticmethod
def _find_parallel_branch_recursive(
node: Dict,
target_id: str,
current_parallel: Optional[Dict],
current_branch: Optional[Dict]
) -> Optional[Tuple[Dict, Dict]]:
"""递归查找节点所在的并行分支"""
if not node or not isinstance(node, dict):
return None
# 检查当前节点
if node.get('id') == target_id:
if current_parallel and current_branch:
return (current_parallel, current_branch)
return None
node_type = node.get('type')
# 如果是并行节点,在其分支中查找
if node_type == 'parallel':
branches = node.get('branches', [])
for branch in branches:
branch_children = branch.get('children')
if branch_children:
result = FlowUtils._find_parallel_branch_recursive(
branch_children, target_id, node, branch
)
if result:
return result
# 检查子节点
children = node.get('children')
if children:
result = FlowUtils._find_parallel_branch_recursive(
children, target_id, current_parallel, current_branch
)
if result:
return result
# 检查条件分支
if node_type == 'condition':
branches = node.get('branches', [])
for branch in branches:
branch_children = branch.get('children')
if branch_children:
result = FlowUtils._find_parallel_branch_recursive(
branch_children, target_id, current_parallel, current_branch
)
if result:
return result
return None
@staticmethod
def find_condition_branch_for_node(flow_def: Dict, node_id: str) -> Optional[Dict]:
"""
查找节点所在的条件分支,返回条件节点
Args:
flow_def: 流程定义
node_id: 节点ID
Returns:
条件节点字典,如果节点不在条件分支内则返回 None
"""
nodes = flow_def.get('nodes')
if not nodes:
return None
return FlowUtils._find_condition_branch_recursive(nodes, node_id, None)
@staticmethod
def _find_condition_branch_recursive(
node: Dict,
target_id: str,
current_condition: Optional[Dict],
) -> Optional[Dict]:
"""递归查找节点所在的条件分支"""
if not node or not isinstance(node, dict):
return None
if node.get('id') == target_id:
return current_condition
node_type = node.get('type')
# 如果是条件节点,在其分支中查找(标记当前条件节点)
if node_type == 'condition':
branches = node.get('branches', [])
for branch in branches:
branch_children = branch.get('children')
if branch_children:
result = FlowUtils._find_condition_branch_recursive(
branch_children, target_id, node
)
if result is not None:
return result
# 如果是并行节点,在其分支中查找(保持当前条件上下文)
if node_type == 'parallel':
branches = node.get('branches', [])
for branch in branches:
branch_children = branch.get('children')
if branch_children:
result = FlowUtils._find_condition_branch_recursive(
branch_children, target_id, current_condition
)
if result is not None:
return result
# 检查子节点
children = node.get('children')
if children:
result = FlowUtils._find_condition_branch_recursive(
children, target_id, current_condition
)
if result is not None:
return result
return None
@staticmethod
def find_innermost_branch_for_node(
flow_def: Dict, node_id: str
) -> Optional[Tuple[str, Dict, Optional[Dict]]]:
"""
查找节点所在的最内层分支归属(可能是条件分支或并行分支)。
用于 _advance_to_next 中正确处理嵌套场景(如并行内嵌条件、条件内嵌并行)。
Returns:
('condition', condition_node, None) 或
('parallel', parallel_node, branch) 或
None
"""
nodes = flow_def.get('nodes')
if not nodes:
return None
return FlowUtils._find_innermost_branch_recursive(nodes, node_id, None)
@staticmethod
def _find_innermost_branch_recursive(
node: Dict,
target_id: str,
innermost: Optional[Tuple[str, Dict, Optional[Dict]]],
) -> Optional[Tuple[str, Dict, Optional[Dict]]]:
if not node or not isinstance(node, dict):
return None
if node.get('id') == target_id:
return innermost
node_type = node.get('type')
if node_type == 'condition':
for branch in node.get('branches', []):
branch_children = branch.get('children')
if branch_children:
result = FlowUtils._find_innermost_branch_recursive(
branch_children, target_id, ('condition', node, None)
)
if result is not None:
return result
if node_type == 'parallel':
for branch in node.get('branches', []):
branch_children = branch.get('children')
if branch_children:
result = FlowUtils._find_innermost_branch_recursive(
branch_children, target_id, ('parallel', node, branch)
)
if result is not None:
return result
children = node.get('children')
if children:
result = FlowUtils._find_innermost_branch_recursive(
children, target_id, innermost
)
if result is not None:
return result
return None
@staticmethod
def find_parent_node_id(flow_definition: Dict, target_node_id: str) -> Optional[str]:
"""
从流程定义中查找目标节点的父节点ID(仅审批/办理节点)
"""
nodes = flow_definition.get('nodes', {})
def find_parent(node: Dict, parent_id: Optional[str] = None) -> Optional[str]:
if not node or not isinstance(node, dict):
return None
node_id = node.get('id')
node_type = node.get('type')
# 检查子节点
children = node.get('children')
if children:
if isinstance(children, dict):
if children.get('id') == target_node_id:
if node_type in ('approval', 'handle'):
return node_id
return parent_id
result = find_parent(children, node_id if node_type in ('approval', 'handle') else parent_id)
if result:
return result
# 检查分支
branches = node.get('branches', [])
for branch in branches:
branch_children = branch.get('children')
if branch_children:
if isinstance(branch_children, dict) and branch_children.get('id') == target_node_id:
if node_type in ('approval', 'handle'):
return node_id
return parent_id
result = find_parent(branch_children, node_id if node_type in ('approval', 'handle') else parent_id)
if result:
return result
return None
return find_parent(nodes)
class FormDataUtils:
"""表单数据工具类"""
@staticmethod
async def save_form_data(db, form_code: str, form_data: Dict) -> str:
"""
保存表单数据
Args:
db: 数据库会话
form_code: 表单编码
form_data: 表单数据(可能是扁平结构或 {main, sub_tables} 结构)
Returns:
str: 数据ID
"""
from online_dev.form_data_manager.service import FormDataService
# 创建表单数据服务
service = await FormDataService.create_service(db, form_code)
# 判断 form_data 是否已经是结构化数据
if "main" in form_data:
# 已经是 {main, sub_tables} 结构,直接使用
data = form_data
else:
# 扁平结构,需要包装
data = {"main": form_data, "sub_tables": {}}
# 保存数据
try:
result = await service.create(db, data)
return result.get("id", "")
except Exception as e:
logger.error(f"保存表单数据失败: {e}")
await db.rollback()
raise ValueError(f"保存表单数据失败: {e}")
@staticmethod
async def load_form_data(db, form_code: str, form_data_id: str) -> Dict:
"""
加载表单数据
Args:
db: 数据库会话
form_code: 表单编码
form_data_id: 表单数据ID
Returns:
Dict: 表单数据(扁平结构,包含关联字段的 _name 后缀)
"""
if not form_code or not form_data_id:
return {}
try:
from online_dev.form_data_manager.service import FormDataService
# 创建表单数据服务
service = await FormDataService.create_service(db, form_code)
# 获取数据
result = await service.get(db, form_data_id)
# 提取主表数据,转为扁平结构
form_data = {}
if result:
# 主表字段
for key, value in result.items():
if key not in ('sub_tables', 'id'):
form_data[key] = value
# 填充关联字段的显示名称(_name 后缀)
if form_data:
# 从 form_config 中提取所有关联字段
relation_fields = {}
form_config = service._form_config
def extract_relation_fields(items):
"""递归提取关联字段"""
if not items:
return
for item in items:
item_type = item.get("type", "")
field = item.get("field", "")
# 容器类型,递归处理
if item_type == "grid":
for col in item.get("columns", []):
extract_relation_fields(col.get("children", []))
elif item_type in ["collapse", "tabs"]:
for panel in item.get("children", []) or item.get("items", []):
extract_relation_fields(panel.get("children", []))
elif item_type == "sub-table":
extract_relation_fields(item.get("children", []))
elif field and item_type in ["user-selector", "dept-selector", "post-selector", "role-selector", "region-selector"]:
# 关联字段类型映射
type_mapping = {
"user-selector": "core_user",
"dept-selector": "core_dept",
"post-selector": "core_post",
"role-selector": "core_role",
"region-selector": "core_region",
}
relation_table = type_mapping.get(item_type)
if relation_table:
relation_fields[field] = {
"display_field": f"{field}_name",
"relation_table": relation_table,
"relation_key": "id",
"display_column": "name",
}
extract_relation_fields(form_config.get("items", []))
if relation_fields:
filled = await service._fill_relation_display_names(db, [form_data], relation_fields)
if filled:
form_data = filled[0]
return form_data
except Exception as e:
logger.error(f"加载表单数据失败: {e}")
return {}
@staticmethod
async def update_form_data(db, form_code: str, form_data_id: str, form_data: Dict) -> None:
"""更新表单数据"""
if not form_code or not form_data_id:
return
try:
from online_dev.form_data_manager.service import FormDataService
# 创建表单数据服务
service = await FormDataService.create_service(db, form_code)
# 构建数据结构
data = {"main": form_data, "sub_tables": {}}
# 更新数据
await service.update(db, form_data_id, data)
except Exception as e:
logger.error(f"更新表单数据失败: {e}")