production_task_step_assignee.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. """生产任务步骤负责人 CRUD"""
  2. from __future__ import annotations
  3. from datetime import datetime
  4. from typing import Dict, List, Optional, Set
  5. from sqlalchemy.orm import Session
  6. from app.core.db_collation import eq_unicode
  7. from app.core.utils import generate_uuid
  8. from app.models.models import BizProductionTaskStep, BizProductionTaskStepAssignee, SysUser
  9. from app.services.web_auth_roles import list_user_ids_with_effective_role_name
  10. PRODUCTION_STEP_ASSIGNEE_ROLE = "实验专员"
  11. def _active_users_in_tenant(
  12. db: Session, user_ids: List[str], tenant_no: str
  13. ) -> Set[str]:
  14. ids = [x.strip() for x in user_ids if (x or "").strip()]
  15. if not ids:
  16. return set()
  17. rows = (
  18. db.query(SysUser.id)
  19. .filter(
  20. SysUser.id.in_(ids),
  21. SysUser.tenant_no == tenant_no,
  22. SysUser.del_flag == 0,
  23. SysUser.status == "1",
  24. )
  25. .all()
  26. )
  27. return {r[0] for r in rows}
  28. def _active_users_with_role_in_tenant(
  29. db: Session, user_ids: List[str], tenant_no: str, role_name: str
  30. ) -> Set[str]:
  31. """校验候选用户是否拥有指定角色(直接绑定 ∪ 职位继承)。"""
  32. ids = [x.strip() for x in user_ids if (x or "").strip()]
  33. rn = (role_name or "").strip()
  34. if not ids or not rn:
  35. return set()
  36. return list_user_ids_with_effective_role_name(
  37. db,
  38. tenant_no=tenant_no,
  39. role_name=rn,
  40. candidate_user_ids=ids,
  41. )
  42. def validate_assignee_user_ids(
  43. db: Session,
  44. user_ids: List[str],
  45. tenant_no: str,
  46. *,
  47. required_role_name: Optional[str] = PRODUCTION_STEP_ASSIGNEE_ROLE,
  48. ) -> List[str]:
  49. """去重并校验用户有效,返回有序列表。"""
  50. seen: Set[str] = set()
  51. ordered: List[str] = []
  52. for uid in user_ids:
  53. u = (uid or "").strip()
  54. if not u or u in seen:
  55. continue
  56. seen.add(u)
  57. ordered.append(u)
  58. if not ordered:
  59. raise ValueError("请至少指定一名负责人")
  60. valid = _active_users_in_tenant(db, ordered, tenant_no)
  61. invalid = [u for u in ordered if u not in valid]
  62. if invalid:
  63. raise ValueError("存在无效或已停用的负责人用户")
  64. rn = (required_role_name or "").strip()
  65. if rn:
  66. role_valid = _active_users_with_role_in_tenant(db, ordered, tenant_no, rn)
  67. role_invalid = [u for u in ordered if u not in role_valid]
  68. if role_invalid:
  69. raise ValueError(f"负责人须为「{rn}」角色用户")
  70. return ordered
  71. def sync_assignees_for_step(
  72. db: Session,
  73. step_id: str,
  74. user_ids: List[str],
  75. *,
  76. tenant_no: str,
  77. operator_id: Optional[str] = None,
  78. create_dept_id: str = "",
  79. ) -> None:
  80. """全量替换步骤负责人(软删旧记录 + 插入新记录)。"""
  81. normalized = validate_assignee_user_ids(db, user_ids, tenant_no)
  82. now = datetime.now()
  83. existing = (
  84. db.query(BizProductionTaskStepAssignee)
  85. .filter(
  86. BizProductionTaskStepAssignee.step_id == step_id,
  87. BizProductionTaskStepAssignee.tenant_no == tenant_no,
  88. BizProductionTaskStepAssignee.del_flag == 0,
  89. )
  90. .all()
  91. )
  92. keep_ids = set(normalized)
  93. for row in existing:
  94. if row.user_id not in keep_ids:
  95. row.del_flag = 1
  96. row.update_by = operator_id
  97. row.update_time = now
  98. db.add(row)
  99. existing_map = {r.user_id: r for r in existing if r.del_flag == 0}
  100. for idx, uid in enumerate(normalized):
  101. if uid in existing_map:
  102. row = existing_map[uid]
  103. row.order_num = idx + 1
  104. row.update_by = operator_id
  105. row.update_time = now
  106. db.add(row)
  107. continue
  108. db.add(
  109. BizProductionTaskStepAssignee(
  110. id=generate_uuid(),
  111. step_id=step_id,
  112. user_id=uid,
  113. tenant_no=tenant_no,
  114. order_num=idx + 1,
  115. create_by=operator_id,
  116. create_dept_id=create_dept_id or "",
  117. create_time=now,
  118. update_by=operator_id,
  119. update_time=now,
  120. del_flag=0,
  121. )
  122. )
  123. def enrich_step_assignees(
  124. db: Session, steps: List[Dict], *, tenant_no: str
  125. ) -> None:
  126. """批量填充 assigneeUserIds / assigneeNames / assignees。"""
  127. step_ids = [s["id"] for s in steps if s.get("id")]
  128. if not step_ids:
  129. return
  130. rows = (
  131. db.query(BizProductionTaskStepAssignee, SysUser)
  132. .outerjoin(SysUser, eq_unicode(SysUser.id, BizProductionTaskStepAssignee.user_id))
  133. .filter(
  134. BizProductionTaskStepAssignee.step_id.in_(step_ids),
  135. BizProductionTaskStepAssignee.tenant_no == tenant_no,
  136. BizProductionTaskStepAssignee.del_flag == 0,
  137. )
  138. .order_by(
  139. BizProductionTaskStepAssignee.order_num.asc(),
  140. BizProductionTaskStepAssignee.create_time.asc(),
  141. )
  142. .all()
  143. )
  144. by_step: Dict[str, List] = {}
  145. for assignee, user in rows:
  146. by_step.setdefault(assignee.step_id, []).append((assignee, user))
  147. for step in steps:
  148. pairs = by_step.get(step["id"], [])
  149. step["assigneeUserIds"] = [a.user_id for a, _ in pairs]
  150. step["assigneeNames"] = [
  151. f"{(u.nick_name or u.user_name or '')}({u.user_no or ''})".strip()
  152. for _, u in pairs
  153. if u
  154. ]
  155. step["assignees"] = [
  156. {
  157. "userId": a.user_id,
  158. "nickName": (u.nick_name or u.user_name or "") if u else "",
  159. "userNo": (u.user_no or "") if u else "",
  160. }
  161. for a, u in pairs
  162. ]
  163. def enrich_step_completed_by_names(db: Session, steps: List[Dict]) -> None:
  164. uids = list({(s.get("completedBy") or "").strip() for s in steps if (s.get("completedBy") or "").strip()})
  165. if not uids:
  166. return
  167. users = {
  168. u.id: u
  169. for u in db.query(SysUser).filter(SysUser.id.in_(uids), SysUser.del_flag == 0).all()
  170. }
  171. for step in steps:
  172. uid = (step.get("completedBy") or "").strip()
  173. if not uid:
  174. step["completedByName"] = ""
  175. continue
  176. u = users.get(uid)
  177. step["completedByName"] = (
  178. f"{(u.nick_name or u.user_name or '')}({u.user_no or ''})".strip() if u else uid
  179. )
  180. def user_is_step_assignee(
  181. db: Session, step_id: str, user_id: str, tenant_no: str
  182. ) -> bool:
  183. uid = (user_id or "").strip()
  184. if not uid:
  185. return False
  186. row = (
  187. db.query(BizProductionTaskStepAssignee.id)
  188. .filter(
  189. BizProductionTaskStepAssignee.step_id == step_id,
  190. BizProductionTaskStepAssignee.user_id == uid,
  191. BizProductionTaskStepAssignee.tenant_no == tenant_no,
  192. BizProductionTaskStepAssignee.del_flag == 0,
  193. )
  194. .first()
  195. )
  196. return row is not None
  197. def user_is_any_step_assignee_of_task(
  198. db: Session, task_id: str, user_id: str, tenant_no: str
  199. ) -> bool:
  200. uid = (user_id or "").strip()
  201. if not uid:
  202. return False
  203. row = (
  204. db.query(BizProductionTaskStepAssignee.id)
  205. .join(
  206. BizProductionTaskStep,
  207. BizProductionTaskStep.id == BizProductionTaskStepAssignee.step_id,
  208. )
  209. .filter(
  210. BizProductionTaskStep.task_id == task_id,
  211. BizProductionTaskStep.del_flag == 0,
  212. BizProductionTaskStepAssignee.user_id == uid,
  213. BizProductionTaskStepAssignee.tenant_no == tenant_no,
  214. BizProductionTaskStepAssignee.del_flag == 0,
  215. )
  216. .first()
  217. )
  218. return row is not None