oa_assignee.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. """OA 审批人解析(不读 sys_role)"""
  2. from __future__ import annotations
  3. from typing import Any, Dict, List, Optional, Tuple
  4. from sqlalchemy.orm import Session
  5. from app.crud import oa_approval as oa_crud
  6. from app.models.models import SysDept, SysUser
  7. class AssigneeResolveError(ValueError):
  8. """审批人解析失败"""
  9. def _active_user_id(db: Session, user_id: Optional[str], tenant_no: str) -> Optional[str]:
  10. uid = (user_id or "").strip()
  11. if not uid:
  12. return None
  13. u = (
  14. db.query(SysUser)
  15. .filter(
  16. SysUser.id == uid,
  17. SysUser.tenant_no == tenant_no,
  18. SysUser.del_flag == 0,
  19. SysUser.status == "1",
  20. )
  21. .first()
  22. )
  23. return u.id if u else None
  24. def resolve_dept_leader(
  25. db: Session, *, tenant_no: str, initiator_dept_id: Optional[str]
  26. ) -> Tuple[List[str], Dict[str, Any]]:
  27. dept_id = (initiator_dept_id or "").strip()
  28. if not dept_id:
  29. raise AssigneeResolveError("发起人部门为空,无法解析部门负责人")
  30. dept = (
  31. db.query(SysDept)
  32. .filter(SysDept.id == dept_id, SysDept.tenant_no == tenant_no, SysDept.del_flag == 0)
  33. .first()
  34. )
  35. if not dept:
  36. raise AssigneeResolveError("发起人部门不存在")
  37. leader_uid = _active_user_id(db, getattr(dept, "leader_user_id", None), tenant_no)
  38. if not leader_uid:
  39. raise AssigneeResolveError("部门负责人未配置或用户无效,请在组织管理中维护负责人")
  40. return [leader_uid], {"resolve_source": "dept_leader"}
  41. def resolve_direct_supervisor(
  42. db: Session,
  43. *,
  44. tenant_no: str,
  45. initiator_user_id: str,
  46. initiator_dept_id: Optional[str],
  47. fallback_to_dept_leader: bool = True,
  48. ) -> Tuple[List[str], Dict[str, Any]]:
  49. initiator = (
  50. db.query(SysUser)
  51. .filter(
  52. SysUser.id == initiator_user_id,
  53. SysUser.tenant_no == tenant_no,
  54. SysUser.del_flag == 0,
  55. )
  56. .first()
  57. )
  58. if not initiator:
  59. raise AssigneeResolveError("发起人不存在")
  60. supervisor_uid = _active_user_id(
  61. db, getattr(initiator, "supervisor_user_id", None), tenant_no
  62. )
  63. if supervisor_uid:
  64. if supervisor_uid == initiator_user_id:
  65. raise AssigneeResolveError("直属主管不能是发起人本人,请修正用户主管配置")
  66. return [supervisor_uid], {"resolve_source": "direct_supervisor"}
  67. if fallback_to_dept_leader:
  68. users, meta = resolve_dept_leader(
  69. db, tenant_no=tenant_no, initiator_dept_id=initiator_dept_id or initiator.dept_id
  70. )
  71. meta = {**meta, "resolve_source": "supervisor_fallback_dept_leader"}
  72. return users, meta
  73. raise AssigneeResolveError("未配置直属主管,且未开启回退部门负责人")
  74. def resolve_assignees(
  75. db: Session,
  76. *,
  77. tenant_no: str,
  78. assignee_config: Dict[str, Any],
  79. initiator_user_id: str,
  80. initiator_dept_id: Optional[str] = None,
  81. advanced: Optional[Dict[str, Any]] = None,
  82. initiator_select_user_ids: Optional[List[str]] = None,
  83. ) -> Tuple[List[str], Dict[str, Any]]:
  84. """
  85. 按节点 approverConfig 解析处理人。
  86. assigneeType: users | oa_roles | dept_leader | direct_supervisor | initiator_select
  87. """
  88. cfg = assignee_config or {}
  89. assignee_type = (cfg.get("assigneeType") or cfg.get("assignee_type") or "").strip()
  90. advanced = advanced or {}
  91. fallback = advanced.get("supervisorFallbackToDeptLeader", True)
  92. if isinstance(fallback, str):
  93. fallback = fallback.lower() in ("1", "true", "yes")
  94. if assignee_type == "users":
  95. ids = cfg.get("userIds") or cfg.get("user_ids") or []
  96. out: List[str] = []
  97. seen = set()
  98. for uid in ids:
  99. real = _active_user_id(db, uid, tenant_no)
  100. if real and real not in seen:
  101. seen.add(real)
  102. out.append(real)
  103. if not out:
  104. raise AssigneeResolveError("指定成员为空或均无效")
  105. return out, {"resolve_source": "users"}
  106. if assignee_type == "oa_roles":
  107. role_ids = cfg.get("oaRoleIds") or cfg.get("oa_role_ids") or []
  108. # 也支持按编码
  109. codes = cfg.get("oaRoleCodes") or cfg.get("oa_role_codes") or []
  110. if codes:
  111. from app.models.models import OaRole
  112. extra = (
  113. db.query(OaRole.id)
  114. .filter(
  115. OaRole.tenant_no == tenant_no,
  116. OaRole.role_code.in_([c.upper() for c in codes]),
  117. OaRole.del_flag == 0,
  118. OaRole.status == "1",
  119. )
  120. .all()
  121. )
  122. role_ids = list(role_ids) + [r.id for r in extra]
  123. users = oa_crud.list_oa_role_user_ids(db, list(role_ids), tenant_no)
  124. # 过滤停用用户
  125. out = []
  126. seen = set()
  127. for uid in users:
  128. real = _active_user_id(db, uid, tenant_no)
  129. if real and real not in seen:
  130. seen.add(real)
  131. out.append(real)
  132. if not out:
  133. raise AssigneeResolveError("工作流角色成员为空或均无效")
  134. return out, {"resolve_source": "oa_roles"}
  135. if assignee_type == "dept_leader":
  136. return resolve_dept_leader(
  137. db, tenant_no=tenant_no, initiator_dept_id=initiator_dept_id
  138. )
  139. if assignee_type == "direct_supervisor":
  140. return resolve_direct_supervisor(
  141. db,
  142. tenant_no=tenant_no,
  143. initiator_user_id=initiator_user_id,
  144. initiator_dept_id=initiator_dept_id,
  145. fallback_to_dept_leader=bool(fallback),
  146. )
  147. if assignee_type == "initiator_select":
  148. ids = initiator_select_user_ids or cfg.get("userIds") or []
  149. out = []
  150. seen = set()
  151. for uid in ids:
  152. real = _active_user_id(db, uid, tenant_no)
  153. if real and real not in seen:
  154. seen.add(real)
  155. out.append(real)
  156. if not out:
  157. raise AssigneeResolveError("发起人未选择审批人")
  158. return out, {"resolve_source": "initiator_select"}
  159. raise AssigneeResolveError(f"不支持的审批人类型: {assignee_type or '(空)'}")