qc_material_amount_auth.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. """质检物料金额查看授权 CRUD(sys_user_role 绑定专用角色)。"""
  2. from __future__ import annotations
  3. from datetime import datetime
  4. from typing import Any, Dict, List, Optional, Tuple
  5. from sqlalchemy import and_, or_
  6. from sqlalchemy.orm import Session
  7. from app.core.qc_material_amount_auth_constants import (
  8. QC_MATERIAL_AMOUNT_VIEW_ROLE_ID,
  9. QC_MATERIAL_AMOUNT_VIEW_ROLE_NAME,
  10. QC_MATERIAL_AMOUNT_AUTH_DEPT_NAME,
  11. )
  12. from app.core.data_scope import _get_child_dept_ids
  13. from app.core.utils import generate_uuid
  14. from app.models.models import SysDept, SysRole, SysUser, SysUserRole
  15. def get_amount_view_role(db: Session, tenant_no: str) -> Optional[SysRole]:
  16. row = (
  17. db.query(SysRole)
  18. .filter(
  19. SysRole.id == QC_MATERIAL_AMOUNT_VIEW_ROLE_ID,
  20. SysRole.tenant_no == tenant_no,
  21. SysRole.del_flag == 0,
  22. SysRole.status == "1",
  23. )
  24. .first()
  25. )
  26. if row:
  27. return row
  28. return (
  29. db.query(SysRole)
  30. .filter(
  31. SysRole.role_name == QC_MATERIAL_AMOUNT_VIEW_ROLE_NAME,
  32. SysRole.tenant_no == tenant_no,
  33. SysRole.del_flag == 0,
  34. SysRole.status == "1",
  35. )
  36. .first()
  37. )
  38. def _get_auth_dept_id(db: Session, tenant_no: str) -> Optional[str]:
  39. row = (
  40. db.query(SysDept.id)
  41. .filter(
  42. SysDept.dept_name == QC_MATERIAL_AMOUNT_AUTH_DEPT_NAME,
  43. SysDept.tenant_no == tenant_no,
  44. SysDept.del_flag == 0,
  45. SysDept.status == "1",
  46. )
  47. .first()
  48. )
  49. return str(row[0]).strip() if row and row[0] else None
  50. def _assert_user_in_auth_dept(db: Session, user: SysUser, tenant_no: str) -> None:
  51. dept_id = _get_auth_dept_id(db, tenant_no)
  52. if not dept_id:
  53. raise ValueError(f"未找到部门「{QC_MATERIAL_AMOUNT_AUTH_DEPT_NAME}」")
  54. user_dept_id = (user.dept_id or "").strip()
  55. if not user_dept_id:
  56. raise ValueError(f"仅可授权「{QC_MATERIAL_AMOUNT_AUTH_DEPT_NAME}」下的用户")
  57. allowed = set(_get_child_dept_ids(db, dept_id, tenant_no))
  58. if user_dept_id not in allowed:
  59. raise ValueError(f"仅可授权「{QC_MATERIAL_AMOUNT_AUTH_DEPT_NAME}」下的用户")
  60. def _user_has_role_binding(db: Session, user_id: str, role_id: str, tenant_no: str) -> bool:
  61. return (
  62. db.query(SysUserRole)
  63. .filter(
  64. SysUserRole.user_id == user_id,
  65. SysUserRole.role_id == role_id,
  66. SysUserRole.tenant_no == tenant_no,
  67. SysUserRole.del_flag == 0,
  68. )
  69. .first()
  70. is not None
  71. )
  72. def _row_to_dict(user: SysUser, dept_name: str, grant_time: Optional[datetime]) -> Dict[str, Any]:
  73. return {
  74. "id": user.id,
  75. "userId": user.id,
  76. "userNo": user.user_no,
  77. "nickName": user.nick_name,
  78. "phoneNo": user.phone_no or "",
  79. "deptName": dept_name,
  80. "status": user.status,
  81. "grantTime": grant_time.strftime("%Y-%m-%d %H:%M:%S") if grant_time else "",
  82. }
  83. def list_authorized_users(
  84. db: Session,
  85. *,
  86. tenant_no: str,
  87. page: int = 1,
  88. page_size: int = 10,
  89. keyword: Optional[str] = None,
  90. ) -> Tuple[List[Dict[str, Any]], int]:
  91. role = get_amount_view_role(db, tenant_no)
  92. if not role:
  93. return [], 0
  94. q = (
  95. db.query(SysUser, SysUserRole.create_time)
  96. .join(
  97. SysUserRole,
  98. and_(
  99. SysUserRole.user_id == SysUser.id,
  100. SysUserRole.role_id == role.id,
  101. SysUserRole.tenant_no == tenant_no,
  102. SysUserRole.del_flag == 0,
  103. ),
  104. )
  105. .filter(SysUser.del_flag == 0, SysUser.tenant_no == tenant_no)
  106. )
  107. kw = (keyword or "").strip()
  108. if kw:
  109. like = f"%{kw}%"
  110. q = q.filter(
  111. or_(
  112. SysUser.user_no.like(like),
  113. SysUser.nick_name.like(like),
  114. SysUser.phone_no.like(like),
  115. )
  116. )
  117. total = q.count()
  118. rows = (
  119. q.order_by(SysUserRole.create_time.desc())
  120. .offset((page - 1) * page_size)
  121. .limit(page_size)
  122. .all()
  123. )
  124. dept_ids = list({u.dept_id for u, _ in rows if u.dept_id})
  125. dept_map: Dict[str, str] = {}
  126. if dept_ids:
  127. for d in db.query(SysDept).filter(SysDept.id.in_(dept_ids), SysDept.del_flag == 0).all():
  128. dept_map[d.id] = d.dept_name or ""
  129. items = [
  130. _row_to_dict(u, dept_map.get(u.dept_id or "", ""), grant_time)
  131. for u, grant_time in rows
  132. ]
  133. return items, total
  134. def grant_amount_view_role(
  135. db: Session,
  136. *,
  137. tenant_no: str,
  138. user_id: str,
  139. operator_id: Optional[str],
  140. ) -> bool:
  141. """绑定专用角色;已绑定则幂等返回 False(表示未新增)。"""
  142. role = get_amount_view_role(db, tenant_no)
  143. if not role:
  144. raise ValueError("未找到「质检物料金额查看」角色,请先执行 migrate-qc-material-amount-auth.sql")
  145. user = (
  146. db.query(SysUser)
  147. .filter(SysUser.id == user_id, SysUser.tenant_no == tenant_no, SysUser.del_flag == 0)
  148. .first()
  149. )
  150. if not user:
  151. raise ValueError("用户不存在")
  152. if user.status != "1":
  153. raise ValueError("用户已停用,无法授权")
  154. _assert_user_in_auth_dept(db, user, tenant_no)
  155. if _user_has_role_binding(db, user_id, role.id, tenant_no):
  156. return False
  157. now = datetime.now()
  158. db.add(
  159. SysUserRole(
  160. id=generate_uuid(),
  161. user_id=user_id,
  162. role_id=role.id,
  163. tenant_no=tenant_no,
  164. order_num=0,
  165. remark=None,
  166. create_by=operator_id,
  167. create_dept_id="",
  168. create_time=now,
  169. update_by=operator_id,
  170. update_time=now,
  171. del_flag=0,
  172. )
  173. )
  174. db.commit()
  175. return True
  176. def revoke_amount_view_role(
  177. db: Session,
  178. *,
  179. tenant_no: str,
  180. user_id: str,
  181. operator_id: Optional[str],
  182. ) -> bool:
  183. """解除专用角色绑定;未绑定则返回 False。"""
  184. role = get_amount_view_role(db, tenant_no)
  185. if not role:
  186. raise ValueError("未找到「质检物料金额查看」角色")
  187. now = datetime.now()
  188. updated = (
  189. db.query(SysUserRole)
  190. .filter(
  191. SysUserRole.user_id == user_id,
  192. SysUserRole.role_id == role.id,
  193. SysUserRole.tenant_no == tenant_no,
  194. SysUserRole.del_flag == 0,
  195. )
  196. .update({"del_flag": 1, "update_by": operator_id, "update_time": now})
  197. )
  198. if updated:
  199. db.commit()
  200. return True
  201. return False