Kaynağa Gözat

feat(backend): add collection region staff CRUD and list summaries

Co-authored-by: Cursor <cursoragent@cursor.com>
zlwu 1 ay önce
ebeveyn
işleme
412ec0771f

+ 289 - 5
backend/app/crud/collection_region.py

@@ -9,14 +9,43 @@ from sqlalchemy import and_, func, or_
 from sqlalchemy.orm import Session
 
 from app.core.utils import generate_uuid
-from app.models.models import BizCollectionHospital, BizCollectionRegion
-
-
-def _region_to_dict(r: BizCollectionRegion, hospital_names: Optional[List[str]] = None) -> Dict[str, Any]:
+from app.models.models import (
+    BizCollectionHospital,
+    BizCollectionRegion,
+    BizCollectionRegionStaff,
+    SysDept,
+    SysUser,
+)
+
+
+def staff_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 build_staff_leader_name_lists(rows: List[Dict[str, Any]]) -> Tuple[List[str], List[str]]:
+    """rows: dicts with nickName/isLeader (already sorted leaders-first preferred)."""
+    leaders = [staff_display_name(r.get("nickName"), r.get("userNo")) for r in rows if r.get("isLeader")]
+    # Keep leader-first order if caller sorted; otherwise partition
+    non_leaders = [staff_display_name(r.get("nickName"), r.get("userNo")) for r in rows if not r.get("isLeader")]
+    staff_names = leaders + non_leaders
+    return staff_names, leaders
+
+
+def _region_to_dict(
+    r: BizCollectionRegion,
+    hospital_names: Optional[List[str]] = None,
+    staff_names: Optional[List[str]] = None,
+    leader_names: Optional[List[str]] = None,
+) -> Dict[str, Any]:
     return {
         "id": r.id,
         "regionName": r.region_name,
         "hospitalNames": hospital_names or [],
+        "staffNames": staff_names or [],
+        "leaderNames": leader_names or [],
         "tenantNo": r.tenant_no,
         "orderNum": r.order_num if r.order_num is not None else 0,
         "remark": r.remark or "",
@@ -46,6 +75,43 @@ def _hospital_names_by_region_ids(db: Session, region_ids: List[str]) -> Dict[st
     return hospital_map
 
 
+def _staff_names_by_region_ids(
+    db: Session, region_ids: List[str]
+) -> Dict[str, Tuple[List[str], List[str]]]:
+    """Return region_id -> (staffNames, leaderNames). Leaders first in staffNames."""
+    out: Dict[str, Tuple[List[str], List[str]]] = {rid: ([], []) for rid in region_ids}
+    if not region_ids:
+        return out
+    rows = (
+        db.query(BizCollectionRegionStaff, SysUser)
+        .outerjoin(SysUser, SysUser.id == BizCollectionRegionStaff.user_id)
+        .filter(
+            and_(
+                BizCollectionRegionStaff.region_id.in_(region_ids),
+                BizCollectionRegionStaff.del_flag == 0,
+            )
+        )
+        .order_by(
+            BizCollectionRegionStaff.is_leader.desc(),
+            BizCollectionRegionStaff.order_num.asc(),
+            BizCollectionRegionStaff.create_time.desc(),
+        )
+        .all()
+    )
+    buckets: Dict[str, List[Dict[str, Any]]] = {rid: [] for rid in region_ids}
+    for staff, user in rows:
+        buckets.setdefault(staff.region_id, []).append(
+            {
+                "nickName": (user.nick_name if user else "") or "",
+                "userNo": (user.user_no if user else "") or "",
+                "isLeader": bool(staff.is_leader),
+            }
+        )
+    for rid, items in buckets.items():
+        out[rid] = build_staff_leader_name_lists(items)
+    return out
+
+
 def _hospital_to_dict(h: BizCollectionHospital) -> Dict[str, Any]:
     return {
         "id": h.id,
@@ -59,6 +125,30 @@ def _hospital_to_dict(h: BizCollectionHospital) -> Dict[str, Any]:
     }
 
 
+def _staff_to_dict(
+    s: BizCollectionRegionStaff,
+    *,
+    nick_name: str = "",
+    user_no: str = "",
+    dept_name: str = "",
+) -> Dict[str, Any]:
+    return {
+        "id": s.id,
+        "regionId": s.region_id,
+        "userId": s.user_id,
+        "userName": staff_display_name(nick_name, user_no),
+        "nickName": nick_name or "",
+        "userNo": user_no or "",
+        "deptName": dept_name or "",
+        "isLeader": bool(s.is_leader),
+        "tenantNo": s.tenant_no,
+        "orderNum": s.order_num if s.order_num is not None else 0,
+        "remark": s.remark or "",
+        "createTime": s.create_time.strftime("%Y-%m-%d %H:%M:%S") if s.create_time else "",
+        "updateTime": s.update_time.strftime("%Y-%m-%d %H:%M:%S") if s.update_time else "",
+    }
+
+
 def list_collection_regions(
     db: Session,
     *,
@@ -90,7 +180,16 @@ def list_collection_regions(
 
     region_ids = [r.id for r in rows]
     hospital_map = _hospital_names_by_region_ids(db, region_ids)
-    return [_region_to_dict(x, hospital_names=hospital_map.get(x.id, [])) for x in rows], int(total)
+    staff_map = _staff_names_by_region_ids(db, region_ids)
+    return [
+        _region_to_dict(
+            x,
+            hospital_names=hospital_map.get(x.id, []),
+            staff_names=staff_map.get(x.id, ([], []))[0],
+            leader_names=staff_map.get(x.id, ([], []))[1],
+        )
+        for x in rows
+    ], int(total)
 
 
 def get_collection_region(db: Session, region_id: str, tenant_no: Optional[str] = None) -> Optional[BizCollectionRegion]:
@@ -183,6 +282,20 @@ def soft_delete_collection_region(db: Session, region_id: str, tenant_no: Option
         h.del_flag = 1
         h.update_time = now
         db.add(h)
+    staff_subs = (
+        db.query(BizCollectionRegionStaff)
+        .filter(
+            and_(
+                BizCollectionRegionStaff.region_id == region_id,
+                BizCollectionRegionStaff.del_flag == 0,
+            )
+        )
+        .all()
+    )
+    for s in staff_subs:
+        s.del_flag = 1
+        s.update_time = now
+        db.add(s)
     db.commit()
 
 
@@ -343,6 +456,177 @@ def soft_delete_hospital(db: Session, hospital_id: str, tenant_no: Optional[str]
     db.commit()
 
 
+def list_staff(
+    db: Session,
+    *,
+    region_id: str,
+    page: int = 1,
+    page_size: int = 10,
+    tenant_no: Optional[str] = None,
+) -> Tuple[List[Dict[str, Any]], int]:
+    region = get_collection_region(db, region_id, tenant_no=tenant_no)
+    if not region:
+        raise ValueError("采集地区不存在")
+
+    def _filtered():
+        q0 = db.query(BizCollectionRegionStaff).filter(
+            and_(BizCollectionRegionStaff.region_id == region_id, BizCollectionRegionStaff.del_flag == 0)
+        )
+        if tenant_no:
+            q0 = q0.filter(BizCollectionRegionStaff.tenant_no == tenant_no)
+        return q0
+
+    total = _filtered().order_by(None).with_entities(func.count(BizCollectionRegionStaff.id)).scalar() or 0
+    page = max(1, page)
+    page_size = min(max(1, page_size), 100)
+    rows = (
+        _filtered()
+        .order_by(
+            BizCollectionRegionStaff.is_leader.desc(),
+            BizCollectionRegionStaff.order_num.asc(),
+            BizCollectionRegionStaff.create_time.desc(),
+        )
+        .offset((page - 1) * page_size)
+        .limit(page_size)
+        .all()
+    )
+    user_ids = [r.user_id for r in rows]
+    user_map: Dict[str, SysUser] = {}
+    dept_map: Dict[str, str] = {}
+    if user_ids:
+        for u in db.query(SysUser).filter(SysUser.id.in_(user_ids)).all():
+            user_map[u.id] = u
+        dept_ids = [u.dept_id for u in user_map.values() if u.dept_id]
+        if dept_ids:
+            for d in db.query(SysDept).filter(SysDept.id.in_(dept_ids), SysDept.del_flag == 0).all():
+                dept_map[d.id] = d.dept_name or ""
+    items: List[Dict[str, Any]] = []
+    for s in rows:
+        u = user_map.get(s.user_id)
+        items.append(
+            _staff_to_dict(
+                s,
+                nick_name=(u.nick_name if u else "") or "",
+                user_no=(u.user_no if u else "") or "",
+                dept_name=dept_map.get((u.dept_id if u else "") or "", ""),
+            )
+        )
+    return items, int(total)
+
+
+def get_staff(db: Session, staff_id: str, tenant_no: Optional[str] = None) -> Optional[BizCollectionRegionStaff]:
+    q = db.query(BizCollectionRegionStaff).filter(
+        and_(BizCollectionRegionStaff.id == staff_id, BizCollectionRegionStaff.del_flag == 0)
+    )
+    if tenant_no:
+        q = q.filter(BizCollectionRegionStaff.tenant_no == tenant_no)
+    return q.first()
+
+
+def create_staff(
+    db: Session,
+    *,
+    region_id: str,
+    user_id: str,
+    is_leader: bool = False,
+    tenant_no: str = "zkxw",
+    order_num: Optional[int] = None,
+    remark: Optional[str] = None,
+    user_id_actor: Optional[str] = None,
+) -> BizCollectionRegionStaff:
+    region = get_collection_region(db, region_id, tenant_no=tenant_no)
+    if not region:
+        raise ValueError("采集地区不存在")
+    uid = (user_id or "").strip()
+    if not uid:
+        raise ValueError("请选择系统人员")
+    user = (
+        db.query(SysUser)
+        .filter(
+            and_(
+                SysUser.id == uid,
+                SysUser.del_flag == 0,
+                SysUser.tenant_no == tenant_no,
+            )
+        )
+        .first()
+    )
+    if not user:
+        raise ValueError("用户不存在")
+    if (user.status or "") != "1":
+        raise ValueError("用户已停用,无法绑定")
+    exists = (
+        db.query(BizCollectionRegionStaff)
+        .filter(
+            and_(
+                BizCollectionRegionStaff.region_id == region_id,
+                BizCollectionRegionStaff.user_id == uid,
+                BizCollectionRegionStaff.del_flag == 0,
+                BizCollectionRegionStaff.tenant_no == tenant_no,
+            )
+        )
+        .first()
+    )
+    if exists:
+        raise ValueError("该人员已绑定到此地区")
+    obj = BizCollectionRegionStaff(
+        id=generate_uuid(),
+        region_id=region_id,
+        user_id=uid,
+        is_leader=1 if is_leader else 0,
+        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_actor,
+        create_dept_id="",
+        create_time=datetime.now(),
+        update_by=user_id_actor,
+        update_time=datetime.now(),
+        del_flag=0,
+    )
+    db.add(obj)
+    db.commit()
+    db.refresh(obj)
+    return obj
+
+
+def update_staff(
+    db: Session,
+    staff_id: str,
+    *,
+    is_leader: Optional[bool] = None,
+    order_num: Optional[int] = None,
+    remark: Optional[str] = None,
+    user_id: Optional[str] = None,
+    tenant_no: Optional[str] = None,
+) -> BizCollectionRegionStaff:
+    obj = get_staff(db, staff_id, tenant_no=tenant_no)
+    if not obj:
+        raise ValueError("绑定记录不存在")
+    if is_leader is not None:
+        obj.is_leader = 1 if is_leader else 0
+    if order_num is not None:
+        obj.order_num = order_num
+    if remark is not None:
+        obj.remark = remark.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_staff(db: Session, staff_id: str, tenant_no: Optional[str] = None) -> None:
+    obj = get_staff(db, staff_id, tenant_no=tenant_no)
+    if not obj:
+        raise ValueError("绑定记录不存在")
+    obj.del_flag = 1
+    obj.update_time = datetime.now()
+    db.add(obj)
+    db.commit()
+
+
 def total_pages(total: int, page_size: int) -> int:
     if total <= 0:
         return 0

+ 26 - 0
backend/tests/test_collection_region_staff_helpers.py

@@ -0,0 +1,26 @@
+import unittest
+
+from app.crud.collection_region import staff_display_name, build_staff_leader_name_lists
+
+
+class CollectionRegionStaffHelperTests(unittest.TestCase):
+    def test_staff_display_name_prefers_nick_name(self):
+        self.assertEqual(staff_display_name("张敏", "SQ001"), "张敏")
+
+    def test_staff_display_name_falls_back_to_user_no(self):
+        self.assertEqual(staff_display_name("", "SQ001"), "SQ001")
+        self.assertEqual(staff_display_name(None, "SQ001"), "SQ001")
+
+    def test_build_staff_leader_name_lists_leaders_first_in_staff(self):
+        rows = [
+            {"userId": "u2", "nickName": "李强", "isLeader": False},
+            {"userId": "u1", "nickName": "张敏", "isLeader": True},
+            {"userId": "u3", "nickName": "王芳", "isLeader": True},
+        ]
+        staff_names, leader_names = build_staff_leader_name_lists(rows)
+        self.assertEqual(leader_names, ["张敏", "王芳"])
+        self.assertEqual(staff_names, ["张敏", "王芳", "李强"])
+
+
+if __name__ == "__main__":
+    unittest.main()