production_task_step_assignee.py 7.9 KB

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