263 lines
9.2 KiB
Python
263 lines
9.2 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
子流程节点处理器
|
|
处理子流程的启动和完成
|
|
"""
|
|
import logging
|
|
import uuid
|
|
from typing import Dict, TYPE_CHECKING
|
|
|
|
from sqlalchemy import select
|
|
|
|
from online_dev.workflow.engine.handlers.base_handler import BaseNodeHandler
|
|
|
|
if TYPE_CHECKING:
|
|
from online_dev.workflow.engine.base import ExecutionContext
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class SubflowHandler(BaseNodeHandler):
|
|
"""
|
|
子流程节点处理器
|
|
|
|
支持:
|
|
- 变量传递(全部/选择/无)
|
|
- 等待子流程完成
|
|
- 超时处理
|
|
- 结果回传
|
|
"""
|
|
|
|
async def execute(self, context: 'ExecutionContext', node: Dict) -> None:
|
|
"""
|
|
执行子流程节点:启动子流程
|
|
"""
|
|
from online_dev.workflow.model import WorkflowDefinition, WorkflowInstance
|
|
from online_dev.workflow.engine.base import ExecutionContext as EC
|
|
|
|
node_id = self.get_node_id(node)
|
|
node_name = self.get_node_name(node)
|
|
node_config = self.get_node_config(node)
|
|
|
|
subflow_id = node_config.get('subflowId', '')
|
|
subflow_name = node_config.get('subflowName', '')
|
|
var_pass_mode = node_config.get('varPassMode', 'all')
|
|
selected_vars = node_config.get('selectedVars', [])
|
|
wait_for_completion = node_config.get('waitForCompletion', True)
|
|
|
|
logger.info(f"子流程节点 - 启动子流程: {subflow_name} ({subflow_id})")
|
|
|
|
if not subflow_id:
|
|
logger.warning(f"子流程节点 {node_id} 未配置子流程,自动跳过")
|
|
await self.advance_to_next(context, node)
|
|
return
|
|
|
|
# 获取子流程定义
|
|
stmt = select(WorkflowDefinition).where(
|
|
WorkflowDefinition.id == subflow_id,
|
|
WorkflowDefinition.status == 'published',
|
|
WorkflowDefinition.is_deleted == False,
|
|
)
|
|
result = await context.db.execute(stmt)
|
|
subflow_definition = result.scalar_one_or_none()
|
|
|
|
if not subflow_definition:
|
|
logger.error(f"子流程定义不存在或未发布: {subflow_id}")
|
|
await self.advance_to_next(context, node)
|
|
return
|
|
|
|
# 更新实例当前节点
|
|
await self.update_instance_node(context, node)
|
|
|
|
# 准备传递给子流程的变量
|
|
if var_pass_mode == 'all':
|
|
subflow_data = context.form_data.copy()
|
|
elif var_pass_mode == 'selected':
|
|
subflow_data = {k: v for k, v in context.form_data.items() if k in selected_vars}
|
|
else:
|
|
subflow_data = {}
|
|
|
|
# 生成子流程实例编号
|
|
sub_instance_no = f"SUB-{context.instance.instance_no}-{uuid.uuid4().hex[:6].upper()}"
|
|
|
|
# 获取超时配置
|
|
timeout = self._calculate_timeout(node_config)
|
|
timeout_action = node_config.get('timeoutAction', 'skip')
|
|
|
|
# 创建子流程实例
|
|
sub_instance = WorkflowInstance(
|
|
workflow_id=str(subflow_definition.id),
|
|
instance_no=sub_instance_no,
|
|
title=f"[子流程] {subflow_name} - {context.instance.title}",
|
|
status='pending',
|
|
initiator_id=context.current_user_id or '',
|
|
form_code=subflow_definition.form_code,
|
|
form_data_id=context.instance.form_data_id,
|
|
is_subflow=True,
|
|
parent_instance_id=str(context.instance.id),
|
|
parent_node_id=node_id,
|
|
subflow_timeout=timeout,
|
|
subflow_timeout_action=timeout_action,
|
|
)
|
|
context.db.add(sub_instance)
|
|
await context.db.flush()
|
|
|
|
# 记录日志
|
|
await self.create_log(
|
|
context, node, 'subflow_start',
|
|
comment=f'启动子流程: {subflow_name}',
|
|
extra_data={
|
|
'subflow_id': subflow_id,
|
|
'subflow_name': subflow_name,
|
|
'sub_instance_id': str(sub_instance.id),
|
|
'sub_instance_no': sub_instance_no,
|
|
'var_pass_mode': var_pass_mode,
|
|
},
|
|
)
|
|
|
|
logger.info(f"子流程实例已创建: {sub_instance_no}")
|
|
|
|
# 启动子流程
|
|
sub_flow_def = subflow_definition.flow_definition
|
|
sub_context = EC(
|
|
instance=sub_instance,
|
|
form_data=subflow_data,
|
|
current_user_id=context.current_user_id,
|
|
flow_definition=sub_flow_def,
|
|
db=context.db,
|
|
)
|
|
|
|
# 找到子流程的开始节点并执行
|
|
start_node = sub_flow_def.get('nodes')
|
|
|
|
if start_node and start_node.get('type') == 'start':
|
|
await self.engine._advance_to_next(sub_context, start_node)
|
|
else:
|
|
logger.error(f"子流程没有开始节点: {subflow_id}")
|
|
|
|
# 如果不等待完成,主流程继续推进
|
|
if not wait_for_completion:
|
|
logger.info(f"子流程不等待完成,主流程继续推进")
|
|
await self.advance_to_next(context, node)
|
|
else:
|
|
logger.info(f"主流程等待子流程完成: {sub_instance_no}")
|
|
|
|
def _calculate_timeout(self, node_config: Dict) -> int:
|
|
"""计算超时秒数"""
|
|
timeout_enabled = node_config.get('timeoutEnabled', False)
|
|
timeout_value = node_config.get('timeout', 24)
|
|
timeout_unit = node_config.get('timeoutUnit', 'hour')
|
|
|
|
if not timeout_enabled or not timeout_value:
|
|
return None
|
|
|
|
if timeout_unit == 'minute':
|
|
return timeout_value * 60
|
|
elif timeout_unit == 'hour':
|
|
return timeout_value * 3600
|
|
elif timeout_unit == 'day':
|
|
return timeout_value * 86400
|
|
return timeout_value * 3600
|
|
|
|
async def resume_parent(self, db, sub_instance) -> None:
|
|
"""
|
|
子流程完成后恢复父流程
|
|
"""
|
|
from online_dev.workflow.model import WorkflowLog, WorkflowInstance, WorkflowDefinition
|
|
from online_dev.workflow.engine.base import ExecutionContext as EC
|
|
from online_dev.workflow.engine.utils import FlowUtils, FormDataUtils
|
|
|
|
if not sub_instance.parent_instance_id:
|
|
return
|
|
|
|
parent_node_id = sub_instance.parent_node_id
|
|
if not parent_node_id:
|
|
return
|
|
|
|
# 获取父流程实例
|
|
stmt = select(WorkflowInstance).where(WorkflowInstance.id == sub_instance.parent_instance_id)
|
|
result = await db.execute(stmt)
|
|
parent_instance = result.scalar_one_or_none()
|
|
|
|
if not parent_instance:
|
|
return
|
|
|
|
logger.info(f"子流程完成,恢复父流程: {parent_instance.instance_no}")
|
|
|
|
# 获取父流程定义
|
|
stmt = select(WorkflowDefinition).where(WorkflowDefinition.id == parent_instance.workflow_id)
|
|
result = await db.execute(stmt)
|
|
parent_workflow = result.scalar_one_or_none()
|
|
|
|
if not parent_workflow:
|
|
return
|
|
|
|
parent_flow_def = parent_workflow.flow_definition
|
|
|
|
# 查找父流程等待的节点
|
|
parent_node = FlowUtils.find_node_by_id(parent_flow_def, parent_node_id)
|
|
if not parent_node:
|
|
logger.error(f"父流程节点不存在: {parent_node_id}")
|
|
return
|
|
|
|
# 获取表单数据
|
|
form_data = await FormDataUtils.load_form_data(
|
|
db,
|
|
parent_instance.form_code,
|
|
parent_instance.form_data_id,
|
|
)
|
|
|
|
# 结果回传
|
|
node_config = parent_node.get('config', {})
|
|
result_pass_mode = node_config.get('resultPassMode', 'none')
|
|
result_vars = node_config.get('resultVars', [])
|
|
|
|
if result_pass_mode != 'none':
|
|
sub_form_data = await FormDataUtils.load_form_data(
|
|
db,
|
|
sub_instance.form_code,
|
|
sub_instance.form_data_id,
|
|
)
|
|
if sub_form_data:
|
|
if result_pass_mode == 'all':
|
|
form_data.update(sub_form_data)
|
|
elif result_pass_mode == 'selected':
|
|
for var in result_vars:
|
|
if var in sub_form_data:
|
|
form_data[var] = sub_form_data[var]
|
|
|
|
# 记录日志
|
|
log = WorkflowLog(
|
|
instance_id=str(parent_instance.id),
|
|
node_id=parent_node_id,
|
|
node_name=parent_node.get('name', '子流程'),
|
|
action='subflow_complete',
|
|
operator_id=sub_instance.initiator_id,
|
|
comment=f'子流程完成: {sub_instance.instance_no}, 状态: {sub_instance.status}',
|
|
extra_data={
|
|
'sub_instance_id': str(sub_instance.id),
|
|
'sub_instance_no': sub_instance.instance_no,
|
|
'sub_status': sub_instance.status,
|
|
},
|
|
)
|
|
db.add(log)
|
|
await db.flush()
|
|
|
|
# 构建上下文并推进父流程
|
|
parent_context = EC(
|
|
instance=parent_instance,
|
|
form_data=form_data,
|
|
current_user_id=sub_instance.initiator_id or '',
|
|
flow_definition=parent_flow_def,
|
|
db=db,
|
|
)
|
|
|
|
# 根据子流程结果决定父流程走向
|
|
if sub_instance.status == 'approved':
|
|
await self.engine._advance_to_next(parent_context, parent_node)
|
|
elif sub_instance.status == 'rejected':
|
|
await self.engine._end_instance(parent_context, 'rejected')
|
|
else:
|
|
await self.engine._advance_to_next(parent_context, parent_node)
|