zlwu 4 місяців тому
батько
коміт
e707ea57ab
21 змінених файлів з 1031 додано та 1797 видалено
  1. 1 0
      backend/app.pid
  2. 26 2
      backend/app/core/storage.py
  3. 7 2
      backend/app/crud/sample_reception.py
  4. 0 0
      backend/app/graphify-out/cache/ast/103ec1aab67d8de507c60c9d47864237740ad63b658f6b99e6b1d6272d564bfc.json
  5. 0 0
      backend/app/graphify-out/cache/ast/4d19f9ae258b570e346270eaacb5cea1a5beedf61c3fe51a788cb96f7f6a39ac.json
  6. 511 1387
      backend/graphify-out/.graphify_analysis.json
  7. 204 122
      backend/graphify-out/.graphify_extract.json
  8. 95 105
      backend/graphify-out/GRAPH_REPORT.md
  9. 1 1
      backend/graphify-out/graph.html
  10. 112 112
      backend/graphify-out/graph.json
  11. 3 3
      backend/graphify-out/manifest.json
  12. 2 1
      deploy/docker/nginx/default.conf
  13. 2 2
      web-admin/graphify-out/.graphify_extract.json
  14. 1 1
      web-admin/graphify-out/GRAPH_REPORT.md
  15. 4 4
      web-admin/graphify-out/manifest.json
  16. 1 0
      web-admin/src/graphify-out/cache/ast/03d22f9d3b3e94abe9e82002d8a1260735d7928d71b6137173ab274b03c765d8.json
  17. 1 0
      web-admin/src/graphify-out/cache/ast/556b7994a7205938cb9f64f46dfca0703f6b28fa8d0839a8846b577ef7efadbe.json
  18. 1 0
      web-admin/src/graphify-out/cache/ast/ce89b93417b570f9eb167eeb263cca11f6a7521a04941ab19a86778e7493edcd.json
  19. 1 0
      web-admin/src/graphify-out/cache/ast/ee9c73f0015eb24223a89e9e15f6ff37362dbf51c3ed8424155c1aed43c17553.json
  20. 54 54
      web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderConfigureBatches.vue
  21. 4 1
      web-admin/src/views/quality-inspection/qc-work-order/index.vue

+ 1 - 0
backend/app.pid

@@ -0,0 +1 @@
+41619

+ 26 - 2
backend/app/core/storage.py

@@ -24,7 +24,7 @@ import base64
 from io import BytesIO
 from datetime import datetime, timedelta
 from urllib.parse import quote
-from typing import Optional, BinaryIO
+from typing import List, Optional, BinaryIO
 from pathlib import Path
 
 from app.core.config import settings
@@ -339,6 +339,29 @@ class MinIOStorage(BaseStorage):
                 return u[idx + len(marker) :]
         return u.lstrip("/")
 
+    def _internal_base_prefixes(self) -> List[str]:
+        """SDK 预签名 URL 使用的内网地址前缀(http/https 均覆盖)。"""
+        endpoint = (settings.MINIO_ENDPOINT or "").strip()
+        if not endpoint:
+            return []
+        bucket = self.bucket_name
+        return [
+            f"http://{endpoint}/{bucket}",
+            f"https://{endpoint}/{bucket}",
+        ]
+
+    def _rewrite_presigned_public_url(self, url: str) -> str:
+        """将 SDK 生成的内网 MinIO 预签名 URL 改写为 MINIO_BASE_URL 对外路径。"""
+        if not url:
+            return url
+        public_base = (self.base_url or "").rstrip("/")
+        if not public_base:
+            return url
+        for prefix in self._internal_base_prefixes():
+            if url.startswith(prefix):
+                return public_base + url[len(prefix) :]
+        return url
+
     def presigned_download_url(
         self, file_path: str, download_name: str, expires_seconds: int = 3600
     ) -> str:
@@ -350,12 +373,13 @@ class MinIOStorage(BaseStorage):
         ascii_name = name.encode("ascii", "ignore").decode() or "download"
         encoded = quote(name, safe="")
         disposition = f'attachment; filename="{ascii_name}"; filename*=UTF-8\'\'{encoded}'
-        return self.client.presigned_get_object(
+        url = self.client.presigned_get_object(
             bucket_name=self.bucket_name,
             object_name=key,
             expires=timedelta(seconds=expires_seconds),
             response_headers={"response-content-disposition": disposition},
         )
+        return self._rewrite_presigned_public_url(url)
 
 
 def get_storage() -> BaseStorage:

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

@@ -32,6 +32,9 @@ from app.models.research_donation import (
 # 科研捐赠表默认 general_ci,样本接收等为 unicode_ci,跨表比较需统一
 _UNICODE_CI = "utf8mb4_unicode_ci"
 
+# 协议/捐赠单已作废时不需样本接收
+_VOIDED_PROTOCOL_STATUS = "yzf"
+
 
 def _donation_sample_id_col(col):
     """捐赠样本 ID 与 biz_sample_reception.donation_sample_id 比较时统一排序规则。"""
@@ -149,7 +152,7 @@ def _donation_sample_detail_rows_query(db: Session):
 
 
 def _donation_sample_pending_query(db: Session, tenant_no: str, keyword: Optional[str] = None):
-    """待接收:捐赠样本且尚无有效接收记录(按租户 + 样本编码判定,与细胞存储一致)。"""
+    """待接收:捐赠样本且尚无有效接收记录(按租户 + 样本编码判定,与细胞存储一致);排除已作废捐赠单。"""
     rec = BizSampleReception
     received_exists = (
         db.query(rec.id)
@@ -167,6 +170,7 @@ def _donation_sample_pending_query(db: Session, tenant_no: str, keyword: Optiona
         BizResearchDonationSample.del_flag == 0,
         BizResearchDonationSample.tenant_no == tenant_no,
         BizResearchDonation.tenant_no == tenant_no,
+        BizResearchDonation.donation_status != _VOIDED_PROTOCOL_STATUS,
         ~received_exists,
     )
     if keyword and keyword.strip():
@@ -282,7 +286,7 @@ def _donation_rows_to_sample_list_items(
 
 
 def _sample_pending_query(db: Session, tenant_no: str, keyword: Optional[str] = None):
-    """待接收:协议样本且尚无有效接收记录(仅按租户 + 样本编码判定)。"""
+    """待接收:协议样本且尚无有效接收记录(仅按租户 + 样本编码判定);排除已作废采集协议单。"""
     rec = BizSampleReception
     received_exists = (
         db.query(rec.id)
@@ -300,6 +304,7 @@ def _sample_pending_query(db: Session, tenant_no: str, keyword: Optional[str] =
         BizProtocolSample.del_flag == 0,
         BizProtocolSample.tenant_no == tenant_no,
         BizCollectionProtocol.tenant_no == tenant_no,
+        BizCollectionProtocol.protocol_status != _VOIDED_PROTOCOL_STATUS,
         ~received_exists,
     )
     if keyword and keyword.strip():

Різницю між файлами не показано, бо вона завелика
+ 0 - 0
backend/app/graphify-out/cache/ast/103ec1aab67d8de507c60c9d47864237740ad63b658f6b99e6b1d6272d564bfc.json


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
backend/app/graphify-out/cache/ast/4d19f9ae258b570e346270eaacb5cea1a5beedf61c3fe51a788cb96f7f6a39ac.json


Різницю між файлами не показано, бо вона завелика
+ 511 - 1387
backend/graphify-out/.graphify_analysis.json


Різницю між файлами не показано, бо вона завелика
+ 204 - 122
backend/graphify-out/.graphify_extract.json


+ 95 - 105
backend/graphify-out/GRAPH_REPORT.md

@@ -1,10 +1,10 @@
 # Graph Report - app  (2026-06-26)
 
 ## Corpus Check
-- Large corpus: 212 files · ~254,769 words. Semantic extraction will be expensive (many Claude tokens). Consider running on a subfolder, or use --no-semantic to run AST-only.
+- Large corpus: 212 files · ~254,854 words. Semantic extraction will be expensive (many Claude tokens). Consider running on a subfolder, or use --no-semantic to run AST-only.
 
 ## Summary
-- 4340 nodes · 23048 edges · 54 communities detected
+- 4344 nodes · 23054 edges · 52 communities detected
 - Extraction: 32% EXTRACTED · 68% INFERRED · 0% AMBIGUOUS · INFERRED: 15583 edges (avg confidence: 0.59)
 - Token cost: 0 input · 0 output
 
@@ -55,14 +55,12 @@
 - [[_COMMUNITY_Community 43|Community 43]]
 - [[_COMMUNITY_Community 44|Community 44]]
 - [[_COMMUNITY_Community 45|Community 45]]
-- [[_COMMUNITY_Community 46|Community 46]]
-- [[_COMMUNITY_Community 47|Community 47]]
+- [[_COMMUNITY_Community 48|Community 48]]
+- [[_COMMUNITY_Community 49|Community 49]]
 - [[_COMMUNITY_Community 50|Community 50]]
 - [[_COMMUNITY_Community 51|Community 51]]
 - [[_COMMUNITY_Community 52|Community 52]]
 - [[_COMMUNITY_Community 53|Community 53]]
-- [[_COMMUNITY_Community 54|Community 54]]
-- [[_COMMUNITY_Community 55|Community 55]]
 
 ## God Nodes (most connected - your core abstractions)
 1. `success_response()` - 658 edges
@@ -81,143 +79,143 @@
   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 (821): attachment_download_url(), create_dept(), create_dict_data(), create_dict_type(), create_express_company(), create_item_unit(), create_menu(), create_post() (+813 more)
+Nodes (824): attachment_download_url(), create_dept(), create_dict_data(), create_dict_type(), create_express_company(), create_item_unit(), create_menu(), create_post() (+816 more)
 
 ### Community 1 - "Community 1"
 Cohesion: 0.01
-Nodes (600): AttachmentDownloadUrlBody, DeptCreateBody, DeptUpdateBody, DictDataCreateBody, DictDataUpdateBody, DictTypeCreateBody, DictTypeUpdateBody, ExpressCompanyCreateBody (+592 more)
+Nodes (566): AttachmentDownloadUrlBody, DeptCreateBody, DeptUpdateBody, DictDataCreateBody, DictDataUpdateBody, DictTypeCreateBody, DictTypeUpdateBody, ExpressCompanyCreateBody (+558 more)
 
 ### Community 2 - "Community 2"
-Cohesion: 0.09
-Nodes (344): 环境检测 — 检测项维护 + 登记管理 API, 保险公司及险种价格管理 API(保险公司 + 开票信息 + 险种价格), ProcessDefCreateBody, ProcessDefUpdateBody, PublishVersionBody, 审批意见:前端传 commentText,兼容 comment。, StartInstanceBody, TaskActionBody (+336 more)
+Cohesion: 0.01
+Nodes (365): CellSampleTypeCreateBody, CellSampleTypeUpdateBody, CollectionRegionCreateBody, CollectionRegionUpdateBody, create_collection_region(), create_hospital(), CustomerCreateBody, CustomerUpdateBody (+357 more)
 
 ### Community 3 - "Community 3"
-Cohesion: 0.01
-Nodes (314): generate_uuid(), batch_stock_in(), batch_stock_out(), consumable_code_exists(), create_consumable(), _dec(), get_consumable(), _is_enabled_status() (+306 more)
+Cohesion: 0.08
+Nodes (364): 环境检测 — 检测项维护 + 登记管理 API, 保险公司及险种价格管理 API(保险公司 + 开票信息 + 险种价格), AccessLogCreateBody, AccessLogUpdateBody, CleanRoomCreateBody, CleanRoomUpdateBody, ExhibitionAppointmentCreateBody, ExhibitionAppointmentUpdateBody (+356 more)
 
 ### Community 4 - "Community 4"
-Cohesion: 0.02
-Nodes (248): _normalize_date_bound(), _persist_protocol_sub_resources(), format_date(), generate_id(), optional_date(), parse_date(), 将字符串 / date / datetime 转为 date 或 None, camel_to_snake() (+240 more)
+Cohesion: 0.01
+Nodes (284): generate_uuid(), batch_stock_in(), batch_stock_out(), consumable_code_exists(), create_consumable(), _dec(), get_consumable(), _is_enabled_status() (+276 more)
 
 ### Community 5 - "Community 5"
-Cohesion: 0.07
-Nodes (252): CellSampleTypeCreateBody, CellSampleTypeUpdateBody, CollectionRegionCreateBody, CollectionRegionUpdateBody, CustomerCreateBody, CustomerUpdateBody, HospitalCreateBody, HospitalUpdateBody (+244 more)
+Cohesion: 0.08
+Nodes (232): AuditCreateBody, BatchDetailIdsBody, BatchRefundFinanceSubmitBody, FinanceConfirmBody, FullmoonTodoInsuranceBody, InvoiceRequestCompleteBody, InvoiceRequestCreateBody, PaymentDetailDraftBody (+224 more)
 
 ### Community 6 - "Community 6"
 Cohesion: 0.02
-Nodes (225): list_ethnics_for_select(), AccessLogCreateBody, AccessLogUpdateBody, CleanRoomCreateBody, CleanRoomUpdateBody, create_access_log(), create_storage_slot(), create_uv_disinfect_log() (+217 more)
+Nodes (197): eq_unicode(), like_unicode(), MySQL 字符串比较/模糊匹配时统一排序规则,避免 Illegal mix of collations。, 跨表字符串等值比较(JOIN / WHERE)时统一为 utf8mb4_unicode_ci。, 将列显式设为 utf8mb4_unicode_ci。, unicode_ci(), batch_review(), _dec() (+189 more)
 
 ### Community 7 - "Community 7"
 Cohesion: 0.02
-Nodes (226): eq_unicode(), like_unicode(), MySQL 字符串比较/模糊匹配时统一排序规则,避免 Illegal mix of collations。, 跨表字符串等值比较(JOIN / WHERE)时统一为 utf8mb4_unicode_ci。, 将列显式设为 utf8mb4_unicode_ci。, unicode_ci(), batch_review(), _dec() (+218 more)
+Nodes (110): get_designer_data(), _error_json(), 数据库异常:返回 JSON 并保留 CORS 头,避免浏览器只报跨域错误, 未捕获异常:统一 JSON 500,确保跨域场景下仍有 Access-Control-Allow-Origin, sqlalchemy_exception_handler(), unhandled_exception_handler(), BaseHTTPMiddleware, ApiCryptoError (+102 more)
 
 ### Community 8 - "Community 8"
 Cohesion: 0.03
-Nodes (167): batch_discard(), batch_options(), _batch_row_to_dict(), batch_stock_in(), batch_stock_out(), _create_batch_and_usage(), delete_batch(), _discard_batch() (+159 more)
+Nodes (154): batch_discard(), batch_options(), _batch_row_to_dict(), batch_stock_in(), batch_stock_out(), _create_batch_and_usage(), delete_batch(), _discard_batch() (+146 more)
 
 ### Community 9 - "Community 9"
 Cohesion: 0.02
-Nodes (107): get_designer_data(), _error_json(), 数据库异常:返回 JSON 并保留 CORS 头,避免浏览器只报跨域错误, 未捕获异常:统一 JSON 500,确保跨域场景下仍有 Access-Control-Allow-Origin, sqlalchemy_exception_handler(), unhandled_exception_handler(), BaseHTTPMiddleware, ApiCryptoError (+99 more)
+Nodes (139): 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(), soft_delete_dict_type() (+131 more)
 
 ### Community 10 - "Community 10"
-Cohesion: 0.03
-Nodes (124): map_payments_by_schedule_ids(), 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() (+116 more)
+Cohesion: 0.05
+Nodes (93): _filter_material_type(), _amount_for_row(), _batch_map(), _deduct_step_usage_legacy_batch(), _display_remark(), _fmt_date_val(), list_available_requisitions_for_prep(), list_linked_step_usages() (+85 more)
 
 ### Community 11 - "Community 11"
-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.05
+Nodes (79): 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() (+71 more)
 
 ### Community 12 - "Community 12"
-Cohesion: 0.08
-Nodes (58): 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() (+50 more)
+Cohesion: 0.36
+Nodes (68): BatchStockInBody, BatchStockInItem, BatchStockOutBody, BatchStockOutItem, ConsumableCreateBody, ConsumablePackCreateBody, ConsumablePackUpdateBody, ConsumableUpdateBody (+60 more)
 
 ### Community 13 - "Community 13"
-Cohesion: 0.07
-Nodes (49): _jwt_user_payload(), Web 端登录用 RSA 公钥(PKCS#1 v1.5,与 JSEncrypt 一致), Web 端登录:账号为 sys_user.user_no,密码为 sys_user.password(bcrypt)。, refresh_token(), web_login(), web_login_rsa_public_key(), lifespan(), create_access_token() (+41 more)
+Cohesion: 0.06
+Nodes (66): auto_create_payment(), complete_application(), create_application(), create_basic_experiment_sale_schedules(), create_infusion_plan(), create_schedule(), _create_schedule_products(), _format_yyyy_mm_dd() (+58 more)
 
 ### Community 14 - "Community 14"
-Cohesion: 0.09
-Nodes (49): api_save_application(), auto_generate_schedules(), complete_application(), create_application(), create_basic_experiment_sale_schedules(), create_infusion_plan(), create_schedule(), _create_schedule_products() (+41 more)
+Cohesion: 0.07
+Nodes (61): 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() (+53 more)
 
 ### Community 15 - "Community 15"
+Cohesion: 0.07
+Nodes (49): _jwt_user_payload(), Web 端登录用 RSA 公钥(PKCS#1 v1.5,与 JSEncrypt 一致), Web 端登录:账号为 sys_user.user_no,密码为 sys_user.password(bcrypt)。, refresh_token(), web_login(), web_login_rsa_public_key(), lifespan(), create_access_token() (+41 more)
+
+### Community 16 - "Community 16"
 Cohesion: 0.11
 Nodes (35): batch_create_sterilize_logs(), batch_create_wash_logs(), batch_discard_garments(), batch_soft_delete_garments(), _count_map(), create_garment(), create_sterilize_log(), create_wash_log() (+27 more)
 
-### Community 16 - "Community 16"
+### Community 17 - "Community 17"
 Cohesion: 0.12
 Nodes (10): get_wechat_pay_client(), get_wechat_pay_v2(), 微信支付工具模块  支持微信支付 V3 API 文档参考: https://pay.weixin.qq.com/docs/v3/apis/index.ht, 统一下单接口 (V3)          Args:             out_trade_no: 商户订单号             total, 小程序调起支付的二次签名参数(与公众号 JSAPI 规则一致), 统一下单 (V2 API)          Args:             out_trade_no: 商户订单号             tot, 生成签名 V3 (SHA256 with RSA), wechat_pay() (+2 more)
 
-### Community 17 - "Community 17"
-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 18 - "Community 18"
-Cohesion: 0.21
-Nodes (15): batch_generate_protocol_nos(), batch_issue_protocol_nos(), batch_revoke_protocol_nos(), create_protocol_no(), get_protocol_no(), list_protocol_nos(), _parse_issued_time(), protocol_no_exists() (+7 more)
+Cohesion: 0.19
+Nodes (27): _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() (+19 more)
 
 ### Community 19 - "Community 19"
-Cohesion: 0.22
-Nodes (14): batch_generate_sample_codes(), batch_issue_sample_codes(), batch_revoke_sample_codes(), create_sample_code(), get_sample_code(), list_sample_codes(), _parse_issued_time(), 样本编码池 CRUD(biz_sample_code) (+6 more)
+Cohesion: 0.15
+Nodes (20): _access_log_to_dict(), _calc_duration_minutes(), create_access_log(), create_clean_room(), create_uv_disinfect_log(), get_access_log(), get_clean_room(), get_uv_disinfect_log() (+12 more)
 
 ### Community 20 - "Community 20"
-Cohesion: 0.22
-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.2
+Nodes (19): _code_exists(), create_material(), _dec(), _fmt_dt(), get_material(), get_material_entity(), get_my_usages(), _is_enabled_status() (+11 more)
 
 ### Community 21 - "Community 21"
-Cohesion: 0.24
-Nodes (9): 微信消息通知服务  支持微信小程序模板消息推送  使用方式:     from app.core.wechat_message import Wech, 发送模板消息                  Args:             openid: 用户openid             templ, send_delivery_notice(), send_delivery_warning(), send_order_status(), send_payment_success(), send_refund_result(), send_urge_notification() (+1 more)
+Cohesion: 0.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)
 
 ### Community 22 - "Community 22"
-Cohesion: 0.23
-Nodes (12): Config, ProductBase, ProductCategoryBase, ProductCategoryCreate, ProductCategoryResponse, ProductCreate, ProductResponse, ProductUnitBase (+4 more)
+Cohesion: 0.27
+Nodes (11): _accept_conclusion_label(), _base_query(), _ensure_notification(), _fmt_dt(), generate_sms_content(), list_notifications(), _normalize_date_bound(), resolve_notification() (+3 more)
 
 ### Community 23 - "Community 23"
+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 24 - "Community 24"
 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 24 - "Community 24"
+### Community 25 - "Community 25"
+Cohesion: 0.24
+Nodes (12): appointment_no_exists(), create_exhibition_appointment(), _format_visit_date(), _generate_appointment_no(), get_exhibition_appointment(), list_exhibition_appointments(), 展览预约 CRUD(biz_exhibition_appointment), 生成预约编号:YYYYMMDD + 6位序号(如 20260515000001) (+4 more)
+
+### Community 26 - "Community 26"
 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 25 - "Community 25"
+### Community 27 - "Community 27"
 Cohesion: 0.24
 Nodes (11): deduct_for_step(), _display_remark(), list_stock_logs(), _parse_receiver_purpose_from_remark(), 耗材变动记录 CRUD(biz_consumable_stock_log), 撤销步骤此前 step_save 扣减,便于重新填报。, 扣减耗材库存并生成变动记录。调用方控制事务,本函数不 commit。, _resolve_receiver_purpose() (+3 more)
 
-### Community 26 - "Community 26"
+### Community 28 - "Community 28"
 Cohesion: 0.27
 Nodes (11): _build_tree(), _cascade_soft_delete(), _collect_with_ancestors(), create_dept(), get_dept(), list_depts(), list_depts_tree(), 保留匹配节点及其全部上级,供关键词检索后仍能正确组树。 (+3 more)
 
-### Community 27 - "Community 27"
+### Community 29 - "Community 29"
 Cohesion: 0.29
 Nodes (9): _build_tree(), _cascade_soft_delete(), create_menu(), get_menu(), list_menus(), list_menus_tree(), _row_to_dict(), soft_delete_menu() (+1 more)
 
-### Community 28 - "Community 28"
-Cohesion: 0.27
-Nodes (8): create_customer(), get_customer(), id_card_taken(), list_customers(), 客户 CRUD(biz_customer), _row_to_dict(), soft_delete_customer(), update_customer()
-
-### Community 29 - "Community 29"
+### Community 30 - "Community 30"
 Cohesion: 0.31
 Nodes (8): create_express_company(), express_code_exists(), get_express_company(), list_express_companies(), 快递公司 CRUD(biz_express_company), _row_to_dict(), soft_delete_express_company(), update_express_company()
 
-### Community 30 - "Community 30"
+### Community 31 - "Community 31"
 Cohesion: 0.38
 Nodes (9): create_check_item(), get_check_item(), item_code_exists(), list_active_check_items(), list_check_items(), _order_query(), _row_to_dict(), soft_delete_check_item() (+1 more)
 
-### Community 31 - "Community 31"
-Cohesion: 0.27
-Nodes (8): create_dict_data(), get_dict_data(), list_dict_data(), list_dict_data_options(), 字典数据 CRUD(sys_dict_data), _row_to_dict(), soft_delete_dict_data(), update_dict_data()
-
 ### Community 32 - "Community 32"
 Cohesion: 0.38
 Nodes (9): create_env_detect_item(), get_env_detect_item(), item_code_exists(), list_active_env_detect_items(), list_env_detect_items(), _order_query(), _row_to_dict(), soft_delete_env_detect_item() (+1 more)
@@ -231,115 +229,107 @@ Cohesion: 0.31
 Nodes (8): create_item_unit(), get_item_unit(), list_item_units(), 物品单位 CRUD(biz_item_unit), _row_to_dict(), soft_delete_item_unit(), unit_code_exists(), update_item_unit()
 
 ### Community 35 - "Community 35"
-Cohesion: 0.31
-Nodes (8): create_sop_document(), get_sop_document(), list_sop_documents(), SOP文档 CRUD(biz_sop_document), _row_to_dict(), soft_delete_sop_document(), sop_code_exists(), update_sop_document()
-
-### Community 36 - "Community 36"
-Cohesion: 0.31
-Nodes (8): create_cell_sample_type(), get_cell_sample_type(), list_cell_sample_types(), 细胞样本类型 CRUD(biz_cell_sample_type), _row_to_dict(), soft_delete_cell_sample_type(), type_code_exists(), update_cell_sample_type()
-
-### Community 37 - "Community 37"
-Cohesion: 0.31
-Nodes (8): create_output_type(), get_output_type(), list_output_types(), 工艺产出物类型 CRUD(biz_output_type), _row_to_dict(), soft_delete_output_type(), type_code_exists(), update_output_type()
-
-### Community 38 - "Community 38"
 Cohesion: 0.36
 Nodes (7): create_post(), get_post(), list_posts(), post_code_exists(), _row_to_dict(), soft_delete_post(), update_post()
 
-### Community 39 - "Community 39"
+### 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 40 - "Community 40"
+### Community 37 - "Community 37"
+Cohesion: 0.39
+Nodes (8): create_lab_item(), get_lab_item(), item_code_exists(), list_active_lab_items(), list_lab_items(), _row_to_dict(), soft_delete_lab_item(), update_lab_item()
+
+### 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 41 - "Community 41"
+### Community 39 - "Community 39"
 Cohesion: 0.29
 Nodes (2): BaseModel, SoftDeleteModel
 
-### Community 42 - "Community 42"
+### Community 40 - "Community 40"
 Cohesion: 0.38
 Nodes (6): derive_legacy_status_from_three(), derive_three_status_from_legacy(), 阶段A兼容:从旧 status 推导三字段。     约束:旧 status 本身混合了支付/配送/订单生命周期,因此只能尽力推断;     若业务侧已有更精确, 阶段A兼容:从三字段反推旧 status(用于旧前端/旧筛选参数兼容)。, ThreeStatus, _to_decimal()
 
-### Community 43 - "Community 43"
+### Community 41 - "Community 41"
 Cohesion: 0.5
 Nodes (3): BaseSettings, Config, Settings
 
-### Community 44 - "Community 44"
+### Community 42 - "Community 42"
 Cohesion: 0.5
 Nodes (2): _auto_fill_create_dept_id(), ORM before_insert 钩子:当 create_dept_id 为空时,自动从请求上下文填充当前用户部门 ID
 
-### Community 45 - "Community 45"
+### Community 43 - "Community 43"
 Cohesion: 0.67
 Nodes (2): get_menu_list(), 运营端菜单接口(与 web-admin 前端静态路由 sqxp.ts 对齐,5 个一级菜单分组)
 
-### Community 46 - "Community 46"
+### Community 44 - "Community 44"
 Cohesion: 1.0
 Nodes (1): 请求级别的用户上下文变量  通过 contextvars 在请求处理链路中传递当前用户信息, 供 SQLAlchemy 事件监听器等底层机制使用,避免逐层手动传
 
-### Community 47 - "Community 47"
+### Community 45 - "Community 45"
 Cohesion: 1.0
 Nodes (1): API 路由包(骨架:公共鉴权 + 静态菜单 + 运营端占位)
 
-### Community 50 - "Community 50"
+### Community 48 - "Community 48"
 Cohesion: 1.0
 Nodes (1): 发送订单状态通知                  Args:             openid: 用户openid             ord
 
-### Community 51 - "Community 51"
+### Community 49 - "Community 49"
 Cohesion: 1.0
 Nodes (1): 发送支付成功通知                  Args:             openid: 用户openid             ord
 
-### Community 52 - "Community 52"
+### Community 50 - "Community 50"
 Cohesion: 1.0
 Nodes (1): 发送配送通知                  Args:             openid: 配送员openid             task
 
-### Community 53 - "Community 53"
+### Community 51 - "Community 51"
 Cohesion: 1.0
 Nodes (1): 发送配送时效预警                  Args:             openid: 配送员openid             ta
 
-### Community 54 - "Community 54"
+### Community 52 - "Community 52"
 Cohesion: 1.0
 Nodes (1): 发送退款结果通知                  Args:             openid: 用户openid             ord
 
-### Community 55 - "Community 55"
+### Community 53 - "Community 53"
 Cohesion: 1.0
 Nodes (1): 发送催单通知给配送员                  Args:             deliver_openid: 配送员openid
 
 ## Knowledge Gaps
-- **129 isolated node(s):** `刷新会话 TTL(refreshToken 续期;已登录用户每次 API 校验通过后滑动续期)`, `写入 refreshToken(用于 one-time 使用/吊销)`, `根据 token 获取当前用户(包含 Redis 会话校验;会话滑动续期)`, `登录凭据 RSA 解密(与前端 JSEncrypt PKCS#1 v1.5 兼容)。`, `加载 RSA 密钥对:优先环境变量,否则使用内置固定默认密钥。` (+124 more)
+- **131 isolated node(s):** `刷新会话 TTL(refreshToken 续期;已登录用户每次 API 校验通过后滑动续期)`, `写入 refreshToken(用于 one-time 使用/吊销)`, `根据 token 获取当前用户(包含 Redis 会话校验;会话滑动续期)`, `登录凭据 RSA 解密(与前端 JSEncrypt PKCS#1 v1.5 兼容)。`, `加载 RSA 密钥对:优先环境变量,否则使用内置固定默认密钥。` (+126 more)
   These have ≤1 connection - possible missing edges or undocumented components.
-- **Thin community `Community 41`** (8 nodes): `BaseModel`, `.to_dict()`, `get_active()`, `base.py`, `SoftDeleteModel`, `.restore()`, `.soft_delete()`, `__tablename__()`
+- **Thin community `Community 39`** (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 44`** (4 nodes): `_auto_fill_create_dept_id()`, `get_db()`, `database.py`, `ORM before_insert 钩子:当 create_dept_id 为空时,自动从请求上下文填充当前用户部门 ID`
+- **Thin community `Community 42`** (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 45`** (3 nodes): `get_menu_list()`, `menu.py`, `运营端菜单接口(与 web-admin 前端静态路由 sqxp.ts 对齐,5 个一级菜单分组)`
+- **Thin community `Community 43`** (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 46`** (2 nodes): `context.py`, `请求级别的用户上下文变量  通过 contextvars 在请求处理链路中传递当前用户信息, 供 SQLAlchemy 事件监听器等底层机制使用,避免逐层手动传`
+- **Thin community `Community 44`** (2 nodes): `context.py`, `请求级别的用户上下文变量  通过 contextvars 在请求处理链路中传递当前用户信息, 供 SQLAlchemy 事件监听器等底层机制使用,避免逐层手动传`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 47`** (2 nodes): `__init__.py`, `API 路由包(骨架:公共鉴权 + 静态菜单 + 运营端占位)`
+- **Thin community `Community 45`** (2 nodes): `__init__.py`, `API 路由包(骨架:公共鉴权 + 静态菜单 + 运营端占位)`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 50`** (1 nodes): `发送订单状态通知                  Args:             openid: 用户openid             ord`
+- **Thin community `Community 48`** (1 nodes): `发送订单状态通知                  Args:             openid: 用户openid             ord`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 51`** (1 nodes): `发送支付成功通知                  Args:             openid: 用户openid             ord`
+- **Thin community `Community 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 52`** (1 nodes): `发送配送通知                  Args:             openid: 配送员openid             task`
+- **Thin community `Community 50`** (1 nodes): `发送配送通知                  Args:             openid: 配送员openid             task`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 53`** (1 nodes): `发送配送时效预警                  Args:             openid: 配送员openid             ta`
+- **Thin community `Community 51`** (1 nodes): `发送配送时效预警                  Args:             openid: 配送员openid             ta`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 54`** (1 nodes): `发送退款结果通知                  Args:             openid: 用户openid             ord`
+- **Thin community `Community 52`** (1 nodes): `发送退款结果通知                  Args:             openid: 用户openid             ord`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
-- **Thin community `Community 55`** (1 nodes): `发送催单通知给配送员                  Args:             deliver_openid: 配送员openid`
+- **Thin community `Community 53`** (1 nodes): `发送催单通知给配送员                  Args:             deliver_openid: 配送员openid`
   Too small to be a meaningful cluster - may be noise or needs more connections extracted.
 
 ## Suggested Questions
 _Questions this graph is uniquely positioned to answer:_
 
-- **Why does `SysUser` connect `Community 1` to `Community 0`, `Community 2`, `Community 3`, `Community 5`, `Community 6`, `Community 7`, `Community 8`, `Community 9`, `Community 10`, `Community 12`, `Community 13`, `Community 18`, `Community 19`, `Community 24`, `Community 25`?**
-  _High betweenness centrality (0.109) - this node is a cross-community bridge._
-- **Why does `generate_uuid()` connect `Community 3` to `Community 0`, `Community 4`, `Community 5`, `Community 6`, `Community 7`, `Community 8`, `Community 9`, `Community 10`, `Community 11`, `Community 12`, `Community 13`, `Community 15`, `Community 17`, `Community 18`, `Community 19`, `Community 20`, `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`, `Community 39`, `Community 40`?**
-  _High betweenness centrality (0.098) - this node is a cross-community bridge._
-- **Why does `success_response()` connect `Community 0` to `Community 1`, `Community 6`, `Community 9`, `Community 11`, `Community 12`, `Community 45`, `Community 13`, `Community 14`?**
+- **Why does `generate_uuid()` connect `Community 4` to `Community 0`, `Community 2`, `Community 5`, `Community 6`, `Community 7`, `Community 8`, `Community 9`, `Community 10`, `Community 11`, `Community 14`, `Community 15`, `Community 16`, `Community 18`, `Community 19`, `Community 20`, `Community 21`, `Community 22`, `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.104) - this node is a cross-community bridge._
+- **Why does `SysUser` connect `Community 1` to `Community 2`, `Community 3`, `Community 5`, `Community 6`, `Community 7`, `Community 8`, `Community 9`, `Community 10`, `Community 11`, `Community 12`, `Community 15`, `Community 26`, `Community 27`?**
+  _High betweenness centrality (0.087) - this node is a cross-community bridge._
+- **Why does `success_response()` connect `Community 0` to `Community 1`, `Community 2`, `Community 7`, `Community 11`, `Community 43`, `Community 14`, `Community 15`?**
   _High betweenness centrality (0.051) - this node is a cross-community bridge._
 - **Are the 714 inferred relationships involving `str` (e.g. with `unhandled_exception_handler()` and `get_current_user_by_token()`) actually correct?**
   _`str` has 714 INFERRED edges - model-reasoned connections that need verification._

Різницю між файлами не показано, бо вона завелика
+ 1 - 1
backend/graphify-out/graph.html


Різницю між файлами не показано, бо вона завелика
+ 112 - 112
backend/graphify-out/graph.json


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

@@ -18,7 +18,7 @@
   "/Users/wuzilong/work/00code/zkxw/backend/app/core/db_collation.py": 1779890253.2250955,
   "/Users/wuzilong/work/00code/zkxw/backend/app/core/context.py": 1779242263.6923044,
   "/Users/wuzilong/work/00code/zkxw/backend/app/core/utils.py": 1780305790.0009735,
-  "/Users/wuzilong/work/00code/zkxw/backend/app/core/storage.py": 1779159580.3795292,
+  "/Users/wuzilong/work/00code/zkxw/backend/app/core/storage.py": 1782437555.6682527,
   "/Users/wuzilong/work/00code/zkxw/backend/app/core/order_status_compat.py": 1778578258.0426462,
   "/Users/wuzilong/work/00code/zkxw/backend/app/core/exceptions.py": 1778578258.0413945,
   "/Users/wuzilong/work/00code/zkxw/backend/app/core/data_scope.py": 1779266701.8721697,
@@ -147,7 +147,7 @@
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/customer.py": 1779686149.7508218,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/workflow.py": 1779265993.677624,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/sample_sms_notification.py": 1781063495.0317714,
-  "/Users/wuzilong/work/00code/zkxw/backend/app/crud/sample_reception.py": 1782377432.963105,
+  "/Users/wuzilong/work/00code/zkxw/backend/app/crud/sample_reception.py": 1782441545.1748214,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/consumable_pack.py": 1779265715.416005,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_inspection_batch.py": 1782384683.8310702,
   "/Users/wuzilong/work/00code/zkxw/backend/app/crud/production_material_prep.py": 1781514953.3551314,
@@ -173,7 +173,7 @@
   "/Users/wuzilong/work/00code/zkxw/backend/app/api/public.py": 1782122639.96012,
   "/Users/wuzilong/work/00code/zkxw/backend/app/api/material.py": 1781522000.6097813,
   "/Users/wuzilong/work/00code/zkxw/backend/app/api/production_material.py": 1782404982.2474263,
-  "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py": 1782405787.3654518,
+  "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py": 1782405946.29283,
   "/Users/wuzilong/work/00code/zkxw/backend/app/api/market.py": 1781522283.6823432,
   "/Users/wuzilong/work/00code/zkxw/backend/app/api/collection_protocol.py": 1782195334.2335806,
   "/Users/wuzilong/work/00code/zkxw/backend/app/api/cleanroom_garment.py": 1781347256.2326367,

+ 2 - 1
deploy/docker/nginx/default.conf

@@ -49,7 +49,8 @@ server {
     location ^~ /minio/zkxw/ {
         proxy_pass http://minio:9000/zkxw/;
         proxy_http_version 1.1;
-        proxy_set_header Host $http_host;
+        # 预签名 URL 按 MINIO_ENDPOINT(minio:9000)签名,Host 须与签名一致
+        proxy_set_header Host minio:9000;
         proxy_set_header X-Real-IP $remote_addr;
         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
         proxy_set_header X-Forwarded-Proto $scheme;

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

@@ -14859,7 +14859,7 @@
       "label": "if()",
       "file_type": "code",
       "source_file": "views/quality-inspection/qc-work-order/index.vue",
-      "source_location": "L459"
+      "source_location": "L462"
     },
     {
       "id": "views_quality_inspection_qc_work_order_qcworkordertaskresultpanel_vue",
@@ -44908,7 +44908,7 @@
       "relation": "contains",
       "confidence": "EXTRACTED",
       "source_file": "views/quality-inspection/qc-work-order/index.vue",
-      "source_location": "L459",
+      "source_location": "L462",
       "weight": 1.0
     },
     {

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

@@ -1,7 +1,7 @@
 # Graph Report - src  (2026-06-26)
 
 ## Corpus Check
-- Large corpus: 811 files · ~539,420 words. Semantic extraction will be expensive (many Claude tokens). Consider running on a subfolder, or use --no-semantic to run AST-only.
+- Large corpus: 811 files · ~539,436 words. Semantic extraction will be expensive (many Claude tokens). Consider running on a subfolder, or use --no-semantic to run AST-only.
 
 ## Summary
 - 2705 nodes · 3770 edges · 58 communities detected

+ 4 - 4
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": 1782406001.435002,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/auto-imports.d.ts": 1782405955.9372323,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/components.d.ts": 1782441725.6780097,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/auto-imports.d.ts": 1782441592.6088834,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/store/index.ts": 1781272556.364766,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/router/index.ts": 1782394569.8806834,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/locales/index.ts": 1781272556.3657222,
@@ -421,11 +421,11 @@
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process-category/index.vue": 1781585388.7112913,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process-category/OutputTypeTab.vue": 1781563767.348404,
   "/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": 1782394624.8193831,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue": 1782441907.192986,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskResultPanel.vue": 1782398341.1529968,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderProtocolGroup.vue": 1782400499.8822792,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue": 1782399918.5252447,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderConfigureBatches.vue": 1782386066.318798,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderConfigureBatches.vue": 1782441905.7874374,
   "/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": 1782399917.944618,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue": 1782392417.2151616,

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

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L1"}, {"id": "qc_work_order_index_if", "label": "if()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L462"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_index_vue", "target": "qc_work_order_index_if", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L462", "weight": 1.0}], "raw_calls": [{"caller_nid": "qc_work_order_index_if", "callee": "warning", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L463"}]}

+ 1 - 0
web-admin/src/graphify-out/cache/ast/556b7994a7205938cb9f64f46dfca0703f6b28fa8d0839a8846b577ef7efadbe.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/ce89b93417b570f9eb167eeb263cca11f6a7521a04941ab19a86778e7493edcd.json

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

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

+ 54 - 54
web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderConfigureBatches.vue

@@ -48,6 +48,59 @@
           </el-form-item>
         </div>
 
+        <section class="qc-batch-config__selected">
+          <div class="qc-batch-config__selected-head">
+            <span class="qc-batch-config__selected-title">已选项目</span>
+            <span class="qc-batch-config__selected-count">
+              共 {{ batchSelectedCount(batch) }} 项
+            </span>
+          </div>
+          <div v-if="batchSelectedCount(batch)" class="qc-batch-config__selected-body">
+            <div v-if="selectedLabItems(batch).length" class="qc-batch-config__selected-group">
+              <span class="qc-batch-config__selected-label">实验室自检</span>
+              <div class="qc-batch-config__selected-tags">
+                <el-tag
+                  v-for="item in selectedLabItems(batch)"
+                  :key="'sel-lab-' + item.id"
+                  type="success"
+                  size="small"
+                  closable
+                  @close="removeLabItem(batch, item.id)"
+                >
+                  {{ item.itemName }}
+                </el-tag>
+              </div>
+            </div>
+            <div v-if="selectedThirdGroups(batch).length" class="qc-batch-config__selected-third">
+              <span class="qc-batch-config__selected-label">第三方检测</span>
+              <div class="qc-batch-config__selected-third-groups">
+                <div
+                  v-for="group in selectedThirdGroups(batch)"
+                  :key="'sel-company-' + group.companyId"
+                  class="qc-batch-config__selected-company"
+                >
+                  <span class="qc-batch-config__selected-company-name">{{
+                    group.companyName || '未命名公司'
+                  }}</span>
+                  <div class="qc-batch-config__selected-tags">
+                    <el-tag
+                      v-for="item in group.items"
+                      :key="'sel-third-' + item.id"
+                      type="primary"
+                      size="small"
+                      closable
+                      @close="removeThirdItem(batch, item.id)"
+                    >
+                      {{ item.itemName }}
+                    </el-tag>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+          <p v-else class="qc-batch-config__selected-empty hint-muted">暂未选择检测项目</p>
+        </section>
+
         <div class="qc-config-split">
           <div class="qc-config-split__panel">
             <div class="qc-config-split__title">实验室自检项目</div>
@@ -109,59 +162,6 @@
             </div>
           </div>
         </div>
-
-        <section class="qc-batch-config__selected">
-          <div class="qc-batch-config__selected-head">
-            <span class="qc-batch-config__selected-title">已选项目</span>
-            <span class="qc-batch-config__selected-count">
-              共 {{ batchSelectedCount(batch) }} 项
-            </span>
-          </div>
-          <div v-if="batchSelectedCount(batch)" class="qc-batch-config__selected-body">
-            <div v-if="selectedLabItems(batch).length" class="qc-batch-config__selected-group">
-              <span class="qc-batch-config__selected-label">实验室自检</span>
-              <div class="qc-batch-config__selected-tags">
-                <el-tag
-                  v-for="item in selectedLabItems(batch)"
-                  :key="'sel-lab-' + item.id"
-                  type="success"
-                  size="small"
-                  closable
-                  @close="removeLabItem(batch, item.id)"
-                >
-                  {{ item.itemName }}
-                </el-tag>
-              </div>
-            </div>
-            <div v-if="selectedThirdGroups(batch).length" class="qc-batch-config__selected-third">
-              <span class="qc-batch-config__selected-label">第三方检测</span>
-              <div class="qc-batch-config__selected-third-groups">
-                <div
-                  v-for="group in selectedThirdGroups(batch)"
-                  :key="'sel-company-' + group.companyId"
-                  class="qc-batch-config__selected-company"
-                >
-                  <span class="qc-batch-config__selected-company-name">{{
-                    group.companyName || '未命名公司'
-                  }}</span>
-                  <div class="qc-batch-config__selected-tags">
-                    <el-tag
-                      v-for="item in group.items"
-                      :key="'sel-third-' + item.id"
-                      type="primary"
-                      size="small"
-                      closable
-                      @close="removeThirdItem(batch, item.id)"
-                    >
-                      {{ item.itemName }}
-                    </el-tag>
-                  </div>
-                </div>
-              </div>
-            </div>
-          </div>
-          <p v-else class="qc-batch-config__selected-empty hint-muted">暂未选择检测项目</p>
-        </section>
       </el-tab-pane>
     </el-tabs>
   </div>
@@ -460,7 +460,7 @@
   }
 
   .qc-batch-config__selected {
-    margin-top: 16px;
+    margin-bottom: 16px;
     padding: 12px 14px;
     border: 1px solid #e2e8f0;
     border-radius: 8px;

+ 4 - 1
web-admin/src/views/quality-inspection/qc-work-order/index.vue

@@ -191,6 +191,9 @@
 
   defineOptions({ name: 'QualityInspectionQcWorkOrder' })
 
+  /** 批次负责人下拉:仅展示拥有该角色的用户 */
+  const QC_BATCH_ASSIGNEE_ROLE = '质检专员'
+
   type QcWorkOrderTabPanelExpose = {
     reload: () => Promise<void>
     setKeyword: (keyword: string) => void
@@ -504,7 +507,7 @@
     const [labList, thirdList, users] = await Promise.all([
       fetchQcLabTestItemsActive(),
       fetchQcThirdPartyItemsActive(),
-      fetchUserOptions().catch(() => [])
+      fetchUserOptions({ roleName: QC_BATCH_ASSIGNEE_ROLE }).catch(() => [])
     ])
     labOptions.value = labList || []
     thirdOptions.value = thirdList || []

Деякі файли не було показано, через те що забагато файлів було змінено