zlwu 5 mesi fa
parent
commit
276299ff27

+ 1 - 1
backend/app.pid

@@ -1 +1 @@
-87270
+12254

+ 20 - 4
backend/app/api/admin.py

@@ -5,6 +5,7 @@ from typing import Any, List, Optional
 
 
 from fastapi import APIRouter, Depends, Query, UploadFile, File, Form
 from fastapi import APIRouter, Depends, Query, UploadFile, File, Form
 from pydantic import BaseModel, ConfigDict, Field
 from pydantic import BaseModel, ConfigDict, Field
+from sqlalchemy import or_
 from sqlalchemy.orm import Session
 from sqlalchemy.orm import Session
 
 
 from app.core.config import settings
 from app.core.config import settings
@@ -25,7 +26,7 @@ from app.crud import menu as menu_svc
 from app.crud import role as role_svc
 from app.crud import role as role_svc
 from app.crud import user as user_svc
 from app.crud import user as user_svc
 from app.crud.collection_protocol import _lookup_attachment_by_url
 from app.crud.collection_protocol import _lookup_attachment_by_url
-from app.models.models import BizAttachment, SysRole, SysUser
+from app.models.models import BizAttachment, SysDept, SysRole, SysUser
 from app.services.web_auth_roles import _map_role_name_to_web
 from app.services.web_auth_roles import _map_role_name_to_web
 
 
 router = APIRouter(prefix="/web/admin", tags=["运营端管理"])
 router = APIRouter(prefix="/web/admin", tags=["运营端管理"])
@@ -272,8 +273,16 @@ async def list_users(
         SysUser.del_flag == 0,
         SysUser.del_flag == 0,
         SysUser.tenant_no == tenant_no,
         SysUser.tenant_no == tenant_no,
     )
     )
-    if keyword:
-        query = query.filter(SysUser.nick_name.like(f"%{keyword}%"))
+    kw = (keyword or "").strip()
+    if kw:
+        like = f"%{kw}%"
+        query = query.filter(
+            or_(
+                SysUser.user_no.like(like),
+                SysUser.nick_name.like(like),
+                SysUser.phone_no.like(like),
+            )
+        )
     if deptId:
     if deptId:
         query = query.filter(SysUser.dept_id == deptId)
         query = query.filter(SysUser.dept_id == deptId)
     if status:
     if status:
@@ -281,7 +290,14 @@ async def list_users(
     query = apply_data_scope(query, SysUser, user, db, tenant_no)
     query = apply_data_scope(query, SysUser, user, db, tenant_no)
     total = query.count()
     total = query.count()
     items = query.order_by(SysUser.create_time.desc()).offset((page - 1) * pageSize).limit(pageSize).all()
     items = query.order_by(SysUser.create_time.desc()).offset((page - 1) * pageSize).limit(pageSize).all()
-    result_list = [user_svc._row_to_dict(r) for r in items]
+    dept_ids = list({r.dept_id for r in items if r.dept_id})
+    dept_map: dict[str, str] = {}
+    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 ""
+    result_list = [
+        user_svc._row_to_dict(r, dept_name=dept_map.get(r.dept_id or "", "")) for r in items
+    ]
     tp = user_svc.total_pages(total, pageSize)
     tp = user_svc.total_pages(total, pageSize)
     return success_response({"list": result_list, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
     return success_response({"list": result_list, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
 
 

+ 29 - 2
backend/app/crud/dept.py

@@ -35,6 +35,22 @@ def _build_tree(items: List[Dict[str, Any]], parent_id: str = "0") -> List[Dict[
     return children
     return children
 
 
 
 
+def _collect_with_ancestors(
+    items: List[Dict[str, Any]], seed_ids: set[str]
+) -> List[Dict[str, Any]]:
+    """保留匹配节点及其全部上级,供关键词检索后仍能正确组树。"""
+    by_id = {item["id"]: item for item in items if item.get("id")}
+    keep: set[str] = set()
+    for sid in seed_ids:
+        cur = (sid or "").strip()
+        while cur and cur != "0":
+            if cur not in by_id:
+                break
+            keep.add(cur)
+            cur = (by_id[cur].get("parentId") or "0").strip()
+    return [by_id[i] for i in keep if i in by_id]
+
+
 def list_depts(
 def list_depts(
     db: Session,
     db: Session,
     *,
     *,
@@ -58,8 +74,19 @@ def list_depts_tree(
     keyword: Optional[str] = None,
     keyword: Optional[str] = None,
     tenant_no: Optional[str] = None,
     tenant_no: Optional[str] = None,
 ) -> List[Dict[str, Any]]:
 ) -> List[Dict[str, Any]]:
-    flat = list_depts(db, keyword=keyword, tenant_no=tenant_no)
-    return _build_tree(flat, "0")
+    kw = (keyword or "").strip()
+    if not kw:
+        flat = list_depts(db, keyword=None, tenant_no=tenant_no)
+        return _build_tree(flat, "0")
+
+    matched = list_depts(db, keyword=keyword, tenant_no=tenant_no)
+    if not matched:
+        return []
+
+    all_flat = list_depts(db, keyword=None, tenant_no=tenant_no)
+    seed_ids = {m["id"] for m in matched if m.get("id")}
+    expanded = _collect_with_ancestors(all_flat, seed_ids)
+    return _build_tree(expanded, "0")
 
 
 
 
 def get_dept(db: Session, dept_id: str) -> Optional[SysDept]:
 def get_dept(db: Session, dept_id: str) -> Optional[SysDept]:

File diff suppressed because it is too large
+ 0 - 0
backend/app/graphify-out/cache/ast/5f0e9bae2fbc86fffb6c205f941007475943651b59cdb146bc43d9b9fee77b71.json


File diff suppressed because it is too large
+ 789 - 1082
backend/graphify-out/.graphify_analysis.json


+ 92 - 22
backend/graphify-out/.graphify_extract.json

@@ -11550,54 +11550,68 @@
       "source_file": "crud/dept.py",
       "source_file": "crud/dept.py",
       "source_location": "L30"
       "source_location": "L30"
     },
     },
+    {
+      "id": "crud_dept_collect_with_ancestors",
+      "label": "_collect_with_ancestors()",
+      "file_type": "code",
+      "source_file": "crud/dept.py",
+      "source_location": "L38"
+    },
     {
     {
       "id": "crud_dept_list_depts",
       "id": "crud_dept_list_depts",
       "label": "list_depts()",
       "label": "list_depts()",
       "file_type": "code",
       "file_type": "code",
       "source_file": "crud/dept.py",
       "source_file": "crud/dept.py",
-      "source_location": "L38"
+      "source_location": "L54"
     },
     },
     {
     {
       "id": "crud_dept_list_depts_tree",
       "id": "crud_dept_list_depts_tree",
       "label": "list_depts_tree()",
       "label": "list_depts_tree()",
       "file_type": "code",
       "file_type": "code",
       "source_file": "crud/dept.py",
       "source_file": "crud/dept.py",
-      "source_location": "L55"
+      "source_location": "L71"
     },
     },
     {
     {
       "id": "crud_dept_get_dept",
       "id": "crud_dept_get_dept",
       "label": "get_dept()",
       "label": "get_dept()",
       "file_type": "code",
       "file_type": "code",
       "source_file": "crud/dept.py",
       "source_file": "crud/dept.py",
-      "source_location": "L65"
+      "source_location": "L92"
     },
     },
     {
     {
       "id": "crud_dept_create_dept",
       "id": "crud_dept_create_dept",
       "label": "create_dept()",
       "label": "create_dept()",
       "file_type": "code",
       "file_type": "code",
       "source_file": "crud/dept.py",
       "source_file": "crud/dept.py",
-      "source_location": "L73"
+      "source_location": "L100"
     },
     },
     {
     {
       "id": "crud_dept_update_dept",
       "id": "crud_dept_update_dept",
       "label": "update_dept()",
       "label": "update_dept()",
       "file_type": "code",
       "file_type": "code",
       "source_file": "crud/dept.py",
       "source_file": "crud/dept.py",
-      "source_location": "L114"
+      "source_location": "L141"
     },
     },
     {
     {
       "id": "crud_dept_cascade_soft_delete",
       "id": "crud_dept_cascade_soft_delete",
       "label": "_cascade_soft_delete()",
       "label": "_cascade_soft_delete()",
       "file_type": "code",
       "file_type": "code",
       "source_file": "crud/dept.py",
       "source_file": "crud/dept.py",
-      "source_location": "L152"
+      "source_location": "L179"
     },
     },
     {
     {
       "id": "crud_dept_soft_delete_dept",
       "id": "crud_dept_soft_delete_dept",
       "label": "soft_delete_dept()",
       "label": "soft_delete_dept()",
       "file_type": "code",
       "file_type": "code",
       "source_file": "crud/dept.py",
       "source_file": "crud/dept.py",
-      "source_location": "L165"
+      "source_location": "L192"
+    },
+    {
+      "id": "crud_dept_rationale_41",
+      "label": "\u4fdd\u7559\u5339\u914d\u8282\u70b9\u53ca\u5176\u5168\u90e8\u4e0a\u7ea7\uff0c\u4f9b\u5173\u952e\u8bcd\u68c0\u7d22\u540e\u4ecd\u80fd\u6b63\u786e\u7ec4\u6811\u3002",
+      "file_type": "rationale",
+      "source_file": "crud/dept.py",
+      "source_location": "L41"
     },
     },
     {
     {
       "id": "crud_base_py",
       "id": "crud_base_py",
@@ -45800,20 +45814,29 @@
     },
     },
     {
     {
       "source": "crud_dept_py",
       "source": "crud_dept_py",
-      "target": "crud_dept_list_depts",
+      "target": "crud_dept_collect_with_ancestors",
       "relation": "contains",
       "relation": "contains",
       "confidence": "EXTRACTED",
       "confidence": "EXTRACTED",
       "source_file": "crud/dept.py",
       "source_file": "crud/dept.py",
       "source_location": "L38",
       "source_location": "L38",
       "weight": 1.0
       "weight": 1.0
     },
     },
+    {
+      "source": "crud_dept_py",
+      "target": "crud_dept_list_depts",
+      "relation": "contains",
+      "confidence": "EXTRACTED",
+      "source_file": "crud/dept.py",
+      "source_location": "L54",
+      "weight": 1.0
+    },
     {
     {
       "source": "crud_dept_py",
       "source": "crud_dept_py",
       "target": "crud_dept_list_depts_tree",
       "target": "crud_dept_list_depts_tree",
       "relation": "contains",
       "relation": "contains",
       "confidence": "EXTRACTED",
       "confidence": "EXTRACTED",
       "source_file": "crud/dept.py",
       "source_file": "crud/dept.py",
-      "source_location": "L55",
+      "source_location": "L71",
       "weight": 1.0
       "weight": 1.0
     },
     },
     {
     {
@@ -45822,7 +45845,7 @@
       "relation": "contains",
       "relation": "contains",
       "confidence": "EXTRACTED",
       "confidence": "EXTRACTED",
       "source_file": "crud/dept.py",
       "source_file": "crud/dept.py",
-      "source_location": "L65",
+      "source_location": "L92",
       "weight": 1.0
       "weight": 1.0
     },
     },
     {
     {
@@ -45831,7 +45854,7 @@
       "relation": "contains",
       "relation": "contains",
       "confidence": "EXTRACTED",
       "confidence": "EXTRACTED",
       "source_file": "crud/dept.py",
       "source_file": "crud/dept.py",
-      "source_location": "L73",
+      "source_location": "L100",
       "weight": 1.0
       "weight": 1.0
     },
     },
     {
     {
@@ -45840,7 +45863,7 @@
       "relation": "contains",
       "relation": "contains",
       "confidence": "EXTRACTED",
       "confidence": "EXTRACTED",
       "source_file": "crud/dept.py",
       "source_file": "crud/dept.py",
-      "source_location": "L114",
+      "source_location": "L141",
       "weight": 1.0
       "weight": 1.0
     },
     },
     {
     {
@@ -45849,7 +45872,7 @@
       "relation": "contains",
       "relation": "contains",
       "confidence": "EXTRACTED",
       "confidence": "EXTRACTED",
       "source_file": "crud/dept.py",
       "source_file": "crud/dept.py",
-      "source_location": "L152",
+      "source_location": "L179",
       "weight": 1.0
       "weight": 1.0
     },
     },
     {
     {
@@ -45858,7 +45881,7 @@
       "relation": "contains",
       "relation": "contains",
       "confidence": "EXTRACTED",
       "confidence": "EXTRACTED",
       "source_file": "crud/dept.py",
       "source_file": "crud/dept.py",
-      "source_location": "L165",
+      "source_location": "L192",
       "weight": 1.0
       "weight": 1.0
     },
     },
     {
     {
@@ -45867,7 +45890,7 @@
       "relation": "calls",
       "relation": "calls",
       "confidence": "EXTRACTED",
       "confidence": "EXTRACTED",
       "source_file": "crud/dept.py",
       "source_file": "crud/dept.py",
-      "source_location": "L52",
+      "source_location": "L68",
       "weight": 1.0
       "weight": 1.0
     },
     },
     {
     {
@@ -45876,7 +45899,7 @@
       "relation": "calls",
       "relation": "calls",
       "confidence": "EXTRACTED",
       "confidence": "EXTRACTED",
       "source_file": "crud/dept.py",
       "source_file": "crud/dept.py",
-      "source_location": "L61",
+      "source_location": "L79",
       "weight": 1.0
       "weight": 1.0
     },
     },
     {
     {
@@ -45885,7 +45908,16 @@
       "relation": "calls",
       "relation": "calls",
       "confidence": "EXTRACTED",
       "confidence": "EXTRACTED",
       "source_file": "crud/dept.py",
       "source_file": "crud/dept.py",
-      "source_location": "L62",
+      "source_location": "L80",
+      "weight": 1.0
+    },
+    {
+      "source": "crud_dept_list_depts_tree",
+      "target": "crud_dept_collect_with_ancestors",
+      "relation": "calls",
+      "confidence": "EXTRACTED",
+      "source_file": "crud/dept.py",
+      "source_location": "L88",
       "weight": 1.0
       "weight": 1.0
     },
     },
     {
     {
@@ -45894,7 +45926,7 @@
       "relation": "calls",
       "relation": "calls",
       "confidence": "EXTRACTED",
       "confidence": "EXTRACTED",
       "source_file": "crud/dept.py",
       "source_file": "crud/dept.py",
-      "source_location": "L127",
+      "source_location": "L154",
       "weight": 1.0
       "weight": 1.0
     },
     },
     {
     {
@@ -45903,7 +45935,7 @@
       "relation": "calls",
       "relation": "calls",
       "confidence": "EXTRACTED",
       "confidence": "EXTRACTED",
       "source_file": "crud/dept.py",
       "source_file": "crud/dept.py",
-      "source_location": "L166",
+      "source_location": "L193",
       "weight": 1.0
       "weight": 1.0
     },
     },
     {
     {
@@ -45912,7 +45944,16 @@
       "relation": "calls",
       "relation": "calls",
       "confidence": "EXTRACTED",
       "confidence": "EXTRACTED",
       "source_file": "crud/dept.py",
       "source_file": "crud/dept.py",
-      "source_location": "L169",
+      "source_location": "L196",
+      "weight": 1.0
+    },
+    {
+      "source": "crud_dept_rationale_41",
+      "target": "crud_dept_collect_with_ancestors",
+      "relation": "rationale_for",
+      "confidence": "EXTRACTED",
+      "source_file": "crud/dept.py",
+      "source_location": "L41",
       "weight": 1.0
       "weight": 1.0
     },
     },
     {
     {
@@ -76011,6 +76052,15 @@
       "source_location": "L12",
       "source_location": "L12",
       "weight": 0.8
       "weight": 0.8
     },
     },
+    {
+      "source": "crud_dept_rationale_41",
+      "target": "models_models_sysdept",
+      "relation": "uses",
+      "confidence": "INFERRED",
+      "source_file": "crud/dept.py",
+      "source_location": "L10",
+      "weight": 0.8
+    },
     {
     {
       "source": "crud_protocol_donation_convert_rationale_1",
       "source": "crud_protocol_donation_convert_rationale_1",
       "target": "models_models_bizcollectionprotocol",
       "target": "models_models_bizcollectionprotocol",
@@ -91422,6 +91472,26 @@
       "source_location": "L31",
       "source_location": "L31",
       "weight": 1.0
       "weight": 1.0
     },
     },
+    {
+      "source": "crud_dept_collect_with_ancestors",
+      "target": "crud_base_crudbase_get",
+      "relation": "calls",
+      "confidence": "INFERRED",
+      "confidence_score": 0.8,
+      "source_file": "crud/dept.py",
+      "source_location": "L42",
+      "weight": 1.0
+    },
+    {
+      "source": "crud_dept_list_depts_tree",
+      "target": "crud_base_crudbase_get",
+      "relation": "calls",
+      "confidence": "INFERRED",
+      "confidence_score": 0.8,
+      "source_file": "crud/dept.py",
+      "source_location": "L87",
+      "weight": 1.0
+    },
     {
     {
       "source": "crud_dept_create_dept",
       "source": "crud_dept_create_dept",
       "target": "models_models_sysdept",
       "target": "models_models_sysdept",
@@ -91429,7 +91499,7 @@
       "confidence": "INFERRED",
       "confidence": "INFERRED",
       "confidence_score": 0.8,
       "confidence_score": 0.8,
       "source_file": "crud/dept.py",
       "source_file": "crud/dept.py",
-      "source_location": "L90",
+      "source_location": "L117",
       "weight": 1.0
       "weight": 1.0
     },
     },
     {
     {
@@ -91439,7 +91509,7 @@
       "confidence": "INFERRED",
       "confidence": "INFERRED",
       "confidence_score": 0.8,
       "confidence_score": 0.8,
       "source_file": "crud/dept.py",
       "source_file": "crud/dept.py",
-      "source_location": "L91",
+      "source_location": "L118",
       "weight": 1.0
       "weight": 1.0
     },
     },
     {
     {

+ 86 - 91
backend/graphify-out/GRAPH_REPORT.md

@@ -1,12 +1,12 @@
 # Graph Report - app  (2026-05-20)
 # Graph Report - app  (2026-05-20)
 
 
 ## Corpus Check
 ## Corpus Check
-- 135 files · ~112,579 words
+- 135 files · ~112,681 words
 - Verdict: corpus is large enough that graph structure adds value.
 - Verdict: corpus is large enough that graph structure adds value.
 
 
 ## Summary
 ## Summary
-- 2318 nodes · 9000 edges · 51 communities detected
-- Extraction: 42% EXTRACTED · 58% INFERRED · 0% AMBIGUOUS · INFERRED: 5206 edges (avg confidence: 0.63)
+- 2320 nodes · 9006 edges · 50 communities detected
+- Extraction: 42% EXTRACTED · 58% INFERRED · 0% AMBIGUOUS · INFERRED: 5209 edges (avg confidence: 0.63)
 - Token cost: 0 input · 0 output
 - Token cost: 0 input · 0 output
 
 
 ## Community Hubs (Navigation)
 ## Community Hubs (Navigation)
@@ -54,13 +54,12 @@
 - [[_COMMUNITY_Community 41|Community 41]]
 - [[_COMMUNITY_Community 41|Community 41]]
 - [[_COMMUNITY_Community 42|Community 42]]
 - [[_COMMUNITY_Community 42|Community 42]]
 - [[_COMMUNITY_Community 43|Community 43]]
 - [[_COMMUNITY_Community 43|Community 43]]
-- [[_COMMUNITY_Community 44|Community 44]]
+- [[_COMMUNITY_Community 46|Community 46]]
 - [[_COMMUNITY_Community 47|Community 47]]
 - [[_COMMUNITY_Community 47|Community 47]]
 - [[_COMMUNITY_Community 48|Community 48]]
 - [[_COMMUNITY_Community 48|Community 48]]
 - [[_COMMUNITY_Community 49|Community 49]]
 - [[_COMMUNITY_Community 49|Community 49]]
 - [[_COMMUNITY_Community 50|Community 50]]
 - [[_COMMUNITY_Community 50|Community 50]]
 - [[_COMMUNITY_Community 51|Community 51]]
 - [[_COMMUNITY_Community 51|Community 51]]
-- [[_COMMUNITY_Community 52|Community 52]]
 
 
 ## God Nodes (most connected - your core abstractions)
 ## God Nodes (most connected - your core abstractions)
 1. `success_response()` - 368 edges
 1. `success_response()` - 368 edges
@@ -90,84 +89,84 @@
 
 
 ### Community 0 - "Community 0"
 ### Community 0 - "Community 0"
 Cohesion: 0.02
 Cohesion: 0.02
-Nodes (414): attachment_download_url(), create_dept(), create_dict_data(), create_dict_type(), create_express_company(), create_item_unit(), create_menu(), create_post() (+406 more)
+Nodes (377): AttachmentDownloadUrlBody, DeptCreateBody, DeptUpdateBody, DictDataCreateBody, DictDataUpdateBody, DictTypeCreateBody, DictTypeUpdateBody, ExpressCompanyCreateBody (+369 more)
 
 
 ### Community 1 - "Community 1"
 ### Community 1 - "Community 1"
 Cohesion: 0.02
 Cohesion: 0.02
-Nodes (347): MaterialBody, MaterialUpdateBody, StockInBody, StockOutBody, CellProductPriceCreateBody, CellProductPriceUpdateBody, CellProductSpecCreateBody, CellProductSpecUpdateBody (+339 more)
+Nodes (370): attachment_download_url(), create_dept(), create_dict_data(), create_dict_type(), create_express_company(), create_item_unit(), create_menu(), create_post() (+362 more)
 
 
 ### Community 2 - "Community 2"
 ### Community 2 - "Community 2"
-Cohesion: 0.01
-Nodes (224): get_designer_data(), generate_uuid(), delete(), get_multi_with_filters(), create_cell_product(), delete_cell_product(), get_cell_product(), list_cell_products() (+216 more)
+Cohesion: 0.06
+Nodes (211): AccessLogCreateBody, AccessLogUpdateBody, CleanRoomCreateBody, CleanRoomUpdateBody, ExhibitionAppointmentCreateBody, ExhibitionAppointmentUpdateBody, 运营支撑 API(洁净室、进出记录、仓储设施、储位、展厅预约), StorageFacilityCreateBody (+203 more)
 
 
 ### Community 3 - "Community 3"
 ### Community 3 - "Community 3"
 Cohesion: 0.05
 Cohesion: 0.05
-Nodes (238): InsuranceCompanyCreateBody, InsuranceCompanyInvoiceCreateBody, InsuranceCompanyInvoiceUpdateBody, InsuranceCompanyUpdateBody, InsuranceProjectPriceCreateBody, InsuranceProjectPriceUpdateBody, 保险公司及险种价格管理 API(保险公司 + 开票信息 + 险种价格), AccessLogCreateBody (+230 more)
+Nodes (179): CellSampleTypeCreateBody, CellSampleTypeUpdateBody, CollectionRegionCreateBody, CollectionRegionUpdateBody, create_collection_region(), create_hospital(), CustomerCreateBody, CustomerUpdateBody (+171 more)
 
 
 ### Community 4 - "Community 4"
 ### Community 4 - "Community 4"
-Cohesion: 0.07
-Nodes (163): AttachmentDownloadUrlBody, DeptCreateBody, DeptUpdateBody, DictDataCreateBody, DictDataUpdateBody, DictTypeCreateBody, DictTypeUpdateBody, ExpressCompanyCreateBody (+155 more)
+Cohesion: 0.02
+Nodes (114): list_cell_sample_types(), list_customers(), list_protocol_nos(), list_sample_codes(), apply_data_scope(), _get_child_dept_ids(), get_user_dept_id(), _resolve_user_role_scopes() (+106 more)
 
 
 ### Community 5 - "Community 5"
 ### Community 5 - "Community 5"
-Cohesion: 0.05
-Nodes (81): _format_attachment_file_size(), urls_to_attachment_list(), complete_production_work_order(), create_production_work_order_from_reception(), _fmt_dt(), _generate_work_order_no(), get_production_work_order(), list_production_work_orders() (+73 more)
+Cohesion: 0.04
+Nodes (124): _persist_protocol_sub_resources(), generate_uuid(), _add_months_yyyymmdd(), align_fq_payment_details_payload(), _apply_submit_receipt_finance(), _apply_submit_refund_finance(), attachments_to_urls(), _audit_to_dict() (+116 more)
 
 
 ### Community 6 - "Community 6"
 ### Community 6 - "Community 6"
-Cohesion: 0.04
-Nodes (63): ApiRequestLogMiddleware, _claims_from_auth(), _format_body_bytes(), _persist_log(), 统一记录 API 请求/响应出入参的中间件, _redact_obj(), _schedule_persist_log(), _should_skip() (+55 more)
+Cohesion: 0.03
+Nodes (86): activate_channel_agreement(), cell_product_price_code_exists(), cell_product_spec_code_exists(), cell_product_type_code_exists(), _channel_agreement_price_exists(), _channel_agreement_price_item_key(), checkup_package_code_exists(), create_cell_product_price() (+78 more)
 
 
 ### Community 7 - "Community 7"
 ### Community 7 - "Community 7"
-Cohesion: 0.06
-Nodes (76): _persist_protocol_sub_resources(), _add_months_yyyymmdd(), align_fq_payment_details_payload(), _apply_submit_receipt_finance(), _apply_submit_refund_finance(), attachments_to_urls(), _audit_to_dict(), batch_submit_receipt_finance() (+68 more)
+Cohesion: 0.05
+Nodes (83): _format_attachment_file_size(), urls_to_attachment_list(), complete_production_work_order(), create_production_work_order_from_reception(), _fmt_dt(), _generate_work_order_no(), get_production_work_order(), list_production_work_orders() (+75 more)
 
 
 ### Community 8 - "Community 8"
 ### Community 8 - "Community 8"
-Cohesion: 0.05
-Nodes (59): list_roles_for_select(), _avatar_url_field(), _build_user_info_payload(), get_web_auth_menu_codes(), _jwt_user_payload(), logout(), 公共接口(Web 登录、刷新 Token、登出;用户模型对齐 docs/sql/zkxw.sql 基线), 登出:吊销 Redis 会话(若已配置 REDIS_HOST)。 (+51 more)
+Cohesion: 0.04
+Nodes (33): BusinessException, InvalidStatusTransitionException, log_delivery_operation(), log_operation(), log_order_operation(), log_settlement_operation(), log_user_operation(), OrderLockedException (+25 more)
 
 
 ### Community 9 - "Community 9"
 ### Community 9 - "Community 9"
-Cohesion: 0.05
-Nodes (26): BusinessException, InvalidStatusTransitionException, log_delivery_operation(), log_operation(), log_order_operation(), log_settlement_operation(), log_user_operation(), OrderLockedException (+18 more)
+Cohesion: 0.06
+Nodes (59): _business_type_label(), _enrich_task_list(), get_business_all_tasks(), _get_business_form_fields(), get_designer_data(), get_instance_detail(), list_completed_tasks(), list_my_instances() (+51 more)
 
 
 ### Community 10 - "Community 10"
 ### Community 10 - "Community 10"
-Cohesion: 0.13
-Nodes (38): _parse_datetime_fields(), parse_datetime(), _audit_to_dict(), batch_delete_donations(), change_status(), _collection_to_dict(), convert_to_storage(), _create_collection() (+30 more)
+Cohesion: 0.05
+Nodes (59): list_roles_for_select(), _avatar_url_field(), _build_user_info_payload(), get_web_auth_menu_codes(), _jwt_user_payload(), logout(), 公共接口(Web 登录、刷新 Token、登出;用户模型对齐 docs/sql/zkxw.sql 基线), 登出:吊销 Redis 会话(若已配置 REDIS_HOST)。 (+51 more)
 
 
 ### Community 11 - "Community 11"
 ### Community 11 - "Community 11"
-Cohesion: 0.16
-Nodes (26): add_step(), copy_step(), create_route(), _create_step_consumable(), _create_step_data_collection(), _create_step_output(), _create_step_reagent(), get_route() (+18 more)
+Cohesion: 0.04
+Nodes (47): BaseHTTPMiddleware, ApiCryptoError, _b64url_decode(), _b64url_encode(), decrypt_json(), derive_key(), encrypt_json(), gen_nonce_hex() (+39 more)
 
 
 ### Community 12 - "Community 12"
 ### Community 12 - "Community 12"
-Cohesion: 0.12
-Nodes (23): BaseHTTPMiddleware, ApiCryptoError, _b64url_decode(), _b64url_encode(), decrypt_json(), derive_key(), encrypt_json(), gen_nonce_hex() (+15 more)
+Cohesion: 0.16
+Nodes (32): _create_insurance_company(), _create_insurance_company_invoice(), _create_insurance_project_price(), delete_insurance_company(), delete_insurance_company_invoice(), delete_insurance_project_price(), _fmt_dt(), _generate_uuid() (+24 more)
 
 
 ### Community 13 - "Community 13"
 ### Community 13 - "Community 13"
-Cohesion: 0.14
-Nodes (8): get_wechat_pay_client(), get_wechat_pay_v2(), 微信支付工具模块  支持微信支付 V3 API 文档参考: https://pay.weixin.qq.com/docs/v3/apis/index.ht, 统一下单接口 (V3)          Args:             out_trade_no: 商户订单号             total, 生成签名 V3 (SHA256 with RSA), wechat_pay(), WechatPayClient, WechatPayV2Client
+Cohesion: 0.12
+Nodes (11): get_wechat_pay_client(), get_wechat_pay_v2(), 微信支付工具模块  支持微信支付 V3 API 文档参考: https://pay.weixin.qq.com/docs/v3/apis/index.ht, 统一下单接口 (V3)          Args:             out_trade_no: 商户订单号             total, 申请退款          Args:             out_trade_no: 商户订单号             total_amount, 小程序调起支付的二次签名参数(与公众号 JSAPI 规则一致), 统一下单 (V2 API)          Args:             out_trade_no: 商户订单号             tot, 生成签名 V3 (SHA256 with RSA) (+3 more)
 
 
 ### Community 14 - "Community 14"
 ### Community 14 - "Community 14"
-Cohesion: 0.13
-Nodes (19): create_collection_region(), create_hospital(), get_collection_region(), get_hospital(), _hospital_to_dict(), list_collection_regions(), list_hospitals(), _region_to_dict() (+11 more)
+Cohesion: 0.16
+Nodes (26): add_step(), copy_step(), create_route(), _create_step_consumable(), _create_step_data_collection(), _create_step_output(), _create_step_reagent(), get_route() (+18 more)
 
 
 ### Community 15 - "Community 15"
 ### Community 15 - "Community 15"
 Cohesion: 0.18
 Cohesion: 0.18
 Nodes (18): company_code_exists(), create_company(), get_company(), list_companies(), list_company_options(), _row_to_dict(), soft_delete_company(), update_company() (+10 more)
 Nodes (18): company_code_exists(), create_company(), get_company(), list_companies(), list_company_options(), _row_to_dict(), soft_delete_company(), update_company() (+10 more)
 
 
 ### Community 16 - "Community 16"
 ### Community 16 - "Community 16"
-Cohesion: 0.22
-Nodes (18): _consumable_row_to_dict(), create_process(), _create_sub_consumable(), _create_sub_data_collection(), _create_sub_output(), _create_sub_reagent(), _data_collection_row_to_dict(), get_process() (+10 more)
+Cohesion: 0.23
+Nodes (15): create_user(), get_user(), get_user_with_relations(), _hash_password(), list_post_ids_for_user(), list_user_ids_bound_to_post(), list_users(), _replace_associations() (+7 more)
 
 
 ### Community 17 - "Community 17"
 ### Community 17 - "Community 17"
-Cohesion: 0.22
-Nodes (12): batch_generate_sample_codes(), batch_issue_sample_codes(), batch_revoke_sample_codes(), create_sample_code(), get_sample_code(), list_sample_codes(), 样本编码池 CRUD(biz_sample_code), 批量生成样本编码: 类型前缀 + 中间段 + 补零序号 (+4 more)
+Cohesion: 0.2
+Nodes (15): batch_create_slots(), collect_descendant_ids(), create_facility(), create_slot(), get_children(), get_facility(), get_slot(), list_all_facilities() (+7 more)
 
 
 ### Community 18 - "Community 18"
 ### Community 18 - "Community 18"
-Cohesion: 0.22
-Nodes (12): batch_generate_protocol_nos(), batch_issue_protocol_nos(), batch_revoke_protocol_nos(), create_protocol_no(), get_protocol_no(), list_protocol_nos(), protocol_no_exists(), 采集协议号池 CRUD(biz_collection_protocol_no) (+4 more)
-
-### Community 19 - "Community 19"
 Cohesion: 0.24
 Cohesion: 0.24
 Nodes (9): 微信消息通知服务  支持微信小程序模板消息推送  使用方式:     from app.core.wechat_message import Wech, 发送模板消息                  Args:             openid: 用户openid             templ, send_delivery_notice(), send_delivery_warning(), send_order_status(), send_payment_success(), send_refund_result(), send_urge_notification() (+1 more)
 Nodes (9): 微信消息通知服务  支持微信小程序模板消息推送  使用方式:     from app.core.wechat_message import Wech, 发送模板消息                  Args:             openid: 用户openid             templ, send_delivery_notice(), send_delivery_warning(), send_order_status(), send_payment_success(), send_refund_result(), send_urge_notification() (+1 more)
 
 
+### Community 19 - "Community 19"
+Cohesion: 0.22
+Nodes (12): Config, InquiryBase, InquiryCreate, InquiryDetailResponse, InquiryItemBase, InquiryItemCreate, InquiryItemResponse, InquiryListResponse (+4 more)
+
 ### Community 20 - "Community 20"
 ### Community 20 - "Community 20"
 Cohesion: 0.23
 Cohesion: 0.23
 Nodes (12): Config, ProductBase, ProductCategoryBase, ProductCategoryCreate, ProductCategoryResponse, ProductCreate, ProductResponse, ProductUnitBase (+4 more)
 Nodes (12): Config, ProductBase, ProductCategoryBase, ProductCategoryCreate, ProductCategoryResponse, ProductCreate, ProductResponse, ProductUnitBase (+4 more)
@@ -177,32 +176,32 @@ Cohesion: 0.31
 Nodes (12): _code_exists(), create_material(), _dec(), _fmt_dt(), get_material(), list_materials(), options_list(), _row_to_dict() (+4 more)
 Nodes (12): _code_exists(), create_material(), _dec(), _fmt_dt(), get_material(), list_materials(), options_list(), _row_to_dict() (+4 more)
 
 
 ### Community 22 - "Community 22"
 ### Community 22 - "Community 22"
+Cohesion: 0.27
+Nodes (10): batch_issue_sample_codes(), batch_revoke_sample_codes(), create_sample_code(), get_sample_code(), list_sample_codes(), 样本编码池 CRUD(biz_sample_code), _row_to_dict(), sample_code_exists() (+2 more)
+
+### Community 23 - "Community 23"
+Cohesion: 0.27
+Nodes (11): _build_tree(), _cascade_soft_delete(), _collect_with_ancestors(), create_dept(), get_dept(), list_depts(), list_depts_tree(), 保留匹配节点及其全部上级,供关键词检索后仍能正确组树。 (+3 more)
+
+### Community 24 - "Community 24"
 Cohesion: 0.29
 Cohesion: 0.29
 Nodes (9): _build_tree(), _cascade_soft_delete(), create_menu(), get_menu(), list_menus(), list_menus_tree(), _row_to_dict(), soft_delete_menu() (+1 more)
 Nodes (9): _build_tree(), _cascade_soft_delete(), create_menu(), get_menu(), list_menus(), list_menus_tree(), _row_to_dict(), soft_delete_menu() (+1 more)
 
 
-### Community 23 - "Community 23"
+### Community 25 - "Community 25"
 Cohesion: 0.31
 Cohesion: 0.31
 Nodes (8): consumable_code_exists(), create_consumable(), get_consumable(), list_consumables(), 耗材 CRUD(biz_consumable), _row_to_dict(), soft_delete_consumable(), update_consumable()
 Nodes (8): consumable_code_exists(), create_consumable(), get_consumable(), list_consumables(), 耗材 CRUD(biz_consumable), _row_to_dict(), soft_delete_consumable(), update_consumable()
 
 
-### Community 24 - "Community 24"
+### Community 26 - "Community 26"
 Cohesion: 0.31
 Cohesion: 0.31
 Nodes (8): create_express_company(), express_code_exists(), get_express_company(), list_express_companies(), 快递公司 CRUD(biz_express_company), _row_to_dict(), soft_delete_express_company(), update_express_company()
 Nodes (8): create_express_company(), express_code_exists(), get_express_company(), list_express_companies(), 快递公司 CRUD(biz_express_company), _row_to_dict(), soft_delete_express_company(), update_express_company()
 
 
-### Community 25 - "Community 25"
+### Community 27 - "Community 27"
 Cohesion: 0.38
 Cohesion: 0.38
 Nodes (9): create_check_item(), get_check_item(), item_code_exists(), list_active_check_items(), list_check_items(), _order_query(), _row_to_dict(), soft_delete_check_item() (+1 more)
 Nodes (9): create_check_item(), get_check_item(), item_code_exists(), list_active_check_items(), list_check_items(), _order_query(), _row_to_dict(), soft_delete_check_item() (+1 more)
 
 
-### Community 26 - "Community 26"
-Cohesion: 0.27
-Nodes (8): create_dict_data(), get_dict_data(), list_dict_data(), list_dict_data_options(), 字典数据 CRUD(sys_dict_data), _row_to_dict(), soft_delete_dict_data(), update_dict_data()
-
-### Community 27 - "Community 27"
-Cohesion: 0.31
-Nodes (8): category_code_exists(), create_process_category(), get_process_category(), list_process_categories(), 工艺工序分类 CRUD(biz_process_category), _row_to_dict(), soft_delete_process_category(), update_process_category()
-
 ### Community 28 - "Community 28"
 ### Community 28 - "Community 28"
 Cohesion: 0.31
 Cohesion: 0.31
-Nodes (8): create_item_unit(), get_item_unit(), list_item_units(), 物品单位 CRUD(biz_item_unit), _row_to_dict(), soft_delete_item_unit(), unit_code_exists(), update_item_unit()
+Nodes (8): category_code_exists(), create_process_category(), get_process_category(), list_process_categories(), 工艺工序分类 CRUD(biz_process_category), _row_to_dict(), soft_delete_process_category(), update_process_category()
 
 
 ### Community 29 - "Community 29"
 ### Community 29 - "Community 29"
 Cohesion: 0.31
 Cohesion: 0.31
@@ -213,120 +212,116 @@ Cohesion: 0.31
 Nodes (8): create_cell_sample_type(), get_cell_sample_type(), list_cell_sample_types(), 细胞样本类型 CRUD(biz_cell_sample_type), _row_to_dict(), soft_delete_cell_sample_type(), type_code_exists(), update_cell_sample_type()
 Nodes (8): create_cell_sample_type(), get_cell_sample_type(), list_cell_sample_types(), 细胞样本类型 CRUD(biz_cell_sample_type), _row_to_dict(), soft_delete_cell_sample_type(), type_code_exists(), update_cell_sample_type()
 
 
 ### Community 31 - "Community 31"
 ### Community 31 - "Community 31"
-Cohesion: 0.33
-Nodes (9): _build_tree(), _cascade_soft_delete(), create_dept(), get_dept(), list_depts(), list_depts_tree(), _row_to_dict(), soft_delete_dept() (+1 more)
-
-### Community 32 - "Community 32"
 Cohesion: 0.31
 Cohesion: 0.31
 Nodes (8): create_output_type(), get_output_type(), list_output_types(), 工艺产出物类型 CRUD(biz_output_type), _row_to_dict(), soft_delete_output_type(), type_code_exists(), update_output_type()
 Nodes (8): create_output_type(), get_output_type(), list_output_types(), 工艺产出物类型 CRUD(biz_output_type), _row_to_dict(), soft_delete_output_type(), type_code_exists(), update_output_type()
 
 
-### Community 33 - "Community 33"
+### Community 32 - "Community 32"
 Cohesion: 0.36
 Cohesion: 0.36
 Nodes (7): create_post(), get_post(), list_posts(), post_code_exists(), _row_to_dict(), soft_delete_post(), update_post()
 Nodes (7): create_post(), get_post(), list_posts(), post_code_exists(), _row_to_dict(), soft_delete_post(), update_post()
 
 
-### Community 34 - "Community 34"
+### Community 33 - "Community 33"
 Cohesion: 0.36
 Cohesion: 0.36
-Nodes (7): create_tenant(), get_tenant(), list_tenants(), _row_to_dict(), soft_delete_tenant(), tenant_no_exists(), update_tenant()
+Nodes (7): create_dict_type(), dict_type_code_exists(), get_dict_type(), list_dict_types(), _row_to_dict(), soft_delete_dict_type(), update_dict_type()
 
 
-### Community 35 - "Community 35"
+### Community 34 - "Community 34"
 Cohesion: 0.39
 Cohesion: 0.39
 Nodes (8): create_lab_item(), get_lab_item(), item_code_exists(), list_active_lab_items(), list_lab_items(), _row_to_dict(), soft_delete_lab_item(), update_lab_item()
 Nodes (8): create_lab_item(), get_lab_item(), item_code_exists(), list_active_lab_items(), list_lab_items(), _row_to_dict(), soft_delete_lab_item(), update_lab_item()
 
 
-### Community 36 - "Community 36"
+### Community 35 - "Community 35"
 Cohesion: 0.36
 Cohesion: 0.36
 Nodes (7): create_site(), get_site(), list_sites(), _row_to_dict(), site_code_exists(), soft_delete_site(), update_site()
 Nodes (7): create_site(), get_site(), list_sites(), _row_to_dict(), site_code_exists(), soft_delete_site(), update_site()
 
 
-### Community 37 - "Community 37"
+### Community 36 - "Community 36"
 Cohesion: 0.29
 Cohesion: 0.29
 Nodes (2): BaseModel, SoftDeleteModel
 Nodes (2): BaseModel, SoftDeleteModel
 
 
-### Community 38 - "Community 38"
+### Community 37 - "Community 37"
 Cohesion: 0.38
 Cohesion: 0.38
 Nodes (6): derive_legacy_status_from_three(), derive_three_status_from_legacy(), 阶段A兼容:从旧 status 推导三字段。     约束:旧 status 本身混合了支付/配送/订单生命周期,因此只能尽力推断;     若业务侧已有更精确, 阶段A兼容:从三字段反推旧 status(用于旧前端/旧筛选参数兼容)。, ThreeStatus, _to_decimal()
 Nodes (6): derive_legacy_status_from_three(), derive_three_status_from_legacy(), 阶段A兼容:从旧 status 推导三字段。     约束:旧 status 本身混合了支付/配送/订单生命周期,因此只能尽力推断;     若业务侧已有更精确, 阶段A兼容:从三字段反推旧 status(用于旧前端/旧筛选参数兼容)。, ThreeStatus, _to_decimal()
 
 
-### Community 39 - "Community 39"
+### Community 38 - "Community 38"
 Cohesion: 0.4
 Cohesion: 0.4
 Nodes (4): list_ethnics_for_select(), list_ethnics(), 民族码表 CRUD(全局 sys_ethnic), _row_to_dict()
 Nodes (4): list_ethnics_for_select(), list_ethnics(), 民族码表 CRUD(全局 sys_ethnic), _row_to_dict()
 
 
-### Community 40 - "Community 40"
+### Community 39 - "Community 39"
 Cohesion: 0.5
 Cohesion: 0.5
 Nodes (3): BaseSettings, Config, Settings
 Nodes (3): BaseSettings, Config, Settings
 
 
-### Community 41 - "Community 41"
+### Community 40 - "Community 40"
 Cohesion: 0.5
 Cohesion: 0.5
 Nodes (2): _auto_fill_create_dept_id(), ORM before_insert 钩子:当 create_dept_id 为空时,自动从请求上下文填充当前用户部门 ID
 Nodes (2): _auto_fill_create_dept_id(), ORM before_insert 钩子:当 create_dept_id 为空时,自动从请求上下文填充当前用户部门 ID
 
 
-### Community 42 - "Community 42"
+### Community 41 - "Community 41"
 Cohesion: 0.67
 Cohesion: 0.67
 Nodes (2): get_menu_list(), 运营端菜单接口(与 web-admin 前端静态路由 sqxp.ts 对齐,供后端菜单模式联调)
 Nodes (2): get_menu_list(), 运营端菜单接口(与 web-admin 前端静态路由 sqxp.ts 对齐,供后端菜单模式联调)
 
 
-### Community 43 - "Community 43"
+### Community 42 - "Community 42"
 Cohesion: 1.0
 Cohesion: 1.0
 Nodes (1): 请求级别的用户上下文变量  通过 contextvars 在请求处理链路中传递当前用户信息, 供 SQLAlchemy 事件监听器等底层机制使用,避免逐层手动传
 Nodes (1): 请求级别的用户上下文变量  通过 contextvars 在请求处理链路中传递当前用户信息, 供 SQLAlchemy 事件监听器等底层机制使用,避免逐层手动传
 
 
-### Community 44 - "Community 44"
+### Community 43 - "Community 43"
 Cohesion: 1.0
 Cohesion: 1.0
 Nodes (1): API 路由包(骨架:公共鉴权 + 静态菜单 + 运营端占位)
 Nodes (1): API 路由包(骨架:公共鉴权 + 静态菜单 + 运营端占位)
 
 
-### Community 47 - "Community 47"
+### Community 46 - "Community 46"
 Cohesion: 1.0
 Cohesion: 1.0
 Nodes (1): 发送订单状态通知                  Args:             openid: 用户openid             ord
 Nodes (1): 发送订单状态通知                  Args:             openid: 用户openid             ord
 
 
-### Community 48 - "Community 48"
+### Community 47 - "Community 47"
 Cohesion: 1.0
 Cohesion: 1.0
 Nodes (1): 发送支付成功通知                  Args:             openid: 用户openid             ord
 Nodes (1): 发送支付成功通知                  Args:             openid: 用户openid             ord
 
 
-### Community 49 - "Community 49"
+### Community 48 - "Community 48"
 Cohesion: 1.0
 Cohesion: 1.0
 Nodes (1): 发送配送通知                  Args:             openid: 配送员openid             task
 Nodes (1): 发送配送通知                  Args:             openid: 配送员openid             task
 
 
-### Community 50 - "Community 50"
+### Community 49 - "Community 49"
 Cohesion: 1.0
 Cohesion: 1.0
 Nodes (1): 发送配送时效预警                  Args:             openid: 配送员openid             ta
 Nodes (1): 发送配送时效预警                  Args:             openid: 配送员openid             ta
 
 
-### Community 51 - "Community 51"
+### Community 50 - "Community 50"
 Cohesion: 1.0
 Cohesion: 1.0
 Nodes (1): 发送退款结果通知                  Args:             openid: 用户openid             ord
 Nodes (1): 发送退款结果通知                  Args:             openid: 用户openid             ord
 
 
-### Community 52 - "Community 52"
+### Community 51 - "Community 51"
 Cohesion: 1.0
 Cohesion: 1.0
 Nodes (1): 发送催单通知给配送员                  Args:             deliver_openid: 配送员openid
 Nodes (1): 发送催单通知给配送员                  Args:             deliver_openid: 配送员openid
 
 
 ## Knowledge Gaps
 ## Knowledge Gaps
 - **86 isolated node(s):** `刷新会话 TTL(refreshToken 续期;已登录用户每次 API 校验通过后滑动续期)`, `写入 refreshToken(用于 one-time 使用/吊销)`, `根据 token 获取当前用户(包含 Redis 会话校验;会话滑动续期)`, `登录凭据 RSA 解密(与前端 JSEncrypt PKCS#1 v1.5 兼容)。`, `Base64 密文 -> JSON 对象(须含 username、password、ts 毫秒时间戳)。` (+81 more)
 - **86 isolated node(s):** `刷新会话 TTL(refreshToken 续期;已登录用户每次 API 校验通过后滑动续期)`, `写入 refreshToken(用于 one-time 使用/吊销)`, `根据 token 获取当前用户(包含 Redis 会话校验;会话滑动续期)`, `登录凭据 RSA 解密(与前端 JSEncrypt PKCS#1 v1.5 兼容)。`, `Base64 密文 -> JSON 对象(须含 username、password、ts 毫秒时间戳)。` (+81 more)
   These have ≤1 connection - possible missing edges or undocumented components.
   These have ≤1 connection - possible missing edges or undocumented components.
-- **Thin community `Community 37`** (8 nodes): `BaseModel`, `.to_dict()`, `get_active()`, `base.py`, `SoftDeleteModel`, `.restore()`, `.soft_delete()`, `__tablename__()`
+- **Thin community `Community 36`** (8 nodes): `BaseModel`, `.to_dict()`, `get_active()`, `base.py`, `SoftDeleteModel`, `.restore()`, `.soft_delete()`, `__tablename__()`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 41`** (4 nodes): `_auto_fill_create_dept_id()`, `get_db()`, `database.py`, `ORM before_insert 钩子:当 create_dept_id 为空时,自动从请求上下文填充当前用户部门 ID`
+- **Thin community `Community 40`** (4 nodes): `_auto_fill_create_dept_id()`, `get_db()`, `database.py`, `ORM before_insert 钩子:当 create_dept_id 为空时,自动从请求上下文填充当前用户部门 ID`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 42`** (3 nodes): `get_menu_list()`, `menu.py`, `运营端菜单接口(与 web-admin 前端静态路由 sqxp.ts 对齐,供后端菜单模式联调)`
+- **Thin community `Community 41`** (3 nodes): `get_menu_list()`, `menu.py`, `运营端菜单接口(与 web-admin 前端静态路由 sqxp.ts 对齐,供后端菜单模式联调)`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 43`** (2 nodes): `context.py`, `请求级别的用户上下文变量  通过 contextvars 在请求处理链路中传递当前用户信息, 供 SQLAlchemy 事件监听器等底层机制使用,避免逐层手动传`
+- **Thin community `Community 42`** (2 nodes): `context.py`, `请求级别的用户上下文变量  通过 contextvars 在请求处理链路中传递当前用户信息, 供 SQLAlchemy 事件监听器等底层机制使用,避免逐层手动传`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 44`** (2 nodes): `__init__.py`, `API 路由包(骨架:公共鉴权 + 静态菜单 + 运营端占位)`
+- **Thin community `Community 43`** (2 nodes): `__init__.py`, `API 路由包(骨架:公共鉴权 + 静态菜单 + 运营端占位)`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 47`** (1 nodes): `发送订单状态通知                  Args:             openid: 用户openid             ord`
+- **Thin community `Community 46`** (1 nodes): `发送订单状态通知                  Args:             openid: 用户openid             ord`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 48`** (1 nodes): `发送支付成功通知                  Args:             openid: 用户openid             ord`
+- **Thin community `Community 47`** (1 nodes): `发送支付成功通知                  Args:             openid: 用户openid             ord`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 49`** (1 nodes): `发送配送通知                  Args:             openid: 配送员openid             task`
+- **Thin community `Community 48`** (1 nodes): `发送配送通知                  Args:             openid: 配送员openid             task`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 50`** (1 nodes): `发送配送时效预警                  Args:             openid: 配送员openid             ta`
+- **Thin community `Community 49`** (1 nodes): `发送配送时效预警                  Args:             openid: 配送员openid             ta`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 51`** (1 nodes): `发送退款结果通知                  Args:             openid: 用户openid             ord`
+- **Thin community `Community 50`** (1 nodes): `发送退款结果通知                  Args:             openid: 用户openid             ord`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 52`** (1 nodes): `发送催单通知给配送员                  Args:             deliver_openid: 配送员openid`
+- **Thin community `Community 51`** (1 nodes): `发送催单通知给配送员                  Args:             deliver_openid: 配送员openid`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
 
 
 ## Suggested Questions
 ## Suggested Questions
 _Questions this graph is uniquely positioned to answer:_
 _Questions this graph is uniquely positioned to answer:_
 
 
-- **Why does `generate_uuid()` connect `Community 2` to `Community 0`, `Community 3`, `Community 4`, `Community 5`, `Community 6`, `Community 7`, `Community 8`, `Community 10`, `Community 11`, `Community 14`, `Community 15`, `Community 16`, `Community 17`, `Community 18`, `Community 21`, `Community 22`, `Community 23`, `Community 24`, `Community 25`, `Community 26`, `Community 27`, `Community 28`, `Community 29`, `Community 30`, `Community 31`, `Community 32`, `Community 33`, `Community 34`, `Community 35`, `Community 36`?**
+- **Why does `generate_uuid()` connect `Community 5` to `Community 1`, `Community 3`, `Community 4`, `Community 6`, `Community 7`, `Community 9`, `Community 10`, `Community 11`, `Community 14`, `Community 15`, `Community 16`, `Community 17`, `Community 21`, `Community 22`, `Community 23`, `Community 24`, `Community 25`, `Community 26`, `Community 27`, `Community 28`, `Community 29`, `Community 30`, `Community 31`, `Community 32`, `Community 33`, `Community 34`, `Community 35`?**
   _High betweenness centrality (0.122) - this node is a cross-community bridge._
   _High betweenness centrality (0.122) - this node is a cross-community bridge._
-- **Why does `success_response()` connect `Community 0` to `Community 1`, `Community 2`, `Community 39`, `Community 8`, `Community 42`?**
-  _High betweenness centrality (0.077) - this node is a cross-community bridge._
-- **Why does `SysUser` connect `Community 4` to `Community 8`, `Community 1`, `Community 3`, `Community 6`?**
-  _High betweenness centrality (0.072) - this node is a cross-community bridge._
+- **Why does `SysUser` connect `Community 0` to `Community 1`, `Community 2`, `Community 3`, `Community 9`, `Community 10`, `Community 12`, `Community 16`?**
+  _High betweenness centrality (0.088) - this node is a cross-community bridge._
+- **Why does `success_response()` connect `Community 1` to `Community 0`, `Community 3`, `Community 4`, `Community 38`, `Community 41`, `Community 10`, `Community 9`, `Community 12`?**
+  _High betweenness centrality (0.066) - this node is a cross-community bridge._
 - **Are the 367 inferred relationships involving `success_response()` (e.g. with `web_login_rsa_public_key()` and `web_login()`) actually correct?**
 - **Are the 367 inferred relationships involving `success_response()` (e.g. with `web_login_rsa_public_key()` and `web_login()`) actually correct?**
   _`success_response()` has 367 INFERRED edges - model-reasoned connections that need verification._
   _`success_response()` has 367 INFERRED edges - model-reasoned connections that need verification._
 - **Are the 332 inferred relationships involving `str` (e.g. with `extract_login_credentials()` and `._make_request()`) actually correct?**
 - **Are the 332 inferred relationships involving `str` (e.g. with `extract_login_credentials()` and `._make_request()`) actually correct?**

File diff suppressed because it is too large
+ 1 - 1
backend/graphify-out/graph.html


File diff suppressed because it is too large
+ 112 - 112
backend/graphify-out/graph.json


+ 1 - 1
backend/graphify-out/manifest.json

@@ -106,7 +106,7 @@
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/collection_protocol_no.py": 1778739676.552736,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/collection_protocol_no.py": 1778739676.552736,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/storage_facility.py": 1778806307.3684402,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/storage_facility.py": 1778806307.3684402,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_sample_type.py": 1778733001.8375483,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_sample_type.py": 1778733001.8375483,
-  "/Users/wuzilong/work/00code/zkxw/backend/app/crud/dept.py": 1778775532.884225,
+  "/Users/wuzilong/work/00code/zkxw/backend/app/crud/dept.py": 1779260197.977622,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/base.py": 1778578258.062207,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/base.py": 1778578258.062207,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/protocol_donation_convert.py": 1779161468.6582925,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/protocol_donation_convert.py": 1779161468.6582925,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_item_param.py": 1779022459.091789,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_item_param.py": 1779022459.091789,

+ 35 - 0
backend/tests/test_dept_tree_search.py

@@ -0,0 +1,35 @@
+"""组织树关键词检索:匹配子节点须带上祖先才能组树。"""
+
+from app.crud.dept import _build_tree, _collect_with_ancestors
+
+
+def _item(dept_id: str, name: str, parent_id: str = "0") -> dict:
+    return {
+        "id": dept_id,
+        "parentId": parent_id,
+        "deptName": name,
+        "orderNum": 0,
+    }
+
+
+def test_collect_with_ancestors_includes_parent_chain():
+    flat = [
+        _item("root", "市场营销部门"),
+        _item("child", "采集部", "root"),
+    ]
+    expanded = _collect_with_ancestors(flat, {"child"})
+    ids = {x["id"] for x in expanded}
+    assert ids == {"root", "child"}
+
+
+def test_build_tree_shows_child_under_parent_after_keyword_expand():
+    flat = [
+        _item("root", "市场营销部门"),
+        _item("child", "采集部", "root"),
+    ]
+    expanded = _collect_with_ancestors(flat, {"child"})
+    tree = _build_tree(expanded, "0")
+    assert len(tree) == 1
+    assert tree[0]["deptName"] == "市场营销部门"
+    assert len(tree[0]["children"]) == 1
+    assert tree[0]["children"][0]["deptName"] == "采集部"

+ 21 - 0
backend/tests/test_user_list_keyword.py

@@ -0,0 +1,21 @@
+"""用户列表关键词:须同时匹配账号、昵称、手机号(与 crud.user.list_users 一致)。"""
+
+import inspect
+
+from app.api import admin as admin_api
+
+
+def test_admin_list_users_keyword_filters_three_fields():
+    """回归:admin 路由不得仅按 nick_name 检索。"""
+    source = inspect.getsource(admin_api.list_users)
+    assert "SysUser.user_no.like" in source
+    assert "SysUser.nick_name.like" in source
+    assert "SysUser.phone_no.like" in source
+    assert "or_(" in source
+
+
+def test_admin_list_users_resolves_dept_name():
+    """回归:列表须解析 deptName 供所属组织列展示。"""
+    source = inspect.getsource(admin_api.list_users)
+    assert "dept_map" in source
+    assert "_row_to_dict(r, dept_name=" in source

+ 1 - 1
web-admin/graphify-out/GRAPH_REPORT.md

@@ -1,7 +1,7 @@
 # Graph Report - src  (2026-05-20)
 # Graph Report - src  (2026-05-20)
 
 
 ## Corpus Check
 ## Corpus Check
-- Large corpus: 575 files · ~284,580 words. Semantic extraction will be expensive (many Claude tokens). Consider running on a subfolder, or use --no-semantic to run AST-only.
+- Large corpus: 575 files · ~284,612 words. Semantic extraction will be expensive (many Claude tokens). Consider running on a subfolder, or use --no-semantic to run AST-only.
 
 
 ## Summary
 ## Summary
 - 1440 nodes · 1839 edges · 43 communities detected
 - 1440 nodes · 1839 edges · 43 communities detected

+ 1 - 1
web-admin/graphify-out/manifest.json

@@ -263,7 +263,7 @@
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/workflow/process/index.vue": 1779227504.2942972,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/workflow/process/index.vue": 1779227504.2942972,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/menu/index.vue": 1779257191.27756,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/menu/index.vue": 1779257191.27756,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/site/index.vue": 1779257191.280453,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/site/index.vue": 1779257191.280453,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/dept/index.vue": 1779259560.1445239,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/dept/index.vue": 1779259846.8851933,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/dict/index.vue": 1779259163.7560437,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/dict/index.vue": 1779259163.7560437,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/leave/index.vue": 1779257191.2768743,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/leave/index.vue": 1779257191.2768743,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/output-type/index.vue": 1779257191.2697432,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/output-type/index.vue": 1779257191.2697432,

File diff suppressed because it is too large
+ 0 - 0
web-admin/src/graphify-out/cache/ast/319796577623b57d279111d56bd6406c69ea3c64f82ac21ace058a6477f65074.json


+ 59 - 22
web-admin/src/views/system/user/index.vue

@@ -88,9 +88,9 @@
               />
               />
               <el-table-column prop="userNo" label="用户账号" min-width="120" align="center" header-align="center" />
               <el-table-column prop="userNo" label="用户账号" min-width="120" align="center" header-align="center" />
               <el-table-column prop="nickName" label="用户名" min-width="100" align="center" header-align="center" />
               <el-table-column prop="nickName" label="用户名" min-width="100" align="center" header-align="center" />
-              <el-table-column prop="deptName" label="所属组织" min-width="160" align="center" header-align="center">
+              <el-table-column prop="deptName" label="所属组织" min-width="200" align="center" header-align="center">
                 <template #default="{ row }">
                 <template #default="{ row }">
-                  {{ row.deptName || '-' }}
+                  {{ formatUserDeptDisplay(row) }}
                 </template>
                 </template>
               </el-table-column>
               </el-table-column>
               <el-table-column prop="phoneNo" label="手机号" min-width="140" align="center" header-align="center">
               <el-table-column prop="phoneNo" label="手机号" min-width="140" align="center" header-align="center">
@@ -219,6 +219,7 @@
   import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
   import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
   import {
   import {
     fetchUserList,
     fetchUserList,
+    fetchUserDetail,
     fetchCreateUser,
     fetchCreateUser,
     fetchUpdateUser,
     fetchUpdateUser,
     fetchDeleteUser,
     fetchDeleteUser,
@@ -226,6 +227,7 @@
     fetchDeptTree,
     fetchDeptTree,
     fetchRoleOptions
     fetchRoleOptions
   } from '@/api/admin'
   } from '@/api/admin'
+  import { buildPathLabelMapFromTreeRoots } from '@/utils/tree-path'
 
 
   const roleOptions = ref<Array<{ label: string; value: string }>>([])
   const roleOptions = ref<Array<{ label: string; value: string }>>([])
 
 
@@ -244,6 +246,29 @@
   const deptTreeRef = ref()
   const deptTreeRef = ref()
   const selectedDeptId = ref<string | null>(null)
   const selectedDeptId = ref<string | null>(null)
 
 
+  const deptPathLabelMap = computed(() =>
+    buildPathLabelMapFromTreeRoots(deptTree.value, {
+      idKey: 'id',
+      nameKey: 'deptName',
+      childrenKey: 'children'
+    })
+  )
+
+  function formatUserDeptDisplay(row: Api.User.UserItem) {
+    const id = (row.deptId || '').trim()
+    if (!id) return '—'
+    return deptPathLabelMap.value.get(id) || row.deptName || '—'
+  }
+
+  function formatUserRoleDisplay(row: Api.User.UserItem) {
+    const ids = row.roleIds || []
+    if (!ids.length) return '—'
+    const labels = ids
+      .map((id) => roleOptions.value.find((o) => o.value === id)?.label)
+      .filter((name): name is string => Boolean(name))
+    return labels.length ? labels.join('、') : '—'
+  }
+
   const searchForm = reactive<{ keyword: string; status: string }>({ keyword: '', status: '' })
   const searchForm = reactive<{ keyword: string; status: string }>({ keyword: '', status: '' })
   const tableData = ref<Api.User.UserItem[]>([])
   const tableData = ref<Api.User.UserItem[]>([])
   const loading = ref(false)
   const loading = ref(false)
@@ -359,24 +384,30 @@
     formRef.value?.clearValidate?.()
     formRef.value?.clearValidate?.()
   }
   }
 
 
-  function handleEdit(row: Api.User.UserItem) {
+  async function handleEdit(row: Api.User.UserItem) {
     isEdit.value = true
     isEdit.value = true
     dialogTitle.value = '编辑用户'
     dialogTitle.value = '编辑用户'
     editingId.value = row.id
     editingId.value = row.id
-    formData.userNo = row.userNo
-    formData.nickName = row.nickName
-    formData.password = ''
-    formData.deptId = row.deptId || ''
-    formData.email = row.email || ''
-    formData.phoneNo = row.phoneNo || ''
-    formData.sex = row.sex || '0'
-    formData.avatar = row.avatar || ''
-    formData.status = row.status || '1'
-    formData.orderNum = row.orderNum ?? 0
-    formData.remark = row.remark || ''
-    formData.roleIds = (row as any).roleIds || []
-    dialogVisible.value = true
-    formRef.value?.clearValidate?.()
+    try {
+      const detail = await fetchUserDetail(row.id)
+      formData.userNo = detail.userNo
+      formData.nickName = detail.nickName
+      formData.password = ''
+      formData.deptId = detail.deptId || ''
+      formData.email = detail.email || ''
+      formData.phoneNo = detail.phoneNo || ''
+      formData.sex = detail.sex || '0'
+      formData.avatar = detail.avatar || ''
+      formData.status = detail.status || '1'
+      formData.orderNum = detail.orderNum ?? 0
+      formData.remark = detail.remark || ''
+      formData.roleIds = detail.roleIds ? [...detail.roleIds] : []
+      dialogVisible.value = true
+      formRef.value?.clearValidate?.()
+    } catch (e) {
+      console.error('加载用户详情失败:', e)
+      ElMessage.error('加载用户详情失败')
+    }
   }
   }
 
 
   async function handleSubmit() {
   async function handleSubmit() {
@@ -393,7 +424,7 @@
           status: formData.status,
           status: formData.status,
           orderNum: formData.orderNum,
           orderNum: formData.orderNum,
           remark: formData.remark?.trim() || undefined,
           remark: formData.remark?.trim() || undefined,
-          roleIds: formData.roleIds.length ? formData.roleIds : undefined
+          roleIds: [...formData.roleIds]
         })
         })
       } else {
       } else {
         await fetchCreateUser({
         await fetchCreateUser({
@@ -443,7 +474,7 @@
         fields: [
         fields: [
           { label: '用户账号', value: row.userNo },
           { label: '用户账号', value: row.userNo },
           { label: '用户名', value: row.nickName },
           { label: '用户名', value: row.nickName },
-          { label: '所属组织', value: row.deptName },
+          { label: '所属组织', value: formatUserDeptDisplay(row) },
           { label: '手机号', value: row.phoneNo },
           { label: '手机号', value: row.phoneNo },
           { label: '邮箱', value: row.email },
           { label: '邮箱', value: row.email },
           {
           {
@@ -451,6 +482,7 @@
             value: ({ '0': '男', '1': '女', '2': '未知' } as Record<string, string>)[row.sex] || '未知'
             value: ({ '0': '男', '1': '女', '2': '未知' } as Record<string, string>)[row.sex] || '未知'
           },
           },
           { label: '状态', value: row.status === '1' ? '正常' : '停用', tagType: row.status === '1' ? 'success' : 'info' },
           { label: '状态', value: row.status === '1' ? '正常' : '停用', tagType: row.status === '1' ? 'success' : 'info' },
+          { label: '角色', value: formatUserRoleDisplay(row), span: true },
           { label: '创建时间', value: row.createTime },
           { label: '创建时间', value: row.createTime },
           { label: '备注', value: row.remark, span: true }
           { label: '备注', value: row.remark, span: true }
         ]
         ]
@@ -458,9 +490,14 @@
     ]
     ]
   })
   })
 
 
-  function handleView(row: Api.User.UserItem) {
-    viewRow.value = row
-    viewVisible.value = true
+  async function handleView(row: Api.User.UserItem) {
+    try {
+      viewRow.value = await fetchUserDetail(row.id)
+      viewVisible.value = true
+    } catch (e) {
+      console.error('加载用户详情失败:', e)
+      ElMessage.error('加载用户详情失败')
+    }
   }
   }
 
 
   onMounted(() => {
   onMounted(() => {

Some files were not shown because too many files changed in this diff