#!/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()