645 lines
24 KiB
Python
645 lines
24 KiB
Python
#!/usr/bin/env python
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
工作流引擎(异步版本)
|
||
负责流程的执行、推进、状态管理等核心逻辑
|
||
"""
|
||
import logging
|
||
from datetime import datetime
|
||
from typing import Dict, Optional, Any
|
||
|
||
from sqlalchemy import select, update
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
||
from online_dev.workflow.engine.assignee_resolver import assignee_resolver
|
||
from online_dev.workflow.engine.base import (
|
||
ExecutionContext,
|
||
TaskAction,
|
||
generate_instance_no,
|
||
)
|
||
from online_dev.workflow.engine.condition_evaluator import condition_evaluator
|
||
from online_dev.workflow.engine.utils import FlowUtils, FormDataUtils
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class WorkflowEngine:
|
||
"""
|
||
工作流引擎
|
||
|
||
职责:
|
||
1. 流程启动 - 创建实例,执行第一个节点
|
||
2. 任务处理 - 审批/拒绝/转交
|
||
3. 流程推进 - 根据条件判断下一节点
|
||
4. 状态管理 - 更新实例和任务状态
|
||
"""
|
||
|
||
def __init__(self):
|
||
self.condition_evaluator = condition_evaluator
|
||
self.assignee_resolver = assignee_resolver
|
||
self._handlers = {}
|
||
|
||
def _get_handler(self, node_type: str):
|
||
"""获取节点处理器(懒加载)"""
|
||
if node_type not in self._handlers:
|
||
from online_dev.workflow.engine.handlers import (
|
||
ApprovalHandler,
|
||
HandleHandler,
|
||
CopyHandler,
|
||
ConditionHandler,
|
||
ParallelHandler,
|
||
DelayNodeHandler,
|
||
NotifyHandler,
|
||
ServiceHandler,
|
||
SubflowHandler,
|
||
DataUpdateHandler,
|
||
)
|
||
handler_map = {
|
||
'approval': ApprovalHandler,
|
||
'handle': HandleHandler,
|
||
'copy': CopyHandler,
|
||
'condition': ConditionHandler,
|
||
'parallel': ParallelHandler,
|
||
'delay': DelayNodeHandler,
|
||
'notify': NotifyHandler,
|
||
'service': ServiceHandler,
|
||
'subflow': SubflowHandler,
|
||
'data_update': DataUpdateHandler,
|
||
}
|
||
handler_class = handler_map.get(node_type)
|
||
if handler_class:
|
||
self._handlers[node_type] = handler_class(self)
|
||
return self._handlers.get(node_type)
|
||
|
||
# ==================== 流程启动 ====================
|
||
|
||
async def start(
|
||
self,
|
||
db: AsyncSession,
|
||
workflow: Any,
|
||
title: str,
|
||
form_data: Dict,
|
||
initiator_id: str,
|
||
) -> Any:
|
||
"""
|
||
启动流程
|
||
|
||
Args:
|
||
db: 数据库会话
|
||
workflow: 流程定义
|
||
title: 流程标题
|
||
form_data: 表单数据
|
||
initiator_id: 发起人ID
|
||
|
||
Returns:
|
||
WorkflowInstance: 流程实例
|
||
"""
|
||
from online_dev.workflow.model import WorkflowInstance, WorkflowLog
|
||
|
||
# 缓存 workflow 属性,避免 save_form_data 中 db.commit() 导致 session 对象过期
|
||
_workflow_id = str(workflow.id)
|
||
_form_code = workflow.form_code
|
||
_flow_definition = workflow.flow_definition
|
||
|
||
# 1. 保存表单数据
|
||
form_data_id = await FormDataUtils.save_form_data(db, _form_code, form_data)
|
||
|
||
# 2. 重新加载展平后的表单数据用于条件评估等引擎逻辑
|
||
# 前端传入的 form_data 可能是 {main: {...}, sub_tables: {...}} 嵌套结构,
|
||
# 而引擎的条件评估器需要展平的 {field: value} 结构
|
||
flat_form_data = await FormDataUtils.load_form_data(db, _form_code, form_data_id)
|
||
|
||
# 3. 创建流程实例
|
||
instance = WorkflowInstance(
|
||
workflow_id=_workflow_id,
|
||
instance_no=generate_instance_no(),
|
||
title=title,
|
||
status='pending',
|
||
initiator_id=initiator_id,
|
||
form_code=_form_code,
|
||
form_data_id=form_data_id,
|
||
current_node_id='start',
|
||
current_node_name='开始',
|
||
)
|
||
db.add(instance)
|
||
await db.flush()
|
||
|
||
# 记录启动日志
|
||
log = WorkflowLog(
|
||
instance_id=str(instance.id),
|
||
node_id='start',
|
||
node_name='开始',
|
||
action='start',
|
||
operator_id=initiator_id,
|
||
comment='发起流程',
|
||
)
|
||
db.add(log)
|
||
await db.flush()
|
||
|
||
# 创建执行上下文
|
||
context = ExecutionContext(
|
||
instance=instance,
|
||
form_data=flat_form_data or form_data,
|
||
current_user_id=initiator_id,
|
||
flow_definition=_flow_definition,
|
||
db=db,
|
||
)
|
||
|
||
# 执行开始节点,推进到下一节点
|
||
start_node = context.flow_definition.get('nodes')
|
||
if start_node and start_node.get('type') == 'start':
|
||
await self._advance_to_next(context, start_node)
|
||
else:
|
||
logger.error("找不到开始节点!")
|
||
|
||
await db.refresh(instance)
|
||
return instance
|
||
|
||
async def restart_instance(self, db: AsyncSession, instance: Any, user_id: str) -> Any:
|
||
"""
|
||
重新启动流程实例(驳回修改后重新提交)
|
||
"""
|
||
from online_dev.workflow.model import WorkflowDefinition
|
||
|
||
stmt = select(WorkflowDefinition).where(WorkflowDefinition.id == instance.workflow_id)
|
||
result = await db.execute(stmt)
|
||
workflow = result.scalar_one_or_none()
|
||
|
||
if not workflow:
|
||
raise ValueError("流程定义不存在")
|
||
|
||
flow_definition = workflow.flow_definition
|
||
if not flow_definition:
|
||
raise ValueError("流程定义为空")
|
||
|
||
# 加载表单数据
|
||
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"restart_instance: 加载表单数据失败: {e}")
|
||
|
||
context = ExecutionContext(
|
||
instance=instance,
|
||
form_data=form_data,
|
||
flow_definition=flow_definition,
|
||
current_user_id=user_id,
|
||
db=db,
|
||
)
|
||
|
||
start_node = flow_definition.get('nodes', {})
|
||
if start_node and start_node.get('type') == 'start':
|
||
instance.status = 'pending'
|
||
db.add(instance)
|
||
await db.flush()
|
||
await self._advance_to_next(context, start_node)
|
||
|
||
await db.refresh(instance)
|
||
return instance
|
||
|
||
# ==================== 任务处理 ====================
|
||
|
||
async def complete_task(
|
||
self,
|
||
db: AsyncSession,
|
||
task: Any,
|
||
action: TaskAction,
|
||
comment: str,
|
||
user_id: str,
|
||
form_data: Optional[Dict] = None,
|
||
return_to: str = None,
|
||
) -> Any:
|
||
"""
|
||
完成任务
|
||
|
||
Args:
|
||
db: 数据库会话
|
||
task: 任务
|
||
action: 操作(approve/reject/return)
|
||
comment: 审批意见
|
||
user_id: 操作用户ID
|
||
form_data: 表单数据(可能被修改)
|
||
return_to: 驳回目标(当 action=RETURN 时使用)
|
||
|
||
Returns:
|
||
WorkflowTask: 更新后的任务
|
||
"""
|
||
from online_dev.workflow.model import WorkflowLog, WorkflowInstance, WorkflowDefinition
|
||
|
||
# 获取实例
|
||
stmt = select(WorkflowInstance).where(WorkflowInstance.id == task.instance_id)
|
||
result = await db.execute(stmt)
|
||
instance = result.scalar_one_or_none()
|
||
|
||
if not instance:
|
||
raise ValueError("流程实例不存在")
|
||
|
||
# 获取流程定义
|
||
stmt = select(WorkflowDefinition).where(WorkflowDefinition.id == instance.workflow_id)
|
||
result = await db.execute(stmt)
|
||
workflow = result.scalar_one_or_none()
|
||
|
||
if not workflow:
|
||
raise ValueError("流程定义不存在")
|
||
|
||
# 更新任务状态
|
||
status_map = {
|
||
TaskAction.APPROVE: 'approved',
|
||
TaskAction.REJECT: 'rejected',
|
||
TaskAction.RETURN: 'returned',
|
||
TaskAction.DELEGATE: 'delegated',
|
||
}
|
||
task.status = status_map.get(action, action.value + 'd')
|
||
task.comment = comment
|
||
task.handled_at = datetime.now()
|
||
db.add(task)
|
||
await db.flush()
|
||
|
||
# 记录日志
|
||
log_extra_data = {}
|
||
# 如果有签名,记录到日志中
|
||
if task.signature_file_id:
|
||
log_extra_data['signature_file_id'] = task.signature_file_id
|
||
if action == TaskAction.RETURN:
|
||
log_extra_data['return_to'] = return_to or 'initiator'
|
||
# 解析实际驳回目标节点名称
|
||
if return_to == 'initiator' or not return_to:
|
||
log_extra_data['return_to_name'] = '发起人'
|
||
elif return_to == 'previous':
|
||
prev_node_id = await self._find_previous_node_id(
|
||
ExecutionContext(
|
||
instance=instance,
|
||
form_data={},
|
||
current_user_id=user_id,
|
||
flow_definition=workflow.flow_definition,
|
||
db=db,
|
||
),
|
||
task.node_id,
|
||
)
|
||
if prev_node_id:
|
||
prev_node = FlowUtils.find_node_by_id(workflow.flow_definition, prev_node_id)
|
||
log_extra_data['return_to_name'] = prev_node.get('name', prev_node_id) if prev_node else prev_node_id
|
||
else:
|
||
log_extra_data['return_to_name'] = '发起人'
|
||
else:
|
||
target_node = FlowUtils.find_node_by_id(workflow.flow_definition, return_to)
|
||
log_extra_data['return_to_name'] = target_node.get('name', return_to) if target_node else return_to
|
||
log = WorkflowLog(
|
||
instance_id=str(instance.id),
|
||
node_id=task.node_id,
|
||
node_name=task.node_name,
|
||
action=action.value,
|
||
operator_id=user_id,
|
||
comment=comment,
|
||
extra_data=log_extra_data,
|
||
)
|
||
db.add(log)
|
||
await db.flush()
|
||
|
||
# 获取表单数据
|
||
if form_data is None:
|
||
form_data = await FormDataUtils.load_form_data(db, instance.form_code, instance.form_data_id)
|
||
|
||
# 创建执行上下文
|
||
context = ExecutionContext(
|
||
instance=instance,
|
||
form_data=form_data,
|
||
current_user_id=user_id,
|
||
flow_definition=workflow.flow_definition,
|
||
db=db,
|
||
)
|
||
|
||
# 获取当前节点配置
|
||
current_node = FlowUtils.find_node_by_id(context.flow_definition, task.node_id)
|
||
|
||
# 完成该任务对应的钉钉待办
|
||
if action in (TaskAction.APPROVE, TaskAction.REJECT, TaskAction.RETURN):
|
||
try:
|
||
from core.message.service import NotifyService
|
||
await NotifyService.complete_dingtalk_todo(db, "workflow_task", str(task.id))
|
||
except Exception as e:
|
||
logger.warning(f"完成钉钉待办失败: {e}")
|
||
|
||
# 根据操作推进流程
|
||
if action == TaskAction.REJECT:
|
||
# 发送发起人通知(拒绝)
|
||
try:
|
||
from online_dev.workflow.engine.handlers.notification_service import WorkflowNotificationService
|
||
await WorkflowNotificationService.send_initiator_notification(context, current_node or {}, 'reject')
|
||
except Exception as e:
|
||
logger.error(f"发送发起人通知失败: {e}")
|
||
await self._end_instance(context, 'rejected')
|
||
elif action == TaskAction.APPROVE:
|
||
# 发送发起人通知(通过)
|
||
try:
|
||
from online_dev.workflow.engine.handlers.notification_service import WorkflowNotificationService
|
||
await WorkflowNotificationService.send_initiator_notification(context, current_node or {}, 'approve')
|
||
except Exception as e:
|
||
logger.error(f"发送发起人通知失败: {e}")
|
||
await self._handle_approval(context, task)
|
||
elif action == TaskAction.RETURN:
|
||
await self._handle_return(context, task, return_to)
|
||
|
||
return task
|
||
|
||
async def transfer_task(
|
||
self,
|
||
db: AsyncSession,
|
||
task: Any,
|
||
to_user: Any,
|
||
comment: str,
|
||
user_id: str,
|
||
) -> Any:
|
||
"""转交任务"""
|
||
from online_dev.workflow.model import WorkflowTask, WorkflowLog
|
||
|
||
task.status = 'transferred'
|
||
task.comment = comment
|
||
task.handled_at = datetime.now()
|
||
task.transferred_to_id = str(to_user.id)
|
||
db.add(task)
|
||
await db.flush()
|
||
|
||
# 完成原任务对应的钉钉待办
|
||
try:
|
||
from core.message.service import NotifyService
|
||
await NotifyService.complete_dingtalk_todo(db, "workflow_task", str(task.id))
|
||
except Exception as e:
|
||
logger.warning(f"转交-清理钉钉待办失败 task={task.id}: {e}")
|
||
|
||
new_task = WorkflowTask(
|
||
instance_id=task.instance_id,
|
||
node_id=task.node_id,
|
||
node_name=task.node_name,
|
||
task_type=task.task_type,
|
||
status='pending',
|
||
assignee_id=str(to_user.id),
|
||
parent_task_id=str(task.id),
|
||
sign_type='transfer',
|
||
timeout_at=task.timeout_at,
|
||
timeout_action=task.timeout_action or '',
|
||
timeout_notified=False,
|
||
)
|
||
db.add(new_task)
|
||
await db.flush()
|
||
|
||
try:
|
||
from online_dev.workflow.engine.handlers.notification_service import (
|
||
WorkflowNotificationService,
|
||
)
|
||
|
||
instance, flow_definition = await WorkflowNotificationService.load_instance_and_flow(
|
||
db, new_task
|
||
)
|
||
if instance:
|
||
await WorkflowNotificationService.notify_pending_task(
|
||
db=db,
|
||
task=new_task,
|
||
instance=instance,
|
||
flow_definition=flow_definition,
|
||
operator_id=user_id,
|
||
)
|
||
except Exception as e:
|
||
logger.warning(f'转办后发送任务通知失败 task={new_task.id}: {e}')
|
||
|
||
log = WorkflowLog(
|
||
instance_id=task.instance_id,
|
||
node_id=task.node_id,
|
||
node_name=task.node_name,
|
||
action='transfer',
|
||
operator_id=user_id,
|
||
comment=f"转交给 {to_user.name or to_user.username}: {comment}",
|
||
extra_data={'to_user_id': str(to_user.id)},
|
||
)
|
||
db.add(log)
|
||
await db.flush()
|
||
|
||
return new_task
|
||
|
||
# ==================== 流程推进 ====================
|
||
|
||
async def _handle_approval(self, context: ExecutionContext, task: Any) -> None:
|
||
"""处理审批通过后的流程推进"""
|
||
handler = self._get_handler('approval')
|
||
if handler:
|
||
await handler.handle_approval(context, task)
|
||
|
||
async def _handle_handle_completion(self, context: ExecutionContext, task: Any) -> None:
|
||
"""处理办理任务完成后的流程推进"""
|
||
handler = self._get_handler('handle')
|
||
if handler:
|
||
await handler.handle_completion(context, task)
|
||
|
||
async def _handle_return(self, context: ExecutionContext, task: Any, return_to: str = None) -> None:
|
||
"""处理驳回操作"""
|
||
from online_dev.workflow.model import WorkflowTask
|
||
|
||
# 先收集待取消任务的ID,用于清理钉钉待办
|
||
pending_stmt = select(WorkflowTask.id).where(
|
||
WorkflowTask.instance_id == str(context.instance.id),
|
||
WorkflowTask.node_id == task.node_id,
|
||
WorkflowTask.status == 'pending',
|
||
)
|
||
pending_result = await context.db.execute(pending_stmt)
|
||
canceled_task_ids = [str(row[0]) for row in pending_result.all()]
|
||
|
||
# 取消当前节点的所有待处理任务
|
||
stmt = update(WorkflowTask).where(
|
||
WorkflowTask.instance_id == str(context.instance.id),
|
||
WorkflowTask.node_id == task.node_id,
|
||
WorkflowTask.status == 'pending',
|
||
).values(status='canceled')
|
||
await context.db.execute(stmt)
|
||
|
||
# 清理被取消任务的钉钉待办
|
||
for tid in canceled_task_ids:
|
||
try:
|
||
from core.message.service import NotifyService
|
||
await NotifyService.complete_dingtalk_todo(context.db, "workflow_task", tid)
|
||
except Exception as e:
|
||
logger.warning(f"驳回-清理钉钉待办失败 task={tid}: {e}")
|
||
|
||
if return_to == 'initiator' or not return_to:
|
||
await self._return_to_initiator(context, task)
|
||
elif return_to == 'previous':
|
||
previous_node_id = await self._find_previous_node_id(context, task.node_id)
|
||
if previous_node_id:
|
||
await self._return_to_node(context, task, previous_node_id)
|
||
else:
|
||
await self._return_to_initiator(context, task)
|
||
else:
|
||
await self._return_to_node(context, task, return_to)
|
||
|
||
async def _return_to_initiator(self, context: ExecutionContext, task: Any) -> None:
|
||
"""驳回给发起人"""
|
||
from online_dev.workflow.model import WorkflowTask
|
||
|
||
instance = context.instance
|
||
|
||
instance.current_node_id = 'start'
|
||
instance.current_node_name = '待修改'
|
||
context.db.add(instance)
|
||
await context.db.flush()
|
||
|
||
# 创建发起人的修改任务
|
||
revise_task = WorkflowTask(
|
||
instance_id=str(instance.id),
|
||
node_id='start',
|
||
node_name='待修改',
|
||
task_type='revise',
|
||
status='pending',
|
||
assignee_id=instance.initiator_id,
|
||
)
|
||
context.db.add(revise_task)
|
||
await context.db.flush()
|
||
|
||
|
||
async def _return_to_node(self, context: ExecutionContext, task: Any, node_id: str) -> None:
|
||
"""驳回到指定节点"""
|
||
target_node = FlowUtils.find_node_by_id(context.flow_definition, node_id)
|
||
if not target_node:
|
||
logger.warning(f"找不到驳回目标节点: {node_id}")
|
||
await self._return_to_initiator(context, task)
|
||
return
|
||
|
||
context.instance.current_node_id = node_id
|
||
context.instance.current_node_name = target_node.get('name', '')
|
||
context.db.add(context.instance)
|
||
await context.db.flush()
|
||
|
||
node_type = target_node.get('type')
|
||
handler = self._get_handler(node_type)
|
||
if handler:
|
||
await handler.execute(context, target_node)
|
||
|
||
|
||
async def _find_previous_node_id(self, context: ExecutionContext, current_node_id: str) -> Optional[str]:
|
||
"""查找上一个节点的ID"""
|
||
from online_dev.workflow.model import WorkflowLog
|
||
|
||
stmt = select(WorkflowLog).where(
|
||
WorkflowLog.instance_id == str(context.instance.id),
|
||
WorkflowLog.action.in_(['approve', 'return']),
|
||
WorkflowLog.node_id != current_node_id,
|
||
).order_by(WorkflowLog.sys_create_datetime.desc()).limit(1)
|
||
|
||
result = await context.db.execute(stmt)
|
||
previous_log = result.scalar_one_or_none()
|
||
|
||
if previous_log and previous_log.node_id:
|
||
return previous_log.node_id
|
||
|
||
return FlowUtils.find_parent_node_id(context.flow_definition, current_node_id)
|
||
|
||
async def _advance_to_next(self, context: ExecutionContext, current_node: Dict) -> None:
|
||
"""推进到下一节点"""
|
||
logger.info(f"_advance_to_next - 当前节点: {current_node.get('id')}, {current_node.get('type')}")
|
||
|
||
next_node = self._get_next_node(context, current_node)
|
||
logger.info(f"_advance_to_next - 下一节点: {next_node}")
|
||
|
||
if not next_node:
|
||
current_id = current_node.get('id', '')
|
||
|
||
# 查找最内层的分支归属(正确处理嵌套:并行内嵌条件、条件内嵌并行等)
|
||
branch_info = FlowUtils.find_innermost_branch_for_node(
|
||
context.flow_definition, current_id
|
||
)
|
||
if branch_info:
|
||
branch_type, branch_node, branch = branch_info
|
||
if branch_type == 'parallel' and branch is not None:
|
||
handler = self._get_handler('parallel')
|
||
if handler:
|
||
await handler.mark_branch_complete(context, branch_node, branch)
|
||
return
|
||
elif branch_type == 'condition':
|
||
logger.info(f"条件分支内节点 {current_id} 完成,从条件节点 {branch_node.get('id')} 继续推进")
|
||
await self._advance_to_next(context, branch_node)
|
||
return
|
||
|
||
logger.warning(f"找不到下一节点,当前节点: {current_id}")
|
||
return
|
||
|
||
node_type = next_node.get('type')
|
||
|
||
if node_type == 'end':
|
||
await self._end_instance(context, 'approved')
|
||
else:
|
||
handler = self._get_handler(node_type)
|
||
if handler:
|
||
await handler.execute(context, next_node)
|
||
else:
|
||
logger.warning(f"未知节点类型: {node_type},自动跳过")
|
||
await self._advance_to_next(context, next_node)
|
||
|
||
def _get_next_node(self, context: ExecutionContext, current_node: Dict) -> Optional[Dict]:
|
||
"""获取下一节点"""
|
||
children = current_node.get('children')
|
||
if children:
|
||
return children
|
||
return None
|
||
|
||
# ==================== 流程结束 ====================
|
||
|
||
async def _end_instance(self, context: ExecutionContext, status: str, current_node: Dict = None) -> None:
|
||
"""结束流程实例"""
|
||
from online_dev.workflow.model import WorkflowTask
|
||
|
||
instance = context.instance
|
||
instance.status = status
|
||
instance.completed_at = datetime.now()
|
||
instance.current_node_id = 'end'
|
||
instance.current_node_name = '结束'
|
||
context.db.add(instance)
|
||
await context.db.flush()
|
||
|
||
# 查找即将被取消的待处理任务ID(用于清理钉钉待办)
|
||
pending_task_stmt = select(WorkflowTask.id).where(
|
||
WorkflowTask.instance_id == str(instance.id),
|
||
WorkflowTask.status == 'pending',
|
||
WorkflowTask.task_type != 'copy',
|
||
)
|
||
pending_result = await context.db.execute(pending_task_stmt)
|
||
pending_task_ids = [str(row[0]) for row in pending_result.all()]
|
||
|
||
# 取消所有待处理任务(排除抄送任务,抄送任务保留供用户查阅)
|
||
stmt = update(WorkflowTask).where(
|
||
WorkflowTask.instance_id == str(instance.id),
|
||
WorkflowTask.status == 'pending',
|
||
WorkflowTask.task_type != 'copy',
|
||
).values(status='canceled')
|
||
await context.db.execute(stmt)
|
||
|
||
# 删除/完成被取消任务对应的钉钉待办
|
||
for task_id in pending_task_ids:
|
||
try:
|
||
from core.message.service import NotifyService
|
||
if status in ('approved', 'rejected'):
|
||
await NotifyService.complete_dingtalk_todo(context.db, "workflow_task", task_id)
|
||
else:
|
||
await NotifyService.delete_dingtalk_todo(context.db, "workflow_task", task_id)
|
||
except Exception as e:
|
||
logger.warning(f"清理钉钉待办失败 task={task_id}: {e}")
|
||
|
||
# 发送流程完成通知给发起人
|
||
try:
|
||
from online_dev.workflow.engine.handlers.notification_service import WorkflowNotificationService
|
||
await WorkflowNotificationService.send_instance_complete_notification(context, status)
|
||
except Exception as e:
|
||
logger.error(f"发送流程完成通知失败: {e}")
|
||
|
||
# 如果是子流程,通知父流程继续
|
||
if instance.parent_instance_id:
|
||
logger.info(f"子流程结束,通知父流程: {instance.parent_instance_id}")
|
||
await self._resume_parent_after_subflow(context.db, instance)
|
||
|
||
async def _resume_parent_after_subflow(self, db: AsyncSession, sub_instance) -> None:
|
||
"""子流程完成后恢复父流程"""
|
||
handler = self._get_handler('subflow')
|
||
if handler:
|
||
await handler.resume_parent(db, sub_instance)
|
||
|
||
# 全局引擎实例
|
||
workflow_engine = WorkflowEngine()
|