Files

354 lines
12 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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()