#!/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}")