post.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. """职位 CRUD(sys_post)"""
  2. from __future__ import annotations
  3. import math
  4. from datetime import datetime
  5. from typing import Any, Dict, List, Optional, Tuple
  6. from sqlalchemy import and_, func, or_
  7. from sqlalchemy.orm import Session
  8. from app.core.utils import generate_uuid
  9. from app.models.models import SysPost, SysPostRole, SysRole, SysUser, SysUserPost
  10. def _user_display_name(nick_name: Optional[str], user_no: Optional[str]) -> str:
  11. n = (nick_name or "").strip()
  12. if n:
  13. return n
  14. return (user_no or "").strip()
  15. def _bound_user_names_by_post_ids(
  16. db: Session, post_ids: List[str], tenant_no: str
  17. ) -> Dict[str, List[str]]:
  18. """批量查询职位关联用户显示名(nick_name 优先,否则 user_no)。"""
  19. out: Dict[str, List[str]] = {}
  20. ids = [pid for pid in (post_ids or []) if pid]
  21. tenant = (tenant_no or "").strip()
  22. if not ids or not tenant:
  23. return out
  24. rows = (
  25. db.query(SysUserPost.post_id, SysUser.nick_name, SysUser.user_no)
  26. .join(
  27. SysUser,
  28. and_(
  29. SysUser.id == SysUserPost.user_id,
  30. SysUser.del_flag == 0,
  31. ),
  32. )
  33. .filter(
  34. and_(
  35. SysUserPost.post_id.in_(ids),
  36. SysUserPost.tenant_no == tenant,
  37. SysUserPost.del_flag == 0,
  38. )
  39. )
  40. .all()
  41. )
  42. for post_id, nick_name, user_no in rows:
  43. name = _user_display_name(nick_name, user_no)
  44. if not name:
  45. continue
  46. out.setdefault(post_id, []).append(name)
  47. return out
  48. def _row_to_dict(p: SysPost, bound_user_names: Optional[List[str]] = None) -> Dict[str, Any]:
  49. return {
  50. "id": p.id,
  51. "postName": p.post_name,
  52. "postCode": p.post_code,
  53. "status": p.status,
  54. "tenantNo": p.tenant_no,
  55. "orderNum": p.order_num if p.order_num is not None else 0,
  56. "remark": p.remark or "",
  57. "boundUserNames": list(bound_user_names or []),
  58. "createTime": p.create_time.strftime("%Y-%m-%d %H:%M:%S") if p.create_time else "",
  59. "updateTime": p.update_time.strftime("%Y-%m-%d %H:%M:%S") if p.update_time else "",
  60. }
  61. def list_posts(
  62. db: Session,
  63. *,
  64. page: int = 1,
  65. page_size: int = 10,
  66. keyword: Optional[str] = None,
  67. tenant_no: Optional[str] = None,
  68. user_info=None,
  69. ) -> Tuple[List[Dict[str, Any]], int]:
  70. def _filtered():
  71. q0 = db.query(SysPost).filter(SysPost.del_flag == 0)
  72. if tenant_no:
  73. q0 = q0.filter(SysPost.tenant_no == tenant_no)
  74. kw = (keyword or "").strip()
  75. if kw:
  76. like = f"%{kw}%"
  77. q0 = q0.filter(or_(SysPost.post_name.like(like), SysPost.post_code.like(like)))
  78. if user_info:
  79. from app.core.data_scope import apply_data_scope
  80. q0 = apply_data_scope(q0, SysPost, user_info, db, tenant_no)
  81. return q0
  82. total = _filtered().order_by(None).with_entities(func.count(SysPost.id)).scalar() or 0
  83. page = max(1, page)
  84. page_size = min(max(1, page_size), 100)
  85. rows = (
  86. _filtered()
  87. .order_by(SysPost.create_time.desc())
  88. .offset((page - 1) * page_size)
  89. .limit(page_size)
  90. .all()
  91. )
  92. name_map = _bound_user_names_by_post_ids(db, [x.id for x in rows], tenant_no or "")
  93. return [
  94. _row_to_dict(x, bound_user_names=name_map.get(x.id, [])) for x in rows
  95. ], int(total)
  96. def get_post(db: Session, post_id: str) -> Optional[SysPost]:
  97. return (
  98. db.query(SysPost)
  99. .filter(and_(SysPost.id == post_id, SysPost.del_flag == 0))
  100. .first()
  101. )
  102. def post_code_exists(db: Session, code: str, tenant_no: str, exclude_id: Optional[str] = None) -> bool:
  103. q = db.query(SysPost).filter(and_(SysPost.post_code == code.strip(), SysPost.tenant_no == tenant_no, SysPost.del_flag == 0))
  104. if exclude_id:
  105. q = q.filter(SysPost.id != exclude_id)
  106. return q.first() is not None
  107. def create_post(
  108. db: Session,
  109. *,
  110. post_name: str,
  111. post_code: str,
  112. status: str = "1",
  113. tenant_no: str = "zkxw",
  114. order_num: Optional[int] = None,
  115. remark: Optional[str] = None,
  116. user_id: Optional[str] = None,
  117. ) -> SysPost:
  118. pc = (post_code or "").strip()
  119. if not pc:
  120. raise ValueError("职位编码不能为空")
  121. if post_code_exists(db, pc, tenant_no):
  122. raise ValueError("职位编码已存在")
  123. obj = SysPost(
  124. id=generate_uuid(),
  125. post_name=(post_name or "").strip() or pc,
  126. post_code=pc,
  127. status=status or "1",
  128. tenant_no=tenant_no,
  129. order_num=order_num if order_num is not None else 0,
  130. remark=(remark or "").strip() or None,
  131. create_by=user_id,
  132. create_dept_id="",
  133. create_time=datetime.now(),
  134. update_by=user_id,
  135. update_time=datetime.now(),
  136. del_flag=0,
  137. )
  138. db.add(obj)
  139. db.commit()
  140. db.refresh(obj)
  141. return obj
  142. def update_post(
  143. db: Session,
  144. post_id: str,
  145. *,
  146. post_name: Optional[str] = None,
  147. post_code: Optional[str] = None,
  148. status: Optional[str] = None,
  149. order_num: Optional[int] = None,
  150. remark: Optional[str] = None,
  151. user_id: Optional[str] = None,
  152. ) -> SysPost:
  153. obj = get_post(db, post_id)
  154. if not obj:
  155. raise ValueError("职位不存在")
  156. if post_code is not None:
  157. new_code = post_code.strip()
  158. if not new_code:
  159. raise ValueError("职位编码不能为空")
  160. if new_code != obj.post_code and post_code_exists(db, new_code, obj.tenant_no, exclude_id=post_id):
  161. raise ValueError("职位编码已存在")
  162. obj.post_code = new_code
  163. if post_name is not None:
  164. obj.post_name = (post_name or "").strip() or obj.post_name
  165. if status is not None:
  166. obj.status = status
  167. if order_num is not None:
  168. obj.order_num = order_num
  169. if remark is not None:
  170. obj.remark = (remark or "").strip() or None
  171. obj.update_by = user_id
  172. obj.update_time = datetime.now()
  173. db.add(obj)
  174. db.commit()
  175. db.refresh(obj)
  176. return obj
  177. def soft_delete_post(db: Session, post_id: str) -> None:
  178. obj = get_post(db, post_id)
  179. if not obj:
  180. raise ValueError("职位不存在")
  181. now = datetime.now()
  182. obj.del_flag = 1
  183. obj.update_time = now
  184. db.add(obj)
  185. db.query(SysPostRole).filter(
  186. and_(SysPostRole.post_id == post_id, SysPostRole.del_flag == 0)
  187. ).update({"del_flag": 1, "update_time": now}, synchronize_session=False)
  188. db.commit()
  189. def list_role_ids_for_post(db: Session, post_id: str, tenant_no: str) -> List[str]:
  190. rows = (
  191. db.query(SysPostRole.role_id)
  192. .filter(
  193. and_(
  194. SysPostRole.post_id == post_id,
  195. SysPostRole.tenant_no == tenant_no,
  196. SysPostRole.del_flag == 0,
  197. )
  198. )
  199. .all()
  200. )
  201. return [r[0] for r in rows]
  202. def sync_post_role_bindings(
  203. db: Session,
  204. post_id: str,
  205. tenant_no: str,
  206. role_ids: List[str],
  207. updater_id: Optional[str],
  208. ) -> None:
  209. """将本职位绑定的角色集合全量替换为 role_ids。"""
  210. ordered = list(dict.fromkeys([rid for rid in (role_ids or []) if rid]))
  211. for rid in ordered:
  212. role = (
  213. db.query(SysRole)
  214. .filter(
  215. and_(
  216. SysRole.id == rid,
  217. SysRole.del_flag == 0,
  218. SysRole.tenant_no == tenant_no,
  219. )
  220. )
  221. .first()
  222. )
  223. if not role:
  224. raise ValueError("存在无效角色或不属于当前租户")
  225. now = datetime.now()
  226. db.query(SysPostRole).filter(
  227. and_(
  228. SysPostRole.post_id == post_id,
  229. SysPostRole.tenant_no == tenant_no,
  230. SysPostRole.del_flag == 0,
  231. )
  232. ).update({"del_flag": 1, "update_time": now, "update_by": updater_id}, synchronize_session=False)
  233. for rid in ordered:
  234. db.add(
  235. SysPostRole(
  236. id=generate_uuid(),
  237. post_id=post_id,
  238. role_id=rid,
  239. tenant_no=tenant_no,
  240. order_num=0,
  241. remark=None,
  242. create_by=updater_id,
  243. create_dept_id="",
  244. create_time=now,
  245. update_by=updater_id,
  246. update_time=now,
  247. del_flag=0,
  248. )
  249. )
  250. db.commit()
  251. def total_pages(total: int, page_size: int) -> int:
  252. if total <= 0:
  253. return 0
  254. return int(math.ceil(total / float(page_size)))