413 lines
14 KiB
Python
413 lines
14 KiB
Python
#!/usr/bin/env python
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
通知服务
|
||
处理流程中各种通知的发送
|
||
集成 core.message 消息服务
|
||
"""
|
||
import logging
|
||
from typing import Any, Dict, List, Optional, TYPE_CHECKING
|
||
|
||
if TYPE_CHECKING:
|
||
from online_dev.workflow.engine.base import ExecutionContext
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
def _resolve_task_notify_channels(node: Dict) -> Optional[List[str]]:
|
||
"""
|
||
解析节点 taskNotify 渠道。
|
||
返回 None 表示明确关闭任务通知。
|
||
"""
|
||
node_config = node.get('config', {}) if node else {}
|
||
task_notify = node_config.get('taskNotify') or {}
|
||
if task_notify.get('enabled') is False:
|
||
return None
|
||
return task_notify.get('channels') or ['site']
|
||
|
||
|
||
class WorkflowNotificationService:
|
||
"""
|
||
工作流通知服务
|
||
|
||
统一处理流程中的各种通知:
|
||
- 任务通知(待审批/待办理/抄送)
|
||
- 发起人通知(通过/拒绝/完成)
|
||
- 超时通知
|
||
- 流程完成通知
|
||
"""
|
||
|
||
@staticmethod
|
||
async def send_task_notification(
|
||
context: 'ExecutionContext',
|
||
task: Any,
|
||
assignee_id: str,
|
||
task_type: str,
|
||
node: Dict,
|
||
) -> None:
|
||
"""
|
||
发送任务通知
|
||
|
||
Args:
|
||
context: 执行上下文
|
||
task: 任务对象
|
||
assignee_id: 处理人ID
|
||
task_type: 任务类型
|
||
node: 节点配置
|
||
"""
|
||
from core.message.service import NotifyService
|
||
|
||
channels = _resolve_task_notify_channels(node)
|
||
if channels is None:
|
||
return
|
||
|
||
type_labels = {
|
||
'approval': '审批',
|
||
'handle': '办理',
|
||
'copy': '抄送',
|
||
}
|
||
type_label = type_labels.get(task_type, '处理')
|
||
|
||
# 获取发起人名称
|
||
initiator_name = '未知'
|
||
if context.instance.initiator_id:
|
||
from core.user.model import User
|
||
from sqlalchemy import select
|
||
stmt = select(User).where(User.id == context.instance.initiator_id)
|
||
result = await context.db.execute(stmt)
|
||
initiator = result.scalar_one_or_none()
|
||
if initiator:
|
||
initiator_name = initiator.name or initiator.username
|
||
|
||
instance_title = context.instance.title
|
||
title = f"【{instance_title}】待{type_label}"
|
||
content = f"{initiator_name} 发起的【{instance_title}】需要您{type_label},请及时处理。"
|
||
|
||
try:
|
||
await NotifyService.send(
|
||
db=context.db,
|
||
recipient_ids=[assignee_id],
|
||
title=title,
|
||
content=content,
|
||
channels=channels,
|
||
msg_type='workflow',
|
||
link_type='workflow_task',
|
||
link_id=str(task.id),
|
||
sender_id=context.current_user_id or None,
|
||
)
|
||
logger.info(f"任务通知已发送: {assignee_id}, 类型: {task_type}, 渠道: {channels}")
|
||
except Exception as e:
|
||
logger.error(f"发送任务通知失败: {e}")
|
||
|
||
@staticmethod
|
||
async def send_initiator_notification(
|
||
context: 'ExecutionContext',
|
||
node: Dict,
|
||
action: str,
|
||
) -> None:
|
||
"""
|
||
发送发起人通知
|
||
|
||
Args:
|
||
context: 执行上下文
|
||
node: 当前节点
|
||
action: 操作类型 (approve/reject/complete)
|
||
"""
|
||
from core.message.service import NotifyService
|
||
|
||
node_config = node.get('config', {}) if node else {}
|
||
initiator_notify = node_config.get('initiatorNotify', {})
|
||
|
||
# 检查是否需要通知
|
||
should_notify = False
|
||
if action == 'approve':
|
||
should_notify = initiator_notify.get('onApprove', False)
|
||
elif action == 'reject':
|
||
should_notify = initiator_notify.get('onReject', True)
|
||
elif action == 'complete':
|
||
should_notify = initiator_notify.get('onComplete', True)
|
||
|
||
if not should_notify:
|
||
return
|
||
|
||
initiator_id = context.instance.initiator_id
|
||
if not initiator_id:
|
||
return
|
||
|
||
# 读取节点配置的通知渠道,默认站内信
|
||
channels = initiator_notify.get('channels') or ['site']
|
||
|
||
node_name = node.get('name', '节点')
|
||
instance_title = context.instance.title
|
||
|
||
action_labels = {
|
||
'approve': '已通过',
|
||
'reject': '已拒绝',
|
||
'complete': '已完成',
|
||
}
|
||
action_label = action_labels.get(action, '已处理')
|
||
|
||
title = f"【{instance_title}】{action_label}"
|
||
content = f"您发起的【{instance_title}】在【{node_name}】{action_label}。"
|
||
|
||
if action == 'reject':
|
||
content += "请查看详情了解原因。"
|
||
|
||
try:
|
||
await NotifyService.send(
|
||
db=context.db,
|
||
recipient_ids=[str(initiator_id)],
|
||
title=title,
|
||
content=content,
|
||
channels=channels,
|
||
msg_type='workflow',
|
||
link_type='workflow_instance',
|
||
link_id=str(context.instance.id),
|
||
sender_id=context.current_user_id or None,
|
||
)
|
||
logger.info(f"发起人通知已发送: {initiator_id}, 操作: {action}, 渠道: {channels}")
|
||
except Exception as e:
|
||
logger.error(f"发送发起人通知失败: {e}")
|
||
|
||
@staticmethod
|
||
async def send_instance_complete_notification(
|
||
context: 'ExecutionContext',
|
||
status: str,
|
||
) -> None:
|
||
"""
|
||
发送流程完成通知给发起人
|
||
|
||
Args:
|
||
context: 执行上下文
|
||
status: 流程状态 (approved/rejected)
|
||
"""
|
||
from core.message.service import NotifyService
|
||
|
||
initiator_id = context.instance.initiator_id
|
||
if not initiator_id:
|
||
return
|
||
|
||
instance_title = context.instance.title
|
||
|
||
if status == 'approved':
|
||
title = f"【{instance_title}】已通过"
|
||
content = f"您发起的【{instance_title}】已全部审批通过。"
|
||
elif status == 'rejected':
|
||
title = f"【{instance_title}】已被拒绝"
|
||
content = f"您发起的【{instance_title}】已被拒绝,请查看详情了解原因。"
|
||
else:
|
||
title = f"【{instance_title}】已结束"
|
||
content = f"您发起的【{instance_title}】已结束。"
|
||
|
||
# 流程完成通知使用站内信(无节点配置可读取)
|
||
try:
|
||
await NotifyService.send(
|
||
db=context.db,
|
||
recipient_ids=[str(initiator_id)],
|
||
title=title,
|
||
content=content,
|
||
channels=['site'],
|
||
msg_type='workflow',
|
||
link_type='workflow_instance',
|
||
link_id=str(context.instance.id),
|
||
)
|
||
logger.info(f"流程完成通知已发送: {initiator_id}, 状态: {status}")
|
||
except Exception as e:
|
||
logger.error(f"发送流程完成通知失败: {e}")
|
||
|
||
@staticmethod
|
||
async def send_timeout_notification(
|
||
db,
|
||
task,
|
||
instance,
|
||
assignee,
|
||
) -> None:
|
||
"""
|
||
发送任务超时通知
|
||
|
||
Args:
|
||
db: 数据库会话
|
||
task: 任务对象
|
||
instance: 流程实例
|
||
assignee: 处理人
|
||
"""
|
||
from core.message.service import NotifyService
|
||
|
||
if not assignee:
|
||
return
|
||
|
||
# 获取发起人名称
|
||
initiator_name = ''
|
||
if instance.initiator_id:
|
||
from core.user.model import User
|
||
from sqlalchemy import select
|
||
stmt = select(User).where(User.id == instance.initiator_id)
|
||
result = await db.execute(stmt)
|
||
initiator = result.scalar_one_or_none()
|
||
if initiator:
|
||
initiator_name = initiator.name or initiator.username
|
||
|
||
title = '任务超时提醒'
|
||
content = (
|
||
f'您有一个待办任务已超时,请尽快处理。\n'
|
||
f'流程标题:{instance.title}\n'
|
||
f'当前节点:{task.node_name}\n'
|
||
f'发起人:{initiator_name}'
|
||
)
|
||
|
||
# 超时通知使用站内信
|
||
try:
|
||
await NotifyService.send(
|
||
db=db,
|
||
recipient_ids=[str(assignee.id)],
|
||
title=title,
|
||
content=content,
|
||
channels=['site'],
|
||
msg_type='workflow',
|
||
link_type='workflow_task',
|
||
link_id=str(task.id),
|
||
sender_id=str(instance.initiator_id) if instance.initiator_id else None,
|
||
)
|
||
logger.info(f"超时通知已发送: task={task.id}, assignee={assignee.id}")
|
||
except Exception as e:
|
||
logger.error(f"发送超时通知失败: {e}")
|
||
|
||
@staticmethod
|
||
async def load_instance_and_flow(db, task: Any):
|
||
"""加载任务关联的实例与流程定义。"""
|
||
from online_dev.workflow.model import WorkflowDefinition, WorkflowInstance
|
||
from sqlalchemy import select
|
||
|
||
stmt = select(WorkflowInstance).where(
|
||
WorkflowInstance.id == task.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,
|
||
WorkflowDefinition.is_deleted == False,
|
||
)
|
||
result = await db.execute(stmt)
|
||
workflow = result.scalar_one_or_none()
|
||
flow_definition = workflow.flow_definition if workflow else {}
|
||
return instance, flow_definition or {}
|
||
|
||
@staticmethod
|
||
async def notify_pending_task(
|
||
db,
|
||
task: Any,
|
||
instance: Any,
|
||
flow_definition: Dict,
|
||
operator_id: str = None,
|
||
) -> None:
|
||
"""
|
||
为已创建的任务发送通知(转办/委派等场景复用 create_task 逻辑)。
|
||
"""
|
||
from online_dev.workflow.engine.base import ExecutionContext
|
||
from online_dev.workflow.engine.utils import FlowUtils
|
||
|
||
node = FlowUtils.find_node_by_id(flow_definition or {}, task.node_id)
|
||
if not node:
|
||
node = {
|
||
'id': task.node_id,
|
||
'name': task.node_name,
|
||
'type': task.task_type,
|
||
'config': {},
|
||
}
|
||
|
||
context = ExecutionContext(
|
||
instance=instance,
|
||
form_data={},
|
||
current_user_id=operator_id or '',
|
||
flow_definition=flow_definition or {},
|
||
db=db,
|
||
)
|
||
await WorkflowNotificationService.send_task_notification(
|
||
context=context,
|
||
task=task,
|
||
assignee_id=str(task.assignee_id),
|
||
task_type=task.task_type,
|
||
node=node,
|
||
)
|
||
|
||
@staticmethod
|
||
async def send_urge_notifications(
|
||
db,
|
||
instance: Any,
|
||
pending_tasks: list,
|
||
flow_definition: Dict,
|
||
operator_id: str,
|
||
) -> int:
|
||
"""
|
||
催办:向待办人发送通知,渠道与节点 taskNotify 一致。
|
||
|
||
Returns:
|
||
成功发送通知的去重处理人数量
|
||
"""
|
||
from core.message.service import NotifyService
|
||
from core.user.model import User
|
||
from online_dev.workflow.engine.utils import FlowUtils
|
||
from sqlalchemy import select
|
||
|
||
if not pending_tasks:
|
||
return 0
|
||
|
||
stmt = select(User).where(User.id == operator_id)
|
||
result = await db.execute(stmt)
|
||
initiator = result.scalar_one_or_none()
|
||
initiator_name = (
|
||
(initiator.name or initiator.username) if initiator else '发起人'
|
||
)
|
||
|
||
instance_title = instance.title or '流程'
|
||
notified_assignees: set[str] = set()
|
||
sent_count = 0
|
||
|
||
for task in pending_tasks:
|
||
assignee_id = str(task.assignee_id)
|
||
if assignee_id in notified_assignees:
|
||
continue
|
||
|
||
node = FlowUtils.find_node_by_id(flow_definition or {}, task.node_id)
|
||
channels = _resolve_task_notify_channels(node or {})
|
||
if channels is None:
|
||
continue
|
||
|
||
type_labels = {
|
||
'approval': '审批',
|
||
'handle': '办理',
|
||
}
|
||
type_label = type_labels.get(task.task_type, '处理')
|
||
title = f'【{instance_title}】催办提醒'
|
||
content = (
|
||
f'{initiator_name} 催办您尽快完成【{instance_title}】的{type_label},'
|
||
f'当前节点:{task.node_name or "待处理"}。'
|
||
)
|
||
|
||
try:
|
||
await NotifyService.send(
|
||
db=db,
|
||
recipient_ids=[assignee_id],
|
||
title=title,
|
||
content=content,
|
||
channels=channels,
|
||
msg_type='workflow',
|
||
link_type='workflow_task',
|
||
link_id=str(task.id),
|
||
sender_id=operator_id,
|
||
)
|
||
notified_assignees.add(assignee_id)
|
||
sent_count += 1
|
||
logger.info(
|
||
f'催办通知已发送: instance={instance.id}, assignee={assignee_id}, '
|
||
f'channels={channels}'
|
||
)
|
||
except Exception as e:
|
||
logger.error(f'发送催办通知失败 assignee={assignee_id}: {e}')
|
||
|
||
return sent_count
|