feat: restore source parity and harden agent runtime
This commit is contained in:
@@ -0,0 +1,353 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
审批人解析器
|
||||
负责根据节点配置解析实际的审批人列表
|
||||
"""
|
||||
import logging
|
||||
from typing import List, Dict, Any
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AssigneeResolver:
|
||||
"""
|
||||
审批人解析器
|
||||
|
||||
支持的审批人类型:
|
||||
- user: 指定用户
|
||||
- role: 指定角色(获取角色下所有用户)
|
||||
- department: 指定部门(获取部门下所有用户)
|
||||
- superior: 上级主管(根据发起人的部门层级)
|
||||
- manager: 直属经理(发起人的直属上级)
|
||||
- initiator: 发起人自己
|
||||
- form_field: 表单字段(从表单数据中获取用户)
|
||||
"""
|
||||
|
||||
async def resolve(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
node_config: Dict,
|
||||
instance: Any,
|
||||
form_data: Dict,
|
||||
) -> List[str]:
|
||||
"""
|
||||
解析审批人
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
node_config: 节点配置(包含 assigneeType, assignees 等)
|
||||
instance: 流程实例
|
||||
form_data: 表单数据
|
||||
|
||||
Returns:
|
||||
List[str]: 用户ID列表
|
||||
"""
|
||||
assignee_type = node_config.get('assigneeType', 'user')
|
||||
|
||||
resolvers = {
|
||||
'user': self._resolve_users,
|
||||
'role': self._resolve_by_role,
|
||||
'department': self._resolve_by_department,
|
||||
'superior': self._resolve_superior,
|
||||
'manager': self._resolve_manager,
|
||||
'initiator': self._resolve_initiator,
|
||||
'form_field': self._resolve_from_form,
|
||||
}
|
||||
|
||||
resolver = resolvers.get(assignee_type)
|
||||
if not resolver:
|
||||
logger.warning(f"未知的审批人类型: {assignee_type}")
|
||||
return []
|
||||
|
||||
try:
|
||||
return await resolver(db, node_config, instance, form_data)
|
||||
except Exception as e:
|
||||
logger.exception(f"解析审批人失败: {e}")
|
||||
return []
|
||||
|
||||
async def _resolve_users(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
node_config: Dict,
|
||||
instance: Any,
|
||||
form_data: Dict,
|
||||
) -> List[str]:
|
||||
"""指定用户"""
|
||||
assignees = node_config.get('assignees', [])
|
||||
logger.info(f"[_resolve_users] assignees from config: {assignees}")
|
||||
result = [str(uid) for uid in assignees] if assignees else []
|
||||
logger.info(f"[_resolve_users] result: {result}")
|
||||
return result
|
||||
|
||||
async def _resolve_by_role(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
node_config: Dict,
|
||||
instance: Any,
|
||||
form_data: Dict,
|
||||
) -> List[str]:
|
||||
"""根据角色获取用户(通过用户角色关联表)"""
|
||||
from core.user.model import User
|
||||
from core.user.user_role_model import UserRole
|
||||
|
||||
role_ids = node_config.get('assignees', [])
|
||||
if not role_ids:
|
||||
return []
|
||||
|
||||
# 通过 UserRole 关联表查询拥有指定角色的用户
|
||||
stmt = select(User.id).join(
|
||||
UserRole,
|
||||
User.id == UserRole.user_id
|
||||
).where(
|
||||
UserRole.role_id.in_(role_ids),
|
||||
User.is_deleted == False,
|
||||
User.is_active == True,
|
||||
).distinct()
|
||||
|
||||
result = await db.execute(stmt)
|
||||
users = result.scalars().all()
|
||||
|
||||
logger.info(f"[_resolve_by_role] role_ids: {role_ids}, found users: {users}")
|
||||
return [str(uid) for uid in users]
|
||||
|
||||
async def _resolve_by_department(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
node_config: Dict,
|
||||
instance: Any,
|
||||
form_data: Dict,
|
||||
) -> List[str]:
|
||||
"""根据部门获取用户"""
|
||||
from core.user.model import User
|
||||
|
||||
dept_ids = node_config.get('assignees', [])
|
||||
if not dept_ids:
|
||||
return []
|
||||
|
||||
stmt = select(User.id).where(
|
||||
User.dept_id.in_(dept_ids),
|
||||
User.is_deleted == False,
|
||||
User.is_active == True,
|
||||
).distinct()
|
||||
|
||||
result = await db.execute(stmt)
|
||||
users = result.scalars().all()
|
||||
|
||||
return [str(uid) for uid in users]
|
||||
|
||||
async def _resolve_superior(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
node_config: Dict,
|
||||
instance: Any,
|
||||
form_data: Dict,
|
||||
) -> List[str]:
|
||||
"""
|
||||
获取上级主管
|
||||
|
||||
assigneeLevel 含义:
|
||||
- 1 = 直接上级(当前部门的负责人)
|
||||
- 2 = 上级的上级(父部门的负责人)
|
||||
- 以此类推
|
||||
"""
|
||||
from core.user.model import User
|
||||
from core.dept.model import Dept
|
||||
|
||||
level = node_config.get('assigneeLevel', 1)
|
||||
initiator_id = instance.initiator_id
|
||||
|
||||
# 获取发起人信息
|
||||
stmt = select(User).where(User.id == initiator_id)
|
||||
result = await db.execute(stmt)
|
||||
initiator = result.scalar_one_or_none()
|
||||
|
||||
if not initiator or not initiator.dept_id:
|
||||
logger.warning(f"发起人 {initiator_id} 没有部门信息")
|
||||
return []
|
||||
|
||||
# 获取部门信息
|
||||
current_dept_id = initiator.dept_id
|
||||
for _ in range(level - 1):
|
||||
stmt = select(Dept).where(Dept.id == current_dept_id)
|
||||
result = await db.execute(stmt)
|
||||
dept = result.scalar_one_or_none()
|
||||
if dept and dept.parent_id:
|
||||
current_dept_id = dept.parent_id
|
||||
else:
|
||||
break
|
||||
|
||||
# 获取部门负责人
|
||||
stmt = select(Dept).where(Dept.id == current_dept_id)
|
||||
result = await db.execute(stmt)
|
||||
current_dept = result.scalar_one_or_none()
|
||||
|
||||
if current_dept and current_dept.lead_id:
|
||||
# 如果负责人是发起人自己,尝试向上找
|
||||
if str(current_dept.lead_id) == str(initiator_id):
|
||||
logger.info(f"部门负责人是发起人自己,尝试向上查找")
|
||||
if current_dept.parent_id:
|
||||
stmt = select(Dept).where(Dept.id == current_dept.parent_id)
|
||||
result = await db.execute(stmt)
|
||||
parent_dept = result.scalar_one_or_none()
|
||||
if parent_dept and parent_dept.lead_id:
|
||||
return [str(parent_dept.lead_id)]
|
||||
return [str(current_dept.lead_id)]
|
||||
|
||||
logger.warning(f"部门没有负责人")
|
||||
return []
|
||||
|
||||
async def _resolve_manager(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
node_config: Dict,
|
||||
instance: Any,
|
||||
form_data: Dict,
|
||||
) -> List[str]:
|
||||
"""
|
||||
获取直属经理
|
||||
|
||||
assigneeLevel 含义:
|
||||
- 1 = 直属经理(发起人的 manager)
|
||||
- 2 = 经理的经理(发起人的 manager 的 manager)
|
||||
- 以此类推
|
||||
"""
|
||||
from core.user.model import User
|
||||
|
||||
level = node_config.get('assigneeLevel', 1)
|
||||
initiator_id = instance.initiator_id
|
||||
|
||||
current_user_id = initiator_id
|
||||
for _ in range(level):
|
||||
stmt = select(User).where(User.id == current_user_id)
|
||||
result = await db.execute(stmt)
|
||||
user = result.scalar_one_or_none()
|
||||
|
||||
if user and user.manager_id:
|
||||
current_user_id = user.manager_id
|
||||
else:
|
||||
logger.warning(f"用户没有设置直属经理")
|
||||
return []
|
||||
|
||||
return [str(current_user_id)]
|
||||
|
||||
async def _resolve_initiator(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
node_config: Dict,
|
||||
instance: Any,
|
||||
form_data: Dict,
|
||||
) -> List[str]:
|
||||
"""发起人自己"""
|
||||
if instance.initiator_id:
|
||||
return [str(instance.initiator_id)]
|
||||
return []
|
||||
|
||||
async def _resolve_from_form(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
node_config: Dict,
|
||||
instance: Any,
|
||||
form_data: Dict,
|
||||
) -> List[str]:
|
||||
"""从表单字段获取用户,支持多字段收集,支持 form-selector 间接查询"""
|
||||
field_names = node_config.get('assigneeFields') or node_config.get('recipientFields')
|
||||
if not field_names:
|
||||
single = node_config.get('assigneeField') or node_config.get('recipientField', '')
|
||||
field_names = [single] if single else []
|
||||
|
||||
if not field_names:
|
||||
return []
|
||||
|
||||
# 构建 form-selector 字段映射表: fieldName -> {formCode, userField}
|
||||
field_mappings = {}
|
||||
for mapping in (node_config.get('assigneeFieldMappings') or node_config.get('recipientFieldMappings') or []):
|
||||
field_mappings[mapping['fieldName']] = mapping
|
||||
|
||||
result = []
|
||||
seen = set()
|
||||
for field_name in field_names:
|
||||
value = form_data.get(field_name)
|
||||
if not value:
|
||||
continue
|
||||
|
||||
mapping = field_mappings.get(field_name)
|
||||
if mapping:
|
||||
# form-selector 字段: value 是引用表单记录的 ID,需要间接查询用户字段
|
||||
user_ids = await self._resolve_form_selector_field(
|
||||
db, value, mapping.get('formCode', ''), mapping.get('userField', '')
|
||||
)
|
||||
for uid_str in user_ids:
|
||||
if uid_str and uid_str not in seen:
|
||||
seen.add(uid_str)
|
||||
result.append(uid_str)
|
||||
else:
|
||||
# user-selector 字段: value 直接就是用户 ID
|
||||
values = value if isinstance(value, list) else [value]
|
||||
for uid in values:
|
||||
uid_str = str(uid)
|
||||
if uid_str and uid_str not in seen:
|
||||
seen.add(uid_str)
|
||||
result.append(uid_str)
|
||||
|
||||
return result
|
||||
|
||||
async def _resolve_form_selector_field(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
record_ids: Any,
|
||||
form_code: str,
|
||||
user_field: str,
|
||||
) -> List[str]:
|
||||
"""
|
||||
从 form-selector 引用的表单记录中提取用户字段值
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
record_ids: 引用的表单记录 ID(单个或列表)
|
||||
form_code: 引用的表单编码
|
||||
user_field: 引用表单中的用户字段名
|
||||
"""
|
||||
if not form_code or not user_field:
|
||||
logger.warning(f"form-selector 映射配置不完整: form_code={form_code}, user_field={user_field}")
|
||||
return []
|
||||
|
||||
ids = record_ids if isinstance(record_ids, list) else [record_ids]
|
||||
if not ids:
|
||||
return []
|
||||
|
||||
result = []
|
||||
try:
|
||||
from online_dev.form_data_manager.service import FormDataService
|
||||
|
||||
service = await FormDataService.create_service(db, form_code)
|
||||
|
||||
for record_id in ids:
|
||||
try:
|
||||
record = await service.get(db, str(record_id))
|
||||
if not record:
|
||||
logger.warning(f"form-selector 引用记录不存在: form_code={form_code}, id={record_id}")
|
||||
continue
|
||||
|
||||
user_value = record.get(user_field)
|
||||
if not user_value:
|
||||
continue
|
||||
|
||||
user_values = user_value if isinstance(user_value, list) else [user_value]
|
||||
for uid in user_values:
|
||||
result.append(str(uid))
|
||||
except Exception as e:
|
||||
logger.warning(f"查询 form-selector 引用记录失败: form_code={form_code}, id={record_id}, error={e}")
|
||||
continue
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"form-selector 字段解析失败: {e}")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
# 全局实例
|
||||
assignee_resolver = AssigneeResolver()
|
||||
Reference in New Issue
Block a user