zlwu před 4 měsíci
rodič
revize
58a47b96a5
38 změnil soubory, kde provedl 1864 přidání a 1946 odebrání
  1. binární
      backend.zip
  2. 1 1
      backend/app.pid
  3. 37 0
      backend/app/crud/production_work_order.py
  4. 29 0
      backend/app/crud/qc_work_order.py
  5. 0 0
      backend/app/graphify-out/cache/ast/aa792b69770f91c72fde0f6c156a8ef36d479c214e6aa59b2464a01ff71158ce.json
  6. 0 0
      backend/app/graphify-out/cache/ast/fa490691861edaf4e9d7d91dedc9ee7ed9d3058c6acc5b86d2cde4aaaa970df3.json
  7. 196 420
      backend/graphify-out/.graphify_analysis.json
  8. 171 164
      backend/graphify-out/.graphify_extract.json
  9. 81 76
      backend/graphify-out/GRAPH_REPORT.md
  10. 1 1
      backend/graphify-out/graph.html
  11. 112 112
      backend/graphify-out/graph.json
  12. 2 2
      backend/graphify-out/manifest.json
  13. 454 540
      web-admin/graphify-out/.graphify_analysis.json
  14. 90 397
      web-admin/graphify-out/.graphify_extract.json
  15. 106 101
      web-admin/graphify-out/GRAPH_REPORT.md
  16. 1 1
      web-admin/graphify-out/graph.html
  17. 2 2
      web-admin/graphify-out/graph.json
  18. 10 10
      web-admin/graphify-out/manifest.json
  19. 6 0
      web-admin/src/api/production-work-order.ts
  20. 6 0
      web-admin/src/api/qc-work-order.ts
  21. 1 0
      web-admin/src/graphify-out/cache/ast/07468df71a20f2c75ba9ecba1b5bca2a83bf63b875c6e68b99471d2613b64293.json
  22. 1 0
      web-admin/src/graphify-out/cache/ast/4506079098039c10a6e677f125472c7d3a685a0e496e98cd3c05dfa5bb835571.json
  23. 1 0
      web-admin/src/graphify-out/cache/ast/5fb0f30d87bbb940863761ce01607887a6afe05edbf636d2737a59eb342f0e0f.json
  24. 1 0
      web-admin/src/graphify-out/cache/ast/686495c44689c783c66328617a5cb322c6aa79a8e840ca290342c5f1bf85359d.json
  25. 0 0
      web-admin/src/graphify-out/cache/ast/70726652cbfe3dbe97bded90eebec9252542e32db5aa36528c947c84c4130e80.json
  26. 0 0
      web-admin/src/graphify-out/cache/ast/85094e0d7ef8fc73ef477c4d44b0a8e52d14f8ac244d19055575a0806416f936.json
  27. 1 0
      web-admin/src/graphify-out/cache/ast/8569ccf4780741f68bc4f973b51e8228576837ee8c990396b4c28749c2a1049e.json
  28. 0 0
      web-admin/src/graphify-out/cache/ast/8c12c9f17b5c67ae2fca703244f83a62ee956584c022393c68c2ac94170bb69c.json
  29. 1 0
      web-admin/src/graphify-out/cache/ast/a9bcfc991213e67bd27ede64d5fb16d000c64125160d3bd28fee64ab987a9853.json
  30. 0 0
      web-admin/src/graphify-out/cache/ast/b5a7ddf14e286e97ad6202f461aabb2c4eda32e426efdcee6e2be243505431e4.json
  31. 0 0
      web-admin/src/graphify-out/cache/ast/df70c7fe1152f96f10eccfa304bbd477f0dabdb25d41a377f622eddb772aadb2.json
  32. 1 0
      web-admin/src/graphify-out/cache/ast/e37e8fbc54360e71a9e407b4886f3b80267bbcd78929c4f949d7b4962af8acc4.json
  33. 0 0
      web-admin/src/graphify-out/cache/ast/f7f50431271417e71aa52d0168f65dfc871f5100323ae337a66ab13435c3867b.json
  34. 8 0
      web-admin/src/utils/production-work-order-card-meta.ts
  35. 8 0
      web-admin/src/utils/qc-work-order-card-meta.ts
  36. 129 0
      web-admin/src/views/pilot-service/pilot-application/ApplicationFormDialog.vue
  37. 243 118
      web-admin/src/views/production/production-work-order/ProductionWorkOrderCardList.vue
  38. 164 1
      web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue

binární
backend.zip


+ 1 - 1
backend/app.pid

@@ -1 +1 @@
-66684
+57731

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

@@ -1,6 +1,7 @@
 """入库生产工单 CRUD(含临时生产工单)"""
 from __future__ import annotations
 
+from collections import defaultdict
 from datetime import datetime
 from typing import Any, Dict, List, Optional, Tuple
 
@@ -24,6 +25,8 @@ from app.models.models import (
     BizProductionTaskStepOutput,
     BizProductionTaskStepReagent,
     BizProductionWorkOrder,
+    BizPilotInfusionSchedule,
+    BizPilotInfusionScheduleProduct,
     BizProtocolSample,
     BizSampleReception,
 )
@@ -133,6 +136,15 @@ _BIZ_TYPE_LABELS = {
 }
 
 
+def _dec_price(v: Any) -> Optional[float]:
+    if v is None:
+        return None
+    try:
+        return float(v)
+    except (TypeError, ValueError):
+        return None
+
+
 def _enrich_outbound_items(
     db: Session, tenant_no: str, items: List[Dict[str, Any]]
 ) -> List[Dict[str, Any]]:
@@ -156,6 +168,30 @@ def _enrich_outbound_items(
     if not outbound_ids:
         return items
 
+    products_map: Dict[str, List[Dict[str, Any]]] = defaultdict(list)
+    for product in (
+        db.query(BizPilotInfusionScheduleProduct)
+        .filter(
+            BizPilotInfusionScheduleProduct.infusion_schedule_id.in_(outbound_ids),
+            BizPilotInfusionScheduleProduct.tenant_no == tenant_no,
+            BizPilotInfusionScheduleProduct.del_flag == 0,
+        )
+        .order_by(BizPilotInfusionScheduleProduct.order_num.asc())
+        .all()
+    ):
+        products_map[product.infusion_schedule_id].append(
+            {
+                "projectCode": product.project_code or "",
+                "projectName": product.project_name or "",
+                "cellTypeName": product.cell_type_name or "",
+                "specText": product.spec_text or "",
+                "agreementPrice": _dec_price(product.agreement_price),
+                "actualPrice": _dec_price(product.actual_price),
+                "quantity": product.quantity,
+                "productStatus": product.product_status or "",
+            }
+        )
+
     schedules = (
         db.query(BizPilotInfusionSchedule)
         .filter(
@@ -239,6 +275,7 @@ def _enrich_outbound_items(
         it["specText"] = sched.spec_text or ""
         it["infusionTime"] = _fmt_dt(sched.infusion_time) if sched.infusion_time else ""
         it["infusionLocation"] = sched.infusion_location or ""
+        it["products"] = products_map.get(it["infusionScheduleId"], [])
 
     return items
 

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

@@ -1,6 +1,7 @@
 """质检工单 CRUD"""
 from __future__ import annotations
 
+from collections import defaultdict
 from datetime import datetime
 from decimal import Decimal
 from typing import Any, Dict, List, Optional, Tuple
@@ -213,6 +214,31 @@ def _enrich_outbound_work_orders(
     schedule_ids = list({x.get("infusionScheduleId") for x in outbound if x.get("infusionScheduleId")})
     if not schedule_ids:
         return items
+
+    products_map: Dict[str, List[Dict[str, Any]]] = defaultdict(list)
+    for product in (
+        db.query(BizPilotInfusionScheduleProduct)
+        .filter(
+            BizPilotInfusionScheduleProduct.infusion_schedule_id.in_(schedule_ids),
+            BizPilotInfusionScheduleProduct.tenant_no == tenant_no,
+            BizPilotInfusionScheduleProduct.del_flag == 0,
+        )
+        .order_by(BizPilotInfusionScheduleProduct.order_num.asc())
+        .all()
+    ):
+        products_map[product.infusion_schedule_id].append(
+            {
+                "projectCode": product.project_code or "",
+                "projectName": product.project_name or "",
+                "cellTypeName": product.cell_type_name or "",
+                "specText": product.spec_text or "",
+                "agreementPrice": _dec(product.agreement_price),
+                "actualPrice": _dec(product.actual_price),
+                "quantity": product.quantity,
+                "productStatus": product.product_status or "",
+            }
+        )
+
     # 查输注计划 → 输注方案 → 申请单 → 患者
     schedules = {
         s.id: s
@@ -287,7 +313,10 @@ def _enrich_outbound_work_orders(
         item["patientName"] = patient.patient_name if patient else ""
         item["sortNo"] = course.get("sortNo")
         item["totalCourseCount"] = course.get("totalCourseCount")
+        item["cellType"] = sched.cell_type or ""
+        item["specText"] = sched.spec_text or ""
         item["infusionTime"] = _fmt_dt(sched.infusion_time)
+        item["products"] = products_map.get(sid, [])
     return items
 
 

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
backend/app/graphify-out/cache/ast/aa792b69770f91c72fde0f6c156a8ef36d479c214e6aa59b2464a01ff71158ce.json


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
backend/app/graphify-out/cache/ast/fa490691861edaf4e9d7d91dedc9ee7ed9d3058c6acc5b86d2cde4aaaa970df3.json


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 196 - 420
backend/graphify-out/.graphify_analysis.json


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 171 - 164
backend/graphify-out/.graphify_extract.json


+ 81 - 76
backend/graphify-out/GRAPH_REPORT.md

@@ -1,11 +1,11 @@
 # Graph Report - app  (2026-06-17)
 
 ## Corpus Check
-- Large corpus: 208 files · ~244,444 words. Semantic extraction will be expensive (many Claude tokens). Consider running on a subfolder, or use --no-semantic to run AST-only.
+- Large corpus: 208 files · ~244,597 words. Semantic extraction will be expensive (many Claude tokens). Consider running on a subfolder, or use --no-semantic to run AST-only.
 
 ## Summary
-- 4189 nodes · 22047 edges · 52 communities detected
-- Extraction: 33% EXTRACTED · 67% INFERRED · 0% AMBIGUOUS · INFERRED: 14869 edges (avg confidence: 0.59)
+- 4190 nodes · 22061 edges · 53 communities detected
+- Extraction: 33% EXTRACTED · 67% INFERRED · 0% AMBIGUOUS · INFERRED: 14880 edges (avg confidence: 0.59)
 - Token cost: 0 input · 0 output
 
 ## Community Hubs (Navigation)
@@ -55,12 +55,13 @@
 - [[_COMMUNITY_Community 43|Community 43]]
 - [[_COMMUNITY_Community 44|Community 44]]
 - [[_COMMUNITY_Community 45|Community 45]]
-- [[_COMMUNITY_Community 48|Community 48]]
+- [[_COMMUNITY_Community 46|Community 46]]
 - [[_COMMUNITY_Community 49|Community 49]]
 - [[_COMMUNITY_Community 50|Community 50]]
 - [[_COMMUNITY_Community 51|Community 51]]
 - [[_COMMUNITY_Community 52|Community 52]]
 - [[_COMMUNITY_Community 53|Community 53]]
+- [[_COMMUNITY_Community 54|Community 54]]
 
 ## God Nodes (most connected - your core abstractions)
 1. `success_response()` - 643 edges
@@ -79,103 +80,103 @@
   core/api_request_log_middleware.py → models/models.py
 - `get_menu_list()` --calls--> `success_response()`  [INFERRED]
   api/menu.py → core/response.py
+- `mark_requisition_fully_used()` --calls--> `apply_data_scope()`  [INFERRED]
+  crud/production_material_usage.py → core/data_scope.py
 - `保留匹配节点及其全部上级,供关键词检索后仍能正确组树。` --uses--> `SysDept`  [INFERRED]
   crud/dept.py → models/models.py
 - `物品单位 CRUD(biz_item_unit)` --uses--> `BizItemUnit`  [INFERRED]
   crud/item_unit.py → models/models.py
-- `快递公司 CRUD(biz_express_company)` --uses--> `BizExpressCompany`  [INFERRED]
-  crud/express_company.py → models/models.py
 
 ## Communities
 
 ### Community 0 - "Community 0"
 Cohesion: 0.01
-Nodes (807): attachment_download_url(), create_dept(), create_dict_data(), create_dict_type(), create_express_company(), create_item_unit(), create_menu(), create_post() (+799 more)
+Nodes (800): attachment_download_url(), create_dept(), create_dict_data(), create_dict_type(), create_express_company(), create_item_unit(), create_menu(), create_post() (+792 more)
 
 ### Community 1 - "Community 1"
 Cohesion: 0.01
-Nodes (587): AttachmentDownloadUrlBody, DeptCreateBody, DeptUpdateBody, DictDataCreateBody, DictDataUpdateBody, DictTypeCreateBody, DictTypeUpdateBody, ExpressCompanyCreateBody (+579 more)
+Nodes (582): AttachmentDownloadUrlBody, DeptCreateBody, DeptUpdateBody, DictDataCreateBody, DictDataUpdateBody, DictTypeCreateBody, DictTypeUpdateBody, ExpressCompanyCreateBody (+574 more)
 
 ### Community 2 - "Community 2"
-Cohesion: 0.07
-Nodes (479): BatchRefundFinanceSubmitBody, 细胞存储 — 采集协议单 / 审核 / 财务确认 / 分期预警 API, If mother info exists but no customerId, create a customer record and link it., RefundFinanceSubmitBody, Base, 细胞产品 CRUD(biz_cell_product), 解析任务样本投入物,返回祖先链单段(样本终止节点)。, 管理端手动改状态:允许在合法枚举间任意变更,并同步库位占用。 (+471 more)
+Cohesion: 0.1
+Nodes (315): 环境检测 — 检测项维护 + 登记管理 API, 保险公司及险种价格管理 API(保险公司 + 开票信息 + 险种价格), 审批意见:前端传 commentText,兼容 comment。, TaskActionBody, Base, 设备 CRUD(biz_equipment), 将 BizMaterialUsage 行转换为字典(用于我的领用记录)。, 当前登录人的手动出库领用记录:领用人字段存昵称或用户 ID(与生产/质检一致)。 (+307 more)
 
 ### Community 3 - "Community 3"
 Cohesion: 0.01
-Nodes (278): _ensure_customer_for_mother(), _persist_protocol_sub_resources(), list_ethnics_for_select(), apply_data_scope(), _get_child_dept_ids(), get_user_dept_id(), _resolve_user_role_scopes(), format_date() (+270 more)
+Nodes (258): _ensure_customer_for_mother(), _persist_protocol_sub_resources(), list_ethnics_for_select(), apply_data_scope(), _get_child_dept_ids(), get_user_dept_id(), _resolve_user_role_scopes(), format_date() (+250 more)
 
 ### Community 4 - "Community 4"
 Cohesion: 0.01
-Nodes (283): generate_uuid(), batch_stock_in(), batch_stock_out(), consumable_code_exists(), create_consumable(), _dec(), get_consumable(), _is_enabled_status() (+275 more)
+Nodes (262): set_current_process_route(), generate_uuid(), batch_create_sterilize_logs(), batch_create_wash_logs(), batch_discard_garments(), batch_soft_delete_garments(), _count_map(), create_garment() (+254 more)
 
 ### Community 5 - "Community 5"
 Cohesion: 0.02
-Nodes (235): eq_unicode(), like_unicode(), MySQL 字符串比较/模糊匹配时统一排序规则,避免 Illegal mix of collations。, 跨表字符串等值比较(JOIN / WHERE)时统一为 utf8mb4_unicode_ci。, 将列显式设为 utf8mb4_unicode_ci。, unicode_ci(), _format_attachment_file_size(), urls_to_attachment_list() (+227 more)
+Nodes (225): eq_unicode(), like_unicode(), MySQL 字符串比较/模糊匹配时统一排序规则,避免 Illegal mix of collations。, 跨表字符串等值比较(JOIN / WHERE)时统一为 utf8mb4_unicode_ci。, 将列显式设为 utf8mb4_unicode_ci。, unicode_ci(), _format_attachment_file_size(), urls_to_attachment_list() (+217 more)
 
 ### Community 6 - "Community 6"
-Cohesion: 0.03
-Nodes (161): batch_discard(), batch_options(), _batch_row_to_dict(), batch_stock_in(), batch_stock_out(), _create_batch_and_usage(), delete_batch(), _discard_batch() (+153 more)
+Cohesion: 0.08
+Nodes (227): AuditCreateBody, BatchDetailIdsBody, BatchRefundFinanceSubmitBody, FinanceConfirmBody, FullmoonTodoInsuranceBody, InvoiceRequestCompleteBody, InvoiceRequestCreateBody, ProtocolCreateBody (+219 more)
 
 ### Community 7 - "Community 7"
 Cohesion: 0.02
-Nodes (103): get_designer_data(), _error_json(), 数据库异常:返回 JSON 并保留 CORS 头,避免浏览器只报跨域错误, 未捕获异常:统一 JSON 500,确保跨域场景下仍有 Access-Control-Allow-Origin, sqlalchemy_exception_handler(), unhandled_exception_handler(), BaseHTTPMiddleware, ApiCryptoError (+95 more)
+Nodes (180): _avatar_url_field(), _build_current_user_payload(), _build_user_info_payload(), get_current_user_profile(), _jwt_user_payload(), logout(), 公共接口(Web 登录、刷新 Token、登出;用户模型对齐 docs/sql/zkxw.sql 基线), Web 端登录用 RSA 公钥(PKCS#1 v1.5,与 JSEncrypt 一致) (+172 more)
 
 ### Community 8 - "Community 8"
 Cohesion: 0.02
-Nodes (151): 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() (+143 more)
+Nodes (196): batch_discard(), batch_options(), _batch_row_to_dict(), batch_stock_in(), batch_stock_out(), _create_batch_and_usage(), delete_batch(), _discard_batch() (+188 more)
 
 ### Community 9 - "Community 9"
-Cohesion: 0.03
-Nodes (112): _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 基线) (+104 more)
+Cohesion: 0.02
+Nodes (104): get_designer_data(), _error_json(), 数据库异常:返回 JSON 并保留 CORS 头,避免浏览器只报跨域错误, 未捕获异常:统一 JSON 500,确保跨域场景下仍有 Access-Control-Allow-Origin, sqlalchemy_exception_handler(), unhandled_exception_handler(), BaseHTTPMiddleware, ApiCryptoError (+96 more)
 
 ### Community 10 - "Community 10"
-Cohesion: 0.05
-Nodes (93): AccessLogCreateBody, AccessLogUpdateBody, CleanRoomCreateBody, CleanRoomUpdateBody, ExhibitionAppointmentCreateBody, ExhibitionAppointmentUpdateBody, OccupancyCreateBody, 运营支撑 API(洁净室、进出记录、仓储设施、储位、展厅预约) (+85 more)
+Cohesion: 0.03
+Nodes (116): 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() (+108 more)
 
 ### Community 11 - "Community 11"
-Cohesion: 0.36
-Nodes (67): BatchStockInBody, BatchStockInItem, BatchStockOutBody, BatchStockOutItem, ConsumableCreateBody, ConsumablePackCreateBody, ConsumablePackUpdateBody, ConsumableUpdateBody (+59 more)
+Cohesion: 0.05
+Nodes (117): AccessLogCreateBody, AccessLogUpdateBody, batch_create_storage_slots(), CleanRoomCreateBody, CleanRoomUpdateBody, create_access_log(), create_storage_slot(), create_uv_disinfect_log() (+109 more)
 
 ### Community 12 - "Community 12"
-Cohesion: 0.07
-Nodes (66): batch_delete_research_donations(), change_donation_status(), convert_from_storage(), convert_to_storage(), create_research_donation(), delete_research_donation(), _get_current_user_id(), get_research_donation_detail() (+58 more)
+Cohesion: 0.36
+Nodes (67): BatchStockInBody, BatchStockInItem, BatchStockOutBody, BatchStockOutItem, ConsumableCreateBody, ConsumablePackCreateBody, ConsumablePackUpdateBody, ConsumableUpdateBody (+59 more)
 
 ### Community 13 - "Community 13"
-Cohesion: 0.06
-Nodes (67): 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() (+59 more)
+Cohesion: 0.08
+Nodes (57): api_save_application(), auto_generate_schedules(), complete_application(), create_application(), create_basic_experiment_sale_schedules(), create_infusion_plan(), create_schedule(), _create_schedule_products() (+49 more)
 
 ### Community 14 - "Community 14"
-Cohesion: 0.11
-Nodes (39): _amount_for_row(), _batch_map(), deduct_for_task(), _deduct_task_usage_legacy_batch(), _display_remark(), _fmt_date_val(), _linked_qc_task_no_map(), list_linked_task_usages() (+31 more)
+Cohesion: 0.08
+Nodes (55): batch_delete_research_donations(), change_donation_status(), convert_from_storage(), convert_to_storage(), create_research_donation(), delete_research_donation(), _get_current_user_id(), get_research_donation_detail() (+47 more)
 
 ### 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)
-
-### Community 16 - "Community 16"
 Cohesion: 0.12
 Nodes (11): get_wechat_pay_client(), get_wechat_pay_v2(), 微信支付工具模块  支持微信支付 V3 API 文档参考: https://pay.weixin.qq.com/docs/v3/apis/index.ht, 统一下单接口 (V3)          Args:             out_trade_no: 商户订单号             total, 申请退款          Args:             out_trade_no: 商户订单号             total_amount, 小程序调起支付的二次签名参数(与公众号 JSAPI 规则一致), 统一下单 (V2 API)          Args:             out_trade_no: 商户订单号             tot, 生成签名 V3 (SHA256 with RSA) (+3 more)
 
+### Community 16 - "Community 16"
+Cohesion: 0.2
+Nodes (19): _code_exists(), create_material(), _dec(), _fmt_dt(), get_material(), get_material_entity(), get_my_usages(), _is_enabled_status() (+11 more)
+
 ### Community 17 - "Community 17"
-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)
+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 18 - "Community 18"
-Cohesion: 0.18
-Nodes (18): company_code_exists(), create_company(), get_company(), list_companies(), list_company_options(), _row_to_dict(), soft_delete_company(), update_company() (+10 more)
+Cohesion: 0.31
+Nodes (11): create_equipment(), _dec(), equipment_code_exists(), equipment_is_referenced(), get_equipment(), list_equipments(), _norm_status(), _parse_date() (+3 more)
 
 ### Community 19 - "Community 19"
-Cohesion: 0.21
-Nodes (13): create_collection_region(), create_hospital(), get_collection_region(), get_hospital(), _hospital_names_by_region_ids(), _hospital_to_dict(), list_collection_regions(), list_hospitals() (+5 more)
+Cohesion: 0.27
+Nodes (11): create_consumable_pack(), get_consumable_pack(), get_pack_detail(), _item_row_to_dict(), list_all_packs(), list_consumable_packs(), pack_code_exists(), 耗材包 CRUD(biz_consumable_pack + biz_consumable_pack_item) (+3 more)
 
 ### Community 20 - "Community 20"
-Cohesion: 0.31
-Nodes (11): create_equipment(), _dec(), equipment_code_exists(), equipment_is_referenced(), get_equipment(), list_equipments(), _norm_status(), _parse_date() (+3 more)
-
-### Community 21 - "Community 21"
 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 21 - "Community 21"
+Cohesion: 0.27
+Nodes (10): create_collection_region(), create_hospital(), get_collection_region(), get_hospital(), _hospital_to_dict(), list_hospitals(), soft_delete_collection_region(), soft_delete_hospital() (+2 more)
+
 ### Community 22 - "Community 22"
 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)
@@ -230,107 +231,111 @@ Nodes (7): create_post(), get_post(), list_posts(), post_code_exists(), _row_to_
 
 ### 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()
+Nodes (8): company_code_exists(), create_company(), get_company(), list_companies(), list_company_options(), _row_to_dict(), soft_delete_company(), update_company()
 
 ### Community 36 - "Community 36"
+Cohesion: 0.36
+Nodes (7): create_tenant(), get_tenant(), list_tenants(), _row_to_dict(), soft_delete_tenant(), tenant_no_exists(), update_tenant()
+
+### Community 37 - "Community 37"
 Cohesion: 0.33
 Nodes (7): _cascade_soft_delete(), create_menu(), get_menu(), list_menus(), _row_to_dict(), soft_delete_menu(), update_menu()
 
-### Community 37 - "Community 37"
+### Community 38 - "Community 38"
 Cohesion: 0.36
 Nodes (7): create_site(), get_site(), list_sites(), _row_to_dict(), site_code_exists(), soft_delete_site(), update_site()
 
-### Community 38 - "Community 38"
+### Community 39 - "Community 39"
 Cohesion: 0.25
 Nodes (7): 菜单 code 权限守卫(V0.3:通过 sys_user_role → sys_role_menu → sys_menu.perms), 运营端角色守卫:admin / super_admin 可访问。, 接口菜单权限守卫。super_admin 直接放行。, 接口菜单权限守卫:拥有任一 menuCode 即可访问。super_admin 直接放行。, require_admin_or_super_admin(), require_any_menu_code(), require_menu_code()
 
-### Community 39 - "Community 39"
+### Community 40 - "Community 40"
 Cohesion: 0.29
 Nodes (2): BaseModel, SoftDeleteModel
 
-### Community 40 - "Community 40"
+### Community 41 - "Community 41"
 Cohesion: 0.38
 Nodes (6): derive_legacy_status_from_three(), derive_three_status_from_legacy(), 阶段A兼容:从旧 status 推导三字段。     约束:旧 status 本身混合了支付/配送/订单生命周期,因此只能尽力推断;     若业务侧已有更精确, 阶段A兼容:从三字段反推旧 status(用于旧前端/旧筛选参数兼容)。, ThreeStatus, _to_decimal()
 
-### Community 41 - "Community 41"
+### Community 42 - "Community 42"
 Cohesion: 0.5
 Nodes (3): BaseSettings, Config, Settings
 
-### Community 42 - "Community 42"
+### Community 43 - "Community 43"
 Cohesion: 0.5
 Nodes (2): _auto_fill_create_dept_id(), ORM before_insert 钩子:当 create_dept_id 为空时,自动从请求上下文填充当前用户部门 ID
 
-### Community 43 - "Community 43"
+### Community 44 - "Community 44"
 Cohesion: 0.67
 Nodes (2): get_menu_list(), 运营端菜单接口(与 web-admin 前端静态路由 sqxp.ts 对齐,5 个一级菜单分组)
 
-### Community 44 - "Community 44"
+### Community 45 - "Community 45"
 Cohesion: 1.0
 Nodes (1): 请求级别的用户上下文变量  通过 contextvars 在请求处理链路中传递当前用户信息, 供 SQLAlchemy 事件监听器等底层机制使用,避免逐层手动传
 
-### Community 45 - "Community 45"
+### Community 46 - "Community 46"
 Cohesion: 1.0
 Nodes (1): API 路由包(骨架:公共鉴权 + 静态菜单 + 运营端占位)
 
-### Community 48 - "Community 48"
+### Community 49 - "Community 49"
 Cohesion: 1.0
 Nodes (1): 发送订单状态通知                  Args:             openid: 用户openid             ord
 
-### Community 49 - "Community 49"
+### Community 50 - "Community 50"
 Cohesion: 1.0
 Nodes (1): 发送支付成功通知                  Args:             openid: 用户openid             ord
 
-### Community 50 - "Community 50"
+### Community 51 - "Community 51"
 Cohesion: 1.0
 Nodes (1): 发送配送通知                  Args:             openid: 配送员openid             task
 
-### Community 51 - "Community 51"
+### Community 52 - "Community 52"
 Cohesion: 1.0
 Nodes (1): 发送配送时效预警                  Args:             openid: 配送员openid             ta
 
-### Community 52 - "Community 52"
+### Community 53 - "Community 53"
 Cohesion: 1.0
 Nodes (1): 发送退款结果通知                  Args:             openid: 用户openid             ord
 
-### Community 53 - "Community 53"
+### Community 54 - "Community 54"
 Cohesion: 1.0
 Nodes (1): 发送催单通知给配送员                  Args:             deliver_openid: 配送员openid
 
 ## Knowledge Gaps
 - **127 isolated node(s):** `刷新会话 TTL(refreshToken 续期;已登录用户每次 API 校验通过后滑动续期)`, `写入 refreshToken(用于 one-time 使用/吊销)`, `根据 token 获取当前用户(包含 Redis 会话校验;会话滑动续期)`, `登录凭据 RSA 解密(与前端 JSEncrypt PKCS#1 v1.5 兼容)。`, `Base64 密文 -> JSON 对象(须含 username、password、ts 毫秒时间戳)。` (+122 more)
   These have ≤1 connection - possible missing edges or undocumented components.
-- **Thin community `Community 39`** (8 nodes): `BaseModel`, `.to_dict()`, `get_active()`, `base.py`, `SoftDeleteModel`, `.restore()`, `.soft_delete()`, `__tablename__()`
+- **Thin community `Community 40`** (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 42`** (4 nodes): `_auto_fill_create_dept_id()`, `get_db()`, `database.py`, `ORM before_insert 钩子:当 create_dept_id 为空时,自动从请求上下文填充当前用户部门 ID`
+- **Thin community `Community 43`** (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 43`** (3 nodes): `get_menu_list()`, `menu.py`, `运营端菜单接口(与 web-admin 前端静态路由 sqxp.ts 对齐,5 个一级菜单分组)`
+- **Thin community `Community 44`** (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 44`** (2 nodes): `context.py`, `请求级别的用户上下文变量  通过 contextvars 在请求处理链路中传递当前用户信息, 供 SQLAlchemy 事件监听器等底层机制使用,避免逐层手动传`
+- **Thin community `Community 45`** (2 nodes): `context.py`, `请求级别的用户上下文变量  通过 contextvars 在请求处理链路中传递当前用户信息, 供 SQLAlchemy 事件监听器等底层机制使用,避免逐层手动传`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 45`** (2 nodes): `__init__.py`, `API 路由包(骨架:公共鉴权 + 静态菜单 + 运营端占位)`
+- **Thin community `Community 46`** (2 nodes): `__init__.py`, `API 路由包(骨架:公共鉴权 + 静态菜单 + 运营端占位)`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 48`** (1 nodes): `发送订单状态通知                  Args:             openid: 用户openid             ord`
+- **Thin community `Community 49`** (1 nodes): `发送订单状态通知                  Args:             openid: 用户openid             ord`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 49`** (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 50`** (1 nodes): `发送配送通知                  Args:             openid: 配送员openid             task`
+- **Thin community `Community 51`** (1 nodes): `发送配送通知                  Args:             openid: 配送员openid             task`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 51`** (1 nodes): `发送配送时效预警                  Args:             openid: 配送员openid             ta`
+- **Thin community `Community 52`** (1 nodes): `发送配送时效预警                  Args:             openid: 配送员openid             ta`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 52`** (1 nodes): `发送退款结果通知                  Args:             openid: 用户openid             ord`
+- **Thin community `Community 53`** (1 nodes): `发送退款结果通知                  Args:             openid: 用户openid             ord`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 53`** (1 nodes): `发送催单通知给配送员                  Args:             deliver_openid: 配送员openid`
+- **Thin community `Community 54`** (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 4`, `Community 5`, `Community 6`, `Community 8`, `Community 9`, `Community 10`, `Community 11`, `Community 12`, `Community 14`, `Community 17`, `Community 21`, `Community 23`?**
-  _High betweenness centrality (0.106) - this node is a cross-community bridge._
-- **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 10`, `Community 12`, `Community 13`, `Community 14`, `Community 15`, `Community 17`, `Community 18`, `Community 19`, `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`, `Community 33`, `Community 34`, `Community 35`, `Community 36`, `Community 37`?**
-  _High betweenness centrality (0.079) - 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 43`, `Community 12`, `Community 13`?**
-  _High betweenness centrality (0.058) - this node is a cross-community bridge._
+- **Why does `SysUser` connect `Community 1` to `Community 0`, `Community 2`, `Community 3`, `Community 5`, `Community 6`, `Community 7`, `Community 8`, `Community 9`, `Community 11`, `Community 12`, `Community 14`, `Community 20`, `Community 23`?**
+  _High betweenness centrality (0.105) - this node is a cross-community bridge._
+- **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 10`, `Community 11`, `Community 13`, `Community 14`, `Community 16`, `Community 18`, `Community 19`, `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`, `Community 33`, `Community 34`, `Community 35`, `Community 36`, `Community 37`, `Community 38`?**
+  _High betweenness centrality (0.082) - this node is a cross-community bridge._
+- **Why does `success_response()` connect `Community 0` to `Community 1`, `Community 3`, `Community 4`, `Community 7`, `Community 9`, `Community 10`, `Community 11`, `Community 44`, `Community 13`, `Community 14`?**
+  _High betweenness centrality (0.065) - this node is a cross-community bridge._
 - **Are the 693 inferred relationships involving `str` (e.g. with `unhandled_exception_handler()` and `get_current_user_by_token()`) actually correct?**
   _`str` has 693 INFERRED edges - model-reasoned connections that need verification._
 - **Are the 642 inferred relationships involving `success_response()` (e.g. with `list_storage_release_reviews()` and `get_storage_release_review_detail()`) actually correct?**

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 1
backend/graphify-out/graph.html


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 112 - 112
backend/graphify-out/graph.json


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

@@ -85,7 +85,7 @@
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/sample_reception_check_result.py": 1779255909.8554008,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/production_material_stock_batch.py": 1781513705.4688208,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/user.py": 1780539127.1805215,
-  "/Users/wuzilong/work/00code/zkxw/backend/app/crud/production_work_order.py": 1781583479.8739135,
+  "/Users/wuzilong/work/00code/zkxw/backend/app/crud/production_work_order.py": 1781696877.7453213,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/material.py": 1781522283.6438622,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/dict_type.py": 1779265621.5212462,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/storage_occupancy.py": 1781335540.360182,
@@ -136,7 +136,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": 1781607961.140553,
+  "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py": 1781698777.252274,
   "/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": 1781145773.959546,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/outbound_release_material.py": 1781612877.0137436,

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 454 - 540
web-admin/graphify-out/.graphify_analysis.json


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 90 - 397
web-admin/graphify-out/.graphify_extract.json


+ 106 - 101
web-admin/graphify-out/GRAPH_REPORT.md

@@ -1,11 +1,11 @@
 # Graph Report - src  (2026-06-17)
 
 ## Corpus Check
-- Large corpus: 784 files · ~519,508 words. Semantic extraction will be expensive (many Claude tokens). Consider running on a subfolder, or use --no-semantic to run AST-only.
+- Large corpus: 784 files · ~520,683 words. Semantic extraction will be expensive (many Claude tokens). Consider running on a subfolder, or use --no-semantic to run AST-only.
 
 ## Summary
-- 2617 nodes · 3644 edges · 53 communities detected
-- Extraction: 78% EXTRACTED · 22% INFERRED · 0% AMBIGUOUS · INFERRED: 799 edges (avg confidence: 0.8)
+- 2595 nodes · 3611 edges · 54 communities detected
+- Extraction: 78% EXTRACTED · 22% INFERRED · 0% AMBIGUOUS · INFERRED: 795 edges (avg confidence: 0.8)
 - Token cost: 0 input · 0 output
 
 ## Community Hubs (Navigation)
@@ -44,29 +44,30 @@
 - [[_COMMUNITY_Community 32|Community 32]]
 - [[_COMMUNITY_Community 33|Community 33]]
 - [[_COMMUNITY_Community 34|Community 34]]
-- [[_COMMUNITY_Community 36|Community 36]]
+- [[_COMMUNITY_Community 35|Community 35]]
 - [[_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 43|Community 43]]
+- [[_COMMUNITY_Community 42|Community 42]]
 - [[_COMMUNITY_Community 45|Community 45]]
 - [[_COMMUNITY_Community 46|Community 46]]
-- [[_COMMUNITY_Community 49|Community 49]]
+- [[_COMMUNITY_Community 47|Community 47]]
 - [[_COMMUNITY_Community 50|Community 50]]
-- [[_COMMUNITY_Community 52|Community 52]]
+- [[_COMMUNITY_Community 51|Community 51]]
 - [[_COMMUNITY_Community 53|Community 53]]
 - [[_COMMUNITY_Community 54|Community 54]]
 - [[_COMMUNITY_Community 56|Community 56]]
 - [[_COMMUNITY_Community 58|Community 58]]
-- [[_COMMUNITY_Community 62|Community 62]]
+- [[_COMMUNITY_Community 59|Community 59]]
 - [[_COMMUNITY_Community 64|Community 64]]
+- [[_COMMUNITY_Community 66|Community 66]]
 
 ## God Nodes (most connected - your core abstractions)
 1. `get()` - 265 edges
 2. `push()` - 64 edges
-3. `emit()` - 28 edges
+3. `emit()` - 27 edges
 4. `WebSocketClient` - 26 edges
 5. `MenuProcessor` - 16 edges
 6. `withFullscreenLoading()` - 15 edges
@@ -91,192 +92,192 @@
 
 ### Community 0 - "Community 0"
 Cohesion: 0.01
-Nodes (144): fetchAccessLogList(), fetchActiveRegions(), fetchCategoryList(), fetchCategoryParameters(), fetchCellProductPriceList(), fetchChannelAccount(), fetchChannelAgreementPrices(), fetchChannelAgreements() (+136 more)
+Nodes (142): fetchAccessLogList(), fetchActiveRegions(), fetchCategoryList(), fetchCategoryParameters(), fetchCellProductPriceList(), fetchChannelAccount(), fetchChannelAgreementPrices(), fetchChannelAgreements() (+134 more)
 
 ### Community 1 - "Community 1"
-Cohesion: 0.03
-Nodes (52): fetchGetMenuList(), fetchGetMenuCodes(), fetchGetUserInfo(), fetchLogin(), fetchUpdateProfile(), fetchUploadProfileAvatar(), ComponentLoader, IframeRouteManager (+44 more)
-
-### Community 2 - "Community 2"
 Cohesion: 0.02
 Nodes (69): fetchFullmoonTodoList(), fetchUpdateFullmoonTodoInsurance(), fetchAttachmentDownloadUrl(), fetchCollectionProtocolAuditList(), fetchCollectionProtocolDetail(), fetchCollectionProtocolList(), fetchCreateProtocolInvoiceRequest(), fetchInvoiceEligiblePayments() (+61 more)
 
-### Community 3 - "Community 3"
+### Community 2 - "Community 2"
 Cohesion: 0.02
 Nodes (42): fetchAllConsumablePacks(), fetchAllConsumables(), fetchAllConsumableStockLogs(), fetchAllEquipments(), fetchAllOutputTypes(), fetchAllProcessCategories(), fetchAllProcesses(), fetchAllReagents() (+34 more)
 
-### Community 4 - "Community 4"
+### Community 3 - "Community 3"
 Cohesion: 0.03
-Nodes (60): fetchCreateCustomer(), fetchCustomerList(), fetchDeleteCustomer(), fetchUpdateCustomer(), updateInfusionScheduleStatus(), fetchProductionTaskList(), handleAdd(), handleBatchDelete() (+52 more)
+Nodes (35): fetchGetMenuList(), fetchGetMenuCodes(), fetchGetUserInfo(), ComponentLoader, IframeRouteManager, MenuProcessor, RouteRegistry, RouteTransformer (+27 more)
 
-### Community 5 - "Community 5"
+### Community 4 - "Community 4"
 Cohesion: 0.04
-Nodes (78): batchSavePilotCheckup(), deletePilotCheckupActualItem(), fetchCheckupInstitutionList(), fetchCheckupInstitutionPackageDetail(), fetchCheckupInstitutionPackages(), fetchPilotCheckupActualItems(), fetchPilotCheckupDetail(), fetchPilotCheckupReserveItems() (+70 more)
+Nodes (74): batchSavePilotCheckup(), deletePilotCheckupActualItem(), fetchCheckupInstitutionPackageDetail(), fetchCheckupInstitutionPackages(), fetchPilotCheckupDetail(), markPilotCheckupChecked(), savePilotCheckup(), savePilotCheckupActualItems() (+66 more)
+
+### Community 5 - "Community 5"
+Cohesion: 0.03
+Nodes (51): fetchCheckupInstitutionInvoices(), fetchCheckupInstitutionList(), fetchCheckupInstitutionProjects(), fetchCreateCheckupInstitution(), fetchCreateCheckupInstitutionInvoice(), fetchCreateCheckupInstitutionProject(), fetchUpdateCheckupInstitution(), fetchUpdateCheckupInstitutionInvoice() (+43 more)
 
 ### Community 6 - "Community 6"
-Cohesion: 0.04
-Nodes (66): fetchUploadAttachment(), reload(), handleReportUpload(), formatNowDatetime(), handleReportUpload(), walk(), formatTodayDate(), handleMenuJump() (+58 more)
+Cohesion: 0.05
+Nodes (60): fetchUploadAttachment(), reload(), handleReportUpload(), handleReportUpload(), walk(), handleMenuJump(), openExternalLink(), async() (+52 more)
 
 ### Community 7 - "Community 7"
-Cohesion: 0.03
-Nodes (50): fetchCheckupInstitutionInvoices(), fetchCheckupInstitutionProjects(), fetchCreateCheckupInstitution(), fetchCreateCheckupInstitutionInvoice(), fetchCreateCheckupInstitutionProject(), fetchUpdateCheckupInstitution(), fetchUpdateCheckupInstitutionInvoice(), fetchUpdateCheckupInstitutionProject() (+42 more)
+Cohesion: 0.04
+Nodes (42): fetchMyProductionTasks(), fetchMyQcTaskList(), fetchQcTaskDetail(), fetchQcTaskList(), for(), set(), annotateForkEdges(), chainApiToElements() (+34 more)
 
 ### Community 8 - "Community 8"
 Cohesion: 0.04
-Nodes (43): fetchMyProductionTasks(), fetchMyQcTaskList(), fetchQcTaskDetail(), fetchQcTaskList(), for(), set(), annotateForkEdges(), chainApiToElements() (+35 more)
+Nodes (38): fetchProductionTaskList(), fetchApproveTask(), fetchBusinessAllTasks(), fetchCompletedTasks(), fetchDesignerData(), fetchInstanceDetail(), fetchMyInstances(), fetchPendingTasks() (+30 more)
 
 ### Community 9 - "Community 9"
-Cohesion: 0.04
-Nodes (41): fetchCreateMenu(), fetchDeleteMenu(), fetchMenuTree(), fetchUpdateMenu(), buildInboundBasicSection(), buildInboundCellStorageViewSections(), buildInboundResearchDonationViewSections(), buildReceptionSection() (+33 more)
+Cohesion: 0.05
+Nodes (31): fetchDeleteQcMaterialBatch(), fetchQcMaterialBatchList(), fetchQcMaterialBatchOptions(), fetchQcMaterialList(), fetchQcMaterialOptions(), fetchUpdateQcMaterialBatch(), fetchMarkQcMaterialUsageFullyUsed(), fetchMyQcMaterialUsageList() (+23 more)
 
 ### Community 10 - "Community 10"
 Cohesion: 0.05
 Nodes (34): fetchClinicalPaymentDetail(), fetchClinicalPayments(), fetchClinicalReceiptConfirmed(), fetchClinicalReceiptPending(), fetchClinicalRefundConfirmed(), fetchClinicalRefundPending(), fetchOrderAdjusts(), fetchOrderDetail() (+26 more)
 
 ### Community 11 - "Community 11"
-Cohesion: 0.06
-Nodes (38): buildBaseFields(), buildFeeSection(), buildInboundBasicSection(), buildInboundCellStorageProductionViewSections(), buildInboundResearchDonationProductionViewSections(), buildOutboundBasicExperimentProductionViewSections(), buildOutboundClinicalProductionViewSections(), buildOutboundDetail() (+30 more)
+Cohesion: 0.05
+Nodes (34): fetchCreateMenu(), fetchDeleteMenu(), fetchMenuTree(), fetchUpdateMenu(), buildInboundBasicSection(), buildInboundCellStorageViewSections(), buildInboundResearchDonationViewSections(), buildReceptionSection() (+26 more)
 
 ### Community 12 - "Community 12"
 Cohesion: 0.06
 Nodes (11): fetchGetLatestUpgradeLog(), getDefaultLanguage(), checkStorageCompatibility(), StorageConfig, getSystemStorage(), getSysVersion(), StorageKeyManager, StorageCompatibilityManager (+3 more)
 
 ### Community 13 - "Community 13"
-Cohesion: 0.05
-Nodes (26): fetchDeleteQcMaterialBatch(), fetchQcMaterialBatchList(), fetchQcMaterialBatchOptions(), fetchQcMaterialList(), fetchQcMaterialOptions(), fetchUpdateQcMaterialBatch(), fetchMarkQcMaterialUsageFullyUsed(), financeAttachmentsToUrlString() (+18 more)
+Cohesion: 0.04
+Nodes (28): fetchEthnicList(), formatNowDatetime(), formatHandoverUserLabel(), formatNowDatetime(), fullFileUrl(), handleDownloadAttachment(), handlePreviewAttachment(), isVideoFile() (+20 more)
 
 ### Community 14 - "Community 14"
 Cohesion: 0.05
 Nodes (26): collectIds(), useSettingsHandlers(), useSettingsPanel(), useSettingsState(), RoutePermissionValidator, useCeremony(), useTheme(), walk() (+18 more)
 
 ### Community 15 - "Community 15"
-Cohesion: 0.05
-Nodes (28): handleBlur(), handleFocus(), handleInput(), fetchEthnicList(), formatAmount(), formatAmount(), formatProtocolAmount(), applyDonationBundleToForm() (+20 more)
-
-### Community 16 - "Community 16"
 Cohesion: 0.06
 Nodes (13): fmtMoney(), auditorDisplay(), dash(), ensureUnitsInOptions(), expressName(), formatYm(), formatYmd(), sampleTypeName() (+5 more)
 
+### Community 16 - "Community 16"
+Cohesion: 0.1
+Nodes (2): WebSocketClient, TableCache
+
 ### Community 17 - "Community 17"
-Cohesion: 0.06
-Nodes (34): fetchUserList(), fetchAddTaskStep(), fetchCopyTaskStep(), fetchDeleteTaskStep(), fetchForwardProductionTask(), fetchProductionTaskDetail(), fetchReassignProductionTask(), fetchReorderTaskSteps() (+26 more)
+Cohesion: 0.08
+Nodes (32): toLogin(), base64ToBytes(), bytesToBase64(), decryptJson(), deriveKey(), encryptJson(), fromBase64Url(), hexToBytes() (+24 more)
 
 ### Community 18 - "Community 18"
-Cohesion: 0.1
-Nodes (2): WebSocketClient, TableCache
+Cohesion: 0.08
+Nodes (29): fetchCreateCustomer(), fetchCustomerList(), fetchDeleteCustomer(), fetchUpdateCustomer(), updateInfusionScheduleStatus(), handleAdd(), handleBatchDelete(), handleDelete() (+21 more)
 
 ### Community 19 - "Community 19"
 Cohesion: 0.08
-Nodes (33): fetchRefreshToken(), toLogin(), base64ToBytes(), bytesToBase64(), decryptJson(), deriveKey(), encryptJson(), fromBase64Url() (+25 more)
+Nodes (27): fetchUserOptions(), fetchSampleReceptionCheckItemList(), fetchSampleReceptionCheckItemsActive(), fetchAllocateLabStorageCodes(), fetchSampleReceptionBySampleCode(), fetchSampleReceptionDetail(), fetchSampleReceptionPendingList(), fetchSampleReceptionReceivedList() (+19 more)
 
 ### Community 20 - "Community 20"
 Cohesion: 0.08
-Nodes (27): fetchUserOptions(), fetchSampleReceptionCheckItemList(), fetchSampleReceptionCheckItemsActive(), fetchAllocateLabStorageCodes(), fetchSampleReceptionBySampleCode(), fetchSampleReceptionDetail(), fetchSampleReceptionPendingList(), fetchSampleReceptionReceivedList() (+19 more)
+Nodes (20): maskIdCard(), buildBasicExperimentApplicationFields(), buildDefaultApplicationFields(), buildOutboundPilotContextSections(), applicationStatusLabel(), applicationStatusTagType(), cellProductStatusLabel(), cellProductStatusTagType() (+12 more)
 
 ### Community 21 - "Community 21"
 Cohesion: 0.09
-Nodes (28): completePilotApplication(), deletePilotApplication(), fetchPilotApplicationList(), startPilotApplication(), voidPilotApplication(), clearTableSelection(), handleBatchComplete(), handleBatchStart() (+20 more)
+Nodes (31): handleBlur(), handleFocus(), handleInput(), formatAmount(), formatAmount(), formatProtocolAmount(), buildBaseFields(), buildFeeSection() (+23 more)
 
 ### Community 22 - "Community 22"
 Cohesion: 0.08
-Nodes (16): fetchDeleteMaterialBatch(), fetchMaterialBatchList(), fetchMaterialBatchOptions(), fetchMaterialList(), fetchMaterialOptions(), fetchUpdateMaterialBatch(), fetchMaterialUsageList(), fetchMyMaterialUsageList() (+8 more)
+Nodes (27): fetchCellSampleTypeList(), fetchCreateCellSampleType(), fetchDeleteCellSampleType(), fetchUpdateCellSampleType(), handleAdd(), handleDelete(), handleDropdownCommand(), handleEdit() (+19 more)
 
 ### Community 23 - "Community 23"
-Cohesion: 0.12
-Nodes (24): fetchCellProductSpecList(), fetchCellProductTypeList(), applyProductCodeToLines(), calcExpiryFromFreeze(), createEmptyProductLine(), createQuantityValue(), getLineStorageSlotOptions(), getOtherLineStorageSlotIds() (+16 more)
+Cohesion: 0.09
+Nodes (28): completePilotApplication(), deletePilotApplication(), fetchPilotApplicationList(), startPilotApplication(), voidPilotApplication(), clearTableSelection(), handleBatchComplete(), handleBatchStart() (+20 more)
 
 ### Community 24 - "Community 24"
-Cohesion: 0.13
-Nodes (20): fetchCreateUser(), fetchDeleteUser(), fetchResetPassword(), fetchUpdateUser(), fetchUserDetail(), formatUserDeptDisplay(), handleAdd(), handleBatchDelete() (+12 more)
+Cohesion: 0.1
+Nodes (27): fetchUserList(), fetchAddTaskStep(), fetchCopyTaskStep(), fetchDeleteTaskStep(), fetchForwardProductionTask(), fetchProductionTaskDetail(), fetchReassignProductionTask(), fetchReorderTaskSteps() (+19 more)
 
 ### Community 25 - "Community 25"
-Cohesion: 0.12
-Nodes (15): addCopyButton(), highlightCode(), insertLineNumbers(), isBlockProcessed(), markBlockAsProcessed(), processBlock(), setupHighlightDirective(), setupRippleDirective() (+7 more)
+Cohesion: 0.08
+Nodes (16): fetchDeleteMaterialBatch(), fetchMaterialBatchList(), fetchMaterialBatchOptions(), fetchMaterialList(), fetchMaterialOptions(), fetchUpdateMaterialBatch(), fetchMaterialUsageList(), fetchMyMaterialUsageList() (+8 more)
 
 ### Community 26 - "Community 26"
-Cohesion: 0.11
-Nodes (11): fetchOutboundReleaseMaterial(), fetchOutboundReleaseReviewDetail(), fetchOutboundReleaseReviewList(), fetchReleaseReviewList(), handleReset(), handleSearch(), handleToggleSelect(), loadData() (+3 more)
+Cohesion: 0.12
+Nodes (25): fetchCellProductSpecList(), fetchCellProductTypeList(), fetchMaxPassageProductSeq(), applyProductCodeToLines(), calcExpiryFromFreeze(), createEmptyProductLine(), createQuantityValue(), getLineStorageSlotOptions() (+17 more)
 
 ### Community 27 - "Community 27"
 Cohesion: 0.13
-Nodes (18): fetchCellSampleTypeList(), fetchCreateCellSampleType(), fetchDeleteCellSampleType(), fetchUpdateCellSampleType(), handleAdd(), handleDelete(), handleDropdownCommand(), handleEdit() (+10 more)
+Nodes (18): fetchLogin(), fetchRefreshToken(), fetchUpdateProfile(), fetchUploadProfileAvatar(), buildWebEncryptedLoginBody(), defaultAvatarPathByRole(), hasCustomUserAvatar(), normalizeAvatarStorageKey() (+10 more)
 
 ### Community 28 - "Community 28"
-Cohesion: 0.16
-Nodes (14): fetchCreateEnvDetectItem(), fetchDeleteEnvDetectItem(), fetchEnvDetectItemDetail(), fetchEnvDetectItemList(), fetchEnvDetectItemsActive(), fetchUpdateEnvDetectItem(), handleAdd(), handleBatchDelete() (+6 more)
+Cohesion: 0.13
+Nodes (20): fetchCreateUser(), fetchDeleteUser(), fetchResetPassword(), fetchUpdateUser(), fetchUserDetail(), formatUserDeptDisplay(), handleAdd(), handleBatchDelete() (+12 more)
 
 ### Community 29 - "Community 29"
 Cohesion: 0.12
-Nodes (12): fetchApproveTask(), fetchBusinessAllTasks(), fetchCompletedTasks(), fetchDesignerData(), fetchInstanceDetail(), fetchMyInstances(), fetchPendingTasks(), fetchProcessDefList() (+4 more)
+Nodes (15): addCopyButton(), highlightCode(), insertLineNumbers(), isBlockProcessed(), markBlockAsProcessed(), processBlock(), setupHighlightDirective(), setupRippleDirective() (+7 more)
 
 ### Community 30 - "Community 30"
+Cohesion: 0.11
+Nodes (11): fetchOutboundReleaseMaterial(), fetchOutboundReleaseReviewDetail(), fetchOutboundReleaseReviewList(), fetchReleaseReviewList(), handleReset(), handleSearch(), handleToggleSelect(), loadData() (+3 more)
+
+### Community 31 - "Community 31"
+Cohesion: 0.16
+Nodes (14): fetchCreateEnvDetectItem(), fetchDeleteEnvDetectItem(), fetchEnvDetectItemDetail(), fetchEnvDetectItemList(), fetchEnvDetectItemsActive(), fetchUpdateEnvDetectItem(), handleAdd(), handleBatchDelete() (+6 more)
+
+### Community 32 - "Community 32"
 Cohesion: 0.16
 Nodes (10): useTable(), useTableImpl(), getColumnChecks(), useTableColumns(), createErrorHandler(), createSmartDebounce(), defaultResponseAdapter(), extractPagination() (+2 more)
 
-### Community 31 - "Community 31"
+### Community 33 - "Community 33"
 Cohesion: 0.27
 Nodes (14): useChart(), useChartComponent(), useChartOps(), colourBlend(), getCssVar(), getDarkColor(), getLightColor(), handleElementThemeColor() (+6 more)
 
-### Community 32 - "Community 32"
+### Community 34 - "Community 34"
 Cohesion: 0.33
 Nodes (13): buildProtocolPaymentFlowSteps(), dash(), financeReceiptTime(), formatAmount(), hasFinanceReceipt(), hasFinanceRefund(), hasReceiptSubmit(), hasRefundSubmit() (+5 more)
 
-### Community 33 - "Community 33"
-Cohesion: 0.22
-Nodes (9): fullFileUrl(), handleDownloadAttachment(), handlePreviewAttachment(), isVideoFile(), normalizeDisplayValue(), resolveReviewCellType(), resolveReviewQuantity(), resolveReviewSpecText() (+1 more)
-
-### Community 34 - "Community 34"
+### Community 35 - "Community 35"
 Cohesion: 0.19
 Nodes (6): fetchServiceOrderTxnList(), handleReset(), handleSearch(), handleTabChange(), loadData(), switchTab()
 
-### Community 36 - "Community 36"
+### Community 37 - "Community 37"
 Cohesion: 0.18
 Nodes (3): normalizeStepSubTables(), parseEquipmentIds(), syncEquipmentNamesFromOptions()
 
-### Community 37 - "Community 37"
+### Community 38 - "Community 38"
+Cohesion: 0.17
+Nodes (3): fetchResearchDonationAuditList(), fetchResearchDonationDetail(), fetchResearchDonationList()
+
+### Community 39 - "Community 39"
 Cohesion: 0.24
 Nodes (6): buildInboundMetaFields(), getCardMetaConfig(), getCardMetaConfigForLegacyTab(), parseReceptionTime(), receptionOver24HoursLabel(), receptionOver24HoursTagType()
 
-### Community 38 - "Community 38"
+### Community 40 - "Community 40"
 Cohesion: 0.42
 Nodes (9): buildPaymentFlowSteps(), dash(), formatAmount(), hasFinanceReceipt(), hasFinanceRefund(), hasReceiptSubmit(), hasRefundSubmit(), receiptSubmitTime() (+1 more)
 
-### Community 39 - "Community 39"
-Cohesion: 0.2
-Nodes (3): fetchProductionWorkOrderCellProductOptions(), fetchProductionWorkOrderDetail(), fetchProductionWorkOrderList()
-
-### Community 40 - "Community 40"
+### Community 41 - "Community 41"
 Cohesion: 0.25
 Nodes (2): handleTouchEnd(), setTransition()
 
-### Community 41 - "Community 41"
+### Community 42 - "Community 42"
 Cohesion: 0.28
 Nodes (1): FireworkSystem
 
-### Community 43 - "Community 43"
+### Community 45 - "Community 45"
 Cohesion: 0.28
 Nodes (4): fetchMyProductionMaterialConsumptions(), handleReset(), handleSearch(), loadData()
 
-### Community 45 - "Community 45"
+### Community 46 - "Community 46"
 Cohesion: 0.39
 Nodes (6): isIgnorableScriptError(), normalizeErrorMessage(), registerPromiseErrorHandler(), registerResourceErrorHandler(), scriptErrorHandler(), setupErrorHandle()
 
-### Community 46 - "Community 46"
+### Community 47 - "Community 47"
 Cohesion: 0.36
 Nodes (1): TableHeightCalculator
 
-### Community 49 - "Community 49"
+### Community 50 - "Community 50"
 Cohesion: 0.33
 Nodes (2): logApiCryptoBootInfo(), maskSecret()
 
-### Community 50 - "Community 50"
+### Community 51 - "Community 51"
 Cohesion: 0.47
 Nodes (3): isPaymentDueWithinWarnWindow(), parsePaymentDueYmd(), startOfLocalDay()
 
-### Community 52 - "Community 52"
-Cohesion: 0.33
-Nodes (2): fetchQcLabTestItemList(), fetchQcLabTestItemsActive()
-
 ### Community 53 - "Community 53"
 Cohesion: 0.5
 Nodes (2): formatProtocolDocDeleteConfirm(), formatProtocolDocSubject()
@@ -291,28 +292,30 @@ Nodes (2): if(), logSidebarMenu()
 
 ### Community 58 - "Community 58"
 Cohesion: 0.5
-Nodes (1): fetchStorageEntrySmsList()
+Nodes (1): fetchSampleSmsNotificationList()
 
-### Community 62 - "Community 62"
+### Community 59 - "Community 59"
+Cohesion: 0.5
+Nodes (1): fetchQcSmsList()
+
+### Community 64 - "Community 64"
 Cohesion: 1.0
 Nodes (2): getSettingDefaults(), resetToDefaults()
 
-### Community 64 - "Community 64"
+### Community 66 - "Community 66"
 Cohesion: 1.0
 Nodes (2): copyTextToClipboard(), copyTextWithFallback()
 
 ## Knowledge Gaps
-- **Thin community `Community 18`** (42 nodes): `WebSocketClient`, `.calculateReconnectDelay()`, `.clearAllTimers()`, `.clearConnectionTimers()`, `.clearTimer()`, `.close()`, `.closeCurrentSocketForReconnect()`, `.connect()`, `.connectionStatusText()`, `.constructor()`, `.destroyInstance()`, `.flushMessageQueue()`, `.getInstance()`, `.getReadyStateText()`, `.handleClose()`, `.handleConnectionTimeout()`, `.handleError()`, `.handleMessage()`, `.handleOpen()`, `.init()`, `.isWebSocketConnected()`, `.reconnect()`, `.resetHeartbeat()`, `.send()`, `.startHeartbeat()`, `.startPing()`, `TableCache`, `.cleanupExpired()`, `.clear()`, `.clearByTags()`, `.clearCurrentSearch()`, `.clearPagination()`, `.constructor()`, `.evictLRU()`, `.generateKey()`, `.generateTags()`, `.get()`, `.getStats()`, `.log()`, `.set()`, `index.ts`, `tableCache.ts`
-  Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 40`** (9 nodes): `cleanupEventListeners()`, `handleTouchEnd()`, `handleTouchMove()`, `handleTouchStart()`, `handleWheelScroll()`, `if()`, `setTransition()`, `setupEventListeners()`, `index.vue`
+- **Thin community `Community 16`** (42 nodes): `WebSocketClient`, `.calculateReconnectDelay()`, `.clearAllTimers()`, `.clearConnectionTimers()`, `.clearTimer()`, `.close()`, `.closeCurrentSocketForReconnect()`, `.connect()`, `.connectionStatusText()`, `.constructor()`, `.destroyInstance()`, `.flushMessageQueue()`, `.getInstance()`, `.getReadyStateText()`, `.handleClose()`, `.handleConnectionTimeout()`, `.handleError()`, `.handleMessage()`, `.handleOpen()`, `.init()`, `.isWebSocketConnected()`, `.reconnect()`, `.resetHeartbeat()`, `.send()`, `.startHeartbeat()`, `.startPing()`, `TableCache`, `.cleanupExpired()`, `.clear()`, `.clearByTags()`, `.clearCurrentSearch()`, `.clearPagination()`, `.constructor()`, `.evictLRU()`, `.generateKey()`, `.generateTags()`, `.get()`, `.getStats()`, `.log()`, `.set()`, `index.ts`, `tableCache.ts`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 41`** (9 nodes): `FireworkSystem`, `.constructor()`, `.createFirework()`, `.createParticle()`, `.getAvailableParticle()`, `.initializePool()`, `.preloadAllImages()`, `.preloadImage()`, `index.vue`
+- **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 46`** (8 nodes): `TableHeightCalculator`, `.calculate()`, `.calculateOffset()`, `.calculatePaginationOffset()`, `.constructor()`, `.getHeaderHeight()`, `useTableHeight()`, `useTableHeight.ts`
+- **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 49`** (7 nodes): `App.vue`, `main.ts`, `refreshButtonAutoIcons()`, `refreshPanelTitleIcons()`, `logApiCryptoBootInfo()`, `maskSecret()`, `console.ts`
+- **Thin community `Community 47`** (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 52`** (6 nodes): `fetchCreateQcLabTestItem()`, `fetchDeleteQcLabTestItem()`, `fetchQcLabTestItemList()`, `fetchQcLabTestItemsActive()`, `fetchUpdateQcLabTestItem()`, `qc-lab-test-item.ts`
+- **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 53`** (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.
@@ -320,27 +323,29 @@ Nodes (2): copyTextToClipboard(), copyTextWithFallback()
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
 - **Thin community `Community 56`** (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 58`** (4 nodes): `fetchGenerateStorageEntrySms()`, `fetchStorageEntrySmsList()`, `fetchToggleStorageEntrySmsNotified()`, `storage-entry-sms-notification.ts`
+- **Thin community `Community 58`** (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 59`** (4 nodes): `fetchGenerateQcSms()`, `fetchQcSmsList()`, `fetchToggleQcSmsNotified()`, `qc-sms-notification.ts`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 62`** (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 64`** (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.
 
 ## Suggested Questions
 _Questions this graph is uniquely positioned to answer:_
 
-- **Why does `get()` connect `Community 0` to `Community 1`, `Community 2`, `Community 3`, `Community 4`, `Community 5`, `Community 6`, `Community 7`, `Community 8`, `Community 9`, `Community 10`, `Community 12`, `Community 13`, `Community 14`, `Community 15`, `Community 17`, `Community 20`, `Community 21`, `Community 22`, `Community 23`, `Community 24`, `Community 26`, `Community 27`, `Community 28`, `Community 29`, `Community 34`, `Community 39`, `Community 43`, `Community 52`, `Community 58`?**
-  _High betweenness centrality (0.388) - this node is a cross-community bridge._
-- **Why does `push()` connect `Community 6` to `Community 0`, `Community 2`, `Community 3`, `Community 4`, `Community 5`, `Community 7`, `Community 8`, `Community 9`, `Community 11`, `Community 12`, `Community 14`, `Community 15`, `Community 16`, `Community 17`, `Community 18`, `Community 19`, `Community 21`, `Community 23`, `Community 37`, `Community 41`?**
-  _High betweenness centrality (0.198) - this node is a cross-community bridge._
-- **Why does `fetchGetUserInfo()` connect `Community 1` to `Community 0`?**
-  _High betweenness centrality (0.037) - this node is a cross-community bridge._
+- **Why does `get()` connect `Community 0` to `Community 1`, `Community 2`, `Community 3`, `Community 4`, `Community 5`, `Community 6`, `Community 7`, `Community 8`, `Community 9`, `Community 10`, `Community 11`, `Community 12`, `Community 13`, `Community 14`, `Community 18`, `Community 19`, `Community 22`, `Community 23`, `Community 24`, `Community 25`, `Community 26`, `Community 28`, `Community 30`, `Community 31`, `Community 35`, `Community 38`, `Community 45`, `Community 58`, `Community 59`?**
+  _High betweenness centrality (0.387) - this node is a cross-community bridge._
+- **Why does `push()` connect `Community 6` to `Community 0`, `Community 1`, `Community 2`, `Community 4`, `Community 5`, `Community 7`, `Community 8`, `Community 11`, `Community 12`, `Community 13`, `Community 14`, `Community 15`, `Community 16`, `Community 17`, `Community 20`, `Community 21`, `Community 23`, `Community 26`, `Community 39`, `Community 42`?**
+  _High betweenness centrality (0.182) - this node is a cross-community bridge._
 - **Are the 264 inferred relationships involving `get()` (e.g. with `buildNodeCodePathFromFlat()` and `collectLeafFacilityNodesInTreeOrder()`) actually correct?**
   _`get()` has 264 INFERRED edges - model-reasoned connections that need verification._
 - **Are the 63 inferred relationships involving `push()` (e.g. with `collectLeafFacilityNodesInTreeOrder()` and `formatDetectMethods()`) actually correct?**
   _`push()` has 63 INFERRED edges - model-reasoned connections that need verification._
-- **Are the 27 inferred relationships involving `emit()` (e.g. with `handleSubmit()` and `handleBlur()`) actually correct?**
-  _`emit()` has 27 INFERRED edges - model-reasoned connections that need verification._
+- **Are the 26 inferred relationships involving `emit()` (e.g. with `handleSubmit()` and `handleBlur()`) actually correct?**
+  _`emit()` has 26 INFERRED edges - model-reasoned connections that need verification._
 - **Should `Community 0` be split into smaller, more focused modules?**
-  _Cohesion score 0.01 - nodes in this community are weakly interconnected._
+  _Cohesion score 0.01 - nodes in this community are weakly interconnected._
+- **Should `Community 1` be split into smaller, more focused modules?**
+  _Cohesion score 0.02 - nodes in this community are weakly interconnected._

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 1
web-admin/graphify-out/graph.html


+ 2 - 2
web-admin/graphify-out/graph.json

@@ -21382,7 +21382,7 @@
       "label": "remoteSearchCustomers()",
       "file_type": "code",
       "source_file": "views/pilot-service/pilot-application/ApplicationFormDialog.vue",
-      "source_location": "L2498",
+      "source_location": "L2625",
       "id": "pilot_application_applicationformdialog_remotesearchcustomers",
       "community": 15,
       "norm_label": "remotesearchcustomers()"
@@ -63354,7 +63354,7 @@
       "relation": "contains",
       "confidence": "EXTRACTED",
       "source_file": "views/pilot-service/pilot-application/ApplicationFormDialog.vue",
-      "source_location": "L2498",
+      "source_location": "L2625",
       "weight": 1.0,
       "_src": "views_pilot_service_pilot_application_applicationformdialog_vue",
       "_tgt": "pilot_application_applicationformdialog_remotesearchcustomers",

+ 10 - 10
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": 1781690399.279859,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/auto-imports.d.ts": 1781690383.8771734,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/components.d.ts": 1781698782.7971725,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/auto-imports.d.ts": 1781698274.702177,
   "/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": 1781272556.3652694,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/locales/index.ts": 1781272556.3657222,
@@ -48,13 +48,13 @@
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/fullscreen-loading.ts": 1781272556.3849921,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/qc-material-ui.ts": 1781525352.6976836,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/qc-work-order-tabs.ts": 1781600462.9964879,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/qc-work-order-card-meta.ts": 1781608059.199473,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/qc-work-order-card-meta.ts": 1781698792.9167962,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/router.ts": 1781272556.3875713,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/production-mine-task-groups.ts": 1781583493.924234,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/qc-mine-task-groups.ts": 1781608059.9990764,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/protocol-payment-labels.ts": 1781272556.3878465,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/status-tag-registry.ts": 1781604158.2285209,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/production-work-order-card-meta.ts": 1781607345.7468734,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/production-work-order-card-meta.ts": 1781696893.3885784,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/fetch-paged-list.ts": 1781272556.3903377,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/sort-order.ts": 1781272556.3913012,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/outbound-qc-pilot-view-sections.ts": 1781566346.7618802,
@@ -237,7 +237,7 @@
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/storage-release-review.ts": 1781536765.5084789,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/production-material.ts": 1781510846.4290047,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/workflow.ts": 1781272556.6579714,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/production-work-order.ts": 1781583227.52806,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/production-work-order.ts": 1781696883.6649792,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task-result.ts": 1781272556.6589992,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/protocol-sample-collection.ts": 1781272556.659353,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/material-usage.ts": 1781517319.838243,
@@ -266,7 +266,7 @@
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/material.ts": 1781346022.817811,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/sample-reception.ts": 1781496419.5874305,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/auth.ts": 1781272556.6719868,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts": 1781603236.5340948,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts": 1781698779.261544,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/auth/login/index.vue": 1781272556.6735966,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/refund-confirm/index.vue": 1781272556.6742516,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/my-material-usage/index.vue": 1781518736.690082,
@@ -365,7 +365,7 @@
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskPaperAttachmentsPanel.vue": 1781530960.805927,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/WorkOrderTaskSubList.vue": 1781585388.7005522,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskOutputProductDialog.vue": 1781605192.0421948,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/ProductionWorkOrderCardList.vue": 1781601931.1752002,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/ProductionWorkOrderCardList.vue": 1781698245.2960405,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/ForwardTaskDialog.vue": 1781563767.3550124,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/tabs/OutboundPilotTab.vue": 1781583381.1088736,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/tabs/ProductionWorkOrderTabPanel.vue": 1781583376.390064,
@@ -406,9 +406,9 @@
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/index/index.vue": 1781272556.8548205,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue": 1781592807.1040754,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskResultPanel.vue": 1781589482.0890527,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue": 1781607988.3595095,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue": 1781698817.570356,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderFeeSummaryBar.vue": 1781585388.7152023,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSampleGroup.vue": 1781601939.108729,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSampleGroup.vue": 1781691146.652663,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue": 1781590356.5447705,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderCardList.vue": 1781595924.5325,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderProtocolPaymentPanel.vue": 1781588255.9076052,
@@ -488,7 +488,7 @@
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/pilot-service/pilot-application/PilotApplicationTab.vue": 1781272557.1928792,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/pilot-service/pilot-application/ApplicationViewDialog.vue": 1781682542.890803,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/pilot-service/pilot-application/InfusionScheduleTab.vue": 1781600431.8481567,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/pilot-service/pilot-application/ApplicationFormDialog.vue": 1781682540.789256,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/pilot-service/pilot-application/ApplicationFormDialog.vue": 1781691994.6323533,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/pilot-service/pilot-application/CheckupManagementTab.vue": 1781435201.7785301,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/pilot-service/pilot-application/ClinicalPaymentTab.vue": 1781601413.5946639,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/pilot-service/cell-product-price/index.vue": 1781272557.4568446,

+ 6 - 0
web-admin/src/api/production-work-order.ts

@@ -43,6 +43,12 @@ export interface ProductionWorkOrderItem {
   projectTitle?: string
   /** 基础实验出库:客户名称(列表 enrich) */
   customerName?: string
+  /** 出库工单:输注计划细胞类型(列表 enrich,无产品明细时兜底) */
+  cellType?: string
+  /** 出库工单:输注计划规格(列表 enrich,无产品明细时兜底) */
+  specText?: string
+  /** 出库工单:输注计划关联细胞产品明细(列表 enrich) */
+  products?: OutboundQcDetailProduct[]
   paymentSummary?: QcProtocolPaymentSummary | null
   feeSummary?: QcBasicExperimentFeeSummary | null
 }

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

@@ -127,6 +127,12 @@ export type QcWorkOrder = {
   /** 出库工单:输注计划总次数 */
   totalCourseCount?: number | null
   infusionTime?: string
+  /** 出库工单:输注计划细胞类型(列表 enrich,无产品明细时兜底) */
+  cellType?: string
+  /** 出库工单:输注计划规格(列表 enrich,无产品明细时兜底) */
+  specText?: string
+  /** 计划质检:输注计划关联细胞产品明细(列表 enrich) */
+  products?: OutboundQcDetailProduct[]
   triggerSource?: string
   triggerSourceLabel?: string
   productionWorkOrderId?: string

+ 1 - 0
web-admin/src/graphify-out/cache/ast/07468df71a20f2c75ba9ecba1b5bca2a83bf63b875c6e68b99471d2613b64293.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": []}

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

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

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

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_productionworkordercardlist_vue", "label": "ProductionWorkOrderCardList.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/ProductionWorkOrderCardList.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}

+ 1 - 0
web-admin/src/graphify-out/cache/ast/686495c44689c783c66328617a5cb322c6aa79a8e840ca290342c5f1bf85359d.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": []}

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
web-admin/src/graphify-out/cache/ast/70726652cbfe3dbe97bded90eebec9252542e32db5aa36528c947c84c4130e80.json


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
web-admin/src/graphify-out/cache/ast/85094e0d7ef8fc73ef477c4d44b0a8e52d14f8ac244d19055575a0806416f936.json


+ 1 - 0
web-admin/src/graphify-out/cache/ast/8569ccf4780741f68bc4f973b51e8228576837ee8c990396b4c28749c2a1049e.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": []}

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
web-admin/src/graphify-out/cache/ast/8c12c9f17b5c67ae2fca703244f83a62ee956584c022393c68c2ac94170bb69c.json


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

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_productionworkordercardlist_vue", "label": "ProductionWorkOrderCardList.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/ProductionWorkOrderCardList.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
web-admin/src/graphify-out/cache/ast/b5a7ddf14e286e97ad6202f461aabb2c4eda32e426efdcee6e2be243505431e4.json


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
web-admin/src/graphify-out/cache/ast/df70c7fe1152f96f10eccfa304bbd477f0dabdb25d41a377f622eddb772aadb2.json


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

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

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
web-admin/src/graphify-out/cache/ast/f7f50431271417e71aa52d0168f65dfc871f5100323ae337a66ab13435c3867b.json


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

@@ -25,6 +25,8 @@ export type ProductionWorkOrderCardMetaConfig = {
   tabKey: ProductionWorkOrderTabKey
   showProtocolFeeSummary: boolean
   showOutboundFeeSummary: boolean
+  /** 卡片展示关联申请单/输注计划的细胞产品明细 */
+  showCellProducts: boolean
   metaFields: CardMetaField[]
   allowDispatchQc: boolean
   allowSampleReception: boolean
@@ -181,6 +183,7 @@ const CARD_META_BY_TAB: Record<ProductionWorkOrderTabKey, ProductionWorkOrderCar
     tabKey: 'inbound_cell_storage',
     showProtocolFeeSummary: true,
     showOutboundFeeSummary: false,
+    showCellProducts: false,
     metaFields: buildInboundMetaFields('inbound_cell_storage'),
     allowDispatchQc: true,
     allowSampleReception: true,
@@ -192,6 +195,7 @@ const CARD_META_BY_TAB: Record<ProductionWorkOrderTabKey, ProductionWorkOrderCar
     tabKey: 'inbound_research_donation',
     showProtocolFeeSummary: false,
     showOutboundFeeSummary: false,
+    showCellProducts: false,
     metaFields: buildInboundMetaFields('inbound_research_donation'),
     allowDispatchQc: true,
     allowSampleReception: true,
@@ -203,6 +207,7 @@ const CARD_META_BY_TAB: Record<ProductionWorkOrderTabKey, ProductionWorkOrderCar
     tabKey: 'outbound_pilot_service',
     showProtocolFeeSummary: false,
     showOutboundFeeSummary: false,
+    showCellProducts: true,
     metaFields: outboundPilotClinicalFields,
     allowDispatchQc: true,
     allowSampleReception: false,
@@ -214,6 +219,7 @@ const CARD_META_BY_TAB: Record<ProductionWorkOrderTabKey, ProductionWorkOrderCar
     tabKey: 'outbound_basic_experiment',
     showProtocolFeeSummary: false,
     showOutboundFeeSummary: true,
+    showCellProducts: true,
     metaFields: outboundBasicExperimentFields,
     allowDispatchQc: true,
     allowSampleReception: false,
@@ -225,6 +231,7 @@ const CARD_META_BY_TAB: Record<ProductionWorkOrderTabKey, ProductionWorkOrderCar
     tabKey: 'outbound_clinical_application',
     showProtocolFeeSummary: false,
     showOutboundFeeSummary: true,
+    showCellProducts: false,
     metaFields: outboundPilotClinicalFields,
     allowDispatchQc: false,
     allowSampleReception: false,
@@ -236,6 +243,7 @@ const CARD_META_BY_TAB: Record<ProductionWorkOrderTabKey, ProductionWorkOrderCar
     tabKey: 'temporary',
     showProtocolFeeSummary: false,
     showOutboundFeeSummary: false,
+    showCellProducts: false,
     metaFields: temporaryFields,
     allowDispatchQc: true,
     allowSampleReception: false,

+ 8 - 0
web-admin/src/utils/qc-work-order-card-meta.ts

@@ -33,6 +33,8 @@ export type QcWorkOrderCardMetaConfig = {
   showTriggerTag: boolean
   showProtocolFeeSummary: boolean
   showOutboundFeeSummary: boolean
+  /** 卡片展示计划质检订单细胞产品 / 生产过程质检细胞产品 */
+  showCellProducts: boolean
   metaFields: CardMetaField[]
   buildGroupContextParts: (row: QcWorkOrder) => string[]
   allowEditTemporary: boolean
@@ -209,6 +211,7 @@ const CARD_META_BY_TAB: Record<QcWorkOrderTabKey, QcWorkOrderCardMetaConfig> = {
     showTriggerTag: true,
     showProtocolFeeSummary: true,
     showOutboundFeeSummary: false,
+    showCellProducts: false,
     metaFields: inboundGroupedFields,
     buildGroupContextParts: buildCellStorageGroupContext,
     allowEditTemporary: false,
@@ -221,6 +224,7 @@ const CARD_META_BY_TAB: Record<QcWorkOrderTabKey, QcWorkOrderCardMetaConfig> = {
     showTriggerTag: true,
     showProtocolFeeSummary: false,
     showOutboundFeeSummary: false,
+    showCellProducts: false,
     metaFields: inboundGroupedFields,
     buildGroupContextParts: buildDonationGroupContext,
     allowEditTemporary: false,
@@ -233,6 +237,7 @@ const CARD_META_BY_TAB: Record<QcWorkOrderTabKey, QcWorkOrderCardMetaConfig> = {
     showTriggerTag: true,
     showProtocolFeeSummary: false,
     showOutboundFeeSummary: false,
+    showCellProducts: true,
     metaFields: outboundGroupedFields,
     buildGroupContextParts: buildOutboundPilotGroupContext,
     allowEditTemporary: false,
@@ -245,6 +250,7 @@ const CARD_META_BY_TAB: Record<QcWorkOrderTabKey, QcWorkOrderCardMetaConfig> = {
     showTriggerTag: true,
     showProtocolFeeSummary: false,
     showOutboundFeeSummary: true,
+    showCellProducts: true,
     metaFields: outboundGroupedFields,
     buildGroupContextParts: buildOutboundBasicExperimentGroupContext,
     allowEditTemporary: false,
@@ -257,6 +263,7 @@ const CARD_META_BY_TAB: Record<QcWorkOrderTabKey, QcWorkOrderCardMetaConfig> = {
     showTriggerTag: false,
     showProtocolFeeSummary: false,
     showOutboundFeeSummary: true,
+    showCellProducts: false,
     metaFields: outboundPilotClinicalFields,
     buildGroupContextParts: () => [],
     allowEditTemporary: false,
@@ -269,6 +276,7 @@ const CARD_META_BY_TAB: Record<QcWorkOrderTabKey, QcWorkOrderCardMetaConfig> = {
     showTriggerTag: true,
     showProtocolFeeSummary: false,
     showOutboundFeeSummary: false,
+    showCellProducts: false,
     metaFields: temporaryFields,
     buildGroupContextParts: () => [],
     allowEditTemporary: true,

+ 129 - 0
web-admin/src/views/pilot-service/pilot-application/ApplicationFormDialog.vue

@@ -1639,6 +1639,133 @@
     return items.length ? items.join(',') : '—'
   })
 
+  /** 未完成结算的输注计划(已完成/已取消不再扣减) */
+  const isPendingSettlementSchedule = (schedule: Record<string, any>): boolean => {
+    const st = normalizeInfusionStatusCode(schedule.infusionStatus)
+    return st !== 'ywc' && st !== 'yqx'
+  }
+
+  const resolveYfkUnitPrice = (cp: Record<string, any>): number => {
+    const agreement = Number(cp.agreementPrice)
+    if (agreement > 0) return agreement
+    return Number(cp.actualPrice) || 0
+  }
+
+  const calcPendingSettlementAmount = (): number =>
+    infusionSchedules.value
+      .filter(isPendingSettlementSchedule)
+      .reduce(
+        (sum, s) =>
+          sum +
+          (s.cellProducts || []).reduce((lineSum: number, cp: any) => {
+            const qty = Math.max(1, Number(cp.quantity) || 1)
+            return lineSum + resolveYfkUnitPrice(cp) * qty
+          }, 0),
+        0
+      )
+
+  type PendingProductQtyItem = {
+    label: string
+    qty: number
+    projectCode: string
+    specText: string
+    cellTypeName: string
+  }
+
+  const calcPendingSettlementQtyByProduct = (): Map<string, PendingProductQtyItem> => {
+    const map = new Map<string, PendingProductQtyItem>()
+    for (const schedule of infusionSchedules.value.filter(isPendingSettlementSchedule)) {
+      for (const cp of schedule.cellProducts || []) {
+        const projectCode = (cp.projectCode || '').trim()
+        const specText = (cp.specText || '').trim()
+        const cellTypeName = (cp.cellTypeName || '').trim()
+        const key = projectCode ? `${projectCode}|${specText}` : `${cellTypeName}|${specText}`
+        if (!key.replace('|', '').trim()) continue
+        const qty = Math.max(1, Number(cp.quantity) || 1)
+        const label = formatCellProductDisplayLabel(cp)
+        const existing = map.get(key)
+        if (existing) {
+          existing.qty += qty
+        } else {
+          map.set(key, { label, qty, projectCode, specText, cellTypeName })
+        }
+      }
+    }
+    return map
+  }
+
+  const findOrderProductCurrentQty = (
+    orderProducts: any[],
+    item: PendingProductQtyItem
+  ): number | null => {
+    const spec = item.specText
+    if (item.projectCode) {
+      const row = orderProducts.find(
+        (p) => (p.projectCode || '') === item.projectCode && (p.specText || '') === spec
+      )
+      if (row) return Number(row.currentQty) || 0
+    }
+    if (item.cellTypeName) {
+      const row = orderProducts.find(
+        (p) =>
+          (p.cellType || p.cellTypeName || '') === item.cellTypeName && (p.specText || '') === spec
+      )
+      if (row) return Number(row.currentQty) || 0
+    }
+    return null
+  }
+
+  /** 保存前校验渠道服务订单余额/份数,仅提示不阻断保存 */
+  const warnIfChannelSettlementInsufficient = async () => {
+    if (isClinical.value || isBasicExperiment.value) return
+    if (!formData.channelServiceOrderId || !formData.channelId) return
+
+    const pendingSchedules = infusionSchedules.value.filter(isPendingSettlementSchedule)
+    const hasProducts = pendingSchedules.some((s) => (s.cellProducts || []).length > 0)
+    if (!hasProducts) return
+
+    let orderDetail: any
+    try {
+      const res: any = await fetchChannelServiceOrderDetail(formData.channelServiceOrderId)
+      orderDetail = res?.data ?? res
+    } catch {
+      return
+    }
+
+    const orderType = orderDetail?.orderType || selectedServiceOrder.value?.orderType || ''
+
+    if (orderType === 'yfk') {
+      const required = calcPendingSettlementAmount()
+      if (required <= 0) return
+      const balance = Number(orderDetail.currentBalance) || 0
+      if (balance < required) {
+        ElMessage.warning(
+          `渠道服务订单余额可能不足:当前余额 ¥${formatMoneyDisplay(balance, 2)},待完成输注计划预估需 ¥${formatMoneyDisplay(required, 2)}。申请单仍会保存,完成输注时可能扣减失败。`
+        )
+      }
+      return
+    }
+
+    if (orderType === 'gmcp') {
+      const orderProducts = Array.isArray(orderDetail.products) ? orderDetail.products : []
+      const requiredMap = calcPendingSettlementQtyByProduct()
+      const shortfalls: string[] = []
+      for (const item of requiredMap.values()) {
+        const available = findOrderProductCurrentQty(orderProducts, item)
+        if (available === null) {
+          shortfalls.push(`「${item.label}」在服务订单中未找到持仓`)
+        } else if (available < item.qty) {
+          shortfalls.push(`「${item.label}」当前剩余 ${available} 份,预估需 ${item.qty} 份`)
+        }
+      }
+      if (shortfalls.length) {
+        ElMessage.warning(
+          `渠道服务订单产品份数可能不足:${shortfalls.join(';')}。申请单仍会保存,完成输注时可能扣减失败。`
+        )
+      }
+    }
+  }
+
   const getCellProductProjectKey = (cp: any): string =>
     (cp?.projectCode || cp?.priceId || '').trim()
 
@@ -2742,6 +2869,8 @@
       }
     }
 
+    await warnIfChannelSettlementInsufficient()
+
     submitting.value = true
     try {
       const attachmentPayload = otherFileList.value.map((a) => ({

+ 243 - 118
web-admin/src/views/production/production-work-order/ProductionWorkOrderCardList.vue

@@ -6,119 +6,172 @@
         <header class="production-work-order-card__head">
           <div class="production-work-order-card__title-band">
             <div class="production-work-order-card__title-row">
-              <el-checkbox
-                v-if="!isMineMode"
-                :model-value="isSelected(row.id)"
-                class="production-work-order-card__checkbox"
-                @change="(val) => handleCardSelect(row, Boolean(val))"
-              />
-              <span class="production-work-order-card__index">{{ cardIndex(idx) }}</span>
-              <span class="production-work-order-card__no">{{ row.workOrderNo || '—' }}</span>
-              <el-tag :type="productionWoStatusTag(row.workOrderStatus).type" size="small">
-                {{ productionWoStatusTag(row.workOrderStatus).label }}
-              </el-tag>
-            </div>
-          </div>
-          <div class="production-work-order-card__head-body">
-            <div class="production-work-order-card__head-main">
-              <dl class="production-work-order-card__meta">
-                <template v-for="field in visibleMetaFields(row)" :key="field.label">
-                  <div
-                    class="production-work-order-card__meta-item"
-                    :class="{ 'production-work-order-card__meta-item--wide': field.wide }"
-                  >
-                    <dt>{{ field.label }}</dt>
-                    <dd>
-                      <el-tag
-                        v-if="field.kind === 'tag'"
-                        :type="field.getTagType?.(row) || 'info'"
-                        size="small"
-                      >
-                        {{ field.getValue(row) }}
-                      </el-tag>
-                      <template v-else>{{ field.getValue(row) }}</template>
-                    </dd>
-                  </div>
-                </template>
-              </dl>
-            </div>
-            <div v-if="showCardOpsRow" class="production-work-order-card__ops">
-            <el-button
-              v-if="isMineMode"
-              class="btn-op-item primary"
-              type="primary"
-              @click="emit('view', row)"
-            >
-              <el-icon><View /></el-icon>查看
-            </el-button>
-            <el-button
-              v-else-if="showCardDispatchComplete && canDispatchTask(row)"
-              class="btn-op-item primary"
-              type="primary"
-              @click="emit('dispatch', row)"
-            >
-              <el-icon><Position /></el-icon>派发任务
-            </el-button>
-            <el-button
-              v-else-if="!canDispatchTask(row)"
-              class="btn-op-item primary"
-              @click="emit('view', row)"
-            >
-              <el-icon><View /></el-icon>查看
-            </el-button>
-            <el-tooltip
-              v-if="!isMineMode && cardMeta.allowDispatchQc"
-              :content="dispatchQcTooltip(row)"
-              :disabled="canDispatchQc(row)"
-              placement="top"
-            >
-              <span>
+              <div class="production-work-order-card__title-left">
+                <el-checkbox
+                  v-if="!isMineMode"
+                  :model-value="isSelected(row.id)"
+                  class="production-work-order-card__checkbox"
+                  @change="(val) => handleCardSelect(row, Boolean(val))"
+                />
+                <span class="production-work-order-card__index">{{ cardIndex(idx) }}</span>
+                <span class="production-work-order-card__no">{{ row.workOrderNo || '—' }}</span>
+                <el-tag :type="productionWoStatusTag(row.workOrderStatus).type" size="small">
+                  {{ productionWoStatusTag(row.workOrderStatus).label }}
+                </el-tag>
+              </div>
+              <div v-if="showCardOpsRow" class="production-work-order-card__ops">
+                <el-button
+                  v-if="isMineMode"
+                  class="btn-op-item primary"
+                  type="primary"
+                  @click="emit('view', row)"
+                >
+                  <el-icon><View /></el-icon>查看
+                </el-button>
+                <el-button
+                  v-else-if="showCardDispatchComplete && canDispatchTask(row)"
+                  class="btn-op-item primary"
+                  type="primary"
+                  @click="emit('dispatch', row)"
+                >
+                  <el-icon><Position /></el-icon>派发任务
+                </el-button>
+                <el-button
+                  v-else-if="!canDispatchTask(row)"
+                  class="btn-op-item primary"
+                  @click="emit('view', row)"
+                >
+                  <el-icon><View /></el-icon>查看
+                </el-button>
+                <el-tooltip
+                  v-if="!isMineMode && cardMeta.allowDispatchQc"
+                  :content="dispatchQcTooltip(row)"
+                  :disabled="canDispatchQc(row)"
+                  placement="top"
+                >
+                  <span>
+                    <el-button
+                      class="btn-op-item"
+                      :disabled="!canDispatchQc(row)"
+                      @click="emit('dispatch-qc', row)"
+                    >
+                      <el-icon><Document /></el-icon>派发质检工单
+                    </el-button>
+                  </span>
+                </el-tooltip>
                 <el-button
+                  v-if="cardMeta.allowSampleReception"
                   class="btn-op-item"
-                  :disabled="!canDispatchQc(row)"
-                  @click="emit('dispatch-qc', row)"
+                  @click="emit('open-sample-reception', row)"
                 >
-                  <el-icon><Document /></el-icon>派发质检工单
+                  <el-icon><EditPen /></el-icon>样本接收信息填写
                 </el-button>
-              </span>
-            </el-tooltip>
-            <el-button
-              v-if="cardMeta.allowSampleReception"
-              class="btn-op-item"
-              @click="emit('open-sample-reception', row)"
-            >
-              <el-icon><EditPen /></el-icon>样本接收信息填写
-            </el-button>
-            <el-button
-              v-if="!isMineMode && showCardDispatchComplete && canComplete(row)"
-              class="btn-op-item"
-              @click="emit('complete', row)"
-            >
-              <el-icon><Check /></el-icon>完成工单
-            </el-button>
-            <el-dropdown
-              v-if="!isMineMode && hasMoreActions(row)"
-              trigger="hover"
-              @command="(cmd) => emit('command', cmd, row)"
-            >
-              <el-button class="btn-op-item">
-                <el-icon><More /></el-icon>
-              </el-button>
-              <template #dropdown>
-                <el-dropdown-menu>
-                  <el-dropdown-item v-if="canDispatchTask(row)" command="view">
-                    <el-icon><View /></el-icon>查看
-                  </el-dropdown-item>
-                  <el-dropdown-item v-if="canEditWorkOrder(row)" command="edit">
-                    <el-icon><EditPen /></el-icon>编辑
-                  </el-dropdown-item>
-                  <el-dropdown-item v-if="canDeleteWorkOrder(row)" command="delete" divided>
-                    <el-icon><Delete /></el-icon>删除
-                  </el-dropdown-item>
-                </el-dropdown-menu>
-              </template>
-            </el-dropdown>
+                <el-button
+                  v-if="!isMineMode && showCardDispatchComplete && canComplete(row)"
+                  class="btn-op-item"
+                  @click="emit('complete', row)"
+                >
+                  <el-icon><Check /></el-icon>完成工单
+                </el-button>
+                <el-dropdown
+                  v-if="!isMineMode && hasMoreActions(row)"
+                  trigger="hover"
+                  @command="(cmd) => emit('command', cmd, row)"
+                >
+                  <el-button class="btn-op-item">
+                    <el-icon><More /></el-icon>
+                  </el-button>
+                  <template #dropdown>
+                    <el-dropdown-menu>
+                      <el-dropdown-item v-if="canDispatchTask(row)" command="view">
+                        <el-icon><View /></el-icon>查看
+                      </el-dropdown-item>
+                      <el-dropdown-item v-if="canEditWorkOrder(row)" command="edit">
+                        <el-icon><EditPen /></el-icon>编辑
+                      </el-dropdown-item>
+                      <el-dropdown-item v-if="canDeleteWorkOrder(row)" command="delete" divided>
+                        <el-icon><Delete /></el-icon>删除
+                      </el-dropdown-item>
+                    </el-dropdown-menu>
+                  </template>
+                </el-dropdown>
+              </div>
+            </div>
           </div>
+          <div class="production-work-order-card__head-body">
+            <dl class="production-work-order-card__meta">
+              <template v-for="field in visibleMetaFields(row)" :key="field.label">
+                <div
+                  class="production-work-order-card__meta-item"
+                  :class="{ 'production-work-order-card__meta-item--wide': field.wide }"
+                >
+                  <dt>{{ field.label }}</dt>
+                  <dd>
+                    <el-tag
+                      v-if="field.kind === 'tag'"
+                      :type="field.getTagType?.(row) || 'info'"
+                      size="small"
+                    >
+                      {{ field.getValue(row) }}
+                    </el-tag>
+                    <template v-else>{{ field.getValue(row) }}</template>
+                  </dd>
+                </div>
+              </template>
+            </dl>
+            <section v-if="cardMeta.showCellProducts" class="production-work-order-card__cells">
+              <div v-if="cellProducts(row).length" class="production-work-order-card__cell-list">
+                <div
+                  v-for="(product, pi) in cellProducts(row)"
+                  :key="`${product.projectCode || product.projectName || pi}`"
+                  class="production-work-order-card__cell-item"
+                >
+                  <dl class="production-work-order-card__cell-fields">
+                    <div class="production-work-order-card__cell-field">
+                      <dt>项目编码</dt>
+                      <dd>{{ product.projectCode || '—' }}</dd>
+                    </div>
+                    <div class="production-work-order-card__cell-field">
+                      <dt>项目名称</dt>
+                      <dd>{{ product.projectName || '—' }}</dd>
+                    </div>
+                    <div class="production-work-order-card__cell-field">
+                      <dt>细胞类型</dt>
+                      <dd>{{ product.cellTypeName || row.cellType || '—' }}</dd>
+                    </div>
+                    <div class="production-work-order-card__cell-field">
+                      <dt>规格</dt>
+                      <dd>{{ product.specText || row.specText || '—' }}</dd>
+                    </div>
+                    <div class="production-work-order-card__cell-field">
+                      <dt>数量</dt>
+                      <dd>{{ product.quantity != null ? product.quantity : '—' }}</dd>
+                    </div>
+                    <div class="production-work-order-card__cell-field">
+                      <dt>产品状态</dt>
+                      <dd>
+                        <el-tag
+                          v-if="product.productStatus"
+                          :type="productStatusTagType(product.productStatus) as TagType"
+                          size="small"
+                        >
+                          {{ productStatusLabel(product.productStatus, row.bizType || '') }}
+                        </el-tag>
+                        <span v-else>—</span>
+                      </dd>
+                    </div>
+                  </dl>
+                </div>
+              </div>
+              <p v-else class="production-work-order-card__cells-fallback">
+                <template v-if="row.cellType || row.specText">
+                  <span v-if="row.cellType">细胞类型:{{ row.cellType }}</span>
+                  <span v-if="row.cellType && row.specText"> · </span>
+                  <span v-if="row.specText">规格:{{ row.specText }}</span>
+                </template>
+                <template v-else>暂无细胞产品明细</template>
+              </p>
+            </section>
           </div>
         </header>
         <QcWorkOrderProtocolPaymentPanel
@@ -168,9 +221,11 @@
   import { computed } from 'vue'
   import { View, Position, Document, Check, More, EditPen, Delete } from '@element-plus/icons-vue'
   import type { ProductionWorkOrderItem } from '@/api/production-work-order'
+  import type { OutboundQcDetailProduct } from '@/api/qc-work-order'
   import type { ProductionWorkOrderCardMetaConfig } from '@/utils/production-work-order-card-meta'
   import { getCardMetaConfigForLegacyTab } from '@/utils/production-work-order-card-meta'
   import type { TagType } from '@/utils/status-tag-registry'
+  import { productStatusLabel, productStatusTagType } from '@/utils/status-tag-registry'
   import WorkOrderTaskSubList from './WorkOrderTaskSubList.vue'
   import QcWorkOrderFeeSummaryBar from '@/views/quality-inspection/qc-work-order/QcWorkOrderFeeSummaryBar.vue'
   import QcWorkOrderProtocolPaymentPanel from '@/views/quality-inspection/qc-work-order/QcWorkOrderProtocolPaymentPanel.vue'
@@ -238,6 +293,10 @@
     return cardMeta.value.showOutboundFeeSummary && !!row.feeSummary
   }
 
+  function cellProducts(row: ProductionWorkOrderItem): OutboundQcDetailProduct[] {
+    return row.products?.length ? row.products : []
+  }
+
   function productionWoStatusTag(status: string): { label: string; type: TagType } {
     const map: Record<string, { label: string; type: TagType }> = {
       dpz: { label: '待配置', type: 'info' },
@@ -351,23 +410,27 @@
 
   .production-work-order-card__head-body {
     display: flex;
-    flex-wrap: wrap;
-    align-items: flex-start;
-    gap: 12px 20px;
-    padding: 14px 16px;
+    flex-direction: column;
+    gap: 12px;
+    padding: 12px 16px;
     border-bottom: 1px solid #e2e8f0;
   }
 
-  .production-work-order-card__head-main {
-    flex: 1;
-    min-width: 280px;
+  .production-work-order-card__title-row {
+    display: flex;
+    flex-wrap: wrap;
+    align-items: center;
+    justify-content: space-between;
+    gap: 8px 12px;
   }
 
-  .production-work-order-card__title-row {
+  .production-work-order-card__title-left {
     display: flex;
     flex-wrap: wrap;
     align-items: center;
     gap: 8px;
+    flex: 1;
+    min-width: 0;
   }
 
   .production-work-order-card__checkbox {
@@ -423,14 +486,76 @@
     }
   }
 
+  .production-work-order-card__cells {
+    padding: 0;
+  }
+
+  .production-work-order-card__cell-list {
+    display: flex;
+    flex-direction: column;
+    gap: 6px;
+  }
+
+  .production-work-order-card__cell-item {
+    &:not(:last-child) {
+      margin-bottom: 8px;
+    }
+  }
+
+  .production-work-order-card__cell-fields {
+    display: flex;
+    flex-wrap: wrap;
+    align-items: center;
+    gap: 10px 12px;
+    margin: 0;
+  }
+
+  .production-work-order-card__cell-field {
+    display: inline-flex;
+    align-items: center;
+    gap: 6px;
+    min-width: 0;
+    max-width: 100%;
+
+    dt {
+      flex-shrink: 0;
+      margin: 0;
+      font-size: 11px;
+      font-weight: 500;
+      color: #b91c1c;
+      line-height: 1.4;
+      white-space: nowrap;
+
+      &::after {
+        content: ':';
+      }
+    }
+
+    dd {
+      margin: 0;
+      font-size: 13px;
+      font-weight: 600;
+      color: #dc2626;
+      line-height: 1.4;
+      word-break: break-word;
+    }
+  }
+
+  .production-work-order-card__cells-fallback {
+    margin: 0;
+    font-size: 13px;
+    color: #dc2626;
+    line-height: 1.5;
+  }
+
   .production-work-order-card__ops {
     display: flex;
     flex-wrap: wrap;
     align-items: center;
     justify-content: flex-end;
-    gap: 8px;
+    gap: 6px;
     flex-shrink: 0;
-    align-self: flex-end;
+    margin-left: auto;
   }
 
   :deep(.production-work-order-card__fee-in-head) {

+ 164 - 1
web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue

@@ -42,6 +42,76 @@
               </div>
             </template>
           </dl>
+          <section
+            v-if="showCellProductSection(row)"
+            class="qc-work-order-card__cells"
+          >
+            <template v-if="isScheduleTrigger(row)">
+              <div v-if="scheduleCellProducts(row).length" class="qc-work-order-card__cell-list">
+                <div
+                  v-for="(product, pi) in scheduleCellProducts(row)"
+                  :key="`${product.projectCode || product.projectName || pi}`"
+                  class="qc-work-order-card__cell-item"
+                >
+                  <dl class="qc-work-order-card__cell-fields">
+                    <div class="qc-work-order-card__cell-field">
+                      <dt>项目编码</dt>
+                      <dd>{{ product.projectCode || '—' }}</dd>
+                    </div>
+                    <div class="qc-work-order-card__cell-field">
+                      <dt>项目名称</dt>
+                      <dd>{{ product.projectName || '—' }}</dd>
+                    </div>
+                    <div class="qc-work-order-card__cell-field">
+                      <dt>细胞类型</dt>
+                      <dd>{{ product.cellTypeName || row.cellType || '—' }}</dd>
+                    </div>
+                    <div class="qc-work-order-card__cell-field">
+                      <dt>规格</dt>
+                      <dd>{{ product.specText || row.specText || '—' }}</dd>
+                    </div>
+                    <div class="qc-work-order-card__cell-field">
+                      <dt>数量</dt>
+                      <dd>{{ product.quantity != null ? product.quantity : '—' }}</dd>
+                    </div>
+                    <div class="qc-work-order-card__cell-field">
+                      <dt>产品状态</dt>
+                      <dd>
+                        <el-tag
+                          v-if="product.productStatus"
+                          :type="productStatusTagType(product.productStatus) as TagType"
+                          size="small"
+                        >
+                          {{ productStatusLabel(product.productStatus, row.bizType || '') }}
+                        </el-tag>
+                        <span v-else>—</span>
+                      </dd>
+                    </div>
+                  </dl>
+                </div>
+              </div>
+              <p v-else class="qc-work-order-card__cells-fallback">
+                <template v-if="row.cellType || row.specText">
+                  <span v-if="row.cellType">细胞类型:{{ row.cellType }}</span>
+                  <span v-if="row.cellType && row.specText"> · </span>
+                  <span v-if="row.specText">规格:{{ row.specText }}</span>
+                </template>
+                <template v-else>暂无细胞产品明细</template>
+              </p>
+            </template>
+            <template v-else-if="isProductionTrigger(row)">
+              <dl class="qc-work-order-card__cell-fields">
+                <div
+                  v-for="field in productionCellProductFields(row)"
+                  :key="field.label"
+                  class="qc-work-order-card__cell-field"
+                >
+                  <dt>{{ field.label }}</dt>
+                  <dd>{{ field.value }}</dd>
+                </div>
+              </dl>
+            </template>
+          </section>
         </div>
         <div
           v-if="showWorkOrderOps || showWorkOrderViewOnly"
@@ -122,10 +192,11 @@
 <script setup lang="ts">
   import { computed } from 'vue'
   import { Setting, View, More, EditPen, Delete } from '@element-plus/icons-vue'
-  import type { QcWorkOrder } from '@/api/qc-work-order'
+  import type { QcWorkOrder, OutboundQcDetailProduct } from '@/api/qc-work-order'
   import type { QcTask } from '@/api/qc-task'
   import { workOrderStatusLabel } from '@/utils/qc-labels'
   import type { TagType } from '@/utils/status-tag-registry'
+  import { productStatusLabel, productStatusTagType } from '@/utils/status-tag-registry'
   import type { QcWorkOrderCardMetaConfig } from '@/utils/qc-work-order-card-meta'
   import { triggerSourceTabLabel, temporaryQcSourceLabel, temporaryQcSourceTagType } from '@/utils/qc-work-order-groups'
   import QcWorkOrderTaskSubList from './QcWorkOrderTaskSubList.vue'
@@ -181,6 +252,35 @@
     return 'success'
   }
 
+  function showCellProductSection(row: QcWorkOrder) {
+    if (!props.cardMetaConfig.showCellProducts) return false
+    const ts = row.triggerSource || ''
+    return ts === 'schedule' || ts === 'production'
+  }
+
+  function isScheduleTrigger(row: QcWorkOrder) {
+    return (row.triggerSource || '') === 'schedule'
+  }
+
+  function isProductionTrigger(row: QcWorkOrder) {
+    return row.triggerSource === 'production'
+  }
+
+  function scheduleCellProducts(row: QcWorkOrder): OutboundQcDetailProduct[] {
+    return row.products?.length ? row.products : []
+  }
+
+  function productionCellProductFields(row: QcWorkOrder) {
+    const val = (v?: string | null) => String(v || '').trim() || '—'
+    return [
+      { label: '产品编码', value: val(row.cellProductCode) },
+      { label: '产品名称', value: val(row.cellProductName) },
+      { label: '产品类型', value: val(row.cellProductTypeName) },
+      { label: '代次', value: val(row.cellProductPassage) },
+      { label: '规格', value: val(row.cellProductSpecName) }
+    ]
+  }
+
   function resolveTriggerTagLabel(row: QcWorkOrder) {
     if (props.cardMetaConfig.tabKey === 'temporary') {
       return temporaryQcSourceLabel(row)
@@ -325,6 +425,69 @@
     }
   }
 
+  .qc-work-order-card__cells {
+    margin-top: 10px;
+    padding: 0;
+  }
+
+  .qc-work-order-card__cell-list {
+    display: flex;
+    flex-direction: column;
+    gap: 6px;
+  }
+
+  .qc-work-order-card__cell-item {
+    &:not(:last-child) {
+      margin-bottom: 8px;
+    }
+  }
+
+  .qc-work-order-card__cell-fields {
+    display: flex;
+    flex-wrap: wrap;
+    align-items: center;
+    gap: 10px 12px;
+    margin: 0;
+  }
+
+  .qc-work-order-card__cell-field {
+    display: inline-flex;
+    align-items: center;
+    gap: 6px;
+    min-width: 0;
+    max-width: 100%;
+
+    dt {
+      flex-shrink: 0;
+      margin: 0;
+      font-size: 11px;
+      font-weight: 500;
+      color: #b91c1c;
+      line-height: 1.4;
+      white-space: nowrap;
+
+      &::after {
+        content: ':';
+      }
+    }
+
+    dd {
+      margin: 0;
+      font-size: 13px;
+      font-weight: 600;
+      color: #dc2626;
+      line-height: 1.4;
+      word-break: break-word;
+    }
+  }
+
+  .qc-work-order-card__cells-fallback {
+    margin: 0;
+    font-size: 13px;
+    color: #dc2626;
+    line-height: 1.5;
+  }
+
   :deep(.qc-work-order-card__fee-in-head) {
     align-self: stretch;
     width: calc(100% + 32px);

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů