zlwu 3 maanden geleden
bovenliggende
commit
f4bbe3e06e
56 gewijzigde bestanden met toevoegingen van 2570 en 1636 verwijderingen
  1. 1 1
      backend/app.pid
  2. 25 0
      backend/app/api/qc.py
  3. 37 0
      backend/app/crud/qc_work_order.py
  4. 19 7
      backend/app/crud/sample_reception.py
  5. 0 0
      backend/app/graphify-out/cache/ast/110cae0ca8b08ae4ff2e081147bb98bb112019c5e18c7844d5ff1451cab856fb.json
  6. 0 0
      backend/app/graphify-out/cache/ast/53cee027db2c0949ee583b1d8c32839f241fb02b657eb6cfffe55e501b713b41.json
  7. 0 0
      backend/app/graphify-out/cache/ast/9ed2725793d85a58faad64ce49a084d11c4af7249d05c6461421a43b6759c57a.json
  8. 311 258
      backend/graphify-out/.graphify_analysis.json
  9. 212 191
      backend/graphify-out/.graphify_extract.json
  10. 110 90
      backend/graphify-out/GRAPH_REPORT.md
  11. 1 1
      backend/graphify-out/graph.html
  12. 112 112
      backend/graphify-out/graph.json
  13. 3 3
      backend/graphify-out/manifest.json
  14. 61 0
      docs/superpowers/plans/2026-07-07-qc-inbound-sibling-navigate.md
  15. 280 0
      docs/superpowers/specs/2026-07-07-qc-inbound-sibling-navigate-design.md
  16. 449 411
      web-admin/graphify-out/.graphify_analysis.json
  17. 201 157
      web-admin/graphify-out/.graphify_extract.json
  18. 97 134
      web-admin/graphify-out/GRAPH_REPORT.md
  19. 1 1
      web-admin/graphify-out/graph.html
  20. 112 112
      web-admin/graphify-out/graph.json
  21. 18 17
      web-admin/graphify-out/manifest.json
  22. 7 0
      web-admin/src/api/qc-work-order.ts
  23. 0 0
      web-admin/src/graphify-out/cache/ast/1448617f65b50a27c789ed9e32297d24cec30aafa557516d2743fcc3011e5a35.json
  24. 1 0
      web-admin/src/graphify-out/cache/ast/24bdd1fd6cf73c0c03b4667697521a37ca67465b5edb1f482bb340b36c5bbe1b.json
  25. 0 0
      web-admin/src/graphify-out/cache/ast/27195f83e562208d912cc24b736c281093838b0830b5339d7093bbf068687725.json
  26. 0 0
      web-admin/src/graphify-out/cache/ast/5a2843e39759c7f3861e74ef84d48d0ddf3ecfd6ee05aaa34241c758f8c3af26.json
  27. 1 0
      web-admin/src/graphify-out/cache/ast/6d3a7533ababb5eef8d2ae60ed350a334b6f2f5e96a9dd28e289b0aa38814f2f.json
  28. 0 0
      web-admin/src/graphify-out/cache/ast/7c767bdcd44b4a96edf45eeb9b70e5dca6fa71926b632726be5f6f0861939a79.json
  29. 1 0
      web-admin/src/graphify-out/cache/ast/86204fdb8bd7d7f856b8219276068c03c566fb8548ee0a577ff4fec510f48dce.json
  30. 1 0
      web-admin/src/graphify-out/cache/ast/8687e1fe6209a5bea7177981d77c2607c3c22309f474d9ae8e5b43d884e66c93.json
  31. 0 0
      web-admin/src/graphify-out/cache/ast/8feba8387b5111d436ba57e3fdbd8f8677a81cf5fd1334edfddc28e5739984cd.json
  32. 0 0
      web-admin/src/graphify-out/cache/ast/a2e498e680b5aa7fda265a08adfeca4afb6b829ca7608ada4548c630fe056a40.json
  33. 1 0
      web-admin/src/graphify-out/cache/ast/ab95ec7f060b010ed5ba2de2c51d424f942f6fdbd2ee7798cf6ae73599b0eadc.json
  34. 0 0
      web-admin/src/graphify-out/cache/ast/cd0a056993530cff82ee09bb63f51a80744e4b46d081d9d3e755bc0a6d0a5907.json
  35. 0 0
      web-admin/src/graphify-out/cache/ast/e0bdae8b916e556b6b8f7e68f2caf994429d741b54eae91fc80aacc06c757fb1.json
  36. 0 0
      web-admin/src/graphify-out/cache/ast/f3bdeb38b36b5c718ee9e2f672fcc982d2cb6085d0a777e894fe16df4fbc42f6.json
  37. 1 0
      web-admin/src/graphify-out/cache/ast/f47c05a3d0b3e0e590c1ae7ecca71bdbbbdabaf6f67062ccbaf3d5a9cbe1493f.json
  38. 0 0
      web-admin/src/graphify-out/cache/ast/f4fad86d130de78ef4836c3ad237111459906d1b40aa34df42ab2e119c2e49cd.json
  39. 0 0
      web-admin/src/graphify-out/cache/ast/f5fdc9fe6deede0e31ab3caa7bf024b308132d09b37d2aad8dbc3c6c610d0d86.json
  40. 0 0
      web-admin/src/graphify-out/cache/ast/f748e7bc1cef9e7232cfe08d75ab9c3094783ba5e8ba498fc7beefa90c48afd5.json
  41. 1 1
      web-admin/src/utils/production-work-order-card-meta.ts
  42. 12 0
      web-admin/src/utils/production-work-order-category.ts
  43. 4 3
      web-admin/src/utils/qc-work-order-inbound-sample-cards.ts
  44. 4 8
      web-admin/src/utils/sample-reception-view-sections.ts
  45. 1 1
      web-admin/src/views/cell-storage/protocol-audit/SampleReceptionInfoTab.vue
  46. 0 9
      web-admin/src/views/cell-storage/protocol/ProtocolFormDialog.vue
  47. 29 5
      web-admin/src/views/production/production-work-order/TaskExecutionDialog.vue
  48. 8 1
      web-admin/src/views/production/production-work-order/useTaskStepEditor.ts
  49. 8 6
      web-admin/src/views/quality-inspection/pre-transit-sample/PreTransitProtocolCardList.vue
  50. 110 45
      web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderCardList.vue
  51. 63 4
      web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSampleGroup.vue
  52. 208 0
      web-admin/src/views/quality-inspection/qc-work-order/composables/useInboundSiblingNavigation.ts
  53. 35 0
      web-admin/src/views/quality-inspection/qc-work-order/composables/useQcWorkOrderTabPage.ts
  54. 10 4
      web-admin/src/views/quality-inspection/qc-work-order/tabs/QcWorkOrderTabPanel.vue
  55. 1 3
      web-admin/src/views/quality-inspection/sample-reception/SampleReceptionCardList.vue
  56. 13 51
      web-admin/src/views/quality-inspection/sample-reception/index.vue

+ 1 - 1
backend/app.pid

@@ -1 +1 @@
-58815
+60896

+ 25 - 0
backend/app/api/qc.py

@@ -718,6 +718,31 @@ async def qc_work_order_mine_tab_unfinished_counts(
         return error_response(400, str(e))
 
 
+@router.get("/qc-work-orders/by-sample")
+async def list_qc_work_orders_by_sample(
+    sampleCode: str = Query(..., alias="sampleCode"),
+    receptionSource: str = Query(..., alias="receptionSource"),
+    db: Session = Depends(get_db),
+    user: dict = Depends(
+        require_any_menu_code("biz:qcWorkOrder:list", "biz:myQcTask:list")
+    ),
+):
+    try:
+        tenant_no = _get_tenant_no(db, user)
+        items = wo_svc.list_work_orders_by_sample(
+            db,
+            sample_code=sampleCode,
+            reception_source=receptionSource,
+            tenant_no=tenant_no,
+            user_info=user,
+        )
+        return success_response({"list": items})
+    except PermissionError as e:
+        return error_response(403, str(e))
+    except Exception as e:
+        return error_response(400, str(e))
+
+
 @router.get("/qc-work-orders/{work_order_id}")
 async def get_qc_work_order_detail(
     work_order_id: str,

+ 37 - 0
backend/app/crud/qc_work_order.py

@@ -1713,6 +1713,43 @@ def list_work_orders(
     return items, int(total)
 
 
+def list_work_orders_by_sample(
+    db: Session,
+    *,
+    sample_code: str,
+    reception_source: str,
+    tenant_no: Optional[str],
+    user_info: Optional[Dict[str, Any]] = None,
+) -> List[Dict[str, Any]]:
+    """按样本编码精确查询入库质检工单(同协议样本跳转插入卡)。"""
+    code = (sample_code or "").strip()
+    rs = (reception_source or "").strip()
+    if not code or not rs:
+        return []
+    q = db.query(BizQcWorkOrder).filter(
+        BizQcWorkOrder.del_flag == 0,
+        BizQcWorkOrder.work_order_type == "inbound",
+        BizQcWorkOrder.sample_code == code,
+        BizQcWorkOrder.reception_source == rs,
+    )
+    if tenant_no:
+        q = q.filter(BizQcWorkOrder.tenant_no == tenant_no)
+    if user_info:
+        from app.core.data_scope import apply_data_scope
+
+        q = apply_data_scope(q, BizQcWorkOrder, user_info, db, tenant_no)
+    rows = q.order_by(BizQcWorkOrder.create_time.desc()).all()
+    items = [_wo_to_dict(x) for x in rows]
+    items = _enrich_work_order_list_items(db, items)
+    if tenant_no:
+        items = _enrich_work_order_qc_targets(db, tenant_no, items)
+        items = _enrich_production_work_order_refs(db, items)
+        items = _enrich_outbound_work_orders(db, tenant_no, items)
+        items = enrich_work_order_fee_summaries(db, items, tenant_no)
+    items = _enrich_unfinished_badge_counts(db, items)
+    return items
+
+
 def build_work_order_list_summaries(
     db: Session,
     work_orders: List[BizQcWorkOrder],

+ 19 - 7
backend/app/crud/sample_reception.py

@@ -856,14 +856,17 @@ def create_sample_reception(
     from app.crud import sample_reception_lab_code as lab_code_svc
 
     resolved_lab_code: Optional[str] = None
+    code_raw = (lab_storage_code or "").strip()
     if conclusion == "js":
-        code_raw = (lab_storage_code or "").strip()
         resolved_lab_code = lab_code_svc.validate_lab_storage_code(code_raw)
         lab_code_svc.assert_lab_storage_code_available(
             db, tenant_no=tenant_no, code=resolved_lab_code
         )
-    elif (lab_storage_code or "").strip():
-        raise ValueError("拒收样本无需填写实验室入库编码")
+    elif code_raw:
+        resolved_lab_code = lab_code_svc.validate_lab_storage_code(code_raw)
+        lab_code_svc.assert_lab_storage_code_available(
+            db, tenant_no=tenant_no, code=resolved_lab_code
+        )
 
     handler_display = (handler_name or "").strip()
     if handler_user_id and not handler_display:
@@ -1056,10 +1059,17 @@ def update_sample_reception(
 
     from app.crud import sample_reception_lab_code as lab_code_svc
 
-    if conclusion == "bj":
-        rec.lab_storage_code = None
-    elif conclusion == "js":
-        code_raw = (lab_storage_code or "").strip()
+    code_raw = (lab_storage_code or "").strip()
+    if conclusion == "js":
+        parsed_code = lab_code_svc.validate_lab_storage_code(code_raw)
+        lab_code_svc.assert_lab_storage_code_available(
+            db,
+            tenant_no=tenant_no,
+            code=parsed_code,
+            exclude_reception_id=reception_id,
+        )
+        rec.lab_storage_code = parsed_code
+    elif code_raw:
         parsed_code = lab_code_svc.validate_lab_storage_code(code_raw)
         lab_code_svc.assert_lab_storage_code_available(
             db,
@@ -1068,6 +1078,8 @@ def update_sample_reception(
             exclude_reception_id=reception_id,
         )
         rec.lab_storage_code = parsed_code
+    else:
+        rec.lab_storage_code = None
 
     handler_display = (handler_name or "").strip()
     if handler_user_id and not handler_display:

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


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


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


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


File diff suppressed because it is too large
+ 212 - 191
backend/graphify-out/.graphify_extract.json


+ 110 - 90
backend/graphify-out/GRAPH_REPORT.md

@@ -1,11 +1,11 @@
-# Graph Report - app  (2026-07-06)
+# Graph Report - app  (2026-07-07)
 
 ## Corpus Check
-- Large corpus: 217 files · ~263,522 words. Semantic extraction will be expensive (many Claude tokens). Consider running on a subfolder, or use --no-semantic to run AST-only.
+- Large corpus: 217 files · ~263,710 words. Semantic extraction will be expensive (many Claude tokens). Consider running on a subfolder, or use --no-semantic to run AST-only.
 
 ## Summary
-- 4491 nodes · 24195 edges · 46 communities detected
-- Extraction: 32% EXTRACTED · 68% INFERRED · 0% AMBIGUOUS · INFERRED: 16492 edges (avg confidence: 0.59)
+- 4494 nodes · 24239 edges · 50 communities detected
+- Extraction: 32% EXTRACTED · 68% INFERRED · 0% AMBIGUOUS · INFERRED: 16527 edges (avg confidence: 0.59)
 - Token cost: 0 input · 0 output
 
 ## Community Hubs (Navigation)
@@ -49,18 +49,22 @@
 - [[_COMMUNITY_Community 37|Community 37]]
 - [[_COMMUNITY_Community 38|Community 38]]
 - [[_COMMUNITY_Community 39|Community 39]]
+- [[_COMMUNITY_Community 40|Community 40]]
+- [[_COMMUNITY_Community 41|Community 41]]
 - [[_COMMUNITY_Community 42|Community 42]]
 - [[_COMMUNITY_Community 43|Community 43]]
-- [[_COMMUNITY_Community 44|Community 44]]
-- [[_COMMUNITY_Community 45|Community 45]]
 - [[_COMMUNITY_Community 46|Community 46]]
 - [[_COMMUNITY_Community 47|Community 47]]
+- [[_COMMUNITY_Community 48|Community 48]]
+- [[_COMMUNITY_Community 49|Community 49]]
+- [[_COMMUNITY_Community 50|Community 50]]
+- [[_COMMUNITY_Community 51|Community 51]]
 
 ## God Nodes (most connected - your core abstractions)
-1. `success_response()` - 670 edges
-2. `error_response()` - 554 edges
-3. `SysUser` - 501 edges
-4. `BizQcTaskResult` - 315 edges
+1. `success_response()` - 671 edges
+2. `error_response()` - 555 edges
+3. `SysUser` - 502 edges
+4. `BizQcTaskResult` - 316 edges
 5. `BizProductionMaterial` - 311 edges
 6. `BizProductionMaterialUsage` - 306 edges
 7. `BizQcMaterialUsage` - 303 edges
@@ -84,59 +88,59 @@
 
 ### Community 0 - "Community 0"
 Cohesion: 0.01
-Nodes (843): attachment_download_url(), create_dept(), create_dict_data(), create_dict_type(), create_express_company(), create_item_unit(), create_menu(), create_post() (+835 more)
+Nodes (866): attachment_download_url(), create_dept(), create_dict_data(), create_dict_type(), create_express_company(), create_item_unit(), create_menu(), create_post() (+858 more)
 
 ### Community 1 - "Community 1"
 Cohesion: 0.01
-Nodes (621): AttachmentDownloadUrlBody, DeptCreateBody, DeptUpdateBody, DictDataCreateBody, DictDataUpdateBody, DictTypeCreateBody, DictTypeUpdateBody, ExpressCompanyCreateBody (+613 more)
+Nodes (616): AttachmentDownloadUrlBody, DeptCreateBody, DeptUpdateBody, DictDataCreateBody, DictDataUpdateBody, DictTypeCreateBody, DictTypeUpdateBody, ExpressCompanyCreateBody (+608 more)
 
 ### Community 2 - "Community 2"
-Cohesion: 0.06
-Nodes (531): BatchRefundFinanceSubmitBody, 细胞存储 — 采集协议单 / 审核 / 财务确认 / 分期预警 API, 与列表接口一致:按租户 + 角色数据权限判断是否可访问该协议单。, If mother info exists but no customerId, create a customer record and link it., RefundFinanceSubmitBody, Base, 细胞产品 CRUD(biz_cell_product), 解析任务样本投入物,返回祖先链单段(样本终止节点)。 (+523 more)
+Cohesion: 0.07
+Nodes (504): 细胞存储 — 采集协议单 / 审核 / 财务确认 / 分期预警 API, 与列表接口一致:按租户 + 角色数据权限判断是否可访问该协议单。, If mother info exists but no customerId, create a customer record and link it., Base, 细胞产品 CRUD(biz_cell_product), 解析任务样本投入物,返回祖先链单段(样本终止节点)。, 管理端手动改状态:允许在合法枚举间任意变更,并同步库位占用。, 去掉末尾 -P{n}-XX 或 -XX,得到代次产品编码的基础前缀。 (+496 more)
 
 ### Community 3 - "Community 3"
 Cohesion: 0.01
-Nodes (330): _normalize_date_bound(), list_ethnics_for_select(), list_clean_rooms(), batch_delete_research_donations(), change_donation_status(), convert_from_storage(), convert_to_storage(), create_research_donation() (+322 more)
+Nodes (388): _normalize_date_bound(), list_ethnics_for_select(), apply_data_scope(), _get_child_dept_ids(), get_user_dept_id(), _resolve_user_role_scopes(), format_date(), ApiRequestLogCRUD (+380 more)
 
 ### Community 4 - "Community 4"
 Cohesion: 0.01
-Nodes (274): generate_uuid(), create_collection_region(), create_hospital(), get_collection_region(), get_hospital(), _hospital_to_dict(), list_hospitals(), 按医院名称模糊检索,返回医院及所属采集地区信息。 (+266 more)
+Nodes (310): generate_uuid(), map_payments_by_schedule_ids(), batch_stock_in(), batch_stock_out(), consumable_code_exists(), create_consumable(), _dec(), get_consumable() (+302 more)
 
 ### Community 5 - "Community 5"
 Cohesion: 0.02
-Nodes (197): _code_exists(), create_material(), _dec(), _fmt_dt(), get_material(), get_material_entity(), get_my_usages(), _is_enabled_status() (+189 more)
+Nodes (252): eq_unicode(), like_unicode(), MySQL 字符串比较/模糊匹配时统一排序规则,避免 Illegal mix of collations。, 跨表字符串等值比较(JOIN / WHERE)时统一为 utf8mb4_unicode_ci。, 将列显式设为 utf8mb4_unicode_ci。, unicode_ci(), build_void_type_label_map(), format_protocol_status_label() (+244 more)
 
 ### Community 6 - "Community 6"
 Cohesion: 0.02
-Nodes (194): eq_unicode(), like_unicode(), MySQL 字符串比较/模糊匹配时统一排序规则,避免 Illegal mix of collations。, 跨表字符串等值比较(JOIN / WHERE)时统一为 utf8mb4_unicode_ci。, 将列显式设为 utf8mb4_unicode_ci。, unicode_ci(), build_void_type_label_map(), format_protocol_status_label() (+186 more)
+Nodes (185): batch_discard(), batch_options(), _batch_row_to_dict(), batch_stock_in(), batch_stock_out(), _create_batch_and_usage(), delete_batch(), _discard_batch() (+177 more)
 
 ### Community 7 - "Community 7"
 Cohesion: 0.02
-Nodes (177): api_save_application(), get_protocol_nos_used_in_protocols(), create_dict_type(), dict_type_code_exists(), get_dict_type(), list_dict_types(), 字典类型 CRUD(sys_dict_type), _row_to_dict() (+169 more)
+Nodes (116): get_designer_data(), _error_json(), lifespan(), 数据库异常:返回 JSON 并保留 CORS 头,避免浏览器只报跨域错误, 未捕获异常:统一 JSON 500,确保跨域场景下仍有 Access-Control-Allow-Origin, sqlalchemy_exception_handler(), unhandled_exception_handler(), BaseHTTPMiddleware (+108 more)
 
 ### Community 8 - "Community 8"
 Cohesion: 0.02
-Nodes (112): get_designer_data(), _error_json(), 数据库异常:返回 JSON 并保留 CORS 头,避免浏览器只报跨域错误, 未捕获异常:统一 JSON 500,确保跨域场景下仍有 Access-Control-Allow-Origin, sqlalchemy_exception_handler(), unhandled_exception_handler(), BaseHTTPMiddleware, ApiCryptoError (+104 more)
+Nodes (149): _avatar_url_field(), _build_current_user_payload(), _build_user_info_payload(), get_current_user_profile(), get_web_auth_menu_codes(), _jwt_user_payload(), logout(), 公共接口(Web 登录、刷新 Token、登出;用户模型对齐 docs/sql/zkxw.sql 基线) (+141 more)
 
 ### Community 9 - "Community 9"
-Cohesion: 0.03
-Nodes (147): _ancestor_link_from_cell_product_input(), _ancestor_sample_chain_link(), _apply_cell_product_status_change(), _build_ancestor_chain(), _build_descendant_chain(), _build_facility_path_label(), build_split_product_codes(), create_cell_product() (+139 more)
+Cohesion: 0.04
+Nodes (124): AccessLogCreateBody, AccessLogUpdateBody, CleanRoomCreateBody, CleanRoomUpdateBody, ExhibitionAppointmentCreateBody, ExhibitionAppointmentUpdateBody, list_storage_slot_stats(), OccupancyCreateBody (+116 more)
 
 ### Community 10 - "Community 10"
-Cohesion: 0.04
-Nodes (147): AccessLogCreateBody, AccessLogUpdateBody, batch_create_storage_slots(), CleanRoomCreateBody, CleanRoomUpdateBody, create_access_log(), create_storage_slot(), create_uv_disinfect_log() (+139 more)
+Cohesion: 0.34
+Nodes (73): BatchStockInBody, BatchStockInItem, BatchStockOutBody, BatchStockOutItem, ConsumableCreateBody, ConsumablePackCreateBody, ConsumablePackUpdateBody, ConsumableUpdateBody (+65 more)
 
 ### Community 11 - "Community 11"
-Cohesion: 0.03
-Nodes (139): _format_attachment_file_size(), urls_to_attachment_list(), _accept_conclusion_label(), apply_planned_start_time_step_filter(), _attach_outbound_detail(), _attach_reception_detail(), complete_production_work_order(), configure_temp_production_work_order() (+131 more)
+Cohesion: 0.05
+Nodes (69): generate_id(), optional_date(), parse_date(), 将字符串 / date / datetime 转为 date 或 None, camel_to_snake(), get_model_attr(), HistoryCRUD, allocate_next_biz_no() (+61 more)
 
 ### Community 12 - "Community 12"
-Cohesion: 0.34
-Nodes (73): BatchStockInBody, BatchStockInItem, BatchStockOutBody, BatchStockOutItem, ConsumableCreateBody, ConsumablePackCreateBody, ConsumablePackUpdateBody, ConsumableUpdateBody (+65 more)
+Cohesion: 0.06
+Nodes (62): get_next_cell_product_code(), _ancestor_link_from_cell_product_input(), _ancestor_sample_chain_link(), _apply_cell_product_status_change(), _build_ancestor_chain(), _build_descendant_chain(), _build_facility_path_label(), build_split_product_codes() (+54 more)
 
 ### Community 13 - "Community 13"
 Cohesion: 0.06
-Nodes (56): _avatar_url_field(), _build_user_info_payload(), _jwt_user_payload(), logout(), 公共接口(Web 登录、刷新 Token、登出;用户模型对齐 docs/sql/zkxw.sql 基线), Web 端登录用 RSA 公钥(PKCS#1 v1.5,与 JSEncrypt 一致), Web 端登录:账号为 sys_user.user_no,密码为 sys_user.password(bcrypt)。, 登出:吊销 Redis 会话(若已配置 REDIS_HOST)。 (+48 more)
+Nodes (61): complete_application(), create_basic_experiment_sale_schedules(), create_infusion_plan(), create_schedule(), _create_schedule_products(), _format_yyyy_mm_dd(), _generate_application_no(), get_application() (+53 more)
 
 ### Community 14 - "Community 14"
 Cohesion: 0.11
@@ -144,168 +148,184 @@ Nodes (39): _amount_for_row(), _batch_map(), deduct_for_task(), _deduct_task_usa
 
 ### Community 15 - "Community 15"
 Cohesion: 0.11
-Nodes (36): batch_create_sterilize_logs(), batch_create_wash_logs(), batch_discard_garments(), batch_soft_delete_garments(), _count_map(), create_garment(), create_sterilize_log(), create_wash_log() (+28 more)
+Nodes (35): batch_create_sterilize_logs(), batch_create_wash_logs(), batch_discard_garments(), batch_soft_delete_garments(), _count_map(), create_garment(), create_sterilize_log(), create_wash_log() (+27 more)
 
 ### Community 16 - "Community 16"
 Cohesion: 0.12
-Nodes (10): 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, 小程序调起支付的二次签名参数(与公众号 JSAPI 规则一致), 统一下单 (V2 API)          Args:             out_trade_no: 商户订单号             tot, 生成签名 V3 (SHA256 with RSA), wechat_pay() (+2 more)
+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 17 - "Community 17"
-Cohesion: 0.27
-Nodes (12): create_equipment(), _dec(), equipment_code_exists(), equipment_is_referenced(), get_equipment(), list_equipments(), _norm_status(), _parse_date() (+4 more)
+Cohesion: 0.16
+Nodes (22): _code_exists(), create_material(), _dec(), _fmt_dt(), get_material(), get_material_entity(), get_my_usages(), _is_enabled_status() (+14 more)
 
 ### Community 18 - "Community 18"
-Cohesion: 0.26
-Nodes (12): batch_issue_sample_codes(), batch_revoke_sample_codes(), create_sample_code(), get_sample_code(), list_sample_codes(), _parse_issued_time(), 样本编码池 CRUD(biz_sample_code), _resolve_issued_user_display_name() (+4 more)
+Cohesion: 0.17
+Nodes (21): batch_stock_in(), batch_stock_out(), code_exists(), create_material(), _dec(), _fmt_dt(), get_material(), _is_enabled_status() (+13 more)
 
 ### Community 19 - "Community 19"
-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)
+Cohesion: 0.14
+Nodes (17): _build_tree(), _cascade_soft_delete(), create_menu(), get_menu(), list_menus(), list_menus_tree(), _row_to_dict(), soft_delete_menu() (+9 more)
 
 ### Community 20 - "Community 20"
-Cohesion: 0.32
-Nodes (11): create_env_detect_record(), _generate_record_no(), get_env_detect_record(), _join_csv(), list_env_detect_records(), _resolve_inspector_snapshots(), _resolve_item_snapshot(), _row_to_dict() (+3 more)
+Cohesion: 0.18
+Nodes (15): create_collection_region(), create_hospital(), get_collection_region(), get_hospital(), _hospital_names_by_region_ids(), _hospital_to_dict(), list_collection_regions(), list_hospitals() (+7 more)
 
 ### Community 21 - "Community 21"
+Cohesion: 0.26
+Nodes (12): batch_issue_sample_codes(), batch_revoke_sample_codes(), create_sample_code(), get_sample_code(), list_sample_codes(), _parse_issued_time(), 样本编码池 CRUD(biz_sample_code), _resolve_issued_user_display_name() (+4 more)
+
+### Community 22 - "Community 22"
+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)
+
+### Community 23 - "Community 23"
 Cohesion: 0.24
 Nodes (11): deduct_for_step(), _display_remark(), list_stock_logs(), _parse_receiver_purpose_from_remark(), 试剂变动记录 CRUD(biz_reagent_stock_log), 撤销步骤此前 step_save 扣减,便于重新填报。, 扣减试剂库存并生成变动记录。调用方控制事务,本函数不 commit。, _resolve_receiver_purpose() (+3 more)
 
-### Community 22 - "Community 22"
+### Community 24 - "Community 24"
+Cohesion: 0.32
+Nodes (11): create_env_detect_record(), _generate_record_no(), get_env_detect_record(), _join_csv(), list_env_detect_records(), _resolve_inspector_snapshots(), _resolve_item_snapshot(), _row_to_dict() (+3 more)
+
+### Community 25 - "Community 25"
 Cohesion: 0.24
 Nodes (11): deduct_for_step(), _display_remark(), list_stock_logs(), _parse_receiver_purpose_from_remark(), 耗材变动记录 CRUD(biz_consumable_stock_log), 撤销步骤此前 step_save 扣减,便于重新填报。, 扣减耗材库存并生成变动记录。调用方控制事务,本函数不 commit。, _resolve_receiver_purpose() (+3 more)
 
-### Community 23 - "Community 23"
+### Community 26 - "Community 26"
 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"
+### Community 27 - "Community 27"
 Cohesion: 0.27
 Nodes (8): create_customer(), get_customer(), id_card_taken(), list_customers(), 客户 CRUD(biz_customer), _row_to_dict(), soft_delete_customer(), update_customer()
 
-### Community 25 - "Community 25"
+### Community 28 - "Community 28"
 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()
 
-### Community 26 - "Community 26"
+### Community 29 - "Community 29"
 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)
 
-### Community 27 - "Community 27"
+### Community 30 - "Community 30"
 Cohesion: 0.38
 Nodes (9): create_env_detect_item(), get_env_detect_item(), item_code_exists(), list_active_env_detect_items(), list_env_detect_items(), _order_query(), _row_to_dict(), soft_delete_env_detect_item() (+1 more)
 
-### Community 28 - "Community 28"
-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 29 - "Community 29"
+### Community 31 - "Community 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()
 
-### Community 30 - "Community 30"
+### Community 32 - "Community 32"
 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()
 
-### Community 31 - "Community 31"
+### Community 33 - "Community 33"
 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()
 
-### Community 32 - "Community 32"
+### Community 34 - "Community 34"
+Cohesion: 0.36
+Nodes (7): create_post(), get_post(), list_posts(), post_code_exists(), _row_to_dict(), soft_delete_post(), update_post()
+
+### Community 35 - "Community 35"
+Cohesion: 0.36
+Nodes (7): create_tenant(), get_tenant(), list_tenants(), _row_to_dict(), soft_delete_tenant(), tenant_no_exists(), update_tenant()
+
+### Community 36 - "Community 36"
 Cohesion: 0.36
 Nodes (7): create_site(), get_site(), list_sites(), _row_to_dict(), site_code_exists(), soft_delete_site(), update_site()
 
-### Community 33 - "Community 33"
+### Community 37 - "Community 37"
 Cohesion: 0.29
 Nodes (2): BaseModel, SoftDeleteModel
 
-### Community 34 - "Community 34"
+### Community 38 - "Community 38"
 Cohesion: 0.38
 Nodes (6): derive_legacy_status_from_three(), derive_three_status_from_legacy(), 阶段A兼容:从旧 status 推导三字段。     约束:旧 status 本身混合了支付/配送/订单生命周期,因此只能尽力推断;     若业务侧已有更精确, 阶段A兼容:从三字段反推旧 status(用于旧前端/旧筛选参数兼容)。, ThreeStatus, _to_decimal()
 
-### Community 35 - "Community 35"
+### Community 39 - "Community 39"
 Cohesion: 0.5
 Nodes (3): BaseSettings, Config, Settings
 
-### Community 36 - "Community 36"
+### Community 40 - "Community 40"
 Cohesion: 0.5
 Nodes (2): _auto_fill_create_dept_id(), ORM before_insert 钩子:当 create_dept_id 为空时,自动从请求上下文填充当前用户部门 ID
 
-### Community 37 - "Community 37"
+### Community 41 - "Community 41"
 Cohesion: 0.67
 Nodes (2): get_menu_list(), 运营端菜单接口(与 web-admin 前端静态路由 sqxp.ts 对齐,5 个一级菜单分组)
 
-### Community 38 - "Community 38"
+### Community 42 - "Community 42"
 Cohesion: 1.0
 Nodes (1): 请求级别的用户上下文变量  通过 contextvars 在请求处理链路中传递当前用户信息, 供 SQLAlchemy 事件监听器等底层机制使用,避免逐层手动传
 
-### Community 39 - "Community 39"
+### Community 43 - "Community 43"
 Cohesion: 1.0
 Nodes (1): API 路由包(骨架:公共鉴权 + 静态菜单 + 运营端占位)
 
-### Community 42 - "Community 42"
+### Community 46 - "Community 46"
 Cohesion: 1.0
 Nodes (1): 发送订单状态通知                  Args:             openid: 用户openid             ord
 
-### Community 43 - "Community 43"
+### Community 47 - "Community 47"
 Cohesion: 1.0
 Nodes (1): 发送支付成功通知                  Args:             openid: 用户openid             ord
 
-### Community 44 - "Community 44"
+### Community 48 - "Community 48"
 Cohesion: 1.0
 Nodes (1): 发送配送通知                  Args:             openid: 配送员openid             task
 
-### Community 45 - "Community 45"
+### Community 49 - "Community 49"
 Cohesion: 1.0
 Nodes (1): 发送配送时效预警                  Args:             openid: 配送员openid             ta
 
-### Community 46 - "Community 46"
+### Community 50 - "Community 50"
 Cohesion: 1.0
 Nodes (1): 发送退款结果通知                  Args:             openid: 用户openid             ord
 
-### Community 47 - "Community 47"
+### Community 51 - "Community 51"
 Cohesion: 1.0
 Nodes (1): 发送催单通知给配送员                  Args:             deliver_openid: 配送员openid
 
 ## Knowledge Gaps
 - **130 isolated node(s):** `刷新会话 TTL(refreshToken 续期;已登录用户每次 API 校验通过后滑动续期)`, `写入 refreshToken(用于 one-time 使用/吊销)`, `根据 token 获取当前用户(包含 Redis 会话校验;会话滑动续期)`, `登录凭据 RSA 解密(与前端 JSEncrypt PKCS#1 v1.5 兼容)。`, `加载 RSA 密钥对:优先环境变量,否则使用内置固定默认密钥。` (+125 more)
   These have ≤1 connection - possible missing edges or undocumented components.
-- **Thin community `Community 33`** (8 nodes): `BaseModel`, `.to_dict()`, `get_active()`, `base.py`, `SoftDeleteModel`, `.restore()`, `.soft_delete()`, `__tablename__()`
+- **Thin community `Community 37`** (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.
-- **Thin community `Community 36`** (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.
-- **Thin community `Community 37`** (3 nodes): `get_menu_list()`, `menu.py`, `运营端菜单接口(与 web-admin 前端静态路由 sqxp.ts 对齐,5 个一级菜单分组)`
+- **Thin community `Community 41`** (3 nodes): `get_menu_list()`, `menu.py`, `运营端菜单接口(与 web-admin 前端静态路由 sqxp.ts 对齐,5 个一级菜单分组)`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 38`** (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.
-- **Thin community `Community 39`** (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.
-- **Thin community `Community 42`** (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.
-- **Thin community `Community 43`** (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.
-- **Thin community `Community 44`** (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.
-- **Thin community `Community 45`** (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.
-- **Thin community `Community 46`** (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.
-- **Thin community `Community 47`** (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.
 
 ## Suggested Questions
 _Questions this graph is uniquely positioned to answer:_
 
-- **Why does `SysUser` connect `Community 1` to `Community 0`, `Community 2`, `Community 3`, `Community 5`, `Community 6`, `Community 7`, `Community 10`, `Community 12`, `Community 13`, `Community 14`, `Community 18`, `Community 21`, `Community 22`?**
+- **Why does `SysUser` connect `Community 1` to `Community 0`, `Community 2`, `Community 3`, `Community 4`, `Community 5`, `Community 6`, `Community 8`, `Community 9`, `Community 10`, `Community 14`, `Community 17`, `Community 21`, `Community 23`, `Community 25`?**
   _High betweenness centrality (0.104) - this node is a cross-community bridge._
-- **Why does `generate_uuid()` connect `Community 4` to `Community 0`, `Community 1`, `Community 2`, `Community 3`, `Community 5`, `Community 6`, `Community 7`, `Community 8`, `Community 9`, `Community 10`, `Community 11`, `Community 13`, `Community 14`, `Community 15`, `Community 17`, `Community 18`, `Community 20`, `Community 21`, `Community 22`, `Community 23`, `Community 24`, `Community 25`, `Community 26`, `Community 27`, `Community 28`, `Community 29`, `Community 30`, `Community 31`, `Community 32`?**
-  _High betweenness centrality (0.074) - this node is a cross-community bridge._
-- **Why does `success_response()` connect `Community 0` to `Community 1`, `Community 3`, `Community 37`, `Community 7`, `Community 8`, `Community 10`, `Community 13`?**
-  _High betweenness centrality (0.062) - this node is a cross-community bridge._
-- **Are the 737 inferred relationships involving `str` (e.g. with `unhandled_exception_handler()` and `get_current_user_by_token()`) actually correct?**
-  _`str` has 737 INFERRED edges - model-reasoned connections that need verification._
-- **Are the 669 inferred relationships involving `success_response()` (e.g. with `list_storage_release_reviews()` and `get_storage_release_review_detail()`) actually correct?**
-  _`success_response()` has 669 INFERRED edges - model-reasoned connections that need verification._
-- **Are the 553 inferred relationships involving `error_response()` (e.g. with `_json_error_response()` and `list_storage_release_reviews()`) actually correct?**
-  _`error_response()` has 553 INFERRED edges - model-reasoned connections that need verification._
-- **Are the 499 inferred relationships involving `SysUser` (e.g. with `数据权限过滤(V0.3:基于角色 data_scope 的查询级行级过滤)  data_scope 值:   1 = 全部数据   2 = 本部门数据   3` and `从 SysUser 读取用户的 dept_id。`) actually correct?**
-  _`SysUser` has 499 INFERRED edges - model-reasoned connections that need verification._
+- **Why does `generate_uuid()` connect `Community 4` to `Community 0`, `Community 1`, `Community 3`, `Community 5`, `Community 6`, `Community 7`, `Community 8`, `Community 9`, `Community 11`, `Community 12`, `Community 14`, `Community 15`, `Community 17`, `Community 18`, `Community 19`, `Community 20`, `Community 21`, `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`?**
+  _High betweenness centrality (0.089) - this node is a cross-community bridge._
+- **Why does `success_response()` connect `Community 0` to `Community 1`, `Community 3`, `Community 7`, `Community 8`, `Community 9`, `Community 41`, `Community 12`?**
+  _High betweenness centrality (0.055) - this node is a cross-community bridge._
+- **Are the 738 inferred relationships involving `str` (e.g. with `unhandled_exception_handler()` and `get_current_user_by_token()`) actually correct?**
+  _`str` has 738 INFERRED edges - model-reasoned connections that need verification._
+- **Are the 670 inferred relationships involving `success_response()` (e.g. with `list_storage_release_reviews()` and `get_storage_release_review_detail()`) actually correct?**
+  _`success_response()` has 670 INFERRED edges - model-reasoned connections that need verification._
+- **Are the 554 inferred relationships involving `error_response()` (e.g. with `_json_error_response()` and `list_storage_release_reviews()`) actually correct?**
+  _`error_response()` has 554 INFERRED edges - model-reasoned connections that need verification._
+- **Are the 500 inferred relationships involving `SysUser` (e.g. with `数据权限过滤(V0.3:基于角色 data_scope 的查询级行级过滤)  data_scope 值:   1 = 全部数据   2 = 本部门数据   3` and `从 SysUser 读取用户的 dept_id。`) actually correct?**
+  _`SysUser` has 500 INFERRED edges - model-reasoned connections that need verification._

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


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

@@ -142,7 +142,7 @@
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/env_detect_item.py": 1781145689.9525824,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/process_category.py": 1779265700.0606413,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/item_unit.py": 1779265653.7504313,
-  "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py": 1783327846.1726813,
+  "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py": 1783392547.510364,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/outbound_handover_inventory.py": 1781598316.476379,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/env_detect_record.py": 1782376492.2365673,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/outbound_release_material.py": 1781612877.0137436,
@@ -151,7 +151,7 @@
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/customer.py": 1779686149.7508218,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/workflow.py": 1779265993.677624,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/sample_sms_notification.py": 1781063495.0317714,
-  "/Users/wuzilong/work/00code/zkxw/backend/app/crud/sample_reception.py": 1783308348.4658842,
+  "/Users/wuzilong/work/00code/zkxw/backend/app/crud/sample_reception.py": 1783389101.2508402,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/consumable_pack.py": 1779265715.416005,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_inspection_batch.py": 1782984951.4695187,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/production_material_prep.py": 1781514953.3551314,
@@ -177,7 +177,7 @@
   "/Users/wuzilong/work/00code/zkxw/backend/app/api/public.py": 1782122639.96012,
   "/Users/wuzilong/work/00code/zkxw/backend/app/api/material.py": 1782894693.6582327,
   "/Users/wuzilong/work/00code/zkxw/backend/app/api/production_material.py": 1782404982.2474263,
-  "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py": 1783322915.4871485,
+  "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py": 1783392548.7993915,
   "/Users/wuzilong/work/00code/zkxw/backend/app/api/market.py": 1781522283.6823432,
   "/Users/wuzilong/work/00code/zkxw/backend/app/api/sample_production_reception.py": 1782718998.90251,
   "/Users/wuzilong/work/00code/zkxw/backend/app/api/collection_protocol.py": 1782982337.6828277,

+ 61 - 0
docs/superpowers/plans/2026-07-07-qc-inbound-sibling-navigate.md

@@ -0,0 +1,61 @@
+# 同协议样本点击跳转与按需插入卡片 Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** 入库质检(细胞存储/科研捐赠)同协议样本胶囊可点击;列表内滚动高亮,列表外按样本拉工单并在来源卡下方插入兄弟样本卡。
+
+**Architecture:** 后端新增 `GET /qc-work-orders/by-sample` 精确查工单并 enrich;前端 `useInboundSiblingNavigation` 管理插入卡与滚动;`QcWorkOrderSampleGroup` 发 `sibling-navigate` 事件;`QcWorkOrderCardList` 渲染插入卡。
+
+**Tech Stack:** FastAPI + SQLAlchemy · Vue 3 + Element Plus · 现有 `QcWorkOrderSampleGroup` 卡片体系
+
+**Spec:** `docs/superpowers/specs/2026-07-07-qc-inbound-sibling-navigate-design.md`
+
+---
+
+### Task 1: 后端 by-sample 查询
+
+**Files:**
+- Modify: `backend/app/crud/qc_work_order.py`
+- Modify: `backend/app/api/qc.py`
+
+- [ ] 在 `qc_work_order.py` 新增 `list_work_orders_by_sample`(tenant + 数据权限 + 列表 enrich)
+- [ ] 在 `qc.py` 注册 `GET /qc-work-orders/by-sample`(置于 `/{work_order_id}` 之前),权限 `require_any_menu_code("biz:qcWorkOrder:list", "biz:myQcTask:list")`
+
+### Task 2: 前端 API 与 link 模型
+
+**Files:**
+- Modify: `web-admin/src/api/qc-work-order.ts`
+- Modify: `web-admin/src/utils/qc-work-order-inbound-sample-cards.ts`
+
+- [ ] 新增 `fetchQcWorkOrdersBySample`
+- [ ] `QcInboundProtocolSiblingLink` 增加 `sampleCode`;`buildProtocolSiblingLinksFromSamples` / `buildLinksFromVisibleGroups` 写入
+
+### Task 3: useInboundSiblingNavigation composable
+
+**Files:**
+- Create: `web-admin/src/views/quality-inspection/qc-work-order/composables/useInboundSiblingNavigation.ts`
+
+- [ ] 实现 `navigateToSibling` / `clearInsertedCards` / `insertedCardsAfter`
+- [ ] manage 模式拉 inspection batches;mine 模式按 sampleCode keyword 过滤我的任务
+
+### Task 4: 集成 Tab 页与 CardList
+
+**Files:**
+- Modify: `web-admin/src/views/quality-inspection/qc-work-order/composables/useQcWorkOrderTabPage.ts`
+- Modify: `web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderCardList.vue`
+- Modify: `web-admin/src/views/quality-inspection/qc-work-order/tabs/QcWorkOrderTabPanel.vue`
+
+- [ ] `loadData` 开头 `clearInsertedCards`
+- [ ] CardList 入库分支渲染插入卡 + 合并 preset tasks/batches
+
+### Task 5: SampleGroup 可点击芯片
+
+**Files:**
+- Modify: `web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSampleGroup.vue`
+
+- [ ] 芯片改 button、emit `sibling-navigate`、`isInsertedSibling` 标识、loading 禁用
+
+### Task 6: 验证
+
+- [ ] `pnpm lint`(改动文件)
+- [ ] `./scripts/update-graph.sh frontend`

+ 280 - 0
docs/superpowers/specs/2026-07-07-qc-inbound-sibling-navigate-design.md

@@ -0,0 +1,280 @@
+# 入库质检 · 同协议样本点击跳转与按需插入卡片
+
+> 日期:2026-07-07  
+> 状态:已批准(2026-07-07)  
+> 范围:`web-admin` 【质检工单】【我的质检任务】入库类 Tab(细胞存储、科研捐赠);后端按样本编码查工单小接口  
+> 关联:`2026-06-25-qc-work-order-protocol-group-design.md`(历史协议组方案,现网未接入列表)、`2026-06-16-qc-work-order-tab-split-design.md`
+
+---
+
+## 1. 背景与目标
+
+### 1.1 问题
+
+细胞存储、科研捐赠入库质检列表中,样本卡标题栏展示 **「同协议样本」** 胶囊(实验室编号 + 样本类型),数据来自后端 `protocolSiblingSamples` enrich。
+
+现状:
+
+- 胶囊为纯展示 `<span>`,**不可点击**;
+- 虽已有 `scrollToInboundSampleCard(anchorId)` 工具函数,但未绑定到 UI;
+- 兄弟样本常**不在当前列表**(分页、筛选、我的任务无指派等),无法跳转查看。
+
+### 1.2 目标
+
+1. 「同协议样本」胶囊可点击,跳转到对应样本的质检卡片。
+2. 目标样本**已在当前列表 DOM 中**:平滑滚动 + 高亮(复用 `scrollToInboundSampleCard`)。
+3. 目标样本**不在当前列表**:在**点击来源样本卡正下方**按需插入一张完整样本卡,滚动并高亮。
+4. 【质检工单】与【我的质检任务】**共用**同一套导航逻辑与卡片组件。
+
+### 1.3 非目标
+
+- 不改变出库、临时、「所有」Tab 中非入库样本卡的布局(「所有」Tab 中入库样本组若已有同协议芯片,一并支持本能力)。
+- 不恢复/接入 `QcWorkOrderProtocolGroup` 协议组外层卡片。
+- 不改变列表分页语义(插入卡不计入 `total`、不触发服务端翻页)。
+- 不做「自动修改筛选条件以定位兄弟样本」。
+- 本期不做弹窗/抽屉替代插入卡的方案。
+
+---
+
+## 2. 已确认产品决策
+
+| 决策点 | 结论 |
+|--------|------|
+| **我的质检任务 · 插入卡内容** | **选项 A**:仍插入完整工单卡;任务区可为空;操作区仅「查看」等只读能力(与现网 `showWorkOrderViewOnly` 一致) |
+| **插入位置** | 紧贴**点击来源**那张样本卡下方,不按全局排序重排 |
+| **点击当前样本芯片** | 仅高亮,不重复插入、不请求接口 |
+| **插入卡生命周期** | Tab 切换、搜索/重置、翻页、`loadData()` 重载时**清空**所有插入卡 |
+| **去重** | 同一 `sampleCode` 已在主列表或已插入 → 只滚动到已有卡,不重复插入 |
+| **无质检工单** | Toast:`该样本暂无质检工单`(不插入空壳卡) |
+
+---
+
+## 3. 交互设计
+
+### 3.1 芯片样式与状态
+
+| 状态 | 表现 |
+|------|------|
+| 当前样本(`is-current`) | 保持现有高亮样式;`cursor: default`;点击仅触发高亮动画 |
+| 可跳转样本 | `cursor: pointer`;hover 边框/底色略加深;`role="button"` + 键盘可聚焦 |
+| 加载中(拉取兄弟样本) | 来源卡下方显示细条 loading(`v-loading` 或 skeleton),禁止重复点击 |
+
+### 3.2 点击流程
+
+```
+用户点击芯片(非当前)
+  → 解析 targetSampleCode(来自 link.sampleCode)
+  → 若 document.getElementById(anchorId) 存在
+       → scrollToInboundSampleCard(anchorId)
+  → 否则
+       → 若 insertedCards 或主列表已含该 sampleCode → 滚动到已有 anchor
+       → 否则 POST/GET 按样本拉工单
+            → 有工单:在来源卡下插入 SampleGroup,再滚动高亮
+            → 无工单:ElMessage.info
+```
+
+### 3.3 插入卡视觉区分
+
+插入卡在标题栏副标题区增加轻量 muted 文案:**「同协议关联」**(或同等语义 Tag),与主列表卡区分;其余结构与普通 `QcWorkOrderSampleGroup` 一致。
+
+### 3.4 我的质检任务 · 插入卡
+
+- `pageMode=mine` → `showWorkOrderViewOnly=true`,`showWorkOrderOps=false`。
+- `presetTasksByWorkOrderId`:仅填入**当前用户**在该工单下的任务;无任务则为 `{}` 或空数组 → 任务区自然为空。
+- 仍展示工单元信息、触发来源 Tab、查看弹窗入口。
+
+---
+
+## 4. 数据与 API
+
+### 4.1 前端 Link 模型扩展
+
+`QcInboundProtocolSiblingLink` 增加 `sampleCode`(拉数主键;`anchorId` 仍由 `buildInboundSampleCardAnchorId(sampleCode)` 生成):
+
+```ts
+export type QcInboundProtocolSiblingLink = {
+  sampleCode: string
+  anchorId: string
+  labStorageCode: string
+  sampleTypeName: string
+}
+```
+
+`buildProtocolSiblingLinksFromSamples` 从 `QcProtocolSiblingSample.sampleCode` 写入。
+
+### 4.2 新增后端接口(推荐)
+
+**`GET /api/web/admin/qc-work-orders/by-sample`**
+
+| 参数 | 必填 | 说明 |
+|------|------|------|
+| `sampleCode` | 是 | 样本编码,精确匹配 |
+| `receptionSource` | 是 | `cell_storage` \| `research_donation`,与 Tab 一致 |
+
+**响应**:`{ list: QcWorkOrder[] }`(0~N 条,同一样本可有多条工单:接收 + 生产过程等)
+
+**查询规则**:
+
+- `tenant_no` = 当前用户租户
+- `del_flag = 0`
+- `work_order_type = inbound`
+- `sample_code` 精确匹配
+- `reception_source` 匹配
+- 应用数据权限(与列表一致)
+- 列表 enrich:`_enrich_work_order_list_items`、`_enrich_work_order_qc_targets`、`_enrich_unfinished_badge_counts` 等,**含 `protocolSiblingSamples`**
+
+**不用现有 `keyword` 的原因**:列表关键词为 `LIKE` 模糊匹配,不可靠。
+
+### 4.3 我的任务 · 任务数据
+
+插入卡渲染前,对返回的 `workOrder.id` 列表调用现有「我的任务」查询(可批量 by workOrderIds,或逐工单 `fetchMyQcTaskList` 带 workOrderId 过滤——以实现时现有 API 能力为准),仅合并指派给当前用户的任务到 `presetTasksByWorkOrderId`。
+
+若无批量接口,首期可接受:插入后对每个 workOrderId 并行请求 mine tasks(通常 1~2 条工单,可接受)。
+
+### 4.4 质检工单 · 批次数据
+
+`pageMode=manage` 时,插入卡工单 ID 走现有 `fetchQcInspectionBatchesBatch` 写入 `presetBatchesByWorkOrderId`。
+
+---
+
+## 5. 前端架构
+
+### 5.1 状态:composable `useInboundSiblingNavigation`
+
+建议新建 `composables/useInboundSiblingNavigation.ts`,由 `useQcWorkOrderTabPage` 持有并下传 `QcWorkOrderCardList`。
+
+```ts
+type InsertedInboundSiblingCard = {
+  sampleCode: string
+  anchorId: string
+  insertedAfterAnchorId: string
+  group: QcWorkOrderSampleGroupModel
+  protocolSiblingLinks: QcInboundProtocolSiblingLink[]
+  showProtocolPaymentAtGroup: boolean
+  loading?: boolean
+  isInserted: true
+}
+
+// 状态
+insertedCards: Ref<InsertedInboundSiblingCard[]>
+navigating: Ref<boolean>
+
+// 方法
+async function navigateToSibling(payload: {
+  targetSampleCode: string
+  sourceAnchorId: string
+  receptionSource: string
+  protocolSiblingLinks: QcInboundProtocolSiblingLink[]
+}): Promise<void>
+
+function clearInsertedCards(): void
+```
+
+**`navigateToSibling` 逻辑**:
+
+1. 计算 `targetAnchorId`。
+2. 若 DOM 存在或 `insertedCards`/主列表 `groupKey` 已含 → `scrollToInboundSampleCard`。
+3. 否则 `fetchQcWorkOrdersBySample` → `groupInboundQcWorkOrders` → 构建 `protocolSiblingLinks`(复用 `buildProtocolSiblingLinksFromSamples`)→ push 到 `insertedCards`(`insertedAfterAnchorId = sourceAnchorId`)→ nextTick 后滚动。
+
+**清空时机**:`loadData` 开头、`handleTabChange`、`handleReset`、翻页事件。
+
+### 5.2 渲染顺序(`QcWorkOrderCardList`)
+
+入库 grouped 分支:
+
+```vue
+<template v-for="item in inboundSampleListItems" :key="item.group.groupKey">
+  <QcWorkOrderSampleGroup ... @sibling-navigate="onSiblingNavigate" />
+  <QcWorkOrderSampleGroup
+    v-for="ins in insertedCardsAfter(item.cardAnchorId)"
+    :key="'ins-' + ins.sampleCode"
+    :is-inserted-sibling="true"
+    ...
+  />
+</template>
+```
+
+「所有」Tab 中 `kind === 'sample-group'` 的入库项同样支持插入(`resolveProtocolSiblingLinksForGroup` 已有 links)。
+
+### 5.3 组件事件
+
+`QcWorkOrderSampleGroup`:
+
+- 芯片改为 `<button type="button">` 或带 `@click` 的可聚焦元素;
+- 当前芯片 `@click` → 仅 `scrollToInboundSampleCard(cardAnchorId)`;
+- 非当前 → `emit('sibling-navigate', { targetSampleCode, protocolSiblingLinks })`;
+- 新增 prop `isInsertedSibling?: boolean` 控制「同协议关联」标识。
+
+### 5.4 文件改动清单
+
+| 路径 | 改动 |
+|------|------|
+| `web-admin/src/utils/qc-work-order-inbound-sample-cards.ts` | link 增加 `sampleCode` |
+| `web-admin/src/views/.../QcWorkOrderSampleGroup.vue` | 可点击芯片 + emit |
+| `web-admin/src/views/.../QcWorkOrderCardList.vue` | 插入卡渲染、事件桥接 |
+| `web-admin/src/views/.../composables/useInboundSiblingNavigation.ts` | **新建** |
+| `web-admin/src/views/.../composables/useQcWorkOrderTabPage.ts` | 集成 composable、清空、下传 props |
+| `web-admin/src/api/qc-work-order.ts` | `fetchQcWorkOrdersBySample` |
+| `backend/app/crud/qc_work_order.py` | `list_work_orders_by_sample` |
+| `backend/app/api/qc.py`(或 qc_work_order 路由模块) | 注册 GET 路由 |
+
+---
+
+## 6. 边界与错误处理
+
+| 场景 | 处理 |
+|------|------|
+| 协议有样本、尚无质检工单 | Toast,不插入 |
+| 接口 403 / 无数据权限 | Toast:`无权查看该样本质检信息` |
+| 网络失败 | Toast + 不插入;`navigating` 复位 |
+| 连续快速点击不同芯片 | 防抖或队列;以最后一次为准 |
+| 插入后用户翻页 | 插入卡清空(已确认) |
+| 细胞存储 `paymentSummary` | 插入卡 `showProtocolPaymentAtGroup` 按 `buildInboundSampleListItems` 同规则(协议内 payment host) |
+| 特殊样本 | 插入卡继承 enrich 的 `isSpecial` / `specialRemark`,与主列表一致 |
+
+---
+
+## 7. 测试要点
+
+### 7.1 质检工单 · 细胞存储
+
+1. 同协议 2 样本均在当前页 → 点击芯片 → 滚动高亮,无新卡。
+2. 仅 1 样本在列表 → 点击另一芯片 → 下方插入卡,可查看工单。
+3. 换页后插入卡消失。
+4. 无工单样本(若可造数据)→ Toast。
+
+### 7.2 我的质检任务 · 科研捐赠
+
+1. 兄弟样本无我的任务 → 插入卡任务区为空,可「查看」工单。
+2. 兄弟样本有部分我的任务 → 任务区仅显示我的任务。
+
+### 7.3 可访问性
+
+- 芯片可 Tab 聚焦;Enter 触发跳转。
+
+---
+
+## 8. 与历史设计的关系
+
+`2026-06-25-qc-work-order-protocol-group-design.md` 规划协议组 + 样本 Tab;现网为**每样本独立卡 + 标题栏同协议芯片**。本需求在现架构上补**跨卡导航**,不推翻当前列表形态。若未来接入协议组,导航可内聚为组内 Tab 切换,插入卡逻辑可废弃。
+
+---
+
+## 9. 实现顺序建议
+
+1. 后端 `by-sample` 接口 + 前端 API 封装  
+2. `sampleCode` 写入 link 模型  
+3. `useInboundSiblingNavigation` + 仅滚动(列表内互跳)  
+4. 插入卡渲染 + 拉数  
+5. 我的任务 presetTasks 合并  
+6. 样式(pointer、插入标识、loading)  
+7. 联调与边界用例  
+
+---
+
+## 10. 审核记录
+
+| 项 | 确认人 | 日期 |
+|----|--------|------|
+| 我的任务插入卡策略(选项 A) | 产品/用户 | 2026-07-07 |
+| 全文设计 | 待审核 | — |

File diff suppressed because it is too large
+ 449 - 411
web-admin/graphify-out/.graphify_analysis.json


File diff suppressed because it is too large
+ 201 - 157
web-admin/graphify-out/.graphify_extract.json


+ 97 - 134
web-admin/graphify-out/GRAPH_REPORT.md

@@ -1,11 +1,11 @@
-# Graph Report - src  (2026-07-06)
+# Graph Report - src  (2026-07-07)
 
 ## Corpus Check
-- Large corpus: 851 files · ~562,411 words. Semantic extraction will be expensive (many Claude tokens). Consider running on a subfolder, or use --no-semantic to run AST-only.
+- Large corpus: 852 files · ~563,397 words. Semantic extraction will be expensive (many Claude tokens). Consider running on a subfolder, or use --no-semantic to run AST-only.
 
 ## Summary
-- 2857 nodes · 3996 edges · 64 communities detected
-- Extraction: 78% EXTRACTED · 22% INFERRED · 0% AMBIGUOUS · INFERRED: 892 edges (avg confidence: 0.8)
+- 2862 nodes · 4009 edges · 59 communities detected
+- Extraction: 78% EXTRACTED · 22% INFERRED · 0% AMBIGUOUS · INFERRED: 900 edges (avg confidence: 0.8)
 - Token cost: 0 input · 0 output
 
 ## Community Hubs (Navigation)
@@ -50,8 +50,8 @@
 - [[_COMMUNITY_Community 39|Community 39]]
 - [[_COMMUNITY_Community 40|Community 40]]
 - [[_COMMUNITY_Community 41|Community 41]]
-- [[_COMMUNITY_Community 42|Community 42]]
-- [[_COMMUNITY_Community 44|Community 44]]
+- [[_COMMUNITY_Community 43|Community 43]]
+- [[_COMMUNITY_Community 45|Community 45]]
 - [[_COMMUNITY_Community 47|Community 47]]
 - [[_COMMUNITY_Community 48|Community 48]]
 - [[_COMMUNITY_Community 50|Community 50]]
@@ -60,25 +60,20 @@
 - [[_COMMUNITY_Community 53|Community 53]]
 - [[_COMMUNITY_Community 54|Community 54]]
 - [[_COMMUNITY_Community 55|Community 55]]
-- [[_COMMUNITY_Community 56|Community 56]]
 - [[_COMMUNITY_Community 57|Community 57]]
-- [[_COMMUNITY_Community 58|Community 58]]
-- [[_COMMUNITY_Community 60|Community 60]]
-- [[_COMMUNITY_Community 62|Community 62]]
+- [[_COMMUNITY_Community 59|Community 59]]
+- [[_COMMUNITY_Community 61|Community 61]]
 - [[_COMMUNITY_Community 64|Community 64]]
-- [[_COMMUNITY_Community 65|Community 65]]
 - [[_COMMUNITY_Community 66|Community 66]]
-- [[_COMMUNITY_Community 69|Community 69]]
-- [[_COMMUNITY_Community 71|Community 71]]
-- [[_COMMUNITY_Community 79|Community 79]]
-- [[_COMMUNITY_Community 81|Community 81]]
-- [[_COMMUNITY_Community 82|Community 82]]
+- [[_COMMUNITY_Community 74|Community 74]]
+- [[_COMMUNITY_Community 76|Community 76]]
+- [[_COMMUNITY_Community 77|Community 77]]
 
 ## God Nodes (most connected - your core abstractions)
-1. `get()` - 279 edges
-2. `push()` - 77 edges
+1. `get()` - 280 edges
+2. `push()` - 78 edges
 3. `WebSocketClient` - 26 edges
-4. `emit()` - 24 edges
+4. `emit()` - 25 edges
 5. `set()` - 20 edges
 6. `handleDynamicRoutes()` - 18 edges
 7. `parseProtocolStandardImportText()` - 17 edges
@@ -89,10 +84,10 @@
 ## Surprising Connections (you probably didn't know these)
 - `handleBatchDelete()` --calls--> `withFullscreenLoading()`  [INFERRED]
   views/basic-info/customer/index.vue → utils/fullscreen-loading.ts
-- `buildInboundMetaFields()` --calls--> `push()`  [INFERRED]
-  utils/production-work-order-card-meta.ts → views/operations/storage-facility/index.vue
 - `dash()` --calls--> `formatDisplayOrDash()`  [INFERRED]
   components/business/infusion-schedule-outbound-chain-view/index.vue → utils/datetime-display.ts
+- `dash()` --calls--> `formatDisplayOrDash()`  [INFERRED]
+  components/business/clinical-schedule-payment-detail/index.vue → utils/datetime-display.ts
 - `fmtMoney()` --calls--> `formatMoneyDisplay()`  [INFERRED]
   components/business/clinical-schedule-payment-detail/index.vue → utils/money-format.ts
 - `defaultInstallmentRatios()` --calls--> `push()`  [INFERRED]
@@ -102,47 +97,47 @@
 
 ### Community 0 - "Community 0"
 Cohesion: 0.01
-Nodes (156): fetchAccessLogList(), fetchActiveRegions(), fetchCategoryList(), fetchCategoryParameters(), fetchCellProductPriceList(), fetchCellProductSpecList(), fetchCellProductTypeList(), fetchChannelAccount() (+148 more)
+Nodes (162): fetchAccessLogList(), fetchActiveRegions(), fetchCategoryList(), fetchCategoryParameters(), fetchCellProductPriceList(), fetchCellProductSpecList(), fetchCellProductTypeList(), fetchChannelAccount() (+154 more)
 
 ### Community 1 - "Community 1"
-Cohesion: 0.03
-Nodes (73): fetchUploadAttachment(), handleReportUpload(), handleReportUpload(), walk(), buildInboundBasicSection(), buildInboundCellStorageViewSections(), buildInboundResearchDonationViewSections(), buildReceptionSection() (+65 more)
+Cohesion: 0.01
+Nodes (129): fetchUploadAttachment(), fetchMyProductionTasks(), fetchMyQcTaskList(), fetchQcTaskDetail(), fetchQcTaskList(), FireworkSystem, handleReportUpload(), handleReportUpload() (+121 more)
 
 ### Community 2 - "Community 2"
 Cohesion: 0.03
-Nodes (86): batchSavePilotCheckup(), deletePilotCheckupActualItem(), fetchCheckupInstitutionList(), fetchCheckupInstitutionPackageDetail(), fetchCheckupInstitutionPackages(), fetchPilotCheckupDetail(), fetchPilotCheckupList(), markPilotCheckupChecked() (+78 more)
+Nodes (86): batchSavePilotCheckup(), deletePilotCheckupActualItem(), fetchCheckupInstitutionList(), fetchCheckupInstitutionPackageDetail(), fetchCheckupInstitutionPackages(), fetchPilotCheckupActualItems(), fetchPilotCheckupDetail(), fetchPilotCheckupList() (+78 more)
 
 ### Community 3 - "Community 3"
 Cohesion: 0.03
-Nodes (57): handleBlur(), handleFocus(), handleInput(), fetchFullmoonTodoList(), fetchUpdateFullmoonTodoInsurance(), fetchCollectionProtocolAuditList(), fetchCollectionProtocolDetail(), fetchCollectionProtocolList() (+49 more)
-
-### Community 4 - "Community 4"
-Cohesion: 0.03
 Nodes (33): fetchGetMenuList(), ComponentLoader, IframeRouteManager, MenuProcessor, RouteRegistry, RouteTransformer, useAppMode(), useAuth() (+25 more)
 
-### Community 5 - "Community 5"
+### Community 4 - "Community 4"
 Cohesion: 0.04
 Nodes (69): formatNowDatetime(), formatTodayDate(), addPaymentDetail(), applyFqPaymentDefaults(), applyPaymentPresetForMode(), createEmptyTransport(), datetimeToYmd(), defaultInstallmentStartDateYmd() (+61 more)
 
+### Community 5 - "Community 5"
+Cohesion: 0.02
+Nodes (28): fetchAllConsumablePacks(), fetchAllConsumables(), fetchAllConsumableStockLogs(), fetchAllEquipments(), fetchAllOutputTypes(), fetchAllProcessCategories(), fetchAllProcesses(), fetchAllReagents() (+20 more)
+
 ### Community 6 - "Community 6"
-Cohesion: 0.03
-Nodes (57): fetchCreateCustomer(), fetchCustomerList(), fetchDeleteCustomer(), fetchUpdateCustomer(), updateInfusionScheduleStatus(), fetchApproveTask(), fetchBusinessAllTasks(), fetchCompletedTasks() (+49 more)
+Cohesion: 0.04
+Nodes (49): fetchCheckupInstitutionInvoices(), fetchCheckupInstitutionProjects(), fetchCreateCheckupInstitution(), fetchCreateCheckupInstitutionInvoice(), fetchCreateCheckupInstitutionProject(), fetchUpdateCheckupInstitution(), fetchUpdateCheckupInstitutionInvoice(), fetchUpdateCheckupInstitutionProject() (+41 more)
 
 ### Community 7 - "Community 7"
-Cohesion: 0.03
-Nodes (49): fetchCheckupInstitutionInvoices(), fetchCheckupInstitutionProjects(), fetchCreateCheckupInstitution(), fetchCreateCheckupInstitutionInvoice(), fetchCreateCheckupInstitutionProject(), fetchUpdateCheckupInstitution(), fetchUpdateCheckupInstitutionInvoice(), fetchUpdateCheckupInstitutionProject() (+41 more)
+Cohesion: 0.04
+Nodes (47): buildInboundBasicSection(), buildInboundCellStorageViewSections(), buildInboundResearchDonationViewSections(), buildReceptionSection(), buildTransportSection(), resolveInboundProtocolStatus(), buildBaseFields(), buildInboundBasicSection() (+39 more)
 
 ### Community 8 - "Community 8"
-Cohesion: 0.03
-Nodes (54): fetchMyProductionTasks(), fetchMyQcTaskList(), fetchQcTaskDetail(), fetchQcTaskList(), shouldApplyTriggerSourceFilter(), buildMineTableRows(), buildPresetTasksMap(), buildWorkOrderFromTasks() (+46 more)
+Cohesion: 0.04
+Nodes (40): collectIds(), useSettingsHandlers(), useSettingsPanel(), useSettingsState(), RoutePermissionValidator, useCeremony(), useTheme(), if() (+32 more)
 
 ### Community 9 - "Community 9"
 Cohesion: 0.04
-Nodes (42): buildBaseFields(), buildFeeSection(), buildInboundBasicSection(), buildInboundCellStorageProductionViewSections(), buildInboundResearchDonationProductionViewSections(), buildOutboundBasicExperimentProductionViewSections(), buildOutboundClinicalProductionViewSections(), buildOutboundDetail() (+34 more)
+Nodes (45): fetchCreateCustomer(), fetchCustomerList(), fetchDeleteCustomer(), fetchUpdateCustomer(), updateInfusionScheduleStatus(), handleDialogClose(), handleAdd(), handleBatchDelete() (+37 more)
 
 ### Community 10 - "Community 10"
 Cohesion: 0.04
-Nodes (40): useSettingsHandlers(), useSettingsPanel(), useSettingsState(), RoutePermissionValidator, useCeremony(), useTheme(), if(), walk() (+32 more)
+Nodes (44): fetchFullmoonTodoList(), fetchUpdateFullmoonTodoInsurance(), fetchCollectionProtocolAuditList(), fetchCollectionProtocolDetail(), fetchCollectionProtocolList(), fetchCreateProtocolInvoiceRequest(), fetchInvoiceEligiblePayments(), fetchPaymentDetailList() (+36 more)
 
 ### Community 11 - "Community 11"
 Cohesion: 0.05
@@ -154,23 +149,23 @@ Nodes (34): fetchClinicalPaymentDetail(), fetchClinicalPayments(), fetchClinical
 
 ### Community 13 - "Community 13"
 Cohesion: 0.05
-Nodes (30): dash(), fmtMoney(), auditorDisplay(), dash(), ensureUnitsInOptions(), expressName(), formatYm(), formatYmd() (+22 more)
+Nodes (37): fetchProductionReceptionBySampleCode(), fetchProductionReceptionSubmit(), fetchProductionReceptionUpdate(), onUserSelectVisible(), remoteSearchUsers(), buildPayload(), ensureDefaultHandler(), initDialog() (+29 more)
 
 ### Community 14 - "Community 14"
 Cohesion: 0.05
 Nodes (33): qcAttachmentFullUrl(), qcDownloadAttachment(), qcPreviewAttachment(), formatHandoverUserLabel(), formatNowDatetime(), fullFileUrl(), handleDownloadAttachment(), handlePreviewAttachment() (+25 more)
 
 ### Community 15 - "Community 15"
-Cohesion: 0.04
-Nodes (21): fetchAvailableMaterialBatches(), fetchMyProductionMaterialConsumptions(), fetchMyProductionMaterialUsageList(), fetchProductionMaterialBatchList(), fetchProductionMaterialBatchOptions(), fetchProductionMaterialConsumptions(), fetchProductionMaterialFormula(), fetchProductionMaterialLinkedStepUsages() (+13 more)
+Cohesion: 0.06
+Nodes (20): fetchQcMaterialBatchList(), fetchQcMaterialBatchOptions(), fetchQcMaterialList(), fetchQcMaterialOptions(), fetchUpdateQcMaterialBatch(), fetchMarkQcMaterialUsageFullyUsed(), financeAttachmentsToUrlString(), ensureTabMounted() (+12 more)
 
 ### Community 16 - "Community 16"
 Cohesion: 0.06
-Nodes (21): fetchQcMaterialBatchList(), fetchQcMaterialBatchOptions(), fetchQcMaterialList(), fetchQcMaterialOptions(), fetchUpdateQcMaterialBatch(), fetchMarkQcMaterialUsageFullyUsed(), financeAttachmentsToUrlString(), for() (+13 more)
+Nodes (13): fetchProductionWorkOrderQcInfo(), dash(), fmtMoney(), auditorDisplay(), dash(), ensureUnitsInOptions(), expressName(), formatYm() (+5 more)
 
 ### Community 17 - "Community 17"
 Cohesion: 0.06
-Nodes (22): fetchCreateMenu(), fetchDeleteMenu(), fetchMenuTree(), fetchRoleOptions(), fetchUpdateMenu(), collectIds(), postorder(), preorder() (+14 more)
+Nodes (24): fetchCreateMenu(), fetchDeleteMenu(), fetchMenuTree(), fetchRoleOptions(), fetchUpdateMenu(), postorder(), preorder(), applyTreeExpand() (+16 more)
 
 ### Community 18 - "Community 18"
 Cohesion: 0.1
@@ -178,56 +173,56 @@ Nodes (2): WebSocketClient, TableCache
 
 ### Community 19 - "Community 19"
 Cohesion: 0.08
-Nodes (32): toLogin(), base64ToBytes(), bytesToBase64(), decryptJson(), deriveKey(), encryptJson(), fromBase64Url(), hexToBytes() (+24 more)
+Nodes (33): fetchRefreshToken(), toLogin(), base64ToBytes(), bytesToBase64(), decryptJson(), deriveKey(), encryptJson(), fromBase64Url() (+25 more)
 
 ### Community 20 - "Community 20"
-Cohesion: 0.07
-Nodes (24): fetchMineProductionWorkOrderTabStepCounts(), fetchMineProductionWorkOrderTabStepCountsDeduped(), fetchProductionWorkOrderCellProductOptions(), fetchProductionWorkOrderDetail(), fetchProductionWorkOrderList(), fetchProductionWorkOrderQcDispatchOptions(), fetchProductionWorkOrderQcInfo(), fetchProductionWorkOrderTabStepCounts() (+16 more)
-
-### Community 21 - "Community 21"
 Cohesion: 0.09
 Nodes (8): getDefaultLanguage(), checkStorageCompatibility(), StorageConfig, getSystemStorage(), getSysVersion(), StorageKeyManager, StorageCompatibilityManager, validateStorageData()
 
-### Community 22 - "Community 22"
+### Community 21 - "Community 21"
 Cohesion: 0.08
 Nodes (31): fetchUserList(), fetchAddTaskStep(), fetchCopyTaskStep(), fetchDeleteTaskStep(), fetchDispatchProductionTaskSteps(), fetchForwardProductionTask(), fetchProductionTaskDetail(), fetchProductionTaskEditorInit() (+23 more)
 
-### Community 23 - "Community 23"
-Cohesion: 0.08
-Nodes (18): fetchProductionReceptionBySampleCode(), fetchProductionReceptionSubmit(), fetchProductionReceptionUpdate(), onUserSelectVisible(), remoteSearchUsers(), buildPayload(), ensureDefaultHandler(), initDialog() (+10 more)
-
-### Community 24 - "Community 24"
+### Community 22 - "Community 22"
 Cohesion: 0.08
 Nodes (20): createEmptySnapshots(), normalizeConfiguredReagents(), normalizeConsumables(), normalizeDataCollections(), normalizeReagents(), normalizeStepOutputs(), serializeAttachments(), serializeEquipmentIds() (+12 more)
 
-### Community 25 - "Community 25"
+### Community 23 - "Community 23"
+Cohesion: 0.07
+Nodes (14): handleBlur(), handleFocus(), handleInput(), formatAmount(), formatAmount(), formatProtocolAmount(), buildFeeSection(), formatInvoiceAmount() (+6 more)
+
+### Community 24 - "Community 24"
 Cohesion: 0.07
 Nodes (19): fetchDeleteMaterialBatch(), fetchMaterialBatchList(), fetchMaterialBatchOptions(), fetchMaterialList(), fetchMaterialOptions(), fetchUpdateMaterialBatch(), fetchMaterialUsageList(), fetchMyMaterialUsageList() (+11 more)
 
-### Community 26 - "Community 26"
+### Community 25 - "Community 25"
 Cohesion: 0.09
 Nodes (27): completePilotApplication(), deletePilotApplication(), fetchPilotApplicationList(), startPilotApplication(), voidPilotApplication(), handleBatchComplete(), handleBatchStart(), handleBatchVoid() (+19 more)
 
+### Community 26 - "Community 26"
+Cohesion: 0.09
+Nodes (22): fetchMineProductionWorkOrderTabStepCounts(), fetchMineProductionWorkOrderTabStepCountsDeduped(), fetchProductionWorkOrderCellProductOptions(), fetchProductionWorkOrderDetail(), fetchProductionWorkOrderList(), fetchProductionWorkOrderQcDispatchOptions(), fetchProductionWorkOrderTabStepCounts(), fetchProductionWorkOrderTabStepCountsDeduped() (+14 more)
+
 ### Community 27 - "Community 27"
-Cohesion: 0.11
-Nodes (21): fetchGetMenuCodes(), fetchGetUserInfo(), fetchLogin(), fetchRefreshToken(), fetchUpdateProfile(), fetchUploadProfileAvatar(), fetchUserInfo(), buildWebEncryptedLoginBody() (+13 more)
+Cohesion: 0.12
+Nodes (20): fetchGetMenuCodes(), fetchGetUserInfo(), fetchLogin(), fetchUpdateProfile(), fetchUploadProfileAvatar(), fetchUserInfo(), buildWebEncryptedLoginBody(), defaultAvatarPathByRole() (+12 more)
 
 ### Community 28 - "Community 28"
 Cohesion: 0.09
-Nodes (11): fetchMineQcWorkOrderTabUnfinishedCounts(), fetchOutboundQcDetail(), fetchQcWorkOrderDetail(), fetchQcWorkOrderList(), fetchQcWorkOrderTabUnfinishedCounts(), reload(), reloadTabCounts(), handleMenuJump() (+3 more)
+Nodes (12): fetchMineQcWorkOrderTabUnfinishedCounts(), fetchOutboundQcDetail(), fetchQcWorkOrderDetail(), fetchQcWorkOrderList(), fetchQcWorkOrdersBySample(), fetchQcWorkOrderTabUnfinishedCounts(), reload(), reloadTabCounts() (+4 more)
 
 ### Community 29 - "Community 29"
 Cohesion: 0.12
 Nodes (15): addCopyButton(), highlightCode(), insertLineNumbers(), isBlockProcessed(), markBlockAsProcessed(), processBlock(), setupHighlightDirective(), setupRippleDirective() (+7 more)
 
 ### Community 30 - "Community 30"
-Cohesion: 0.13
-Nodes (18): fetchCellSampleTypeList(), fetchCreateCellSampleType(), fetchDeleteCellSampleType(), fetchUpdateCellSampleType(), handleAdd(), handleDelete(), handleDropdownCommand(), handleEdit() (+10 more)
-
-### Community 31 - "Community 31"
 Cohesion: 0.11
 Nodes (11): fetchOutboundReleaseMaterial(), fetchOutboundReleaseReviewDetail(), fetchOutboundReleaseReviewList(), fetchReleaseReviewList(), handleReset(), handleSearch(), handleToggleSelect(), loadData() (+3 more)
 
+### Community 31 - "Community 31"
+Cohesion: 0.13
+Nodes (18): fetchCellSampleTypeList(), fetchCreateCellSampleType(), fetchDeleteCellSampleType(), fetchUpdateCellSampleType(), handleAdd(), handleDelete(), handleDropdownCommand(), handleEdit() (+10 more)
+
 ### Community 32 - "Community 32"
 Cohesion: 0.19
 Nodes (15): fetchAttachmentDownloadUrl(), attachmentFullUrl(), ensureUniqueZipEntryName(), fetchAttachmentBlob(), getAttachmentExtension(), resolveAttachmentFileName(), sanitizeZipPathSegment(), applyInboundLabStorageCodePrefix() (+7 more)
@@ -264,14 +259,14 @@ Nodes (4): addMaterialRow(), nextMaterialRowKey(), onRequisitionSelect(), remove
 Cohesion: 0.25
 Nodes (2): handleTouchEnd(), setTransition()
 
-### Community 42 - "Community 42"
-Cohesion: 0.28
-Nodes (1): FireworkSystem
-
-### Community 44 - "Community 44"
+### Community 43 - "Community 43"
 Cohesion: 0.31
 Nodes (4): annotateForkEdges(), chainApiToElements(), walkAncestorBranch(), walkAncestorParents()
 
+### Community 45 - "Community 45"
+Cohesion: 0.28
+Nodes (4): fetchMyProductionMaterialConsumptions(), handleReset(), handleSearch(), loadData()
+
 ### Community 47 - "Community 47"
 Cohesion: 0.39
 Nodes (6): isIgnorableScriptError(), normalizeErrorMessage(), registerPromiseErrorHandler(), registerResourceErrorHandler(), scriptErrorHandler(), setupErrorHandle()
@@ -289,70 +284,50 @@ Cohesion: 0.38
 Nodes (4): handleFocus(), handleInput(), parseDecimalToNumber(), sanitizeDecimalInput()
 
 ### Community 52 - "Community 52"
-Cohesion: 0.29
-Nodes (2): fetchLeaveDetail(), fetchLeaveList()
-
-### Community 53 - "Community 53"
 Cohesion: 0.4
 Nodes (2): isUnfinishedQcWorkOrder(), resolveQcWorkOrderUnfinishedBadgeCount()
 
-### Community 54 - "Community 54"
+### Community 53 - "Community 53"
 Cohesion: 0.47
 Nodes (3): isPaymentDueWithinWarnWindow(), parsePaymentDueYmd(), startOfLocalDay()
 
-### Community 55 - "Community 55"
-Cohesion: 0.33
-Nodes (2): fetchEnvDetectRecordDetail(), fetchEnvDetectRecordList()
-
-### Community 56 - "Community 56"
-Cohesion: 0.33
-Nodes (2): fetchQcLabTestItemList(), fetchQcLabTestItemsActive()
-
-### Community 57 - "Community 57"
+### Community 54 - "Community 54"
 Cohesion: 0.5
 Nodes (2): formatProtocolDocDeleteConfirm(), formatProtocolDocSubject()
 
-### Community 58 - "Community 58"
+### Community 55 - "Community 55"
 Cohesion: 0.5
 Nodes (2): parseEquipmentIds(), syncEquipmentNamesFromOptions()
 
-### Community 60 - "Community 60"
+### Community 57 - "Community 57"
 Cohesion: 0.5
 Nodes (2): focusInput(), handleKeydown()
 
-### Community 62 - "Community 62"
+### Community 59 - "Community 59"
 Cohesion: 0.67
 Nodes (2): if(), logSidebarMenu()
 
-### Community 64 - "Community 64"
-Cohesion: 0.5
-Nodes (1): fetchStorageEntrySmsList()
-
-### Community 65 - "Community 65"
+### Community 61 - "Community 61"
 Cohesion: 0.5
 Nodes (1): fetchSampleSmsNotificationList()
 
-### Community 66 - "Community 66"
-Cohesion: 0.5
-Nodes (1): fetchQcSmsList()
-
-### Community 69 - "Community 69"
+### Community 64 - "Community 64"
 Cohesion: 1.0
 Nodes (2): getSettingDefaults(), resetToDefaults()
 
-### Community 71 - "Community 71"
+### Community 66 - "Community 66"
 Cohesion: 1.0
 Nodes (2): copyTextToClipboard(), copyTextWithFallback()
 
-### Community 79 - "Community 79"
+### Community 74 - "Community 74"
 Cohesion: 1.0
 Nodes (2): emptyTabCounts(), useProductionWorkOrderTabStepCounts()
 
-### Community 81 - "Community 81"
+### Community 76 - "Community 76"
 Cohesion: 1.0
 Nodes (2): initialProgress(), useQcWorkOrderAttachmentExport()
 
-### Community 82 - "Community 82"
+### Community 77 - "Community 77"
 Cohesion: 1.0
 Nodes (2): emptyTabCounts(), useQcWorkOrderTabUnfinishedCounts()
 
@@ -361,59 +336,47 @@ Nodes (2): emptyTabCounts(), useQcWorkOrderTabUnfinishedCounts()
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
 - **Thin community `Community 41`** (9 nodes): `cleanupEventListeners()`, `handleTouchEnd()`, `handleTouchMove()`, `handleTouchStart()`, `handleWheelScroll()`, `if()`, `setTransition()`, `setupEventListeners()`, `index.vue`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 42`** (9 nodes): `FireworkSystem`, `.constructor()`, `.createFirework()`, `.createParticle()`, `.getAvailableParticle()`, `.initializePool()`, `.preloadAllImages()`, `.preloadImage()`, `index.vue`
-  Too small to be a meaningful cluster - may be noise or needs more connections extracted.
 - **Thin community `Community 48`** (8 nodes): `TableHeightCalculator`, `.calculate()`, `.calculateOffset()`, `.calculatePaginationOffset()`, `.constructor()`, `.getHeaderHeight()`, `useTableHeight()`, `useTableHeight.ts`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
 - **Thin community `Community 50`** (7 nodes): `App.vue`, `main.ts`, `refreshButtonAutoIcons()`, `refreshPanelTitleIcons()`, `logApiCryptoBootInfo()`, `maskSecret()`, `console.ts`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 52`** (7 nodes): `fetchCreateLeave()`, `fetchDeleteLeave()`, `fetchLeaveDetail()`, `fetchLeaveList()`, `fetchSubmitLeave()`, `fetchUpdateLeave()`, `leave.ts`
-  Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 53`** (6 nodes): `formatQcWorkOrderUnfinishedBadgeCount()`, `isUnfinishedQcWorkOrder()`, `qcWorkOrderUnfinishedBadgeTitle()`, `resolveQcWorkOrderUnfinishedBadgeCount()`, `sumQcWorkOrderGroupUnfinishedBadgeCount()`, `qc-work-order-unfinished-badge.ts`
-  Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 55`** (6 nodes): `fetchCreateEnvDetectRecord()`, `fetchDeleteEnvDetectRecord()`, `fetchEnvDetectRecordDetail()`, `fetchEnvDetectRecordList()`, `fetchUpdateEnvDetectRecord()`, `env-detect-record.ts`
-  Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 56`** (6 nodes): `fetchCreateQcLabTestItem()`, `fetchDeleteQcLabTestItem()`, `fetchQcLabTestItemList()`, `fetchQcLabTestItemsActive()`, `fetchUpdateQcLabTestItem()`, `qc-lab-test-item.ts`
-  Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 57`** (5 nodes): `formatProtocolDocBatchDeleteConfirm()`, `formatProtocolDocDeleteConfirm()`, `formatProtocolDocSubject()`, `getProtocolDocTypeLabel()`, `protocol-doc-label.ts`
-  Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 58`** (5 nodes): `equipmentOptionLabel()`, `joinEquipmentIds()`, `parseEquipmentIds()`, `syncEquipmentNamesFromOptions()`, `equipment-select.ts`
+- **Thin community `Community 52`** (6 nodes): `formatQcWorkOrderUnfinishedBadgeCount()`, `isUnfinishedQcWorkOrder()`, `qcWorkOrderUnfinishedBadgeTitle()`, `resolveQcWorkOrderUnfinishedBadgeCount()`, `sumQcWorkOrderGroupUnfinishedBadgeCount()`, `qc-work-order-unfinished-badge.ts`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 60`** (5 nodes): `focusInput()`, `handleKeydown()`, `if()`, `search()`, `index.vue`
+- **Thin community `Community 54`** (5 nodes): `formatProtocolDocBatchDeleteConfirm()`, `formatProtocolDocDeleteConfirm()`, `formatProtocolDocSubject()`, `getProtocolDocTypeLabel()`, `protocol-doc-label.ts`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 62`** (4 nodes): `if()`, `logSidebarMenu()`, `index.vue`, `SidebarSubmenu.vue`
+- **Thin community `Community 55`** (5 nodes): `equipmentOptionLabel()`, `joinEquipmentIds()`, `parseEquipmentIds()`, `syncEquipmentNamesFromOptions()`, `equipment-select.ts`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 64`** (4 nodes): `fetchGenerateStorageEntrySms()`, `fetchStorageEntrySmsList()`, `fetchToggleStorageEntrySmsNotified()`, `storage-entry-sms-notification.ts`
+- **Thin community `Community 57`** (5 nodes): `focusInput()`, `handleKeydown()`, `if()`, `search()`, `index.vue`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 65`** (4 nodes): `fetchGenerateSms()`, `fetchSampleSmsNotificationList()`, `fetchToggleSmsNotified()`, `sample-sms-notification.ts`
+- **Thin community `Community 59`** (4 nodes): `if()`, `logSidebarMenu()`, `index.vue`, `SidebarSubmenu.vue`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 66`** (4 nodes): `fetchGenerateQcSms()`, `fetchQcSmsList()`, `fetchToggleQcSmsNotified()`, `qc-sms-notification.ts`
+- **Thin community `Community 61`** (4 nodes): `fetchGenerateSms()`, `fetchSampleSmsNotificationList()`, `fetchToggleSmsNotified()`, `sample-sms-notification.ts`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 69`** (3 nodes): `getSettingDefaults()`, `resetToDefaults()`, `setting.ts`
+- **Thin community `Community 64`** (3 nodes): `getSettingDefaults()`, `resetToDefaults()`, `setting.ts`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 71`** (3 nodes): `copyTextToClipboard()`, `copyTextWithFallback()`, `copy-text.ts`
+- **Thin community `Community 66`** (3 nodes): `copyTextToClipboard()`, `copyTextWithFallback()`, `copy-text.ts`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 79`** (3 nodes): `emptyTabCounts()`, `useProductionWorkOrderTabStepCounts()`, `useProductionWorkOrderTabStepCounts.ts`
+- **Thin community `Community 74`** (3 nodes): `emptyTabCounts()`, `useProductionWorkOrderTabStepCounts()`, `useProductionWorkOrderTabStepCounts.ts`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 81`** (3 nodes): `initialProgress()`, `useQcWorkOrderAttachmentExport()`, `useQcWorkOrderAttachmentExport.ts`
+- **Thin community `Community 76`** (3 nodes): `initialProgress()`, `useQcWorkOrderAttachmentExport()`, `useQcWorkOrderAttachmentExport.ts`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 82`** (3 nodes): `emptyTabCounts()`, `useQcWorkOrderTabUnfinishedCounts()`, `useQcWorkOrderTabUnfinishedCounts.ts`
+- **Thin community `Community 77`** (3 nodes): `emptyTabCounts()`, `useQcWorkOrderTabUnfinishedCounts()`, `useQcWorkOrderTabUnfinishedCounts.ts`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
 
 ## Suggested Questions
 _Questions this graph is uniquely positioned to answer:_
 
-- **Why does `get()` connect `Community 0` to `Community 1`, `Community 2`, `Community 3`, `Community 5`, `Community 6`, `Community 7`, `Community 8`, `Community 10`, `Community 11`, `Community 12`, `Community 15`, `Community 16`, `Community 17`, `Community 20`, `Community 22`, `Community 23`, `Community 25`, `Community 26`, `Community 27`, `Community 28`, `Community 30`, `Community 31`, `Community 33`, `Community 37`, `Community 39`, `Community 44`, `Community 52`, `Community 55`, `Community 56`, `Community 64`, `Community 65`, `Community 66`?**
-  _High betweenness centrality (0.302) - this node is a cross-community bridge._
-- **Why does `push()` connect `Community 1` to `Community 0`, `Community 2`, `Community 3`, `Community 5`, `Community 7`, `Community 8`, `Community 9`, `Community 10`, `Community 11`, `Community 13`, `Community 14`, `Community 18`, `Community 19`, `Community 20`, `Community 22`, `Community 24`, `Community 26`, `Community 28`, `Community 32`, `Community 40`, `Community 42`?**
-  _High betweenness centrality (0.261) - this node is a cross-community bridge._
-- **Why does `handleDynamicRoutes()` connect `Community 4` to `Community 10`, `Community 27`, `Community 28`, `Community 20`?**
-  _High betweenness centrality (0.036) - this node is a cross-community bridge._
-- **Are the 278 inferred relationships involving `get()` (e.g. with `buildNodeCodePathFromFlat()` and `collectLeafFacilityNodesInTreeOrder()`) actually correct?**
-  _`get()` has 278 INFERRED edges - model-reasoned connections that need verification._
-- **Are the 76 inferred relationships involving `push()` (e.g. with `refreshQcMenuBadges()` and `collectLeafFacilityNodesInTreeOrder()`) actually correct?**
-  _`push()` has 76 INFERRED edges - model-reasoned connections that need verification._
-- **Are the 23 inferred relationships involving `emit()` (e.g. with `handleSubmit()` and `handleDialogClose()`) actually correct?**
-  _`emit()` has 23 INFERRED edges - model-reasoned connections that need verification._
+- **Why does `get()` connect `Community 0` to `Community 1`, `Community 2`, `Community 4`, `Community 5`, `Community 6`, `Community 8`, `Community 9`, `Community 10`, `Community 11`, `Community 12`, `Community 13`, `Community 15`, `Community 16`, `Community 17`, `Community 21`, `Community 24`, `Community 25`, `Community 26`, `Community 27`, `Community 28`, `Community 30`, `Community 31`, `Community 33`, `Community 37`, `Community 39`, `Community 43`, `Community 45`, `Community 61`?**
+  _High betweenness centrality (0.273) - this node is a cross-community bridge._
+- **Why does `push()` connect `Community 1` to `Community 0`, `Community 2`, `Community 4`, `Community 6`, `Community 7`, `Community 8`, `Community 10`, `Community 11`, `Community 13`, `Community 14`, `Community 16`, `Community 17`, `Community 18`, `Community 19`, `Community 21`, `Community 22`, `Community 25`, `Community 26`, `Community 28`, `Community 32`, `Community 40`?**
+  _High betweenness centrality (0.264) - this node is a cross-community bridge._
+- **Why does `handleDynamicRoutes()` connect `Community 3` to `Community 8`, `Community 26`, `Community 27`, `Community 28`?**
+  _High betweenness centrality (0.042) - this node is a cross-community bridge._
+- **Are the 279 inferred relationships involving `get()` (e.g. with `buildNodeCodePathFromFlat()` and `collectLeafFacilityNodesInTreeOrder()`) actually correct?**
+  _`get()` has 279 INFERRED edges - model-reasoned connections that need verification._
+- **Are the 77 inferred relationships involving `push()` (e.g. with `refreshQcMenuBadges()` and `collectLeafFacilityNodesInTreeOrder()`) actually correct?**
+  _`push()` has 77 INFERRED edges - model-reasoned connections that need verification._
+- **Are the 24 inferred relationships involving `emit()` (e.g. with `handleSubmit()` and `handleDialogClose()`) actually correct?**
+  _`emit()` has 24 INFERRED edges - model-reasoned connections that need verification._
 - **Are the 19 inferred relationships involving `set()` (e.g. with `buildNodeCodePathFromFlat()` and `collectLeafFacilityNodesInTreeOrder()`) actually correct?**
   _`set()` has 19 INFERRED edges - model-reasoned connections that need verification._

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


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


+ 18 - 17
web-admin/graphify-out/manifest.json

@@ -17,8 +17,8 @@
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/common/index.ts": 1781272556.3597305,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/directive/directive.d.ts": 1781272556.3601494,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/api/api.d.ts": 1781272556.3606477,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/components.d.ts": 1783339911.9653826,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/auto-imports.d.ts": 1783339868.020893,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/components.d.ts": 1783393157.5275621,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/auto-imports.d.ts": 1783392799.8502862,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/store/index.ts": 1781272556.364766,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/router/index.ts": 1782394569.8806834,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/composables/useAttachmentDropZone.ts": 1782873161.9839864,
@@ -60,7 +60,7 @@
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/expected-cell-count.ts": 1782705039.2515786,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/protocol-payment-labels.ts": 1782893910.9937537,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/status-tag-registry.ts": 1783329871.573473,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/production-work-order-card-meta.ts": 1783306636.6246853,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/production-work-order-card-meta.ts": 1783393101.9713373,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/fetch-paged-list.ts": 1781272556.3903377,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/decimal-input-format.ts": 1782807247.117702,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/sort-order.ts": 1781272556.3913012,
@@ -75,15 +75,15 @@
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/qc-item-param-options.ts": 1782446792.147386,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/clinical-payment-flow-steps.ts": 1782806499.3105526,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/production-work-order-step-badge.ts": 1783307221.345424,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/qc-work-order-inbound-sample-cards.ts": 1783327872.9643228,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/qc-work-order-inbound-sample-cards.ts": 1783392689.28207,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/donation-standard-import-parse.ts": 1782960400.9073825,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/copy-text.ts": 1781272556.4018052,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/tree-path.ts": 1781272556.402468,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/mask-sensitive.ts": 1781272556.4029639,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/sample-reception-view-sections.ts": 1782806513.145186,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/sample-reception-view-sections.ts": 1783389099.9667985,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/production-labels.ts": 1783306730.912153,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/inbound-sample-special-mark.ts": 1782889894.1376855,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/production-work-order-category.ts": 1782703985.4586673,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/production-work-order-category.ts": 1783393556.2431076,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/production-menu-badge.ts": 1783307225.4832592,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/attachment-zip.ts": 1782875325.855345,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/numeric-input-parse.ts": 1782179343.5953708,
@@ -309,7 +309,7 @@
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/material.ts": 1782894693.658258,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/sample-reception.ts": 1782719389.581961,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/auth.ts": 1782122651.4628098,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts": 1783327862.9771214,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts": 1783392689.281915,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/auth/login/index.vue": 1782893854.347133,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/refund-confirm/index.vue": 1782893854.3402505,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/my-material-usage/index.vue": 1782893854.311591,
@@ -327,7 +327,7 @@
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol-audit/StorageEntryInfoTab.vue": 1783326448.897737,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol-audit/SampleCollectionInfoTab.vue": 1783326448.898472,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol-audit/SampleQcInfoTab.vue": 1783326448.8989031,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol-audit/SampleReceptionInfoTab.vue": 1783326448.899512,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol-audit/SampleReceptionInfoTab.vue": 1783389099.954163,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol-audit/StorageCertTab.vue": 1783326448.8999357,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol-audit/FullmoonTodoTab.vue": 1783326448.9010873,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol-audit/ProtocolAuditTab.vue": 1783326448.9027853,
@@ -396,14 +396,14 @@
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/reagent/index.vue": 1783326448.9403174,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue": 1782893853.82419,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/ProductionStepBadge.vue": 1783307223.0443783,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts": 1782822330.679258,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts": 1783393558.504367,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/StepDetailPanel.vue": 1782893853.2023683,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/CompleteProductionTaskDialog.vue": 1782893853.1254928,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskInputEnhancedTable.vue": 1782893853.0951817,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/DispatchQcWorkOrderDialog.vue": 1782893852.9989007,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/SampleReceptionDialog.vue": 1782894085.3789353,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskEquipmentPanel.vue": 1782893853.803821,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskExecutionDialog.vue": 1782893853.724633,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskExecutionDialog.vue": 1783393562.7058115,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/EditTaskInfoDialog.vue": 1782893853.6561613,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskOutputList.vue": 1782893853.4938636,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/DispatchTaskDialog.vue": 1782893853.4321866,
@@ -462,18 +462,18 @@
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue": 1783340649.3724883,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderConfigureBatches.vue": 1782893853.9850519,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderFeeSummaryBar.vue": 1782893853.9414947,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSampleGroup.vue": 1783327679.8364553,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSampleGroup.vue": 1783392668.9228785,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSelectCheckbox.vue": 1783322131.5377831,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderBatchResultTabs.vue": 1782893854.2285423,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcUnfinishedBadge.vue": 1782893854.1479301,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue": 1782961148.468002,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTabBar.vue": 1782893854.0314786,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderBatchSubList.vue": 1782893854.0134735,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderCardList.vue": 1783327877.2203474,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderCardList.vue": 1783392683.9513938,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderProtocolPaymentPanel.vue": 1783330023.9447763,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/tabs/OutboundPilotTab.vue": 1782893854.159883,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/tabs/InboundCellStorageTab.vue": 1782893854.1357596,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/tabs/QcWorkOrderTabPanel.vue": 1783327002.974947,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/tabs/QcWorkOrderTabPanel.vue": 1783392684.8612459,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/tabs/OutboundBasicExperimentTab.vue": 1782893854.2442567,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/tabs/InboundResearchDonationTab.vue": 1782893854.1390157,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/tabs/AllTab.vue": 1782893854.1312714,
@@ -485,10 +485,11 @@
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/composables/useQcWorkOrderTaskViewSections.ts": 1782527340.2357967,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/composables/useQcWorkOrderTabUnfinishedCounts.ts": 1782394182.2860413,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/composables/useInboundQcViewDialog.ts": 1782398606.799624,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/composables/useInboundSiblingNavigation.ts": 1783392689.281982,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/composables/useQcViewAttachmentHelpers.ts": 1781581579.8782947,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/composables/qc-work-order-selection.ts": 1783322122.0742447,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/composables/inbound-view-sections.ts": 1782894738.7775028,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/composables/useQcWorkOrderTabPage.ts": 1783326302.5727773,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/composables/useQcWorkOrderTabPage.ts": 1783392595.7359324,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/view-dialogs/QcWorkOrderSystemViewDialog.vue": 1782893854.2724051,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/view-dialogs/OutboundQcProductTable.vue": 1782893854.2596574,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/view-dialogs/OutboundQcDiagnosisAttachments.vue": 1782893854.2985036,
@@ -508,9 +509,9 @@
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-material/QcMaterialUsageTab.vue": 1783326448.9838498,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-material/QcMaterialBatchAttachmentDialog.vue": 1782893853.076457,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/pre-transit-sample/index.vue": 1783326448.9852715,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/pre-transit-sample/PreTransitProtocolCardList.vue": 1782894112.8165123,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/sample-reception/index.vue": 1783326448.9908016,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/sample-reception/SampleReceptionCardList.vue": 1782894078.8557267,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/pre-transit-sample/PreTransitProtocolCardList.vue": 1783391495.9213836,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/sample-reception/index.vue": 1783389101.2287407,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/sample-reception/SampleReceptionCardList.vue": 1783389098.9469657,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/my-qc-task/index.vue": 1783325990.456282,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/env-detect-record/index.vue": 1782893853.44988,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/env-detect-item/index.vue": 1783326448.9953067,

+ 7 - 0
web-admin/src/api/qc-work-order.ts

@@ -215,6 +215,13 @@ export function fetchMineQcWorkOrderTabUnfinishedCounts() {
   })
 }
 
+export function fetchQcWorkOrdersBySample(params: { sampleCode: string; receptionSource: string }) {
+  return request.get<{ list: QcWorkOrder[] }>({
+    url: '/web/admin/qc-work-orders/by-sample',
+    params
+  })
+}
+
 export function fetchQcWorkOrderList(params?: {
   page?: number
   pageSize?: number

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


+ 1 - 0
web-admin/src/graphify-out/cache/ast/24bdd1fd6cf73c0c03b4667697521a37ca67465b5edb1f482bb340b36c5bbe1b.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_sample_reception_samplereceptioncardlist_vue", "label": "SampleReceptionCardList.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/sample-reception/SampleReceptionCardList.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}

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


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


+ 1 - 0
web-admin/src/graphify-out/cache/ast/6d3a7533ababb5eef8d2ae60ed350a334b6f2f5e96a9dd28e289b0aa38814f2f.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_sample_reception_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/sample-reception/index.vue", "source_location": "L1"}, {"id": "sample_reception_index_ensurecurrentuserinoptions", "label": "ensureCurrentUserInOptions()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/sample-reception/index.vue", "source_location": "L1210"}, {"id": "sample_reception_index_if", "label": "if()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/sample-reception/index.vue", "source_location": "L1469"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_sample_reception_index_vue", "target": "sample_reception_index_ensurecurrentuserinoptions", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/sample-reception/index.vue", "source_location": "L1210", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_sample_reception_index_vue", "target": "sample_reception_index_if", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/sample-reception/index.vue", "source_location": "L1469", "weight": 1.0}], "raw_calls": [{"caller_nid": "sample_reception_index_if", "callee": "warning", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/sample-reception/index.vue", "source_location": "L1470"}]}

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


+ 1 - 0
web-admin/src/graphify-out/cache/ast/86204fdb8bd7d7f856b8219276068c03c566fb8548ee0a577ff4fec510f48dce.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_pre_transit_sample_pretransitprotocolcardlist_vue", "label": "PreTransitProtocolCardList.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/pre-transit-sample/PreTransitProtocolCardList.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}

+ 1 - 0
web-admin/src/graphify-out/cache/ast/8687e1fe6209a5bea7177981d77c2607c3c22309f474d9ae8e5b43d884e66c93.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_cell_storage_protocol_audit_samplereceptioninfotab_vue", "label": "SampleReceptionInfoTab.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol-audit/SampleReceptionInfoTab.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}

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


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


+ 1 - 0
web-admin/src/graphify-out/cache/ast/ab95ec7f060b010ed5ba2de2c51d424f942f6fdbd2ee7798cf6ae73599b0eadc.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_types_import_components_d_ts", "label": "components.d.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/components.d.ts", "source_location": "L1"}], "edges": [], "raw_calls": []}

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


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


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


+ 1 - 0
web-admin/src/graphify-out/cache/ast/f47c05a3d0b3e0e590c1ae7ecca71bdbbbdabaf6f67062ccbaf3d5a9cbe1493f.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_types_import_components_d_ts", "label": "components.d.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/components.d.ts", "source_location": "L1"}], "edges": [], "raw_calls": []}

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


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


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


+ 1 - 1
web-admin/src/utils/production-work-order-card-meta.ts

@@ -179,7 +179,7 @@ const CARD_META_BY_TAB: Record<ProductionWorkOrderTabKey, ProductionWorkOrderCar
   inbound_cell_storage: {
     tabKey: 'inbound_cell_storage',
     showCategoryTag: false,
-    showProtocolFeeSummary: true,
+    showProtocolFeeSummary: false,
     showOutboundFeeSummary: false,
     showCellProducts: false,
     metaFields: buildInboundMetaFields('inbound_cell_storage'),

+ 12 - 0
web-admin/src/utils/production-work-order-category.ts

@@ -101,3 +101,15 @@ export function shouldShowTaskHeaderLabStorageCode(row: {
 }): boolean {
   return INBOUND_LAB_STORAGE_HEADER_CATEGORIES.has(resolveWorkOrderCategoryKey(row))
 }
+
+export function isTemporaryProductionWorkOrder(row: { workOrderType?: string }): boolean {
+  return row.workOrderType === 'temporary'
+}
+
+/** 临时生产工单:任务执行弹窗标题后缀(与卡片主标题一致) */
+export function temporaryProductionExecutionDialogTitle(row: {
+  workOrderRemark?: string
+  workOrderNo?: string
+}): string {
+  return (row.workOrderRemark || '').trim() || row.workOrderNo || '—'
+}

+ 4 - 3
web-admin/src/utils/qc-work-order-inbound-sample-cards.ts

@@ -8,6 +8,7 @@ import {
 import { resolveInboundSampleGroupCardTitle } from '@/utils/qc-work-order-card-meta'
 
 export type QcInboundProtocolSiblingLink = {
+  sampleCode: string
   anchorId: string
   labStorageCode: string
   sampleTypeName: string
@@ -56,13 +57,12 @@ export function buildProtocolSiblingLinksFromSamples(
   group: QcWorkOrderSampleGroupModel,
   samples: QcProtocolSiblingSample[]
 ): QcInboundProtocolSiblingLink[] {
-  const sorted = [...samples].sort((a, b) =>
-    (a.sampleCode || '').localeCompare(b.sampleCode || '')
-  )
+  const sorted = [...samples].sort((a, b) => (a.sampleCode || '').localeCompare(b.sampleCode || ''))
   return sorted.map((sample) => {
     const sampleCode = (sample.sampleCode || '').trim() || group.groupKey
     const labStorageCode = (sample.labStorageCode || '').trim()
     return {
+      sampleCode,
       anchorId: buildInboundSampleCardAnchorId(sampleCode),
       labStorageCode: labStorageCode || '—',
       sampleTypeName: (sample.sampleTypeName || '').trim()
@@ -77,6 +77,7 @@ function buildLinksFromVisibleGroups(
     (a.sampleCode || '').localeCompare(b.sampleCode || '')
   )
   return sorted.map((group) => ({
+    sampleCode: (group.sampleCode || group.groupKey || '').trim(),
     anchorId: buildInboundSampleCardAnchorId(group.groupKey),
     labStorageCode: resolveInboundSampleGroupCardTitle(group.workOrders),
     sampleTypeName: sampleTypeNameForGroup(group)

+ 4 - 8
web-admin/src/utils/sample-reception-view-sections.ts

@@ -16,14 +16,10 @@ export function buildQcReceptionViewFields(
   return [
     { label: '接收时间', value: formatDisplayOrDash(r.receptionTime || d?.receptionTime) },
     { label: '接收经手人', value: String(r.handlerName || d?.handlerName || '—') },
-    ...(accept === 'js'
-      ? [
-          {
-            label: '实验室入库编码',
-            value: String(r.labStorageCode || d?.labStorageCode || '—')
-          }
-        ]
-      : []),
+    {
+      label: '实验室入库编码',
+      value: String(r.labStorageCode || d?.labStorageCode || '—')
+    },
     {
       label: '接收温度(℃)',
       value: r.receiveTemperature ? `${r.receiveTemperature}℃` : '—'

+ 1 - 1
web-admin/src/views/cell-storage/protocol-audit/SampleReceptionInfoTab.vue

@@ -126,7 +126,7 @@
             header-align="center"
           >
             <template #default="{ row }">
-              {{ row.acceptConclusion === 'js' ? row.labStorageCode || '—' : '—' }}
+              {{ row.labStorageCode || '—' }}
             </template>
           </el-table-column>
           <el-table-column

+ 0 - 9
web-admin/src/views/cell-storage/protocol/ProtocolFormDialog.vue

@@ -2847,15 +2847,6 @@
 
       if (parsed.contractAmount != null) {
         filledLabels.push('合同金额')
-        const sampleTotal = formData.samples.reduce(
-          (sum, s) => sum + (Number(s.price) || 0),
-          0
-        )
-        if (sampleTotal > 0 && Math.abs(sampleTotal - Number(parsed.contractAmount)) > 0.01) {
-          warnings.push(
-            `样本优惠价合计 ${sampleTotal} 与合同金额 ${parsed.contractAmount} 不一致,请核对样本价格`
-          )
-        }
       }
 
       if (parsed.transportModeText && !parsed.transportType) {

+ 29 - 5
web-admin/src/views/production/production-work-order/TaskExecutionDialog.vue

@@ -2,7 +2,7 @@
   <div class="task-execution-dialog-root">
     <el-dialog
       v-model="dialogVisible"
-      :title="readonly ? `任务详情 — ${taskName}` : `任务执行 — ${taskName}`"
+      :title="executionDialogTitle"
       width="96vw"
       class="dialog-form-layout execution-dialog"
       align-center
@@ -19,13 +19,17 @@
           <span v-if="showHeaderLabStorageCode" class="execution-header-item"
             >实验室入库编码:<b>{{ headerLabStorageCode }}</b></span
           >
-          <span v-if="!showHeaderLabStorageCode" class="execution-header-item"
+          <span
+            v-if="!showHeaderLabStorageCode && !isTemporaryWorkOrder"
+            class="execution-header-item"
             >任务号:<b>{{ editor.taskDetail.value.taskNo }}</b></span
           >
-          <span v-if="!showHeaderLabStorageCode" class="execution-header-item"
+          <span
+            v-if="!showHeaderLabStorageCode && !isTemporaryWorkOrder"
+            class="execution-header-item"
             >工单号:{{ editor.taskDetail.value.workOrderNo || '—' }}</span
           >
-          <span class="execution-header-item"
+          <span v-if="!isTemporaryWorkOrder" class="execution-header-item"
             >任务名称:{{ editor.taskDetail.value.taskName || '—' }}</span
           >
           <el-tag :type="editor.taskStatusTag(editor.taskDetail.value.taskStatus).type">
@@ -565,7 +569,11 @@
     productionStepAssigneeText,
     productionStepPlannedStartText
   } from '@/utils/production-labels'
-  import { shouldShowTaskHeaderLabStorageCode } from '@/utils/production-work-order-category'
+  import {
+    shouldShowTaskHeaderLabStorageCode,
+    isTemporaryProductionWorkOrder,
+    temporaryProductionExecutionDialogTitle
+  } from '@/utils/production-work-order-category'
 
   const props = withDefaults(
     defineProps<{
@@ -616,6 +624,22 @@
     taskDetail.value ? shouldShowTaskHeaderLabStorageCode(taskDetail.value) : false
   )
 
+  const isTemporaryWorkOrder = computed(() =>
+    taskDetail.value ? isTemporaryProductionWorkOrder(taskDetail.value) : false
+  )
+
+  const executionDialogTitleLabel = computed(() => {
+    if (isTemporaryWorkOrder.value && taskDetail.value) {
+      return temporaryProductionExecutionDialogTitle(taskDetail.value)
+    }
+    return props.taskName || '—'
+  })
+
+  const executionDialogTitle = computed(() => {
+    const prefix = props.readonly ? '任务详情' : '任务执行'
+    return `${prefix} — ${executionDialogTitleLabel.value}`
+  })
+
   const headerLabStorageCode = computed(
     () => (taskDetail.value?.labStorageCode || '').trim() || '—'
   )

+ 8 - 1
web-admin/src/views/production/production-work-order/useTaskStepEditor.ts

@@ -982,7 +982,11 @@ export function useTaskStepEditor() {
     const workOrderId = String(executionDetail.workOrderId || '').trim()
     if (!workOrderId) return executionDetail
 
-    const needsWo = !(executionDetail.labStorageCode || '').trim() || !executionDetail.workOrderType
+    const isTemporary = executionDetail.workOrderType === 'temporary'
+    const needsWo =
+      !(executionDetail.labStorageCode || '').trim() ||
+      !executionDetail.workOrderType ||
+      (isTemporary && !(executionDetail.workOrderRemark || '').trim())
     if (!needsWo) return executionDetail
 
     try {
@@ -1005,6 +1009,9 @@ export function useTaskStepEditor() {
       if (wo?.bizType && !executionDetail.bizType) {
         patch.bizType = wo.bizType
       }
+      if (isTemporary || wo?.workOrderType === 'temporary') {
+        patch.workOrderRemark = (wo?.remark || '').trim()
+      }
       return Object.keys(patch).length ? { ...executionDetail, ...patch } : executionDetail
     } catch {
       return executionDetail

+ 8 - 6
web-admin/src/views/quality-inspection/pre-transit-sample/PreTransitProtocolCardList.vue

@@ -499,20 +499,22 @@
   const expandedKey = ref<string | null>(null)
   let resizeObserver: ResizeObserver | null = null
 
-  function minColWidthForViewport(width: number) {
-    if (width <= 768) return 160
-    if (width <= 1200) return 176
-    return 196
+  /** 与 scoped 样式中 --compact-card-width 及 .protocol-card-row gap 保持一致 */
+  function compactCardLayoutForViewport() {
+    const vw = window.innerWidth
+    if (vw <= 768) return { cardWidth: 184, gap: 8 }
+    if (vw <= 1200) return { cardWidth: 200, gap: GRID_GAP }
+    return { cardWidth: 220, gap: GRID_GAP }
   }
 
   function updateColumnsPerRow() {
     const el = listRef.value
     if (!el) return
     const innerWidth = Math.max(0, el.clientWidth - LIST_PADDING)
-    const minCol = minColWidthForViewport(window.innerWidth)
+    const { cardWidth, gap } = compactCardLayoutForViewport()
     columnsPerRow.value = Math.max(
       1,
-      Math.floor((innerWidth + GRID_GAP) / (minCol + GRID_GAP))
+      Math.floor((innerWidth + gap) / (cardWidth + gap))
     )
   }
 

+ 110 - 45
web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderCardList.vue

@@ -3,28 +3,54 @@
   <div class="list-body list-wrap qc-work-order-cards-body">
     <div v-loading="loading" class="qc-work-order-cards-wrap">
       <template v-if="isAllTabList">
-        <template v-for="(item, idx) in allTabListItems">
-          <QcWorkOrderSampleGroup
-            v-if="item.kind === 'sample-group'"
-            :key="allTabItemKey(item, idx)"
-            :id="allTabInboundAnchorId(item.group)"
-            :group="item.group"
-            :group-index="cardIndex(idx)"
-            :card-anchor-id="allTabInboundAnchorId(item.group)"
-            :protocol-sibling-links="resolveProtocolSiblingLinksForGroup(item.group)"
-            :card-meta-config="cardMetaConfig"
-            :task-refresh-token="taskRefreshToken"
-            :show-work-order-ops="!isMineMode"
-            :show-work-order-view-only="isMineMode"
-            :task-list-mode="isMineMode ? 'mine' : 'workOrder'"
-            :preset-tasks-by-work-order-id="presetTasksByWorkOrderId"
-            :preset-batches-by-work-order-id="presetBatchesByWorkOrderId"
-            @configure="(row) => emit('configure', row)"
-            @view="(row) => emit('view', row)"
-            @command="(cmd, row) => emit('command', cmd, row)"
-            @tasks-changed="emit('tasks-changed')"
-            @sample-special-changed="emit('sample-special-changed')"
-          />
+        <template v-for="(item, idx) in allTabListItems" :key="allTabItemKey(item, idx)">
+          <template v-if="item.kind === 'sample-group'">
+            <QcWorkOrderSampleGroup
+              :id="allTabInboundAnchorId(item.group)"
+              :group="item.group"
+              :group-index="cardIndex(idx)"
+              :card-anchor-id="allTabInboundAnchorId(item.group)"
+              :protocol-sibling-links="resolveProtocolSiblingLinksForGroup(item.group)"
+              :sibling-nav-loading="isSiblingNavLoading(allTabInboundAnchorId(item.group))"
+              :card-meta-config="cardMetaConfig"
+              :task-refresh-token="taskRefreshToken"
+              :show-work-order-ops="!isMineMode"
+              :show-work-order-view-only="isMineMode"
+              :task-list-mode="isMineMode ? 'mine' : 'workOrder'"
+              :preset-tasks-by-work-order-id="presetTasksByWorkOrderId"
+              :preset-batches-by-work-order-id="presetBatchesByWorkOrderId"
+              @sibling-navigate="(payload) => emit('sibling-navigate', payload)"
+              @configure="(row) => emit('configure', row)"
+              @view="(row) => emit('view', row)"
+              @command="(cmd, row) => emit('command', cmd, row)"
+              @tasks-changed="emit('tasks-changed')"
+              @sample-special-changed="emit('sample-special-changed')"
+            />
+            <QcWorkOrderSampleGroup
+              v-for="ins in insertedCardsAfter(allTabInboundAnchorId(item.group))"
+              :key="'ins-all-' + ins.sampleCode"
+              :id="ins.anchorId"
+              :group="ins.group"
+              :group-index="cardIndex(idx)"
+              :card-anchor-id="ins.anchorId"
+              :protocol-sibling-links="ins.protocolSiblingLinks"
+              :show-protocol-payment-at-group="ins.showProtocolPaymentAtGroup"
+              is-inserted-sibling
+              :card-meta-config="cardMetaConfig"
+              :task-refresh-token="taskRefreshToken"
+              :show-work-order-ops="!isMineMode"
+              :show-work-order-view-only="isMineMode"
+              :task-list-mode="isMineMode ? 'mine' : 'workOrder'"
+              :preset-tasks-by-work-order-id="presetTasksByWorkOrderId"
+              :preset-batches-by-work-order-id="presetBatchesByWorkOrderId"
+              @sibling-navigate="(payload) => emit('sibling-navigate', payload)"
+              @configure="(row) => emit('configure', row)"
+              @view="(row) => emit('view', row)"
+              @command="(cmd, row) => emit('command', cmd, row)"
+              @tasks-changed="emit('tasks-changed')"
+              @sample-special-changed="emit('sample-special-changed')"
+            />
+          </template>
           <QcWorkOrderSingleCard
             v-else
             :key="allTabItemKey(item, idx)"
@@ -48,28 +74,54 @@
       </template>
       <template v-else-if="useGroupedList">
         <template v-if="cardMetaUsesInboundGroups(cardMetaConfig.tabKey)">
-          <QcWorkOrderSampleGroup
-            v-for="(item, idx) in inboundSampleListItems"
-            :key="item.group.groupKey"
-            :id="item.cardAnchorId"
-            :group="item.group"
-            :group-index="cardIndex(idx)"
-            :card-anchor-id="item.cardAnchorId"
-            :protocol-sibling-links="item.protocolSiblingLinks"
-            :show-protocol-payment-at-group="item.showProtocolPaymentAtGroup"
-            :card-meta-config="cardMetaConfig"
-            :task-refresh-token="taskRefreshToken"
-            :show-work-order-ops="!isMineMode"
-            :show-work-order-view-only="isMineMode"
-            :task-list-mode="isMineMode ? 'mine' : 'workOrder'"
-            :preset-tasks-by-work-order-id="presetTasksByWorkOrderId"
-            :preset-batches-by-work-order-id="presetBatchesByWorkOrderId"
-            @configure="(row) => emit('configure', row)"
-            @view="(row) => emit('view', row)"
-            @command="(cmd, row) => emit('command', cmd, row)"
-            @tasks-changed="emit('tasks-changed')"
-            @sample-special-changed="emit('sample-special-changed')"
-          />
+          <template v-for="(item, idx) in inboundSampleListItems" :key="item.group.groupKey">
+            <QcWorkOrderSampleGroup
+              :id="item.cardAnchorId"
+              :group="item.group"
+              :group-index="cardIndex(idx)"
+              :card-anchor-id="item.cardAnchorId"
+              :protocol-sibling-links="item.protocolSiblingLinks"
+              :show-protocol-payment-at-group="item.showProtocolPaymentAtGroup"
+              :sibling-nav-loading="isSiblingNavLoading(item.cardAnchorId)"
+              :card-meta-config="cardMetaConfig"
+              :task-refresh-token="taskRefreshToken"
+              :show-work-order-ops="!isMineMode"
+              :show-work-order-view-only="isMineMode"
+              :task-list-mode="isMineMode ? 'mine' : 'workOrder'"
+              :preset-tasks-by-work-order-id="presetTasksByWorkOrderId"
+              :preset-batches-by-work-order-id="presetBatchesByWorkOrderId"
+              @sibling-navigate="(payload) => emit('sibling-navigate', payload)"
+              @configure="(row) => emit('configure', row)"
+              @view="(row) => emit('view', row)"
+              @command="(cmd, row) => emit('command', cmd, row)"
+              @tasks-changed="emit('tasks-changed')"
+              @sample-special-changed="emit('sample-special-changed')"
+            />
+            <QcWorkOrderSampleGroup
+              v-for="ins in insertedCardsAfter(item.cardAnchorId)"
+              :key="'ins-' + ins.sampleCode"
+              :id="ins.anchorId"
+              :group="ins.group"
+              :group-index="cardIndex(idx)"
+              :card-anchor-id="ins.anchorId"
+              :protocol-sibling-links="ins.protocolSiblingLinks"
+              :show-protocol-payment-at-group="ins.showProtocolPaymentAtGroup"
+              is-inserted-sibling
+              :card-meta-config="cardMetaConfig"
+              :task-refresh-token="taskRefreshToken"
+              :show-work-order-ops="!isMineMode"
+              :show-work-order-view-only="isMineMode"
+              :task-list-mode="isMineMode ? 'mine' : 'workOrder'"
+              :preset-tasks-by-work-order-id="presetTasksByWorkOrderId"
+              :preset-batches-by-work-order-id="presetBatchesByWorkOrderId"
+              @sibling-navigate="(payload) => emit('sibling-navigate', payload)"
+              @configure="(row) => emit('configure', row)"
+              @view="(row) => emit('view', row)"
+              @command="(cmd, row) => emit('command', cmd, row)"
+              @tasks-changed="emit('tasks-changed')"
+              @sample-special-changed="emit('sample-special-changed')"
+            />
+          </template>
         </template>
         <template v-else>
           <QcWorkOrderSampleGroup
@@ -149,6 +201,10 @@
     buildInboundSampleCardAnchorId,
     resolveProtocolSiblingLinksForGroup
   } from '@/utils/qc-work-order-inbound-sample-cards'
+  import type {
+    InsertedInboundSiblingCard,
+    InboundSiblingNavigatePayload
+  } from '../composables/useInboundSiblingNavigation'
   import type { QcWorkOrderCardMetaConfig } from '@/utils/qc-work-order-card-meta'
   import {
     cardMetaUsesInboundGroups,
@@ -173,9 +229,13 @@
       pageMode?: 'manage' | 'mine'
       presetTasksByWorkOrderId?: Record<string, QcTask[]>
       presetBatchesByWorkOrderId?: Record<string, QcInspectionBatch[]>
+      insertedCardsAfter?: (anchorId: string) => InsertedInboundSiblingCard[]
+      siblingNavLoadingAfterAnchorId?: string
     }>(),
     {
-      pageMode: 'manage'
+      pageMode: 'manage',
+      insertedCardsAfter: () => () => [],
+      siblingNavLoadingAfterAnchorId: ''
     }
   )
 
@@ -194,6 +254,7 @@
     'sample-special-changed': []
     'page-change': [page: number]
     'page-size-change': [size: number]
+    'sibling-navigate': [payload: InboundSiblingNavigatePayload]
   }>()
 
   const isAllTabList = computed(() => isAllQcWorkOrderTab(props.cardMetaConfig.tabKey))
@@ -228,6 +289,10 @@
   function allTabInboundAnchorId(group: QcWorkOrderSampleGroupModel) {
     return buildInboundSampleCardAnchorId(group.groupKey)
   }
+
+  function isSiblingNavLoading(anchorId: string) {
+    return props.siblingNavLoadingAfterAnchorId === anchorId
+  }
 </script>
 
 <style scoped lang="scss">

+ 63 - 4
web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSampleGroup.vue

@@ -13,23 +13,30 @@
         monospace
       />
       <span class="qc-sample-row__sub">{{ contextSubtitle }}</span>
+      <span v-if="isInsertedSibling" class="qc-sample-row__inserted-tag">同协议关联</span>
       <div
         v-if="protocolSiblingLinks.length > 1"
         class="qc-sample-row__protocol-inline"
         aria-label="同协议样本"
       >
         <span class="qc-sample-row__protocol-label">同协议样本</span>
-        <span
+        <button
           v-for="link in protocolSiblingLinks"
           :key="link.anchorId"
+          type="button"
           class="qc-sample-row__protocol-chip"
-          :class="{ 'is-current': link.anchorId === cardAnchorId }"
+          :class="{
+            'is-current': link.anchorId === cardAnchorId,
+            'is-clickable': link.anchorId !== cardAnchorId
+          }"
+          :disabled="siblingNavLoading"
+          @click="handleSiblingLinkClick(link)"
         >
           <span class="qc-sample-row__protocol-chip-code">{{ link.labStorageCode }}</span>
           <span v-if="link.sampleTypeName" class="qc-sample-row__protocol-chip-type">
             {{ link.sampleTypeName }}
           </span>
-        </span>
+        </button>
       </div>
       <el-tag v-if="showInboundProtocolStatus" :type="protocolStatus.tagType" size="small">
         {{ protocolStatus.label }}
@@ -132,6 +139,8 @@
     resolveProtocolGroupStatus
   } from '@/utils/qc-work-order-card-meta'
   import type { QcInboundProtocolSiblingLink } from '@/utils/qc-work-order-inbound-sample-cards'
+  import { scrollToInboundSampleCard } from '@/utils/qc-work-order-inbound-sample-cards'
+  import type { InboundSiblingNavigatePayload } from './composables/useInboundSiblingNavigation'
   import { resolveEffectiveRowTabKey } from '@/utils/qc-work-order-tabs'
   import { sumQcWorkOrderGroupUnfinishedBadgeCount } from '@/utils/qc-work-order-unfinished-badge'
   import QcUnfinishedBadge from './QcUnfinishedBadge.vue'
@@ -160,6 +169,8 @@
       cardAnchorId?: string
       protocolSiblingLinks?: QcInboundProtocolSiblingLink[]
       showProtocolPaymentAtGroup?: boolean
+      isInsertedSibling?: boolean
+      siblingNavLoading?: boolean
     }>(),
     {
       showWorkOrderOps: true,
@@ -169,7 +180,9 @@
       hideProtocolFeeSummary: false,
       cardAnchorId: '',
       protocolSiblingLinks: () => [],
-      showProtocolPaymentAtGroup: false
+      showProtocolPaymentAtGroup: false,
+      isInsertedSibling: false,
+      siblingNavLoading: false
     }
   )
 
@@ -179,6 +192,7 @@
     command: [cmd: string, row: QcWorkOrder]
     'tasks-changed': []
     'sample-special-changed': []
+    'sibling-navigate': [payload: InboundSiblingNavigatePayload]
   }>()
 
   const sampleSpecialDialogVisible = ref(false)
@@ -287,6 +301,24 @@
     return wo.workOrderNo || '—'
   }
 
+  function handleSiblingLinkClick(link: QcInboundProtocolSiblingLink) {
+    if (props.siblingNavLoading) return
+    const targetSampleCode = (link.sampleCode || '').trim()
+    if (!targetSampleCode) return
+    if (link.anchorId === props.cardAnchorId) {
+      scrollToInboundSampleCard(link.anchorId)
+      return
+    }
+    const row = contextRow.value
+    const receptionSource = (row?.receptionSource || 'cell_storage').trim() || 'cell_storage'
+    emit('sibling-navigate', {
+      targetSampleCode,
+      sourceAnchorId: props.cardAnchorId,
+      receptionSource,
+      protocolSiblingLinks: props.protocolSiblingLinks
+    })
+  }
+
   function workOrderStatusTagType(status: string): TagType {
     if (status === 'ywc') return 'success'
     if (status === 'ypz') return 'warning'
@@ -348,12 +380,39 @@
     background: #fff;
     font-size: 12px;
     color: #047857;
+    font-family: inherit;
+    line-height: 1.4;
 
     &.is-current {
       border-color: var(--primary, #10b981);
       background: var(--primary-bg, #ecfdf5);
       font-weight: 600;
+      cursor: default;
     }
+
+    &.is-clickable {
+      cursor: pointer;
+
+      &:hover:not(:disabled) {
+        border-color: var(--primary, #10b981);
+        background: var(--primary-bg, #ecfdf5);
+      }
+    }
+
+    &:disabled {
+      opacity: 0.65;
+      cursor: not-allowed;
+    }
+  }
+
+  .qc-sample-row__inserted-tag {
+    flex-shrink: 0;
+    font-size: 11px;
+    color: var(--mint-text-muted, #64748b);
+    padding: 2px 8px;
+    border-radius: 999px;
+    background: #f1f5f9;
+    border: 1px solid #e2e8f0;
   }
 
   .qc-sample-row__protocol-chip-code {

+ 208 - 0
web-admin/src/views/quality-inspection/qc-work-order/composables/useInboundSiblingNavigation.ts

@@ -0,0 +1,208 @@
+import { ref, computed, nextTick, type Ref } from 'vue'
+import { ElMessage } from 'element-plus'
+import { fetchQcWorkOrdersBySample, type QcWorkOrder } from '@/api/qc-work-order'
+import { fetchMyQcTaskList, type QcTask } from '@/api/qc-task'
+import { groupInboundQcWorkOrders } from '@/utils/qc-work-order-groups'
+import {
+  buildInboundSampleCardAnchorId,
+  buildProtocolSiblingLinksFromSamples,
+  scrollToInboundSampleCard,
+  type QcInboundProtocolSiblingLink
+} from '@/utils/qc-work-order-inbound-sample-cards'
+import type { QcWorkOrderSampleGroupModel } from '@/utils/qc-work-order-groups'
+import { fetchQcInspectionBatchesBatch, type QcInspectionBatch } from '@/utils/qc-inspection-batch'
+import type { QcWorkOrderTabPageMode } from './useQcWorkOrderTabPage'
+
+export type InsertedInboundSiblingCard = {
+  sampleCode: string
+  anchorId: string
+  insertedAfterAnchorId: string
+  group: QcWorkOrderSampleGroupModel
+  protocolSiblingLinks: QcInboundProtocolSiblingLink[]
+  showProtocolPaymentAtGroup: boolean
+  isInserted: true
+}
+
+export type InboundSiblingNavigatePayload = {
+  targetSampleCode: string
+  sourceAnchorId: string
+  receptionSource: string
+  protocolSiblingLinks: QcInboundProtocolSiblingLink[]
+}
+
+function resolveShowProtocolPaymentAtGroup(workOrders: QcWorkOrder[]) {
+  return workOrders.some((wo) => wo.receptionSource === 'cell_storage' && !!wo.paymentSummary)
+}
+
+function buildInsertedSiblingCard(
+  workOrders: QcWorkOrder[],
+  sampleCode: string,
+  insertedAfterAnchorId: string,
+  protocolSiblingLinks: QcInboundProtocolSiblingLink[]
+): InsertedInboundSiblingCard | null {
+  const groups = groupInboundQcWorkOrders(workOrders)
+  const group = groups[0]
+  if (!group) return null
+  const links =
+    protocolSiblingLinks.length > 1
+      ? protocolSiblingLinks
+      : buildProtocolSiblingLinksFromSamples(group, workOrders[0]?.protocolSiblingSamples || [])
+  return {
+    sampleCode,
+    anchorId: buildInboundSampleCardAnchorId(sampleCode),
+    insertedAfterAnchorId,
+    group,
+    protocolSiblingLinks: links,
+    showProtocolPaymentAtGroup: resolveShowProtocolPaymentAtGroup(workOrders),
+    isInserted: true
+  }
+}
+
+async function fetchMineTasksForWorkOrders(
+  workOrders: QcWorkOrder[],
+  sampleCode: string
+): Promise<Record<string, QcTask[]>> {
+  const idSet = new Set(workOrders.map((wo) => wo.id).filter(Boolean))
+  if (!idSet.size) return {}
+  const res = await fetchMyQcTaskList({
+    keyword: sampleCode,
+    page: 1,
+    pageSize: 100
+  })
+  const map: Record<string, QcTask[]> = {}
+  for (const task of res.list || []) {
+    if (!idSet.has(task.workOrderId)) continue
+    if (!map[task.workOrderId]) map[task.workOrderId] = []
+    map[task.workOrderId].push(task)
+  }
+  return map
+}
+
+export function useInboundSiblingNavigation(options: {
+  listRows: Ref<QcWorkOrder[]>
+  pageMode: Ref<QcWorkOrderTabPageMode>
+}) {
+  const insertedCards = ref<InsertedInboundSiblingCard[]>([])
+  const insertedPresetTasksByWorkOrderId = ref<Record<string, QcTask[]>>({})
+  const insertedPresetBatchesByWorkOrderId = ref<Record<string, QcInspectionBatch[]>>({})
+  const siblingNavLoadingAfterAnchorId = ref('')
+  const navigating = ref(false)
+
+  const mergedPresetTasksByWorkOrderId = computed(() => ({
+    ...insertedPresetTasksByWorkOrderId.value
+  }))
+
+  const mergedPresetBatchesByWorkOrderId = computed(() => ({
+    ...insertedPresetBatchesByWorkOrderId.value
+  }))
+
+  function clearInsertedCards() {
+    insertedCards.value = []
+    insertedPresetTasksByWorkOrderId.value = {}
+    insertedPresetBatchesByWorkOrderId.value = {}
+    siblingNavLoadingAfterAnchorId.value = ''
+  }
+
+  function insertedCardsAfter(anchorId: string) {
+    return insertedCards.value.filter((card) => card.insertedAfterAnchorId === anchorId)
+  }
+
+  function isSampleInMainList(sampleCode: string) {
+    const code = sampleCode.trim()
+    return options.listRows.value.some((row) => (row.sampleCode || '').trim() === code)
+  }
+
+  function findExistingAnchorId(sampleCode: string): string | null {
+    const code = sampleCode.trim()
+    if (!code) return null
+    const anchorId = buildInboundSampleCardAnchorId(code)
+    if (document.getElementById(anchorId)) return anchorId
+    const inserted = insertedCards.value.find((card) => card.sampleCode === code)
+    if (inserted) return inserted.anchorId
+    if (isSampleInMainList(code)) return anchorId
+    return null
+  }
+
+  async function enrichInsertedPresets(workOrders: QcWorkOrder[], sampleCode: string) {
+    if (options.pageMode.value === 'mine') {
+      const taskMap = await fetchMineTasksForWorkOrders(workOrders, sampleCode)
+      insertedPresetTasksByWorkOrderId.value = {
+        ...insertedPresetTasksByWorkOrderId.value,
+        ...taskMap
+      }
+      return
+    }
+    const ids = workOrders.map((wo) => wo.id).filter(Boolean)
+    if (!ids.length) return
+    const batchMap = (await fetchQcInspectionBatchesBatch(ids).catch(() => ({}))) || {}
+    insertedPresetBatchesByWorkOrderId.value = {
+      ...insertedPresetBatchesByWorkOrderId.value,
+      ...batchMap
+    }
+  }
+
+  async function navigateToSibling(payload: InboundSiblingNavigatePayload) {
+    const targetSampleCode = (payload.targetSampleCode || '').trim()
+    const sourceAnchorId = (payload.sourceAnchorId || '').trim()
+    const receptionSource = (payload.receptionSource || '').trim()
+    if (!targetSampleCode || !sourceAnchorId || navigating.value) return
+
+    const targetAnchorId = buildInboundSampleCardAnchorId(targetSampleCode)
+    if (targetAnchorId === sourceAnchorId) {
+      scrollToInboundSampleCard(targetAnchorId)
+      return
+    }
+
+    const existingAnchor = findExistingAnchorId(targetSampleCode)
+    if (existingAnchor) {
+      scrollToInboundSampleCard(existingAnchor)
+      return
+    }
+
+    navigating.value = true
+    siblingNavLoadingAfterAnchorId.value = sourceAnchorId
+    try {
+      const res = await fetchQcWorkOrdersBySample({
+        sampleCode: targetSampleCode,
+        receptionSource
+      })
+      const workOrders = res.list || []
+      if (!workOrders.length) {
+        ElMessage.info('该样本暂无质检工单')
+        return
+      }
+      const card = buildInsertedSiblingCard(
+        workOrders,
+        targetSampleCode,
+        sourceAnchorId,
+        payload.protocolSiblingLinks
+      )
+      if (!card) {
+        ElMessage.info('该样本暂无质检工单')
+        return
+      }
+      insertedCards.value = [
+        ...insertedCards.value.filter((c) => c.sampleCode !== targetSampleCode),
+        card
+      ]
+      await enrichInsertedPresets(workOrders, targetSampleCode)
+      await nextTick()
+      scrollToInboundSampleCard(card.anchorId)
+    } catch {
+      ElMessage.error('加载同协议样本失败,请稍后重试')
+    } finally {
+      navigating.value = false
+      siblingNavLoadingAfterAnchorId.value = ''
+    }
+  }
+
+  return {
+    insertedCards,
+    insertedCardsAfter,
+    siblingNavLoadingAfterAnchorId,
+    mergedPresetTasksByWorkOrderId,
+    mergedPresetBatchesByWorkOrderId,
+    navigateToSibling,
+    clearInsertedCards
+  }
+}

+ 35 - 0
web-admin/src/views/quality-inspection/qc-work-order/composables/useQcWorkOrderTabPage.ts

@@ -25,6 +25,7 @@ import { getCardMetaConfig } from '@/utils/qc-work-order-card-meta'
 import { joinWorkOrderStatusFilter } from '@/utils/status-tag-registry'
 import { fetchAllPagedRecords } from '@/utils/fetch-paged-list'
 import { fetchQcInspectionBatchesBatch, type QcInspectionBatch } from '@/utils/qc-inspection-batch'
+import { useInboundSiblingNavigation } from './useInboundSiblingNavigation'
 
 function shouldApplyTriggerSourceFilter(tabKey: QcWorkOrderTabKey, tabLayout: string) {
   if (isAllQcWorkOrderTab(tabKey)) return true
@@ -62,6 +63,34 @@ export function useQcWorkOrderTabPage(
   const pagination = reactive({ page: 1, pageSize: 100, total: 0 })
   const taskSubListRefreshToken = ref(0)
 
+  const {
+    insertedCardsAfter,
+    siblingNavLoadingAfterAnchorId,
+    mergedPresetTasksByWorkOrderId: insertedPresetTasksByWorkOrderId,
+    mergedPresetBatchesByWorkOrderId: insertedPresetBatchesByWorkOrderId,
+    navigateToSibling,
+    clearInsertedCards
+  } = useInboundSiblingNavigation({
+    listRows: tableData,
+    pageMode: pageModeValue
+  })
+
+  const cardPresetTasksByWorkOrderId = computed(() => ({
+    ...presetTasksByWorkOrderId.value,
+    ...insertedPresetTasksByWorkOrderId.value
+  }))
+
+  const cardPresetBatchesByWorkOrderId = computed(() => {
+    const base = presetBatchesByWorkOrderId.value
+    if (!base && !Object.keys(insertedPresetBatchesByWorkOrderId.value).length) {
+      return undefined
+    }
+    return {
+      ...(base || {}),
+      ...insertedPresetBatchesByWorkOrderId.value
+    }
+  })
+
   const viewVisible = ref(false)
   const viewWorkOrderId = ref('')
   const viewTabKey = ref<QcWorkOrderTabKey>(tabKey.value)
@@ -101,6 +130,7 @@ export function useQcWorkOrderTabPage(
   }
 
   async function loadData() {
+    clearInsertedCards()
     loading.value = true
     try {
       if (pageModeValue.value === 'mine') {
@@ -269,6 +299,11 @@ export function useQcWorkOrderTabPage(
     tableData,
     presetTasksByWorkOrderId,
     presetBatchesByWorkOrderId,
+    cardPresetTasksByWorkOrderId,
+    cardPresetBatchesByWorkOrderId,
+    insertedCardsAfter,
+    siblingNavLoadingAfterAnchorId,
+    navigateToSibling,
     loading,
     pagination,
     taskSubListRefreshToken,

+ 10 - 4
web-admin/src/views/quality-inspection/qc-work-order/tabs/QcWorkOrderTabPanel.vue

@@ -324,8 +324,11 @@
       :page-size="pagination.pageSize"
       :total="pagination.total"
       :task-refresh-token="taskSubListRefreshToken"
-      :preset-tasks-by-work-order-id="presetTasksByWorkOrderId"
-      :preset-batches-by-work-order-id="presetBatchesByWorkOrderId"
+      :preset-tasks-by-work-order-id="cardPresetTasksByWorkOrderId"
+      :preset-batches-by-work-order-id="cardPresetBatchesByWorkOrderId"
+      :inserted-cards-after="insertedCardsAfter"
+      :sibling-nav-loading-after-anchor-id="siblingNavLoadingAfterAnchorId"
+      @sibling-navigate="navigateToSibling"
       @configure="(row) => emit('configure', row)"
       @view="handleView"
       @command="handleCommand"
@@ -442,8 +445,11 @@
     createTimeRange,
     filterExpanded,
     tableData,
-    presetTasksByWorkOrderId,
-    presetBatchesByWorkOrderId,
+    cardPresetTasksByWorkOrderId,
+    cardPresetBatchesByWorkOrderId,
+    insertedCardsAfter,
+    siblingNavLoadingAfterAnchorId,
+    navigateToSibling,
     loading,
     pagination,
     taskSubListRefreshToken,

+ 1 - 3
web-admin/src/views/quality-inspection/sample-reception/SampleReceptionCardList.vue

@@ -69,9 +69,7 @@
               >
                 <dt>实验室入库编码</dt>
                 <dd>
-                  {{
-                    row.acceptConclusion === 'js' ? row.labStorageCode || '—' : '—'
-                  }}
+                  {{ row.labStorageCode || '—' }}
                 </dd>
               </div>
               <div class="sample-reception-card__meta-item">

+ 13 - 51
web-admin/src/views/quality-inspection/sample-reception/index.vue

@@ -293,11 +293,7 @@
                       <el-radio value="bj">拒收</el-radio>
                     </el-radio-group>
                   </el-form-item>
-                  <el-form-item
-                    v-if="showLabStorageCodeField"
-                    label="实验室入库编码"
-                    prop="labStorageCode"
-                  >
+                  <el-form-item label="实验室入库编码" prop="labStorageCode">
                     <el-input
                       v-model="receiveForm.labStorageCode"
                       placeholder="请填写"
@@ -462,10 +458,7 @@
               ><span class="label">接收温度(℃)</span
               >{{ viewRow.receiveTemperature ? viewRow.receiveTemperature + '℃' : '-' }}</div
             >
-            <div
-              v-if="viewRow.acceptConclusion === 'js'"
-              class="readonly-item"
-            >
+            <div class="readonly-item">
               <span class="label">实验室入库编码</span>{{ viewRow.labStorageCode || '-' }}
             </div>
             <div class="readonly-item">
@@ -653,10 +646,10 @@
                     <el-radio value="bj">拒收</el-radio>
                   </el-radio-group>
                 </el-form-item>
-                <el-form-item v-if="entry.acceptConclusion === 'js'" label="实验室入库编码" required>
-                  <template #label>
-                    实验室入库编码
-                  </template>
+                <el-form-item
+                  label="实验室入库编码"
+                  :required="entry.acceptConclusion === 'js'"
+                >
                   <el-input
                     v-model="entry.labStorageCode"
                     placeholder="请填写"
@@ -891,10 +884,6 @@
   const labStorageCodeManuallyEdited = ref(false)
   const lastAllocatedReceptionDate = ref('')
 
-  const showLabStorageCodeField = computed(
-    () => receiveForm.acceptConclusion === 'js'
-  )
-
   const receiveRules = {
     receptionTime: [{ required: true, message: '请选择样本接收时间', trigger: 'change' }],
     handlerUserId: [{ required: true, message: '请选择经手人', trigger: 'change' }],
@@ -1137,10 +1126,7 @@
   }
 
   function refreshReceiveLabStorageCode(force = false) {
-    if (receiveForm.acceptConclusion !== 'js') {
-      receiveForm.labStorageCode = ''
-      return
-    }
+    if (receiveForm.acceptConclusion !== 'js') return
     if (!force && labStorageCodeManuallyEdited.value) return
 
     const row = currentRow.value
@@ -1166,23 +1152,13 @@
   }
 
   function onReceiveAcceptConclusionChange(val: string | number | boolean | undefined) {
-    if (val === 'bj') {
-      receiveForm.labStorageCode = ''
-      labStorageCodeManuallyEdited.value = false
-      lastAllocatedReceptionDate.value = ''
-      return
-    }
+    if (val === 'bj') return
     refreshReceiveLabStorageCode(true)
   }
 
   function allocateBatchLabStorageCodes() {
     for (const entry of batchEntries.value) {
-      if (entry.acceptConclusion !== 'js') {
-        entry.labStorageCode = ''
-        entry.labStorageCodeManuallyEdited = false
-        entry.lastAllocatedReceptionDate = ''
-        continue
-      }
+      if (entry.acceptConclusion !== 'js') continue
       if (entry.labStorageCodeManuallyEdited) continue
       entry.labStorageCode = resolvePrefilledLabStorageCode(
         entry.sourceType,
@@ -1194,12 +1170,7 @@
   }
 
   function refreshBatchEntryLabStorageCode(entry: BatchReceiveEntry, force = false) {
-    if (entry.acceptConclusion !== 'js') {
-      entry.labStorageCode = ''
-      entry.labStorageCodeManuallyEdited = false
-      entry.lastAllocatedReceptionDate = ''
-      return
-    }
+    if (entry.acceptConclusion !== 'js') return
     if (!force && entry.labStorageCodeManuallyEdited) return
     entry.labStorageCode = resolvePrefilledLabStorageCode(
       entry.sourceType,
@@ -1219,12 +1190,7 @@
   }
 
   function onBatchAcceptConclusionChange(entry: BatchReceiveEntry) {
-    if (entry.acceptConclusion === 'bj') {
-      entry.labStorageCode = ''
-      entry.labStorageCodeManuallyEdited = false
-      entry.lastAllocatedReceptionDate = ''
-      return
-    }
+    if (entry.acceptConclusion === 'bj') return
     refreshBatchEntryLabStorageCode(entry, true)
   }
 
@@ -1370,8 +1336,7 @@
             receiveTemperature: entry.receiveTemperature || undefined,
             checks: buildChecksFromEntry(entry),
             acceptConclusion: entry.acceptConclusion,
-            labStorageCode:
-              entry.acceptConclusion === 'js' ? entry.labStorageCode || undefined : undefined,
+            labStorageCode: String(entry.labStorageCode || '').trim() || undefined,
             receiveAttachments: financeAttachmentsToUrlString(entry.attachments) || undefined,
             remark: entry.remark || undefined
           })
@@ -1582,10 +1547,7 @@
         checked: !!checkValues[item.id]
       })),
       acceptConclusion: receiveForm.acceptConclusion,
-      labStorageCode:
-        receiveForm.acceptConclusion === 'js'
-          ? receiveForm.labStorageCode || undefined
-          : undefined,
+      labStorageCode: String(receiveForm.labStorageCode || '').trim() || undefined,
       receiveAttachments: financeAttachmentsToUrlString(attachments.value) || undefined,
       remark: receiveForm.remark || undefined
     }

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