Files
zkxw/backend/app/crud/post.py
T
zlwuandCursor 3e60ed56f0 feat(post): list name link and show bound user names
Enrich post list with boundUserNames and open view dialog from the name column.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 14:33:52 +08:00

284 lines
8.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""职位 CRUD(sys_post)"""
from __future__ import annotations
import math
from datetime import datetime
from typing import Any, Dict, List, Optional, Tuple
from sqlalchemy import and_, func, or_
from sqlalchemy.orm import Session
from app.core.utils import generate_uuid
from app.models.models import SysPost, SysPostRole, SysRole, SysUser, SysUserPost
def _user_display_name(nick_name: Optional[str], user_no: Optional[str]) -> str:
n = (nick_name or "").strip()
if n:
return n
return (user_no or "").strip()
def _bound_user_names_by_post_ids(
db: Session, post_ids: List[str], tenant_no: str
) -> Dict[str, List[str]]:
"""批量查询职位关联用户显示名(nick_name 优先,否则 user_no)。"""
out: Dict[str, List[str]] = {}
ids = [pid for pid in (post_ids or []) if pid]
tenant = (tenant_no or "").strip()
if not ids or not tenant:
return out
rows = (
db.query(SysUserPost.post_id, SysUser.nick_name, SysUser.user_no)
.join(
SysUser,
and_(
SysUser.id == SysUserPost.user_id,
SysUser.del_flag == 0,
),
)
.filter(
and_(
SysUserPost.post_id.in_(ids),
SysUserPost.tenant_no == tenant,
SysUserPost.del_flag == 0,
)
)
.all()
)
for post_id, nick_name, user_no in rows:
name = _user_display_name(nick_name, user_no)
if not name:
continue
out.setdefault(post_id, []).append(name)
return out
def _row_to_dict(p: SysPost, bound_user_names: Optional[List[str]] = None) -> Dict[str, Any]:
return {
"id": p.id,
"postName": p.post_name,
"postCode": p.post_code,
"status": p.status,
"tenantNo": p.tenant_no,
"orderNum": p.order_num if p.order_num is not None else 0,
"remark": p.remark or "",
"boundUserNames": list(bound_user_names or []),
"createTime": p.create_time.strftime("%Y-%m-%d %H:%M:%S") if p.create_time else "",
"updateTime": p.update_time.strftime("%Y-%m-%d %H:%M:%S") if p.update_time else "",
}
def list_posts(
db: Session,
*,
page: int = 1,
page_size: int = 10,
keyword: Optional[str] = None,
tenant_no: Optional[str] = None,
user_info=None,
) -> Tuple[List[Dict[str, Any]], int]:
def _filtered():
q0 = db.query(SysPost).filter(SysPost.del_flag == 0)
if tenant_no:
q0 = q0.filter(SysPost.tenant_no == tenant_no)
kw = (keyword or "").strip()
if kw:
like = f"%{kw}%"
q0 = q0.filter(or_(SysPost.post_name.like(like), SysPost.post_code.like(like)))
if user_info:
from app.core.data_scope import apply_data_scope
q0 = apply_data_scope(q0, SysPost, user_info, db, tenant_no)
return q0
total = _filtered().order_by(None).with_entities(func.count(SysPost.id)).scalar() or 0
page = max(1, page)
page_size = min(max(1, page_size), 100)
rows = (
_filtered()
.order_by(SysPost.create_time.desc())
.offset((page - 1) * page_size)
.limit(page_size)
.all()
)
name_map = _bound_user_names_by_post_ids(db, [x.id for x in rows], tenant_no or "")
return [
_row_to_dict(x, bound_user_names=name_map.get(x.id, [])) for x in rows
], int(total)
def get_post(db: Session, post_id: str) -> Optional[SysPost]:
return (
db.query(SysPost)
.filter(and_(SysPost.id == post_id, SysPost.del_flag == 0))
.first()
)
def post_code_exists(db: Session, code: str, tenant_no: str, exclude_id: Optional[str] = None) -> bool:
q = db.query(SysPost).filter(and_(SysPost.post_code == code.strip(), SysPost.tenant_no == tenant_no, SysPost.del_flag == 0))
if exclude_id:
q = q.filter(SysPost.id != exclude_id)
return q.first() is not None
def create_post(
db: Session,
*,
post_name: str,
post_code: str,
status: str = "1",
tenant_no: str = "zkxw",
order_num: Optional[int] = None,
remark: Optional[str] = None,
user_id: Optional[str] = None,
) -> SysPost:
pc = (post_code or "").strip()
if not pc:
raise ValueError("职位编码不能为空")
if post_code_exists(db, pc, tenant_no):
raise ValueError("职位编码已存在")
obj = SysPost(
id=generate_uuid(),
post_name=(post_name or "").strip() or pc,
post_code=pc,
status=status or "1",
tenant_no=tenant_no,
order_num=order_num if order_num is not None else 0,
remark=(remark or "").strip() or None,
create_by=user_id,
create_dept_id="",
create_time=datetime.now(),
update_by=user_id,
update_time=datetime.now(),
del_flag=0,
)
db.add(obj)
db.commit()
db.refresh(obj)
return obj
def update_post(
db: Session,
post_id: str,
*,
post_name: Optional[str] = None,
post_code: Optional[str] = None,
status: Optional[str] = None,
order_num: Optional[int] = None,
remark: Optional[str] = None,
user_id: Optional[str] = None,
) -> SysPost:
obj = get_post(db, post_id)
if not obj:
raise ValueError("职位不存在")
if post_code is not None:
new_code = post_code.strip()
if not new_code:
raise ValueError("职位编码不能为空")
if new_code != obj.post_code and post_code_exists(db, new_code, obj.tenant_no, exclude_id=post_id):
raise ValueError("职位编码已存在")
obj.post_code = new_code
if post_name is not None:
obj.post_name = (post_name or "").strip() or obj.post_name
if status is not None:
obj.status = status
if order_num is not None:
obj.order_num = order_num
if remark is not None:
obj.remark = (remark or "").strip() or None
obj.update_by = user_id
obj.update_time = datetime.now()
db.add(obj)
db.commit()
db.refresh(obj)
return obj
def soft_delete_post(db: Session, post_id: str) -> None:
obj = get_post(db, post_id)
if not obj:
raise ValueError("职位不存在")
now = datetime.now()
obj.del_flag = 1
obj.update_time = now
db.add(obj)
db.query(SysPostRole).filter(
and_(SysPostRole.post_id == post_id, SysPostRole.del_flag == 0)
).update({"del_flag": 1, "update_time": now}, synchronize_session=False)
db.commit()
def list_role_ids_for_post(db: Session, post_id: str, tenant_no: str) -> List[str]:
rows = (
db.query(SysPostRole.role_id)
.filter(
and_(
SysPostRole.post_id == post_id,
SysPostRole.tenant_no == tenant_no,
SysPostRole.del_flag == 0,
)
)
.all()
)
return [r[0] for r in rows]
def sync_post_role_bindings(
db: Session,
post_id: str,
tenant_no: str,
role_ids: List[str],
updater_id: Optional[str],
) -> None:
"""将本职位绑定的角色集合全量替换为 role_ids。"""
ordered = list(dict.fromkeys([rid for rid in (role_ids or []) if rid]))
for rid in ordered:
role = (
db.query(SysRole)
.filter(
and_(
SysRole.id == rid,
SysRole.del_flag == 0,
SysRole.tenant_no == tenant_no,
)
)
.first()
)
if not role:
raise ValueError("存在无效角色或不属于当前租户")
now = datetime.now()
db.query(SysPostRole).filter(
and_(
SysPostRole.post_id == post_id,
SysPostRole.tenant_no == tenant_no,
SysPostRole.del_flag == 0,
)
).update({"del_flag": 1, "update_time": now, "update_by": updater_id}, synchronize_session=False)
for rid in ordered:
db.add(
SysPostRole(
id=generate_uuid(),
post_id=post_id,
role_id=rid,
tenant_no=tenant_no,
order_num=0,
remark=None,
create_by=updater_id,
create_dept_id="",
create_time=now,
update_by=updater_id,
update_time=now,
del_flag=0,
)
)
db.commit()
def total_pages(total: int, page_size: int) -> int:
if total <= 0:
return 0
return int(math.ceil(total / float(page_size)))