From 6d910029b6c81db0a2b66caa66952781397b153a Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期六, 29 八月 2026 13:45:52 +0800
Subject: [PATCH] feat(mes): 实现H5溯源页面与二维码内容集成

---
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java                |   88 ++
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/dto/MesWmTransactionSaveReqDTO.java                 |   10 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesProperties.java                                        |   21 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warning/vo/MesWmStockWarningPageReqVO.java             |   27 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesQrCodeConfig.java                                      |   22 
 yudao-server/src/main/resources/application.yaml                                                                                      |    5 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/trace/MesProTracePublicServiceImpl.java                            |  348 ++++++++
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskService.java               |    9 
 tmp_img_view.png                                                                                                                      |    0 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/pub/trace/vo/MesProTracePublicRespVO.java                       |  212 ++++
 yudao-server/src/main/resources/application-jhhg.yaml                                                                                 |    4 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmStockWarningTypeEnum.java                                    |   19 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warning/vo/MesWmStockWarningHandleReqVO.java           |   18 
 docs/warehouse_stock_management_integration.md                                                                                        |  202 ++++
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/warning/MesWmStockWarningMapper.java                          |   25 
 yudao-server/src/main/resources/static/trace.html                                                                                     |  433 ++++++++++
 yudao-server/src/main/resources/application-dev.yaml                                                                                  |    4 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warning/vo/MesWmStockWarningRespVO.java                |   70 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/vo/MesWmStockSummaryRespVO.java          |   42 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/materialstock/MesWmMaterialStockMapper.java                   |   15 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/seedquality/MesQcSeedQualityServiceImpl.java                    |   47 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/warning/MesWmStockWarningServiceImpl.java                       |  237 +++++
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java                                              |    8 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/trace/MesProTracePublicService.java                                |   21 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/seedquality/MesQcSeedQualityService.java                        |   28 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/MesWmStockTakingTaskController.java   |   16 
 yudao-server/src/main/resources/application-yzfx.yaml                                                                                 |    4 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockService.java                    |   35 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/MesWmTransactionServiceImpl.java                    |    9 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/scan/MesProScanEventMapper.java                              |    8 
 yudao-server/src/main/resources/application-thzy.yaml                                                                                 |    6 
 yudao-server/src/main/resources/application-test.yaml                                                                                 |    4 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesServiceImpl.java                  |   21 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskServiceImpl.java           |   86 ++
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/scan/MesProScanEventService.java                               |    9 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/qrcode/MesProQrLabelBuilder.java                                 |   33 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchService.java                                    |   12 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warning/MesWmStockWarningController.java               |   60 +
 yudao-server/src/main/resources/application-local.yaml                                                                                |    4 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/vo/MesWmStockTakingTaskScanReqVO.java |   29 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/warning/MesWmStockWarningDO.java                         |  105 ++
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/seedquality/MesQcSeedQualityMapper.java                       |    8 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/stocktaking/task/MesWmStockTakingTaskLineMapper.java          |    7 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchServiceImpl.java                                |   26 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/warning/MesWmStockWarningService.java                           |   46 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/scan/MesProScanEventServiceImpl.java                           |   17 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/pub/trace/MesProTracePublicController.java                      |   41 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/MesWmMaterialStockController.java        |   26 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/seedquality/MesQcSeedQualityIndicatorMapper.java              |   11 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/job/wm/MesWmStockWarningJob.java                                           |   28 
 50 files changed, 2,547 insertions(+), 19 deletions(-)

diff --git a/docs/warehouse_stock_management_integration.md b/docs/warehouse_stock_management_integration.md
new file mode 100644
index 0000000..8fce5f9
--- /dev/null
+++ b/docs/warehouse_stock_management_integration.md
@@ -0,0 +1,202 @@
+# 浠撳偍浼樺寲 - 涓夊簱瀛樼姸鎬佹祦杞� + 棰勮/姹囨��/鐩樼偣 鍓嶇鑱旇皟鏂规
+
+## 娑夊強椤甸潰
+
+- 搴撳瓨鍙拌处锛坄/mes/wm/material-stock/page`锛�
+- 鐢熶骇绾挎壂鐮佸叆搴擄紙`/mes/pro/scan-in`锛�
+- 绉嶅瓙璐ㄦ锛堢瀛愯川妫�鎻愪氦鐣岄潰锛�
+- 閿�鍞嚭搴擄紙閿�鍞壂鐮佹嫞璐э級
+- 搴撳瓨棰勮锛堟柊澧為〉闈級
+- 搴撳瓨澶氱淮姹囨�伙紙鏂板椤甸潰/闈㈡澘锛�
+- PDA 鎵爜鐩樼偣锛堢洏鐐逛换鍔℃壂鐮佺晫闈級
+
+## 涓氬姟瑙勫垯锛氫笁搴撳瓨鐘舵�佹祦杞�
+
+| 鎿嶄綔 | 搴撳瓨鐘舵�佸彉鍖� | 璇存槑 |
+|------|-------------|------|
+| 鐢熶骇绾挎壂鐮佸叆搴� | 鈫� 鏆傚瓨搴擄紙TEMP=10锛� | 鎵爜琚嬬爜鍏ュ簱鎴愬姛鍚庯紝搴撳瓨杩涘叆鏆傚瓨搴擄紙寰呰川妫�锛夛紝涓嶅啀鐩存帴杩涘叆鍚堟牸搴� |
+| 绉嶅瓙璐ㄦ鎻愪氦銆屽悎鏍笺�� | 鏆傚瓨搴� 鈫� 鍚堟牸搴擄紙QUALIFIED=20锛� | 璐ㄦ缁撴灉=鍚堟牸鏃讹紝鎸夋壒娆″皢鏆傚瓨搴撳簱瀛樻暣鎵硅浆涓哄悎鏍煎簱锛堟寮忓簱瀛橈級 |
+| 閿�鍞壂鐮佸嚭搴� | 鍚堟牸搴� 鈫� 宸插嚭搴擄紙OUT=30锛� | 鎵e噺鍚堟牸搴撳簱瀛橈紝鏁伴噺鈮�0 鏃剁姸鎬佽嚜鍔ㄥ彉涓哄凡鍑哄簱 |
+| 涓嶅悎鏍硷紙璐ㄦ缁撴灉=涓嶅悎鏍硷級 | 鐣欏湪鏆傚瓨搴� | 鏈湡涓嶅仛閫�璐�/鎶ュ簾娴侊紝鐢卞悗缁汉宸ュ鐞� |
+
+> **鏍稿績鍙樺寲**锛氱敓浜х嚎鎵爜鍏ュ簱浠庛�岀洿鎺ヨ繘鍚堟牸搴撱�嶆敼涓恒�屽厛杩涙殏瀛樺簱寰呰川妫�銆嶏紝璐ㄦ鍚堟牸鍚庢墠杩涘叆鍚堟牸搴撱�傚墠绔渶鍦ㄥ簱瀛樺彴璐﹂〉鎸夌姸鎬佺淮搴﹀尯鍒嗗睍绀轰笁涓簱銆�
+
+### 鐢熶骇绾挎壂鐮佸叆搴擄紙鍏抽敭淇锛屽墠绔棤鍙傚彉鍖栵級
+
+- 鎺ュ彛锛歚POST /mes/pro/scan-in/inbound`锛岃姹傚弬鏁�**淇濇寔涓嶅彉**銆�
+- 鍚庣鍐呴儴鍙樺寲锛氭墦閫氱敓浜ф壒娆′綋绯讳笌浠撳簱鎵规浣撶郴锛屽叆搴撴椂鑷姩妗ユ帴浠撳簱鎵规骞跺啓鍏� `batchId`锛屽簱瀛樿繘鍏�**鏆傚瓨搴�**銆�
+- 鍓嶇褰卞搷锛氬叆搴撴垚鍔熷悗锛屽簱瀛樹笉浣撶幇鍦ㄣ�屽悎鏍煎簱銆嶇瓫閫変笅锛岃�屾槸浣撶幇鍦ㄣ�屾殏瀛樺簱銆嶇瓫閫変笅锛涢渶绛夊緟绉嶅瓙璐ㄦ鎻愪氦鍚堟牸鍚庢墠浼氬嚭鐜板湪鍚堟牸搴撱��
+
+## API
+
+### 涓�銆佸簱瀛樺彴璐︼紙宸叉湁鎺ュ彛锛屽缓璁寮烘煡璇㈡潯浠跺睍绀猴級
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /mes/wm/material-stock/page | 搴撳瓨鍙拌处鍒嗛〉锛屾柊澧炴寜 `stockState` 杩囨护 |
+
+**璇锋眰鍙傛暟锛堟柊澧�/寮鸿皟锛夛細**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| stockState | Integer | 鍚� | 搴撳瓨鐘舵�侊細10-鏆傚瓨搴擄紝20-鍚堟牸搴擄紝30-宸插嚭搴� |
+| itemId | Long | 鍚� | 鍝佺锛堢墿鏂欙級ID |
+| batchCode | String | 鍚� | 鎵规鍙� |
+| warehouseId | Long | 鍚� | 浠撳簱 ID |
+
+**鍝嶅簲瀛楁锛堝凡鏈夛紝鏃犻渶鏂板锛夛細** 鍚� itemCode銆乮temName銆乥atchCode銆亀arehouseName銆乹uantity銆乻tockState 绛夈��
+
+### 浜屻�佸簱瀛樺缁存眹鎬伙紙鏂板锛�
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /mes/wm/material-stock/summary-by-item | 鎸夊搧绉嶆眹鎬绘暟閲� |
+| GET | /mes/wm/material-stock/summary-by-batch | 鎸夋壒娆℃眹鎬绘暟閲� |
+| GET | /mes/wm/material-stock/summary-by-warehouse | 鎸変粨搴撴眹鎬绘暟閲� |
+
+**璇锋眰鍙傛暟锛堝潎鍙�夛紝澶嶇敤搴撳瓨鏌ヨ鏉′欢锛夛細**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| stockState | Integer | 鍚� | 搴撳瓨鐘舵�佽繃婊わ紝涓嶄紶鍒欐寜鐘舵�佸垎寮�灞曠ず |
+| itemId | Long | 鍚� | 鍝佺杩囨护 |
+| batchCode | String | 鍚� | 鎵规鍙疯繃婊� |
+| warehouseId | Long | 鍚� | 浠撳簱杩囨护 |
+| areaId / locationId | Long | 鍚� | 搴撲綅/搴撳尯杩囨护 |
+
+**鍝嶅簲瀛楁锛�**
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| itemId / itemCode / itemName | Long/String/String | 鍝佺缁村害锛堟寜鍝佺姹囨�绘椂杩斿洖锛� |
+| batchId / batchCode | Long/String | 鎵规缁村害锛堟寜鎵规姹囨�绘椂杩斿洖锛� |
+| warehouseId / warehouseName | Long/String | 浠撳簱缁村害锛堟寜浠撳簱姹囨�绘椂杩斿洖锛� |
+| stockState / stockStateName | Integer/String | 搴撳瓨鐘舵�佸強鍚嶇О |
+| totalQuantity | BigDecimal | 姹囨�绘暟閲� |
+
+**鍝嶅簲绀轰緥锛�**
+
+```json
+{
+  "code": 0,
+  "data": [
+    { "itemId": 1, "itemCode": "ITEM001", "itemName": "鏃╃ɑ绉嶅瓙", "stockState": 10, "stockStateName": "鏆傚瓨搴�", "totalQuantity": 500.00 },
+    { "itemId": 1, "itemCode": "ITEM001", "itemName": "鏃╃ɑ绉嶅瓙", "stockState": 20, "stockStateName": "鍚堟牸搴�", "totalQuantity": 400.00 }
+  ]
+}
+```
+
+### 涓夈�佸簱瀛橀璀︼紙鏂板锛�
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /mes/wm/stock-warning/page | 棰勮鍒嗛〉锛堣惤搴撹褰曪紝鍚凡澶勭悊鍘嗗彶锛� |
+| GET | /mes/wm/stock-warning/current | 瀹炴椂璁$畻褰撳墠棰勮锛堜笉钀藉簱锛岀绾э級 |
+| POST | /mes/wm/stock-warning/refresh | 閲嶆柊璁$畻骞惰惤搴撳綋鍓嶉璀� |
+| PUT | /mes/wm/stock-warning/handle | 澶勭悊棰勮锛堟爣璁板凡澶勭悊锛� |
+
+**GET /page 璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| itemCode | String | 鍚� | 鍝佺缂栫爜妯$硦鏌ヨ |
+| batchCode | String | 鍚� | 鎵规鍙锋ā绯婃煡璇� |
+| warningType | Integer | 鍚� | 棰勮绫诲瀷锛�10-浣庡簱瀛橈紝20-涓存湡锛�30-搴撻緞 |
+| status | Integer | 鍚� | 澶勭悊鐘舵�侊細10-鏈鐞嗭紝20-宸插鐞� |
+| pageNo / pageSize | Integer | 鏄� | 鍒嗛〉 |
+
+**GET /page 鍝嶅簲瀛楁锛�**
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | Long | 棰勮璁板綍缂栧彿 |
+| itemCode / itemName | String | 鍝佺缂栫爜/鍚嶇О |
+| batchCode | String | 鎵规鍙� |
+| warehouseName | String | 浠撳簱鍚嶇О |
+| warningType / warningTypeName | Integer/String | 棰勮绫诲瀷鍙婂悕绉� |
+| thresholdValue / currentValue | BigDecimal | 闃堝�硷紙浣庡簱瀛�=鏈�浣庡簱瀛樸�佷复鏈�=鍓╀綑澶╂暟闃堝�笺�佸簱榫�=瓒呴緞澶╂暟锛�/ 褰撳墠鍊� |
+| expireDate | String(datetime) | 杩囨湡鏃ユ湡锛堜复鏈熼璀︽墠鏈夛級 |
+| receiptTime | String(datetime) | 鍏ュ簱鏃堕棿锛堝簱榫勯璀︽墠鏈夛級 |
+| status / statusName | Integer/String | 澶勭悊鐘舵�佸強鍚嶇О |
+| handleTime / handleRemark | String/String | 澶勭悊鏃堕棿/澶囨敞 |
+
+**PUT /handle 璇锋眰浣擄細**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| id | Long | 鏄� | 棰勮璁板綍缂栧彿 |
+| handleRemark | String | 鍚� | 澶勭悊澶囨敞 |
+
+**涓氬姟瑙勫垯锛�**
+- 棰勮閽堝**鍚堟牸搴�**锛堟寮忓簱瀛橈級璁$畻锛氫綆搴撳瓨=鍦ㄥ簱鏁伴噺 鈮� 鍝佺鏈�浣庡簱瀛橈紱涓存湡=鎵规鍓╀綑澶╂暟 鈮� 鍝佺鏈夋晥鏈熷ぉ鏁帮紱搴撻緞=鍏ュ簱澶╂暟 鈮� 365 澶┿��
+- 棰勮鍙岄�氶亾锛歚/current` 瀹炴椂璁$畻锛堥�傚悎鐪嬫澘鍒锋柊锛夛紱`/page` 涓哄畾鏃惰惤搴撹褰曪紙閫傚悎鍒楄〃锛屽惈澶勭悊鍘嗗彶锛夈��
+- 瀹氭椂浠诲姟 `MesWmStockWarningJob`锛圦uartz锛孞obHandler锛夋瘡鏃ラ噸绠楋細鍒犻櫎鏃ф湭澶勭悊棰勮銆佸啓鍏ュ綋鍓嶉璀︼紝宸插鐞嗚褰曚繚鐣欎负鍘嗗彶銆�
+
+### 鍥涖�丳DA 鎵爜鐩樼偣锛堟柊澧烇級
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /mes/wm/stock-taking-task/scan | PDA 鎵爜鐩樼偣锛堣В鏋愯拷婧爜瀹氫綅鎵规锛屽啓鍏ョ洏鐐圭粨鏋滐級 |
+| POST | /mes/wm/stock-taking-task/process-difference | 澶勭悊鐩樼偣宸紓锛堢洏鐩堝叆搴�/鐩樹簭鍑哄簱锛岃皟鏁村簱瀛橈級 |
+| POST | /mes/wm/stock-taking-task/finish | 瀹屾垚鐩樼偣浠诲姟锛堝凡鏈夛級 |
+
+**POST /scan 璇锋眰浣擄細**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| taskId | Long | 鏄� | 鐩樼偣浠诲姟缂栧彿 |
+| scanCode | String | 鏄� | 杩芥函鐮�/琚嬬爜/鎵樼洏鐮�/鎵规鍙� |
+| takingQuantity | BigDecimal | 鏄� | 鐩樼偣鏁伴噺 |
+| remark | String | 鍚� | 澶囨敞 |
+
+**鍝嶅簲锛�** 杩斿洖鐩樼偣缁撴灉缂栧彿锛圠ong锛夈��
+
+**涓氬姟瑙勫垯锛�**
+- 浠�**鐩樼偣涓�**鐘舵�佺殑浠诲姟鍏佽鎵爜锛堟彁浜ょ洏鐐逛换鍔″悗锛夈��
+- 鎵爜瑙f瀽椤哄簭锛氭壒娆″彿 鈫� 琚嬬爜 鈫� 鎵樼洏鐮侊紝鏈�缁堝畾浣嶅埌浠撳簱鎵规銆�
+- 鍚屼竴浠诲姟琛�**鍙厑璁哥洏鐐逛竴娆�**锛岄噸澶嶆壂鐮佹姤閿欍�岃鐩樼偣娓呭崟琛屽凡鏈夌洏鐐硅褰曪紝涓嶈兘閲嶅鐩樼偣銆嶏紱濡傞渶淇敼鍏堝垹闄ゅ搴旂洏鐐圭粨鏋滃啀閲嶆柊鎵爜銆�
+- 鎵爜鎵规涓嶅湪浠诲姟鑼冨洿鍐呮椂鎶ャ�屾壂鐮佹壒娆′笉鍦ㄧ洏鐐逛换鍔¤寖鍥村唴銆嶃��
+- 鐩樼偣娴佺▼锛氬垱寤轰换鍔� 鈫� 鎻愪氦锛堣繘鍏ョ洏鐐逛腑锛夆啋 PDA 鎵爜閫愪釜鎵规鐩樼偣 鈫� 瀹屾垚锛堜换鍔♀啋宸插畬鎴愶紝瑙e喕搴撳瓨锛夆啋 澶勭悊宸紓锛堝啓鐩樼泩/鐩樹簭搴撳瓨娴佹按锛夈��
+
+## 鏁版嵁甯﹀叆璇存槑
+
+| 涓婁竴姝� | 涓嬩竴姝� | 甯﹀叆瀛楁 |
+|--------|--------|----------|
+| 鐢熶骇绾挎壂鐮佸叆搴� | 搴撳瓨鍙拌处锛堟殏瀛樺簱锛� | 琚嬬爜鈫掔敓浜ф壒娆♀啋浠撳簱鎵规锛坆atchId/batchCode/itemId/warehouseId 鑷姩妗ユ帴锛� |
+| 绉嶅瓙璐ㄦ鎻愪氦鍚堟牸 | 搴撳瓨鍙拌处锛堝悎鏍煎簱锛� | 鎸夋壒娆″彿锛坆atchCode锛夊皢鏆傚瓨搴撴暣鎵硅浆鍚堟牸搴擄紝鏃犻渶鍓嶇浼犲簱瀛� ID |
+| 閿�鍞壂鐮佸嚭搴� | 搴撳瓨鍙拌处锛堝凡鍑哄簱锛� | 閿�鍞壂鐮佽В鏋愭壒娆� 鈫� 瀹氫綅鍚堟牸搴撳簱瀛� 鈫� 鎵e噺 |
+| PDA 鎵爜鐩樼偣 | 鐩樼偣缁撴灉/浠诲姟琛� | 杩芥函鐮� 鈫� 鎵规 鈫� 浠诲姟琛岋紙taskId+batchId 瀹氫綅锛� |
+
+## 鍓嶇淇敼鐐�
+
+### 1. 搴撳瓨鍙拌处椤�
+- 椤堕儴绛涢�夊鍔犮�屽簱瀛樼姸鎬併�嶄笅鎷夛紙鏆傚瓨搴�/鍚堟牸搴�/宸插嚭搴擄級锛屽搴� `stockState` 鍙傛暟銆�
+- 鍒楄〃澧炲姞銆屽簱瀛樼姸鎬併�嶅垪灞曠ず锛岀姸鎬佸悕绉板彲鐢ㄦ帴鍙h繑鍥炵殑 `stockStateName`锛堝彴璐︼級鎴栧墠绔瓧鍏搞��
+
+### 2. 鐢熶骇绾挎壂鐮佸叆搴撻〉
+- 鎻愮ず鏂囨锛氬叆搴撴垚鍔熻繘鍏ャ�屾殏瀛樺簱锛堝緟璐ㄦ锛夈�嶏紱鍘熴�岀洿鎺ヤ负鍚堟牸搴撳瓨銆嶇殑灞曠ず閫昏緫闇�璋冩暣銆�
+
+### 3. 绉嶅瓙璐ㄦ鎻愪氦
+- 鎻愪氦銆屽悎鏍笺�嶅悗锛屽彲鍒锋柊搴撳瓨鍙拌处銆屽悎鏍煎簱銆嶇湅鍒拌鎵规搴撳瓨锛涜嫢鎻愪氦銆屼笉鍚堟牸銆嶏紝搴撳瓨浠嶅仠鐣欏湪銆屾殏瀛樺簱銆嶃��
+
+### 4. 閿�鍞壂鐮佹嫞璐�
+- 鍑哄簱鎴愬姛鍚庡彲鍒锋柊搴撳瓨鍙拌处锛屻�屽悎鏍煎簱銆嶆墸鍑忋�佹暟閲忊墹0 鐨勬壒娆″嚭鐜板湪銆屽凡鍑哄簱銆嶃��
+
+### 5. 搴撳瓨棰勮椤碉紙鏂板锛�
+- 鍒楄〃灞曠ず `/page` 鏁版嵁锛涙彁渚涖�屽疄鏃惰绠椼�嶆寜閽皟 `/current`锛涖�屽鐞嗐�嶆寜閽脊绐楀~澶囨敞璋� `/handle`锛涖�岄噸鏂拌绠椼�嶆寜閽皟 `/refresh`銆�
+- 棰勮绫诲瀷/澶勭悊鐘舵�佸垪灞曠ず `warningTypeName` / `statusName`銆�
+
+### 6. 搴撳瓨姹囨�婚潰鏉匡紙鏂板锛�
+- 涓変釜姹囨�绘帴鍙e垎鍒嚭鍝佺/鎵规/浠撳簱缁村害鐨勬暟閲忛潰鏉匡紝鏀寔鎸� `stockState` 绛涢�夛紝灞曠ず `totalQuantity`銆�
+
+### 7. PDA 鐩樼偣鎵爜
+- 鐩樼偣浠诲姟璇︽儏杩涘叆銆岀洏鐐逛腑銆嶅悗锛屾彁渚涙壂鐮佽緭鍏ユ锛坄taskId` 鍥哄畾涓哄綋鍓嶄换鍔★紝`scanCode` 鎵爜銆乣takingQuantity` 鎵嬭緭锛夈��
+- 鎵爜鎴愬姛鎻愮ず杩斿洖鐨勭洏鐐圭粨鏋滅紪鍙凤紱閲嶅鐩樼偣闇�鍏堝垹缁撴灉銆�
+
+## 娉ㄦ剰浜嬮」
+
+- 鎵�鏈夋帴鍙e潎涓虹鐞嗗悗鍙版帴鍙o紝闇�甯︾櫥褰� token 璁块棶锛屾潈闄愮偣锛歚mes:wm-stock-warning:query/update`銆乣mes:wm-stock-taking-task:update`銆乣mes:wm-material-stock:query`銆�
+- 棰勮浠呭**鍚堟牸搴�**璁$畻锛涙殏瀛樺簱涓庡凡鍑哄簱涓嶈鍏ラ璀︺��
+- 鐢熶骇绾挎壂鐮佸叆搴撲慨澶嶅悗锛屽巻鍙叉暟鎹腑宸插瓨鍦ㄤ絾鏈ˉ鎺ョ殑鎵规锛屼細鍦ㄤ笅娆℃壂鐮�/璐ㄦ/閿�鍞椂鎸夌敓浜ф壒娆″彿鑷姩妗ユ帴鍒涘缓浠撳簱鎵规锛岃�佹暟鎹棤闇�杩佺Щ銆�
+- 棰勮瀹氭椂浠诲姟闇�鍦ㄧ鐞嗗悗鍙般�岀郴缁熺鐞�-瀹氭椂浠诲姟銆嶆敞鍐� JobHandler锛歚MesWmStockWarningJob`锛堟瘡鏃ユ墽琛屼竴娆★級銆�
+- 姹囨�绘帴鍙d负鍐呭瓨鑱氬悎锛屾暟鎹噺澶ф椂寤鸿鍓嶇鎸夐渶杩囨护锛堜紶 stockState/warehouseId 绛夛級銆�
diff --git a/tmp_img_view.png b/tmp_img_view.png
new file mode 100644
index 0000000..d75a2b0
--- /dev/null
+++ b/tmp_img_view.png
Binary files differ
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/MesWmMaterialStockController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/MesWmMaterialStockController.java
index ed84166..26cb544 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/MesWmMaterialStockController.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/MesWmMaterialStockController.java
@@ -13,6 +13,8 @@
 import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockMoveReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockPageReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockListReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmStockSummaryRespVO;
 import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
 import cn.iocoder.yudao.module.srm.api.supplier.dto.SrmSupplierRespDTO;
 import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
@@ -123,6 +125,30 @@
         return success(true);
     }
 
+    @GetMapping("/summary-by-item")
+    @Operation(summary = "鎸夊搧绉嶆眹鎬诲簱瀛樻暟閲�")
+    @PreAuthorize("@ss.hasPermission('mes:wm-material-stock:query')")
+    public CommonResult<List<MesWmStockSummaryRespVO>> getStockSummaryByItem(
+            MesWmMaterialStockListReqVO reqVO) {
+        return success(materialStockService.getStockSummaryByItem(reqVO));
+    }
+
+    @GetMapping("/summary-by-batch")
+    @Operation(summary = "鎸夋壒娆℃眹鎬诲簱瀛樻暟閲�")
+    @PreAuthorize("@ss.hasPermission('mes:wm-material-stock:query')")
+    public CommonResult<List<MesWmStockSummaryRespVO>> getStockSummaryByBatch(
+            MesWmMaterialStockListReqVO reqVO) {
+        return success(materialStockService.getStockSummaryByBatch(reqVO));
+    }
+
+    @GetMapping("/summary-by-warehouse")
+    @Operation(summary = "鎸変粨搴撴眹鎬诲簱瀛樻暟閲�")
+    @PreAuthorize("@ss.hasPermission('mes:wm-material-stock:query')")
+    public CommonResult<List<MesWmStockSummaryRespVO>> getStockSummaryByWarehouse(
+            MesWmMaterialStockListReqVO reqVO) {
+        return success(materialStockService.getStockSummaryByWarehouse(reqVO));
+    }
+
     @GetMapping("/export-excel")
     @Operation(summary = "瀵煎嚭搴撳瓨鍙拌处 Excel")
     @PreAuthorize("@ss.hasPermission('mes:wm-material-stock:export')")
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/vo/MesWmStockSummaryRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/vo/MesWmStockSummaryRespVO.java
new file mode 100644
index 0000000..840940d
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/vo/MesWmStockSummaryRespVO.java
@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "绠$悊鍚庡彴 - 搴撳瓨姹囨�� Response VO")
+@Data
+public class MesWmStockSummaryRespVO {
+
+    @Schema(description = "鍝佺ID锛堟寜鍝佺姹囨�绘椂杩斿洖锛�")
+    private Long itemId;
+
+    @Schema(description = "鍝佺缂栫爜")
+    private String itemCode;
+
+    @Schema(description = "鍝佺鍚嶇О")
+    private String itemName;
+
+    @Schema(description = "鎵规ID锛堟寜鎵规姹囨�绘椂杩斿洖锛�")
+    private Long batchId;
+
+    @Schema(description = "鎵规鍙�")
+    private String batchCode;
+
+    @Schema(description = "浠撳簱ID锛堟寜浠撳簱姹囨�绘椂杩斿洖锛�")
+    private Long warehouseId;
+
+    @Schema(description = "浠撳簱鍚嶇О")
+    private String warehouseName;
+
+    @Schema(description = "搴撳瓨鐘舵�侊細10-鏆傚瓨搴擄紝20-鍚堟牸搴擄紝30-宸插嚭搴�")
+    private Integer stockState;
+
+    @Schema(description = "搴撳瓨鐘舵�佸悕绉�")
+    private String stockStateName;
+
+    @Schema(description = "姹囨�绘暟閲�")
+    private BigDecimal totalQuantity;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/MesWmStockTakingTaskController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/MesWmStockTakingTaskController.java
index badce9e..a1470c5 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/MesWmStockTakingTaskController.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/MesWmStockTakingTaskController.java
@@ -130,6 +130,22 @@
         return success(true);
     }
 
+    @PostMapping("/scan")
+    @Operation(summary = "PDA 鎵爜鐩樼偣")
+    @PreAuthorize("@ss.hasPermission('mes:wm-stock-taking-task:update')")
+    public CommonResult<Long> scanStockTakingTask(@Valid @RequestBody MesWmStockTakingTaskScanReqVO scanReqVO) {
+        return success(stockTakingTaskService.scanStockTakingTask(scanReqVO));
+    }
+
+    @PostMapping("/process-difference")
+    @Operation(summary = "澶勭悊鐩樼偣宸紓锛岃皟鏁村簱瀛�")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('mes:wm-stock-taking-task:update')")
+    public CommonResult<Boolean> processStockTakingDifference(@RequestParam("id") Long id) {
+        stockTakingTaskService.processStockTakingDifference(id);
+        return success(true);
+    }
+
     // ==================== 鎷兼帴 VO ====================
 
     private List<MesWmStockTakingTaskRespVO> buildTaskRespVOList(List<MesWmStockTakingTaskDO> list) {
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/vo/MesWmStockTakingTaskScanReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/vo/MesWmStockTakingTaskScanReqVO.java
new file mode 100644
index 0000000..6407a0f
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/vo/MesWmStockTakingTaskScanReqVO.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 鐩樼偣浠诲姟 PDA 鎵爜 Request VO")
+@Data
+public class MesWmStockTakingTaskScanReqVO {
+
+    @Schema(description = "鐩樼偣浠诲姟缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "鐩樼偣浠诲姟缂栧彿涓嶈兘涓虹┖")
+    private Long taskId;
+
+    @Schema(description = "杩芥函鐮�/琚嬬爜/鎵樼洏鐮�/鎵规鍙�", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "鎵爜鍐呭涓嶈兘涓虹┖")
+    private String scanCode;
+
+    @Schema(description = "鐩樼偣鏁伴噺", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+    @NotNull(message = "鐩樼偣鏁伴噺涓嶈兘涓虹┖")
+    private BigDecimal takingQuantity;
+
+    @Schema(description = "澶囨敞")
+    private String remark;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warning/MesWmStockWarningController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warning/MesWmStockWarningController.java
new file mode 100644
index 0000000..b569ac3
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warning/MesWmStockWarningController.java
@@ -0,0 +1,60 @@
+package cn.iocoder.yudao.module.mes.controller.admin.wm.warning;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.warning.vo.MesWmStockWarningHandleReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.warning.vo.MesWmStockWarningPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.warning.vo.MesWmStockWarningRespVO;
+import cn.iocoder.yudao.module.mes.service.wm.warning.MesWmStockWarningService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - MES 搴撳瓨棰勮")
+@RestController
+@RequestMapping("/mes/wm/stock-warning")
+@Validated
+public class MesWmStockWarningController {
+
+    @Resource
+    private MesWmStockWarningService stockWarningService;
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰緱搴撳瓨棰勮鍒嗛〉锛堣惤搴撹褰曪級")
+    @PreAuthorize("@ss.hasPermission('mes:wm-stock-warning:query')")
+    public CommonResult<PageResult<MesWmStockWarningRespVO>> getWarningPage(
+            @Valid MesWmStockWarningPageReqVO pageReqVO) {
+        return success(stockWarningService.getWarningPage(pageReqVO));
+    }
+
+    @GetMapping("/current")
+    @Operation(summary = "瀹炴椂璁$畻褰撳墠搴撳瓨棰勮锛堜笉钀藉簱锛�")
+    @PreAuthorize("@ss.hasPermission('mes:wm-stock-warning:query')")
+    public CommonResult<List<MesWmStockWarningRespVO>> getCurrentWarnings() {
+        return success(stockWarningService.getCurrentWarnings());
+    }
+
+    @PostMapping("/refresh")
+    @Operation(summary = "閲嶆柊璁$畻骞惰惤搴撳綋鍓嶅簱瀛橀璀�")
+    @PreAuthorize("@ss.hasPermission('mes:wm-stock-warning:update')")
+    public CommonResult<Integer> refreshWarnings() {
+        return success(stockWarningService.persistCurrentWarnings());
+    }
+
+    @PutMapping("/handle")
+    @Operation(summary = "澶勭悊搴撳瓨棰勮锛堟爣璁板凡澶勭悊锛�")
+    @PreAuthorize("@ss.hasPermission('mes:wm-stock-warning:update')")
+    public CommonResult<Boolean> handleWarning(@Valid @RequestBody MesWmStockWarningHandleReqVO reqVO) {
+        stockWarningService.handle(reqVO);
+        return success(true);
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warning/vo/MesWmStockWarningHandleReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warning/vo/MesWmStockWarningHandleReqVO.java
new file mode 100644
index 0000000..8b872c8
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warning/vo/MesWmStockWarningHandleReqVO.java
@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.module.mes.controller.admin.wm.warning.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Schema(description = "绠$悊鍚庡彴 - 搴撳瓨棰勮澶勭悊 Request VO")
+@Data
+public class MesWmStockWarningHandleReqVO {
+
+    @Schema(description = "棰勮璁板綍缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "棰勮璁板綍缂栧彿涓嶈兘涓虹┖")
+    private Long id;
+
+    @Schema(description = "澶勭悊澶囨敞")
+    private String handleRemark;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warning/vo/MesWmStockWarningPageReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warning/vo/MesWmStockWarningPageReqVO.java
new file mode 100644
index 0000000..a4a2d67
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warning/vo/MesWmStockWarningPageReqVO.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.mes.controller.admin.wm.warning.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "绠$悊鍚庡彴 - 搴撳瓨棰勮鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MesWmStockWarningPageReqVO extends PageParam {
+
+    @Schema(description = "鍝佺缂栫爜")
+    private String itemCode;
+
+    @Schema(description = "鎵规鍙�")
+    private String batchCode;
+
+    @Schema(description = "棰勮绫诲瀷锛�10-浣庡簱瀛橈紝20-涓存湡锛�30-搴撻緞")
+    private Integer warningType;
+
+    @Schema(description = "澶勭悊鐘舵�侊細10-鏈鐞嗭紝20-宸插鐞�")
+    private Integer status;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warning/vo/MesWmStockWarningRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warning/vo/MesWmStockWarningRespVO.java
new file mode 100644
index 0000000..7c6138d
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warning/vo/MesWmStockWarningRespVO.java
@@ -0,0 +1,70 @@
+package cn.iocoder.yudao.module.mes.controller.admin.wm.warning.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 搴撳瓨棰勮 Response VO")
+@Data
+public class MesWmStockWarningRespVO {
+
+    @Schema(description = "缂栧彿")
+    private Long id;
+
+    @Schema(description = "鍝佺ID")
+    private Long itemId;
+
+    @Schema(description = "鍝佺缂栫爜")
+    private String itemCode;
+
+    @Schema(description = "鍝佺鍚嶇О")
+    private String itemName;
+
+    @Schema(description = "鎵规ID")
+    private Long batchId;
+
+    @Schema(description = "鎵规鍙�")
+    private String batchCode;
+
+    @Schema(description = "浠撳簱ID")
+    private Long warehouseId;
+
+    @Schema(description = "浠撳簱鍚嶇О")
+    private String warehouseName;
+
+    @Schema(description = "棰勮绫诲瀷锛�10-浣庡簱瀛橈紝20-涓存湡锛�30-搴撻緞")
+    private Integer warningType;
+
+    @Schema(description = "棰勮绫诲瀷鍚嶇О")
+    private String warningTypeName;
+
+    @Schema(description = "闃堝�硷紙浣庡簱瀛�=鏈�浣庡簱瀛橈紝涓存湡=鍓╀綑澶╂暟锛屽簱榫�=瓒呴緞澶╂暟锛�")
+    private BigDecimal thresholdValue;
+
+    @Schema(description = "褰撳墠鍊硷紙浣庡簱瀛�=鍦ㄥ簱鏁伴噺锛屼复鏈�=鍓╀綑澶╂暟锛屽簱榫�=搴撻緞澶╂暟锛�")
+    private BigDecimal currentValue;
+
+    @Schema(description = "杩囨湡鏃ユ湡锛堜复鏈熼璀︽墠鏈夛級")
+    private LocalDateTime expireDate;
+
+    @Schema(description = "鍏ュ簱鏃堕棿锛堝簱榫勯璀︽墠鏈夛級")
+    private LocalDateTime receiptTime;
+
+    @Schema(description = "澶勭悊鐘舵�侊細10-鏈鐞嗭紝20-宸插鐞�")
+    private Integer status;
+
+    @Schema(description = "澶勭悊鐘舵�佸悕绉�")
+    private String statusName;
+
+    @Schema(description = "澶勭悊浜篒D")
+    private Long handleUserId;
+
+    @Schema(description = "澶勭悊鏃堕棿")
+    private LocalDateTime handleTime;
+
+    @Schema(description = "澶勭悊澶囨敞")
+    private String handleRemark;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/pub/trace/MesProTracePublicController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/pub/trace/MesProTracePublicController.java
new file mode 100644
index 0000000..6d1f910
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/pub/trace/MesProTracePublicController.java
@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.mes.controller.pub.trace;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.mes.controller.pub.trace.vo.MesProTracePublicRespVO;
+import cn.iocoder.yudao.module.mes.service.trace.MesProTracePublicService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.annotation.security.PermitAll;
+import jakarta.validation.constraints.NotEmpty;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+/**
+ * H5 鎵爜婧簮 - 鍏紑鏌ヨ
+ * <p>
+ * 鍏嶇櫥褰曪紙@PermitAll锛夐潰鍚戝叕浼楁彁渚涘叏绋嬫函婧愭煡璇紝浣滀负杩芥函浣撶郴鐨勬牳蹇冧骇鍝佽浇浣撱��
+ * 鍚庣画鏂板鏁版嵁婧愬湪 {@link MesProTracePublicRespVO} 涓拷鍔犱俊鎭尯鍧楀嵆鍙��
+ */
+@Tag(name = "H5 鎵爜婧簮 - 鍏紑鏌ヨ")
+@RestController
+@RequestMapping("/public/mes/trace")
+@Validated
+public class MesProTracePublicController {
+
+    @Resource
+    private MesProTracePublicService service;
+
+    @GetMapping("/query")
+    @PermitAll
+    @Operation(summary = "鏍规嵁杩芥函鐮佹煡璇㈠叏绋嬫函婧愪俊鎭�")
+    public CommonResult<MesProTracePublicRespVO> query(@RequestParam("code") @NotEmpty(message = "杩芥函鐮佷笉鑳戒负绌�") String code) {
+        return success(service.queryTrace(code));
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/pub/trace/vo/MesProTracePublicRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/pub/trace/vo/MesProTracePublicRespVO.java
new file mode 100644
index 0000000..50c395a
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/pub/trace/vo/MesProTracePublicRespVO.java
@@ -0,0 +1,212 @@
+package cn.iocoder.yudao.module.mes.controller.pub.trace.vo;
+
+import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchProcessTraceRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchPurchaseSourceRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchSalesTargetRespVO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * H5 鎵爜婧簮 - 鍏ㄧ▼婧簮淇℃伅 Response VO
+ * <p>
+ * 浠ャ�屼俊鎭尯鍧椼�嶆ā鍧楀寲缁勭粐锛屾牳蹇冧俊鎭紙鍝佺/鎵规/浜哄憳/璐ㄦ/鍏ュ簱锛夊缁堣繑鍥烇紱
+ * 閲囪喘鏉ユ簮銆侀攢鍞幓鍚戙�佸姞宸ユ祦杞负鍙�夋墿灞曞尯鍧楋紙鏈夋暟鎹墠闈炵┖锛夈�傚悗缁柊澧炴暟鎹簮鎸夊悓涓�妯″紡杩藉姞鍖哄潡瀛楁銆�
+ */
+@Schema(description = "H5 鎵爜婧簮 - 鍏ㄧ▼婧簮淇℃伅 Response VO")
+@Data
+public class MesProTracePublicRespVO {
+
+    @Schema(description = "杩芥函鍏ュ彛绫诲瀷锛欱AG-琚嬬爜 / PALLET-鎵樼洏鐮� / BATCH-鎵规鐮�", example = "BAG")
+    private String sourceType;
+
+    @Schema(description = "璇锋眰鐨勮拷婧爜锛堝師濮嬫壂鐮佸�硷級", example = "PC20260828twoITEM_001")
+    private String requestCode;
+
+    // ====== 鍝佺淇℃伅鍖哄潡 ======
+    @Schema(description = "鍝佺缂栫爜")
+    private String itemCode;
+
+    @Schema(description = "鍝佺鍚嶇О")
+    private String itemName;
+
+    @Schema(description = "鍝佺瑙勬牸")
+    private String itemSpecification;
+
+    // ====== 鐢熶骇鎵规淇℃伅鍖哄潡 ======
+    @Schema(description = "鎵规鍙�")
+    private String batchCode;
+
+    @Schema(description = "涓存椂鎵规鐮�")
+    private String tempCode;
+
+    @Schema(description = "鎵规鐘舵��", example = "20")
+    private Integer batchStatus;
+
+    @Schema(description = "鎵规鐘舵�佸悕绉�", example = "鐢熶骇涓�")
+    private String batchStatusName;
+
+    @Schema(description = "鐢熶骇鏃ユ湡")
+    private LocalDateTime produceDate;
+
+    @Schema(description = "浜х嚎缂栫爜")
+    private String lineCode;
+
+    @Schema(description = "浜х嚎鍚嶇О")
+    private String lineName;
+
+    @Schema(description = "璐d换浜虹敤鎴风紪鍙�")
+    private Long ownerUserId;
+
+    @Schema(description = "璐d换浜哄鍚�")
+    private String ownerUserName;
+
+    @Schema(description = "璁″垝鏁伴噺")
+    private BigDecimal planQuantity;
+
+    @Schema(description = "宸茬敓鎴愯鐮佹暟閲�")
+    private Integer bagQuantity;
+
+    @Schema(description = "宸茬敓鎴愭墭鐩樻暟閲�")
+    private Integer palletQuantity;
+
+    @Schema(description = "鍏ュ簱鏃堕棿")
+    private LocalDateTime inboundTime;
+
+    @Schema(description = "澶囨敞")
+    private String remark;
+
+    // ====== 鐢熶骇浜哄憳鍖哄潡 ======
+    @Schema(description = "鐢熶骇浜哄憳鍒楄〃")
+    private List<WorkerVO> workers;
+
+    // ====== 鎵樼洏鍖哄潡 ======
+    @Schema(description = "鎵樼洏鎬绘暟")
+    private Integer palletCount;
+
+    @Schema(description = "鎵樼洏鍒楄〃")
+    private List<PalletVO> pallets;
+
+    // ====== 琚嬬爜鍖哄潡 ======
+    @Schema(description = "琚嬬爜鎬绘暟")
+    private Long bagTotal;
+
+    @Schema(description = "琚嬬爜鍒楄〃锛堟渶澶� 100 鏉★級")
+    private List<BagVO> bags;
+
+    // ====== 鍏ュ簱浜嬩欢鍖哄潡 ======
+    @Schema(description = "鏈�鏂板叆搴撲簨浠�")
+    private InboundVO inbound;
+
+    // ====== 璐ㄦ淇℃伅鍖哄潡 ======
+    @Schema(description = "绉嶅瓙璐ㄦ淇℃伅")
+    private QualityVO quality;
+
+    // ====== 鍙�夋墿灞曞尯鍧楋紙鏈夋暟鎹墠杩斿洖锛岀┖琛ㄨ繑鍥炵┖鍒楄〃锛� ======
+    @Schema(description = "閲囪喘鏉ユ簮锛堝彲閫夛級")
+    private List<MesWmBatchPurchaseSourceRespVO> purchaseSources;
+
+    @Schema(description = "閿�鍞幓鍚戯紙鍙�夛級")
+    private List<MesWmBatchSalesTargetRespVO> salesTargets;
+
+    @Schema(description = "鍔犲伐娴佽浆锛堝彲閫夛級")
+    private List<MesWmBatchProcessTraceRespVO> processTraces;
+
+    @Schema(description = "鐢熶骇浜哄憳")
+    @Data
+    public static class WorkerVO {
+        @Schema(description = "宀椾綅绫诲瀷", example = "20")
+        private Integer workerType;
+        @Schema(description = "宀椾綅绫诲瀷鍚嶇О", example = "鎿嶄綔宸�")
+        private String workerTypeName;
+        @Schema(description = "濮撳悕")
+        private String userName;
+        @Schema(description = "澶囨敞")
+        private String remark;
+    }
+
+    @Schema(description = "鎵樼洏淇℃伅")
+    @Data
+    public static class PalletVO {
+        @Schema(description = "鎵樼洏鐮�")
+        private String code;
+        @Schema(description = "鎵樼洏搴忓彿")
+        private Integer palletNo;
+        @Schema(description = "宸茶琚嬫暟")
+        private Integer bagCount;
+        @Schema(description = "鍙琚嬫暟")
+        private Integer capacity;
+    }
+
+    @Schema(description = "琚嬬爜淇℃伅")
+    @Data
+    public static class BagVO {
+        @Schema(description = "琚嬬爜")
+        private String code;
+        @Schema(description = "琚嬪簭鍙�")
+        private Integer bagNo;
+        @Schema(description = "缁戝畾鎵樼洏鐮�")
+        private String palletCode;
+        @Schema(description = "鐘舵��", example = "20")
+        private Integer status;
+        @Schema(description = "鐘舵�佸悕绉�", example = "宸插鍑哄嵃鍒�")
+        private String statusName;
+        @Schema(description = "鍏ュ簱鏃堕棿")
+        private LocalDateTime inboundTime;
+    }
+
+    @Schema(description = "鍏ュ簱浜嬩欢")
+    @Data
+    public static class InboundVO {
+        @Schema(description = "浜嬩欢鍙�")
+        private String eventId;
+        @Schema(description = "璁惧缂栫爜")
+        private String deviceCode;
+        @Schema(description = "浜х嚎缂栫爜")
+        private String lineCode;
+        @Schema(description = "鍏ュ簱鏁伴噺")
+        private BigDecimal quantity;
+        @Schema(description = "鐘舵��", example = "1")
+        private String status;
+        @Schema(description = "浜嬩欢鏃堕棿")
+        private LocalDateTime eventTime;
+    }
+
+    @Schema(description = "绉嶅瓙璐ㄦ淇℃伅")
+    @Data
+    public static class QualityVO {
+        @Schema(description = "璐ㄦ缁撴灉", example = "1")
+        private Integer result;
+        @Schema(description = "璐ㄦ缁撴灉鍚嶇О", example = "鍚堟牸")
+        private String resultName;
+        @Schema(description = "涓嶅悎鏍煎師鍥�")
+        private String reason;
+        @Schema(description = "妫�楠屽憳濮撳悕")
+        private String inspectorName;
+        @Schema(description = "妫�楠屾椂闂�")
+        private LocalDateTime inspectedAt;
+        @Schema(description = "璐ㄦ鎸囨爣鍒楄〃")
+        private List<IndicatorVO> indicators;
+    }
+
+    @Schema(description = "璐ㄦ鎸囨爣")
+    @Data
+    public static class IndicatorVO {
+        @Schema(description = "鎸囨爣缂栫爜", example = "purity")
+        private String indicatorCode;
+        @Schema(description = "鎸囨爣鍚嶇О", example = "绾害")
+        private String indicatorName;
+        @Schema(description = "妫�娴嬪��")
+        private BigDecimal value;
+        @Schema(description = "涓嬮檺")
+        private BigDecimal minValue;
+        @Schema(description = "涓婇檺")
+        private BigDecimal maxValue;
+        @Schema(description = "鏄惁鍚堟牸")
+        private Boolean pass;
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/warning/MesWmStockWarningDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/warning/MesWmStockWarningDO.java
new file mode 100644
index 0000000..7711092
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/warning/MesWmStockWarningDO.java
@@ -0,0 +1,105 @@
+package cn.iocoder.yudao.module.mes.dal.dataobject.wm.warning;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * MES 搴撳瓨棰勮 DO
+ * <p>
+ * 棰勮绫诲瀷瑙� {@link cn.iocoder.yudao.module.mes.enums.wm.MesWmStockWarningTypeEnum}锛�
+ * 10-浣庡簱瀛橈紝20-涓存湡锛�30-搴撻緞銆傜敱瀹炴椂璁$畻鏌ヨ + 瀹氭椂浠诲姟钀藉簱鍙岄�氶亾浜х敓銆�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("mes_wm_stock_warning")
+@KeySequence("mes_wm_stock_warning_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MesWmStockWarningDO extends BaseDO {
+
+    /**
+     * 缂栧彿
+     */
+    @TableId
+    private Long id;
+    /**
+     * 鍝佺ID
+     *
+     * 鍏宠仈 {@link MesMdmItemDO#getId()}
+     */
+    private Long itemId;
+    /**
+     * 鍝佺缂栫爜
+     */
+    private String itemCode;
+    /**
+     * 鎵规ID
+     *
+     * 鍏宠仈 {@link MesWmBatchDO#getId()}
+     */
+    private Long batchId;
+    /**
+     * 鎵规鍙�
+     */
+    private String batchCode;
+    /**
+     * 浠撳簱ID
+     *
+     * 鍏宠仈 {@link MesWmWarehouseDO#getId()}
+     */
+    private Long warehouseId;
+    /**
+     * 浠撳簱鍚嶇О
+     */
+    private String warehouseName;
+    /**
+     * 棰勮绫诲瀷锛�10-浣庡簱瀛橈紝20-涓存湡锛�30-搴撻緞
+     */
+    private Integer warningType;
+    /**
+     * 闃堝�硷紙浣庡簱瀛�=鏈�浣庡簱瀛橈紝涓存湡=鍓╀綑澶╂暟锛屽簱榫�=瓒呴緞澶╂暟锛�
+     */
+    private BigDecimal thresholdValue;
+    /**
+     * 褰撳墠鍊硷紙浣庡簱瀛�=鍦ㄥ簱鏁伴噺锛屼复鏈�=鍓╀綑澶╂暟锛屽簱榫�=搴撻緞澶╂暟锛�
+     */
+    private BigDecimal currentValue;
+    /**
+     * 杩囨湡鏃ユ湡锛堜复鏈熼璀︽墠鏈夛級
+     */
+    private LocalDateTime expireDate;
+    /**
+     * 鍏ュ簱鏃堕棿锛堝簱榫勯璀︽墠鏈夛級
+     */
+    private LocalDateTime receiptTime;
+    /**
+     * 澶勭悊鐘舵�侊細10-鏈鐞嗭紝20-宸插鐞�
+     */
+    private Integer status;
+    /**
+     * 澶勭悊浜篒D
+     */
+    private Long handleUserId;
+    /**
+     * 澶勭悊鏃堕棿
+     */
+    private LocalDateTime handleTime;
+    /**
+     * 澶勭悊澶囨敞
+     */
+    private String handleRemark;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/scan/MesProScanEventMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/scan/MesProScanEventMapper.java
index 523c172..4246dd6 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/scan/MesProScanEventMapper.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/scan/MesProScanEventMapper.java
@@ -7,6 +7,8 @@
 import cn.iocoder.yudao.module.mes.controller.admin.pro.scan.vo.MesProScanEventPageReqVO;
 import org.apache.ibatis.annotations.Mapper;
 
+import java.util.List;
+
 @Mapper
 public interface MesProScanEventMapper extends BaseMapperX<MesProScanEventDO> {
     default MesProScanEventDO selectByEventIdForUpdate(String eventId) {
@@ -29,4 +31,10 @@
                 .eqIfPresent(MesProScanEventDO::getDeviceCode, deviceCode)
                 .orderByDesc(MesProScanEventDO::getEventTime).last("LIMIT 1"));
     }
+
+    default List<MesProScanEventDO> selectListByBatchId(Long batchId) {
+        return selectList(new LambdaQueryWrapperX<MesProScanEventDO>()
+                .eq(MesProScanEventDO::getBatchId, batchId)
+                .orderByDesc(MesProScanEventDO::getEventTime));
+    }
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/seedquality/MesQcSeedQualityIndicatorMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/seedquality/MesQcSeedQualityIndicatorMapper.java
index 9732d76..a7fc1d8 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/seedquality/MesQcSeedQualityIndicatorMapper.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/seedquality/MesQcSeedQualityIndicatorMapper.java
@@ -1,5 +1,14 @@
 package cn.iocoder.yudao.module.mes.dal.mysql.qc.seedquality;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.mes.dal.dataobject.qc.seedquality.MesQcSeedQualityIndicatorDO;
 import org.apache.ibatis.annotations.Mapper;
-@Mapper public interface MesQcSeedQualityIndicatorMapper extends BaseMapperX<MesQcSeedQualityIndicatorDO> {}
+import java.util.List;
+
+@Mapper public interface MesQcSeedQualityIndicatorMapper extends BaseMapperX<MesQcSeedQualityIndicatorDO> {
+    default List<MesQcSeedQualityIndicatorDO> selectListByQualityId(Long qualityId) {
+        return selectList(new LambdaQueryWrapperX<MesQcSeedQualityIndicatorDO>()
+                .eq(MesQcSeedQualityIndicatorDO::getQualityId, qualityId)
+                .orderByAsc(MesQcSeedQualityIndicatorDO::getId));
+    }
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/seedquality/MesQcSeedQualityMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/seedquality/MesQcSeedQualityMapper.java
index 2e1b159..314133b 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/seedquality/MesQcSeedQualityMapper.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/seedquality/MesQcSeedQualityMapper.java
@@ -5,9 +5,17 @@
 import cn.iocoder.yudao.module.mes.dal.dataobject.qc.seedquality.MesQcSeedQualityDO;
 import org.apache.ibatis.annotations.Mapper;
 
+import java.util.List;
+
 @Mapper
 public interface MesQcSeedQualityMapper extends BaseMapperX<MesQcSeedQualityDO> {
     default MesQcSeedQualityDO selectByCode(String code) {
         return selectOne(new LambdaQueryWrapperX<MesQcSeedQualityDO>().eq(MesQcSeedQualityDO::getCode, code));
     }
+
+    default List<MesQcSeedQualityDO> selectListByBatchCode(String batchCode) {
+        return selectList(new LambdaQueryWrapperX<MesQcSeedQualityDO>()
+                .eq(MesQcSeedQualityDO::getBatchCode, batchCode)
+                .orderByDesc(MesQcSeedQualityDO::getInspectedAt));
+    }
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/materialstock/MesWmMaterialStockMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/materialstock/MesWmMaterialStockMapper.java
index 44636f3..cc2413d 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/materialstock/MesWmMaterialStockMapper.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/materialstock/MesWmMaterialStockMapper.java
@@ -206,4 +206,19 @@
                 .ne(MesWmMaterialStockDO::getQuantity, BigDecimal.ZERO));
     }
 
+    /**
+     * 鎸夋壒娆″彿鏇存柊搴撳瓨鐘舵�侊紙鐢ㄤ簬绉嶅瓙璐ㄦ鍚堟牸鍚庯紝灏嗘殏瀛樺簱鏁存壒杞负鍚堟牸搴擄級
+     *
+     * @param batchId   浠撳簱鎵规ID
+     * @param toState   鐩爣搴撳瓨鐘舵��
+     * @param fromState 鍘熷簱瀛樼姸鎬侊紙闄愬畾鍙洿鏂拌鐘舵�佺殑璁板綍锛�
+     * @return 褰卞搷琛屾暟
+     */
+    default int updateStockStateByBatchId(Long batchId, Integer toState, Integer fromState) {
+        return update(new MesWmMaterialStockDO().setStockState(toState),
+                new LambdaUpdateWrapper<MesWmMaterialStockDO>()
+                        .eq(MesWmMaterialStockDO::getBatchId, batchId)
+                        .eq(MesWmMaterialStockDO::getStockState, fromState));
+    }
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/stocktaking/task/MesWmStockTakingTaskLineMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/stocktaking/task/MesWmStockTakingTaskLineMapper.java
index f3c863f..d75b6be 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/stocktaking/task/MesWmStockTakingTaskLineMapper.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/stocktaking/task/MesWmStockTakingTaskLineMapper.java
@@ -40,4 +40,11 @@
                 .eqIfPresent(MesWmStockTakingTaskLineDO::getAreaId, areaId));
     }
 
+    default MesWmStockTakingTaskLineDO selectByTaskIdAndBatchId(Long taskId, Long batchId) {
+        return selectOne(new LambdaQueryWrapperX<MesWmStockTakingTaskLineDO>()
+                .eq(MesWmStockTakingTaskLineDO::getTaskId, taskId)
+                .eq(MesWmStockTakingTaskLineDO::getBatchId, batchId)
+                .last("LIMIT 1"));
+    }
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/warning/MesWmStockWarningMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/warning/MesWmStockWarningMapper.java
new file mode 100644
index 0000000..f0a83d1
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/warning/MesWmStockWarningMapper.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.mes.dal.mysql.wm.warning;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.warning.vo.MesWmStockWarningPageReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warning.MesWmStockWarningDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * MES 搴撳瓨棰勮 Mapper
+ */
+@Mapper
+public interface MesWmStockWarningMapper extends BaseMapperX<MesWmStockWarningDO> {
+
+    default PageResult<MesWmStockWarningDO> selectPage(MesWmStockWarningPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<MesWmStockWarningDO>()
+                .likeIfPresent(MesWmStockWarningDO::getItemCode, reqVO.getItemCode())
+                .likeIfPresent(MesWmStockWarningDO::getBatchCode, reqVO.getBatchCode())
+                .eqIfPresent(MesWmStockWarningDO::getWarningType, reqVO.getWarningType())
+                .eqIfPresent(MesWmStockWarningDO::getStatus, reqVO.getStatus())
+                .orderByDesc(MesWmStockWarningDO::getCreateTime));
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
index 3657c40..8a00efc 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
@@ -682,6 +682,8 @@
     ErrorCode WM_STOCK_TAKING_TASK_RESULT_NOT_EXISTS = new ErrorCode(1_040_714_119, "鐩樼偣缁撴灉涓嶅瓨鍦�");
     ErrorCode WM_STOCK_TAKING_TASK_LINE_ALREADY_TAKEN = new ErrorCode(1_040_714_120, "璇ョ洏鐐规竻鍗曡宸叉湁鐩樼偣璁板綍锛屼笉鑳介噸澶嶇洏鐐�");
     ErrorCode WM_STOCK_TAKING_TASK_NOT_FINISHED = new ErrorCode(1_040_714_121, "鍙湁宸插畬鎴愮姸鎬佺殑鐩樼偣浠诲姟鎵嶈兘澶勭悊宸紓");
+    ErrorCode WM_STOCK_TAKING_TASK_SCAN_BATCH_NOT_FOUND = new ErrorCode(1_040_714_122, "鐩樼偣鎵爜鏈畾浣嶅埌瀵瑰簲鎵规锛岃鏍稿杩芥函鐮�");
+    ErrorCode WM_STOCK_TAKING_TASK_SCAN_BATCH_NOT_IN_TASK = new ErrorCode(1_040_714_123, "鎵爜鎵规涓嶅湪鐩樼偣浠诲姟鑼冨洿鍐�");
 
     // ========== MES 浠撳簱绠$悊-閿�鍞嚭搴撳崟锛�1-040-714-000锛� ==========
     ErrorCode WM_PRODUCT_SALES_NOT_EXISTS = new ErrorCode(1_040_714_000, "閿�鍞嚭搴撳崟涓嶅瓨鍦�");
@@ -787,6 +789,9 @@
     ErrorCode WM_PACKAGE_HAS_CHILDREN = new ErrorCode(1_040_740_008, "瀛樺湪瀛愮锛屼笉鍏佽鍒犻櫎");
     ErrorCode WM_PACKAGE_LINE_NOT_EXISTS = new ErrorCode(1_040_740_100, "瑁呯鏄庣粏涓嶅瓨鍦�");
 
+    // ========== MES 浠撳簱绠$悊-搴撳瓨棰勮锛�1-040-756-000锛� ==========
+    ErrorCode WM_STOCK_WARNING_NOT_EXISTS = new ErrorCode(1_040_756_000, "搴撳瓨棰勮璁板綍涓嶅瓨鍦�");
+
     // ========== MES 浜у搧璁捐-璁捐椤圭洰锛�1-040-800-000锛� ==========
     ErrorCode PD_PROJECT_NOT_EXISTS = new ErrorCode(1_040_800_000, "璁捐椤圭洰涓嶅瓨鍦�");
     ErrorCode PD_PROJECT_NOT_INITIATE = new ErrorCode(1_040_800_001, "璁捐椤圭洰涓嶆槸寰呭彂甯冪姸鎬侊紝涓嶅厑璁镐慨鏀规垨鍒犻櫎");
@@ -869,4 +874,7 @@
     ErrorCode WMS_APPROVAL_STATUS_INVALID = new ErrorCode(1_040_900_004, "鍗曟嵁鐘舵�佷笉姝g‘锛屾棤娉曟彁浜ゅ鎵�");
     ErrorCode WMS_APPROVAL_ALREADY_SUBMITTED = new ErrorCode(1_040_900_005, "鍗曟嵁宸叉彁浜ゅ鎵癸紝璇峰嬁閲嶅鎻愪氦");
 
+    // ========== MES 鍏紑鎵爜婧簮锛�1-040-760-000锛� ==========
+    ErrorCode MES_TRACE_CODE_NOT_FOUND = new ErrorCode(1_040_760_000, "鏈煡璇㈠埌璇ヨ拷婧爜锛岃鏍稿浜岀淮鐮佹垨鑱旂郴瀹樻柟");
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmStockWarningTypeEnum.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmStockWarningTypeEnum.java
new file mode 100644
index 0000000..545f9aa
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmStockWarningTypeEnum.java
@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.mes.enums.wm;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * MES 搴撳瓨棰勮绫诲瀷鏋氫妇
+ */
+@Getter
+@AllArgsConstructor
+public enum MesWmStockWarningTypeEnum {
+
+    LOW_STOCK(10, "浣庡簱瀛�"),
+    EXPIRING(20, "涓存湡"),
+    AGING(30, "搴撻緞");
+
+    private final Integer type;
+    private final String name;
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesProperties.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesProperties.java
new file mode 100644
index 0000000..4858583
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesProperties.java
@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.mes.framework.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * MES 鑷畾涔夐厤缃」
+ */
+@Data
+@Component
+@ConfigurationProperties(prefix = "mes")
+public class MesProperties {
+
+    /**
+     * H5 婧簮椤甸潰鍦板潃锛坱race.html锛夊熀纭� URL锛岀敤浜庣敓鎴愭惡甯﹁拷婧爜鍙傛暟鐨勪簩缁寸爜鍐呭銆�
+     * 涓虹┖鏃朵簩缁寸爜浠嶇紪鐮佺函鐮佸�硷紙鍏煎鏃х爜锛夈��
+     */
+    private String traceBaseUrl = "";
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesQrCodeConfig.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesQrCodeConfig.java
new file mode 100644
index 0000000..d4da7f7
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesQrCodeConfig.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.mes.framework.config;
+
+import cn.iocoder.yudao.module.mes.framework.qrcode.MesProQrLabelBuilder;
+import jakarta.annotation.PostConstruct;
+import jakarta.annotation.Resource;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * MES 浜岀淮鐮佺浉鍏抽厤缃細鍚姩鏃跺皢 traceBaseUrl 娉ㄥ叆浜岀淮鐮佹爣绛炬瀯寤哄櫒锛岄伩鍏嶄镜鍏� Controller 璋冪敤鐐�
+ */
+@Configuration(proxyBeanMethods = false)
+public class MesQrCodeConfig {
+
+    @Resource
+    private MesProperties mesProperties;
+
+    @PostConstruct
+    public void init() {
+        MesProQrLabelBuilder.setTraceBaseUrl(mesProperties.getTraceBaseUrl());
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/qrcode/MesProQrLabelBuilder.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/qrcode/MesProQrLabelBuilder.java
index 822e040..80b198d 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/qrcode/MesProQrLabelBuilder.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/qrcode/MesProQrLabelBuilder.java
@@ -8,16 +8,39 @@
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.pallet.MesProPalletDO;
 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
 
+import java.nio.charset.StandardCharsets;
+import java.net.URLEncoder;
 import java.util.LinkedHashMap;
 import java.util.Map;
 
 /**
- * MES 浜岀淮鐮佹爣绛炬瀯寤哄櫒锛氱粺涓�"浜岀淮鐮佺紪鐮佺函鐮佸�� + 鍥剧墖鍙犲姞鍙鏂囨湰"鐨勬枃鏈粍瑁呰鍒欙紝
- * 淇濊瘉鎵规銆佹墭鐩樸�佽鐮佷笁绉嶆爣绛剧殑鍏抽敭淇℃伅淇濈暀涓�鑷�
+ * MES 浜岀淮鐮佹爣绛炬瀯寤哄櫒锛氱粺涓�"浜岀淮鐮佺紪鐮佸唴瀹� + 鍥剧墖鍙犲姞鍙鏂囨湰"鐨勬枃鏈粍瑁呰鍒欙紝
+ * 淇濊瘉鎵规銆佹墭鐩樸�佽鐮佷笁绉嶆爣绛剧殑鍏抽敭淇℃伅淇濈暀涓�鑷淬��
+ * <p>
+ * 閰嶇疆浜� {@code mes.trace-base-url} 鏃讹紝浜岀淮鐮佸唴瀹逛负 H5 婧簮鍦板潃锛堝甫 code 鍙傛暟锛夛紝鍚﹀垯鍥為��涓虹函鐮佸�硷紙鍏煎鏃х爜锛�
  */
 public final class MesProQrLabelBuilder {
 
     private MesProQrLabelBuilder() {
+    }
+
+    /**
+     * H5 婧簮椤甸潰鍩虹鍦板潃锛岀敱 {@code MesQrCodeConfig} 鍚姩鏃舵敞鍏ワ紱涓虹┖鏃朵簩缁寸爜缂栫爜绾爜鍊�
+     */
+    private static volatile String traceBaseUrl = "";
+
+    public static void setTraceBaseUrl(String traceBaseUrl) {
+        MesProQrLabelBuilder.traceBaseUrl = StrUtil.blankToDefault(traceBaseUrl, "");
+    }
+
+    /**
+     * 缁勮浜岀淮鐮佸唴瀹癸細閰嶇疆浜嗘函婧愬湴鍧�鍒欐嫾 URL锛屽惁鍒欒繑鍥炵函鐮佸��
+     */
+    private static String buildQrCodeContent(String code) {
+        if (StrUtil.isEmpty(traceBaseUrl)) {
+            return code;
+        }
+        return traceBaseUrl + "?code=" + URLEncoder.encode(code, StandardCharsets.UTF_8);
     }
 
     /**
@@ -51,7 +74,7 @@
         if (user != null) {
             keyValues.put("璐d换浜�", user.getNickname());
         }
-        return QrLabelInfo.builder().fileName(code).code(code).keyValues(keyValues).build();
+        return QrLabelInfo.builder().fileName(code).code(buildQrCodeContent(code)).keyValues(keyValues).build();
     }
 
     /**
@@ -78,7 +101,7 @@
         if (batch != null) {
             fillBatchKeyValues(keyValues, batch, itemMap, userMap);
         }
-        return QrLabelInfo.builder().fileName(code).code(code).keyValues(keyValues).build();
+        return QrLabelInfo.builder().fileName(code).code(buildQrCodeContent(code)).keyValues(keyValues).build();
     }
 
     /**
@@ -98,7 +121,7 @@
         if (batch != null) {
             fillBatchKeyValues(keyValues, batch, itemMap, userMap);
         }
-        return QrLabelInfo.builder().fileName(bagCode.getCode()).code(bagCode.getCode()).keyValues(keyValues).build();
+        return QrLabelInfo.builder().fileName(bagCode.getCode()).code(buildQrCodeContent(bagCode.getCode())).keyValues(keyValues).build();
     }
 
     /**
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/job/wm/MesWmStockWarningJob.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/job/wm/MesWmStockWarningJob.java
new file mode 100644
index 0000000..3dfb86f
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/job/wm/MesWmStockWarningJob.java
@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.mes.job.wm;
+
+import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
+import cn.iocoder.yudao.module.mes.service.wm.warning.MesWmStockWarningService;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+/**
+ * MES 搴撳瓨棰勮瀹氭椂钀藉簱 Job
+ * <p>
+ * 閲嶆柊璁$畻褰撳墠搴撳瓨棰勮锛堜綆搴撳瓨/涓存湡/搴撻緞锛夛紝鍒犻櫎鏃х殑鏈鐞嗛璀﹀悗鍐欏叆锛屼繚鐣欏凡澶勭悊鍘嗗彶銆�
+ */
+@Component
+@Slf4j
+public class MesWmStockWarningJob implements JobHandler {
+
+    @Resource
+    private MesWmStockWarningService stockWarningService;
+
+    @Override
+    public String execute(String param) {
+        int count = stockWarningService.persistCurrentWarnings();
+        log.info("[execute][搴撳瓨棰勮瀹氭椂钀藉簱 {} 鏉", count);
+        return String.format("搴撳瓨棰勮瀹氭椂钀藉簱 %s 鏉�", count);
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/scan/MesProScanEventService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/scan/MesProScanEventService.java
index d4b48fc..fbf2a35 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/scan/MesProScanEventService.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/scan/MesProScanEventService.java
@@ -6,6 +6,7 @@
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.scan.MesProScanEventDO;
 import jakarta.validation.Valid;
 
+import java.util.List;
 import java.util.Map;
 
 public interface MesProScanEventService {
@@ -14,4 +15,12 @@
     PageResult<MesProScanEventDO> getPage(MesProScanEventPageReqVO reqVO);
     MesProScanEventDO getLatest(String deviceCode);
     Map<String, Object> getDashboard();
+
+    /**
+     * 鑾峰緱鎵规涓嬬殑鎵爜鍏ュ簱浜嬩欢鍒楄〃锛堟寜浜嬩欢鏃堕棿鍊掑簭锛夛紝鐢ㄤ簬鍏紑婧簮灞曠ず鏈�鏂板叆搴撲簨浠�
+     *
+     * @param batchId 鎵规缂栧彿
+     * @return 浜嬩欢鍒楄〃
+     */
+    List<MesProScanEventDO> getByBatchId(Long batchId);
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/scan/MesProScanEventServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/scan/MesProScanEventServiceImpl.java
index e2c841c..6ee0402 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/scan/MesProScanEventServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/scan/MesProScanEventServiceImpl.java
@@ -6,13 +6,16 @@
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.scan.MesProScanEventDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO;
 import cn.iocoder.yudao.module.mes.dal.mysql.pro.bagcode.MesProBagCodeMapper;
 import cn.iocoder.yudao.module.mes.dal.mysql.pro.batch.MesProBatchMapper;
 import cn.iocoder.yudao.module.mes.dal.mysql.pro.scan.MesProScanEventMapper;
 import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
 import cn.iocoder.yudao.module.mes.enums.MesProScanEventStatusEnum;
+import cn.iocoder.yudao.module.mes.enums.wm.MesWmStockStateEnum;
 import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
+import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
 import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService;
 import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
 import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseService;
@@ -23,6 +26,7 @@
 
 import java.time.LocalDateTime;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 @Service
@@ -33,6 +37,7 @@
     @Resource private MesProBatchMapper batchMapper;
     @Resource private MesWmTransactionService transactionService;
     @Resource private MesWmWarehouseService warehouseService;
+    @Resource private MesWmBatchService mesWmBatchService;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -60,12 +65,19 @@
         if (warehouse == null) return failed(event, "MES_SCAN_WIP_WAREHOUSE_NOT_EXISTS", "WIP铏氭嫙浠撲笉瀛樺湪");
         event.setWarehouseId(warehouse.getId()).setLocationId(req.getLocationId()).setAreaId(req.getAreaId());
         try {
+            // 鐢熶骇鎵规妗ユ帴浠撳簱鎵规锛氬簱瀛�/娴佹按缁熶竴鎸傚湪 mes_wm_batch 涓婏紝鎵爜鍏ユ殏瀛樺簱寰呰川妫�
+            MesWmBatchDO wmBatch = mesWmBatchService.getOrCreateByProBatch(batch);
+            if (wmBatch == null) {
+                return failed(event, "MES_SCAN_WM_BATCH_NOT_EXISTS", "浠撳簱鎵规妗ユ帴澶辫触");
+            }
             Long txId = transactionService.createTransaction(new MesWmTransactionSaveReqDTO()
                     .setType(MesWmTransactionTypeEnum.IN.getType()).setBizType(MesBizTypeConstants.WM_PRODUCT_PRODUCE)
                     .setBizId(batch.getId()).setBizCode(batch.getCode()).setBizLineId(bag.getId())
-                    .setItemId(batch.getItemId()).setQuantity(req.getQuantity()).setBatchCode(batch.getCode())
+                    .setItemId(batch.getItemId()).setQuantity(req.getQuantity())
+                    .setBatchId(wmBatch.getId()).setBatchCode(batch.getCode())
                     .setWarehouseId(warehouse.getId()).setLocationId(req.getLocationId()).setAreaId(req.getAreaId())
-                    .setReceiptTime(LocalDateTime.now()).setCheckFlag(true));
+                    .setReceiptTime(LocalDateTime.now()).setCheckFlag(true)
+                    .setStockState(MesWmStockStateEnum.TEMP.getState()));
             event.setTransactionId(txId).setStatus(MesProScanEventStatusEnum.SUCCESS).setEventTime(LocalDateTime.now());
             eventMapper.updateById(event);
             bagCodeMapper.markInbound(bag.getId(), event.getId(), req.getDeviceCode(), req.getLineCode());
@@ -86,6 +98,7 @@
     @Override public MesProScanEventDO getByEventId(String eventId) { return eventMapper.selectOne(MesProScanEventDO::getEventId, eventId); }
     @Override public PageResult<MesProScanEventDO> getPage(MesProScanEventPageReqVO reqVO) { return eventMapper.selectPage(reqVO); }
     @Override public MesProScanEventDO getLatest(String deviceCode) { return eventMapper.selectLatest(deviceCode); }
+    @Override public List<MesProScanEventDO> getByBatchId(Long batchId) { return eventMapper.selectListByBatchId(batchId); }
     @Override public Map<String, Object> getDashboard() {
         Map<String, Object> result = new HashMap<>();
         result.put("latest", eventMapper.selectLatest(null));
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/seedquality/MesQcSeedQualityService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/seedquality/MesQcSeedQualityService.java
index 8fec7b6..8fbf16c 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/seedquality/MesQcSeedQualityService.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/seedquality/MesQcSeedQualityService.java
@@ -1,3 +1,29 @@
 package cn.iocoder.yudao.module.mes.service.qc.seedquality;
+
 import cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualitySubmitReqVO;
-public interface MesQcSeedQualityService { Long submit(MesQcSeedQualitySubmitReqVO reqVO); }
+import cn.iocoder.yudao.module.mes.dal.dataobject.qc.seedquality.MesQcSeedQualityDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.qc.seedquality.MesQcSeedQualityIndicatorDO;
+
+import java.util.List;
+
+public interface MesQcSeedQualityService {
+
+    Long submit(MesQcSeedQualitySubmitReqVO reqVO);
+
+    /**
+     * 鏍规嵁鎵规鍙锋煡璇㈣川妫�璁板綍锛堟寜妫�楠屾椂闂村�掑簭锛屽彇鏈�鏂帮級锛岀敤浜庡叕寮�婧簮銆�
+     * 绉嶅瓙璐ㄦ琛ㄦ湭鍒濆鍖栨椂杩斿洖绌哄垪琛紙瀹归敊锛屼笉褰卞搷杩芥函锛夈��
+     *
+     * @param batchCode 鎵规鍙�
+     * @return 璐ㄦ璁板綍鍒楄〃
+     */
+    List<MesQcSeedQualityDO> getQualityByBatchCode(String batchCode);
+
+    /**
+     * 鏍规嵁璐ㄦ璁板綍缂栧彿鏌ヨ璐ㄦ鎸囨爣鍒楄〃
+     *
+     * @param qualityId 璐ㄦ璁板綍缂栧彿
+     * @return 璐ㄦ鎸囨爣鍒楄〃
+     */
+    List<MesQcSeedQualityIndicatorDO> getIndicatorsByQualityId(Long qualityId);
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/seedquality/MesQcSeedQualityServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/seedquality/MesQcSeedQualityServiceImpl.java
index 531c3cf..60b78c1 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/seedquality/MesQcSeedQualityServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/seedquality/MesQcSeedQualityServiceImpl.java
@@ -2,20 +2,31 @@
 
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualitySubmitReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.qc.seedquality.*;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
 import cn.iocoder.yudao.module.mes.dal.mysql.qc.seedquality.*;
+import cn.iocoder.yudao.module.mes.enums.wm.MesWmStockStateEnum;
+import cn.iocoder.yudao.module.mes.service.pro.batch.MesProBatchService;
+import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
+import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService;
 import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.jdbc.BadSqlGrammarException;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 import java.time.LocalDateTime;
 import java.util.*;
 
-@Service @Validated
+@Service @Validated @Slf4j
 public class MesQcSeedQualityServiceImpl implements MesQcSeedQualityService {
     private static final Set<String> REQUIRED = Set.of("purity", "cleanliness", "germination_rate", "moisture");
     @Resource private MesQcSeedQualityMapper qualityMapper;
     @Resource private MesQcSeedQualityIndicatorMapper indicatorMapper;
+    @Resource private MesProBatchService proBatchService;
+    @Resource private MesWmBatchService mesWmBatchService;
+    @Resource private MesWmMaterialStockService materialStockService;
 
     @Override @Transactional(rollbackFor = Exception.class)
     public Long submit(MesQcSeedQualitySubmitReqVO reqVO) {
@@ -36,7 +47,41 @@
         if (!codes.equals(REQUIRED)) throw new IllegalArgumentException("鍥涢」鎸囨爣涓嶅畬鏁�");
         quality.setResult(passed ? 1 : 2);
         qualityMapper.updateById(quality);
+        // 璐ㄦ鍚堟牸 鈫� 搴撳瓨鏁存壒鐢辨殏瀛樺簱锛堝緟璐ㄦ锛夎浆涓哄悎鏍煎簱锛堟寮忓簱瀛橈級
+        if (passed) {
+            MesProBatchDO proBatch = proBatchService.getBatchByCode(reqVO.getBatchCode());
+            if (proBatch != null) {
+                MesWmBatchDO wmBatch = mesWmBatchService.getOrCreateByProBatch(proBatch);
+                if (wmBatch != null) {
+                    int updated = materialStockService.updateStockStateByBatchId(wmBatch.getId(),
+                            MesWmStockStateEnum.QUALIFIED.getState(), MesWmStockStateEnum.TEMP.getState());
+                    log.info("[submit][璐ㄦ鍚堟牸锛屾壒娆� {} 搴撳瓨鐢辨殏瀛樺簱杞悎鏍煎簱锛屽奖鍝� {} 鏉", reqVO.getBatchCode(), updated);
+                }
+            }
+        }
         // 搴撲綅鐢卞悗鍙伴厤缃湇鍔℃帴绠★紝褰撳墠鐗堟湰淇濈暀瀹¤闂幆锛岀姝㈠墠绔紶鍏ュ簱浣嶃��
         return quality.getId();
     }
+
+    @Override
+    public List<MesQcSeedQualityDO> getQualityByBatchCode(String batchCode) {
+        try {
+            return qualityMapper.selectListByBatchCode(batchCode);
+        } catch (BadSqlGrammarException ex) {
+            // 绉嶅瓙璐ㄦ琛ㄦ湭鍒濆鍖栨椂瀹归敊锛屼笉闃诲鍏紑婧簮
+            log.warn("[getQualityByBatchCode][绉嶅瓙璐ㄦ琛ㄦ湭鍒濆鍖栵紝鎵规 {} 鏌ヨ澶辫触: {}]", batchCode, ex.getMessage());
+            return List.of();
+        }
+    }
+
+    @Override
+    public List<MesQcSeedQualityIndicatorDO> getIndicatorsByQualityId(Long qualityId) {
+        try {
+            return indicatorMapper.selectListByQualityId(qualityId);
+        } catch (BadSqlGrammarException ex) {
+            // 绉嶅瓙璐ㄦ琛ㄦ湭鍒濆鍖栨椂瀹归敊
+            log.warn("[getIndicatorsByQualityId][绉嶅瓙璐ㄦ鎸囨爣琛ㄦ湭鍒濆鍖栵紝璐ㄦ {} 鏌ヨ澶辫触: {}]", qualityId, ex.getMessage());
+            return List.of();
+        }
+    }
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/trace/MesProTracePublicService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/trace/MesProTracePublicService.java
new file mode 100644
index 0000000..9122b85
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/trace/MesProTracePublicService.java
@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.mes.service.trace;
+
+import cn.iocoder.yudao.module.mes.controller.pub.trace.vo.MesProTracePublicRespVO;
+
+/**
+ * 鍏紑鎵爜婧簮 Service 鎺ュ彛
+ * <p>
+ * 闈㈠悜鍏紬锛堝厤鐧诲綍锛夛紝鏍规嵁琚嬬爜 / 鎵樼洏鐮� / 鎵规鐮佹煡璇㈠叏绋嬫函婧愪俊鎭�備綔涓鸿拷婧綋绯荤殑鏍稿績鑱氬悎鍏ュ彛锛�
+ * 鍚庣画鏂板鏁版嵁婧愶紙璐ㄦ鎵╁睍銆侀槻浼獙璇併�佸偍杩愪俊鎭瓑锛夊湪鑱氬悎瀹炵幇涓拷鍔犱俊鎭尯鍧楀嵆鍙��
+ */
+public interface MesProTracePublicService {
+
+    /**
+     * 鏍规嵁杩芥函鐮佹煡璇㈠叏绋嬫函婧愪俊鎭�
+     *
+     * @param code 杩芥函鐮侊紙琚嬬爜 / 鎵樼洏鐮� / 鎵规鐮侊紝鍏煎鏁� H5 URL锛�
+     * @return 鍏ㄧ▼婧簮淇℃伅
+     */
+    MesProTracePublicRespVO queryTrace(String code);
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/trace/MesProTracePublicServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/trace/MesProTracePublicServiceImpl.java
new file mode 100644
index 0000000..744bccc
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/trace/MesProTracePublicServiceImpl.java
@@ -0,0 +1,348 @@
+package cn.iocoder.yudao.module.mes.service.trace;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchProcessTraceRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchPurchaseSourceRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchSalesTargetRespVO;
+import cn.iocoder.yudao.module.mes.controller.pub.trace.vo.MesProTracePublicRespVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchWorkerDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.pallet.MesProPalletDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.scan.MesProScanEventDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.qc.seedquality.MesQcSeedQualityDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.qc.seedquality.MesQcSeedQualityIndicatorDO;
+import cn.iocoder.yudao.module.mes.enums.pro.MesProBagCodeStatusEnum;
+import cn.iocoder.yudao.module.mes.enums.pro.MesProBatchStatusEnum;
+import cn.iocoder.yudao.module.mes.enums.pro.MesProBatchWorkerTypeEnum;
+import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService;
+import cn.iocoder.yudao.module.mes.service.pro.bagcode.MesProBagCodeService;
+import cn.iocoder.yudao.module.mes.service.pro.batch.MesProBatchService;
+import cn.iocoder.yudao.module.mes.service.pro.pallet.MesProPalletService;
+import cn.iocoder.yudao.module.mes.service.pro.scan.MesProScanEventService;
+import cn.iocoder.yudao.module.mes.service.qc.seedquality.MesQcSeedQualityService;
+import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.MES_TRACE_CODE_NOT_FOUND;
+
+/**
+ * 鍏紑鎵爜婧簮 Service 瀹炵幇
+ * <p>
+ * 杩芥函鐮佽В鏋愰『搴忥細琚嬬爜 鈫� 鎵樼洏鐮� 鈫� 鎵规鐮侊紙鍚复鏃舵壒娆$爜锛夈�備互鐢熶骇鎵规涓轰富鏁版嵁婧愯仛鍚堝叏绋嬩俊鎭��
+ */
+@Service
+@Validated
+@Slf4j
+public class MesProTracePublicServiceImpl implements MesProTracePublicService {
+
+    /**
+     * H5 琚嬬爜鍒楄〃灞曠ず涓婇檺锛堝叏閲忕粺璁� + 闄愰噺鏄庣粏锛�
+     */
+    private static final int BAG_LIST_LIMIT = 100;
+
+    @Resource
+    private MesProBagCodeService bagCodeService;
+    @Resource
+    private MesProPalletService palletService;
+    @Resource
+    private MesProBatchService batchService;
+    @Resource
+    private MesProScanEventService scanEventService;
+    @Resource
+    private MesQcSeedQualityService qcSeedQualityService;
+    @Resource
+    private MesMdmItemService mdmItemService;
+    @Resource
+    private AdminUserApi adminUserApi;
+    @Resource
+    private MesWmBatchService mesWmBatchService;
+
+    @Override
+    public MesProTracePublicRespVO queryTrace(String code) {
+        String rawCode = StrUtil.trim(StrUtil.nullToEmpty(code));
+        String traceCode = normalize(rawCode);
+        if (StrUtil.isEmpty(traceCode)) {
+            throw exception(MES_TRACE_CODE_NOT_FOUND);
+        }
+        // 1. 瑙f瀽杩芥函鍏ュ彛锛屽畾浣嶆壒娆�
+        TraceEntry entry = resolveEntry(traceCode);
+        if (entry.batchId == null) {
+            throw exception(MES_TRACE_CODE_NOT_FOUND);
+        }
+        // 2. 鑱氬悎鍏ㄧ▼淇℃伅
+        return buildRespVO(rawCode, traceCode, entry.sourceType, entry.batchId);
+    }
+
+    // ==================== 杩芥函鐮佽В鏋� ====================
+
+    /**
+     * 鍏煎鏁� URL 鎵爜锛堜簩缁寸爜鍐呭涓� H5 鍦板潃鏃讹級锛屼粎鎻愬彇 code 鍙傛暟锛涘惁鍒欏師鏍疯繑鍥�
+     */
+    private String normalize(String code) {
+        if (StrUtil.startWithIgnoreCase(code, "http")) {
+            String query = StrUtil.subAfter(code, "?", false);
+            if (StrUtil.isNotEmpty(query)) {
+                // 鍘绘帀 # 閿氱偣
+                query = StrUtil.subBefore(query, "#", false);
+                for (String pair : StrUtil.split(query, "&")) {
+                    String[] kv = pair.split("=", 2);
+                    if (kv.length == 2 && "code".equals(kv[0])) {
+                        return StrUtil.trim(URLDecoder.decode(kv[1], StandardCharsets.UTF_8));
+                    }
+                }
+            }
+            return "";
+        }
+        return code;
+    }
+
+    private TraceEntry resolveEntry(String traceCode) {
+        // 鈶� 琚嬬爜
+        MesProBagCodeDO bag = bagCodeService.getBagCodeByCode(traceCode);
+        if (bag != null) {
+            return new TraceEntry("BAG", bag.getBatchId());
+        }
+        // 鈶� 鎵樼洏鐮�
+        MesProPalletDO pallet = palletService.getPalletByCode(traceCode);
+        if (pallet != null) {
+            return new TraceEntry("PALLET", pallet.getBatchId());
+        }
+        // 鈶� 鎵规鐮侊紙鍚复鏃舵壒娆$爜锛実etBatchByCode 鍐呴儴鍏煎锛�
+        MesProBatchDO batch = batchService.getBatchByCode(traceCode);
+        if (batch != null) {
+            return new TraceEntry("BATCH", batch.getId());
+        }
+        return new TraceEntry("", null);
+    }
+
+    // ==================== 鑱氬悎鏋勫缓 ====================
+
+    private MesProTracePublicRespVO buildRespVO(String requestCode, String traceCode, String sourceType, Long batchId) {
+        MesProBatchDO batch = batchService.getBatch(batchId);
+        if (batch == null) {
+            throw exception(MES_TRACE_CODE_NOT_FOUND);
+        }
+        MesProTracePublicRespVO resp = new MesProTracePublicRespVO();
+        resp.setSourceType(sourceType);
+        resp.setRequestCode(requestCode);
+        // 鍝佺鍖哄潡
+        fillItem(resp, batch);
+        // 鎵规鍖哄潡
+        resp.setBatchCode(batch.getCode()).setTempCode(batch.getTempCode())
+                .setBatchStatus(batch.getStatus()).setBatchStatusName(batchStatusName(batch.getStatus()))
+                .setProduceDate(batch.getProduceDate()).setLineCode(batch.getLineCode()).setLineName(batch.getLineName())
+                .setOwnerUserId(batch.getOwnerUserId()).setOwnerUserName(userName(batch.getOwnerUserId()))
+                .setPlanQuantity(batch.getPlanQuantity()).setBagQuantity(batch.getBagQuantity())
+                .setPalletQuantity(batch.getPalletQuantity()).setInboundTime(batch.getInboundTime())
+                .setRemark(batch.getRemark());
+        // 浜哄憳鍖哄潡
+        resp.setWorkers(buildWorkers(batchId));
+        // 鎵樼洏鍖哄潡
+        fillPallets(resp, batchId);
+        // 琚嬬爜鍖哄潡
+        fillBags(resp, batchId);
+        // 鍏ュ簱浜嬩欢鍖哄潡
+        resp.setInbound(buildInbound(batchId));
+        // 璐ㄦ鍖哄潡
+        resp.setQuality(buildQuality(batch));
+        // 鍙�夋墿灞曞尯鍧楋紙鏈夋暟鎹墠杩斿洖锛�
+        fillOptionalModules(resp, batch);
+        return resp;
+    }
+
+    private void fillItem(MesProTracePublicRespVO resp, MesProBatchDO batch) {
+        if (batch.getItemId() == null) {
+            return;
+        }
+        MesMdmItemDO item = mdmItemService.getItem(batch.getItemId());
+        if (item == null) {
+            return;
+        }
+        resp.setItemCode(item.getCode()).setItemName(item.getName()).setItemSpecification(item.getSpecification());
+    }
+
+    private List<MesProTracePublicRespVO.WorkerVO> buildWorkers(Long batchId) {
+        List<MesProBatchWorkerDO> workers = batchService.getBatchWorkerListByBatchId(batchId);
+        if (CollUtil.isEmpty(workers)) {
+            return List.of();
+        }
+        Set<Long> userIds = convertSet(workers, MesProBatchWorkerDO::getUserId);
+        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
+        return convertList(workers, worker -> {
+            MesProTracePublicRespVO.WorkerVO vo = new MesProTracePublicRespVO.WorkerVO();
+            vo.setWorkerType(worker.getWorkerType());
+            vo.setWorkerTypeName(workerTypeName(worker.getWorkerType()));
+            vo.setRemark(worker.getRemark());
+            AdminUserRespDTO user = userMap.get(worker.getUserId());
+            vo.setUserName(user == null ? null : user.getNickname());
+            return vo;
+        });
+    }
+
+    private void fillPallets(MesProTracePublicRespVO resp, Long batchId) {
+        List<MesProPalletDO> pallets = palletService.getPalletListByBatchId(batchId);
+        resp.setPalletCount(CollUtil.isEmpty(pallets) ? 0 : pallets.size());
+        resp.setPallets(convertList(pallets, pallet -> {
+            MesProTracePublicRespVO.PalletVO vo = new MesProTracePublicRespVO.PalletVO();
+            vo.setCode(pallet.getCode()).setPalletNo(pallet.getPalletNo())
+                    .setBagCount(pallet.getBagCount()).setCapacity(pallet.getCapacity());
+            return vo;
+        }));
+    }
+
+    private void fillBags(MesProTracePublicRespVO resp, Long batchId) {
+        List<MesProBagCodeDO> bags = bagCodeService.getBagCodeListByBatchId(batchId);
+        List<MesProBagCodeDO> safeBags = bags == null ? List.of() : bags;
+        resp.setBagTotal((long) safeBags.size());
+        resp.setBags(convertList(safeBags.stream().limit(BAG_LIST_LIMIT).toList(), bag -> {
+            MesProTracePublicRespVO.BagVO vo = new MesProTracePublicRespVO.BagVO();
+            vo.setCode(bag.getCode()).setBagNo(bag.getBagNo()).setPalletCode(bag.getPalletCode())
+                    .setStatus(bag.getStatus()).setStatusName(bagCodeStatusName(bag.getStatus()))
+                    .setInboundTime(bag.getInboundTime());
+            return vo;
+        }));
+    }
+
+    private MesProTracePublicRespVO.InboundVO buildInbound(Long batchId) {
+        List<MesProScanEventDO> events = scanEventService.getByBatchId(batchId);
+        if (CollUtil.isEmpty(events)) {
+            return null;
+        }
+        MesProScanEventDO event = events.get(0);
+        MesProTracePublicRespVO.InboundVO vo = new MesProTracePublicRespVO.InboundVO();
+        vo.setEventId(event.getEventId()).setDeviceCode(event.getDeviceCode()).setLineCode(event.getLineCode())
+                .setQuantity(event.getQuantity()).setStatus(event.getStatus()).setEventTime(event.getEventTime());
+        return vo;
+    }
+
+    private MesProTracePublicRespVO.QualityVO buildQuality(MesProBatchDO batch) {
+        List<MesQcSeedQualityDO> list = qcSeedQualityService.getQualityByBatchCode(batch.getCode());
+        if (CollUtil.isEmpty(list)) {
+            return null;
+        }
+        MesQcSeedQualityDO quality = list.get(0);
+        MesProTracePublicRespVO.QualityVO vo = new MesProTracePublicRespVO.QualityVO();
+        vo.setResult(quality.getResult()).setResultName(qualityResultName(quality.getResult()))
+                .setReason(quality.getReason()).setInspectorName(userName(quality.getInspectorId()))
+                .setInspectedAt(quality.getInspectedAt());
+        List<MesQcSeedQualityIndicatorDO> indicators = qcSeedQualityService.getIndicatorsByQualityId(quality.getId());
+        vo.setIndicators(convertList(indicators, ind -> {
+            MesProTracePublicRespVO.IndicatorVO i = new MesProTracePublicRespVO.IndicatorVO();
+            i.setIndicatorCode(ind.getIndicatorCode()).setIndicatorName(indicatorName(ind.getIndicatorCode()))
+                    .setValue(ind.getValue()).setMinValue(ind.getMinValue()).setMaxValue(ind.getMaxValue());
+            i.setPass(ind.getValue() != null && ind.getMinValue() != null && ind.getMaxValue() != null
+                    && ind.getValue().compareTo(ind.getMinValue()) >= 0 && ind.getValue().compareTo(ind.getMaxValue()) <= 0);
+            return i;
+        }));
+        return vo;
+    }
+
+    private void fillOptionalModules(MesProTracePublicRespVO resp, MesProBatchDO batch) {
+        try {
+            List<MesWmBatchPurchaseSourceRespVO> purchaseSources = mesWmBatchService.getPurchaseSourceList(batch.getCode());
+            List<MesWmBatchSalesTargetRespVO> salesTargets = mesWmBatchService.getSalesTargetList(batch.getCode());
+            List<MesWmBatchProcessTraceRespVO> processTraces = mesWmBatchService.getProcessTraceList(batch.getCode());
+            resp.setPurchaseSources(CollUtil.isNotEmpty(purchaseSources) ? purchaseSources : List.of());
+            resp.setSalesTargets(CollUtil.isNotEmpty(salesTargets) ? salesTargets : List.of());
+            resp.setProcessTraces(CollUtil.isNotEmpty(processTraces) ? processTraces : List.of());
+        } catch (Exception ex) {
+            // 浠撳簱鎵规浣撶郴锛堥噰璐�/閿�鍞�/娴佽浆锛夋湭鍚敤鏃跺閿欙紝涓嶅奖鍝嶆牳蹇冭拷婧�
+            log.warn("[fillOptionalModules][鎵规 {} 鎵╁睍妯″潡鏌ヨ澶辫触: {}]", batch.getCode(), ex.getMessage());
+            resp.setPurchaseSources(List.of());
+            resp.setSalesTargets(List.of());
+            resp.setProcessTraces(List.of());
+        }
+    }
+
+    // ==================== 鍚嶇О鏄犲皠 ====================
+
+    private String userName(Long userId) {
+        if (userId == null) {
+            return null;
+        }
+        AdminUserRespDTO user = adminUserApi.getUser(userId);
+        return user == null ? null : user.getNickname();
+    }
+
+    private String batchStatusName(Integer status) {
+        if (status == null) {
+            return "";
+        }
+        for (MesProBatchStatusEnum e : MesProBatchStatusEnum.values()) {
+            if (e.getStatus().equals(status)) {
+                return e.getName();
+            }
+        }
+        return String.valueOf(status);
+    }
+
+    private String bagCodeStatusName(Integer status) {
+        if (status == null) {
+            return "";
+        }
+        for (MesProBagCodeStatusEnum e : MesProBagCodeStatusEnum.values()) {
+            if (e.getStatus().equals(status)) {
+                return e.getName();
+            }
+        }
+        return String.valueOf(status);
+    }
+
+    private String workerTypeName(Integer workerType) {
+        if (workerType == null) {
+            return "";
+        }
+        for (MesProBatchWorkerTypeEnum e : MesProBatchWorkerTypeEnum.values()) {
+            if (e.getType().equals(workerType)) {
+                return e.getName();
+            }
+        }
+        return String.valueOf(workerType);
+    }
+
+    private String qualityResultName(Integer result) {
+        if (result == null) {
+            return "";
+        }
+        return switch (result) {
+            case 0 -> "澶勭悊涓�";
+            case 1 -> "鍚堟牸";
+            case 2 -> "涓嶅悎鏍�";
+            default -> String.valueOf(result);
+        };
+    }
+
+    private String indicatorName(String code) {
+        if (code == null) {
+            return "";
+        }
+        return switch (code) {
+            case "purity" -> "绾害";
+            case "cleanliness" -> "鍑�搴�";
+            case "germination_rate" -> "鍙戣娊鐜�";
+            case "moisture" -> "姘村垎";
+            default -> code;
+        };
+    }
+
+    private record TraceEntry(String sourceType, Long batchId) {
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchService.java
index cf58d84..ebc00b9 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchService.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchService.java
@@ -6,6 +6,7 @@
 import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchProcessTraceRespVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchPurchaseSourceRespVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchSalesTargetRespVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
 
 import java.util.List;
@@ -131,4 +132,15 @@
      */
     MesWmBatchDO getBatchBySn(String sn);
 
+    /**
+     * 鏍规嵁鐢熶骇鎵规鑾峰彇鎴栧垱寤轰粨搴撴壒娆★紙鎵撻�氱敓浜ф壒娆′綋绯讳笌浠撳簱鎵规浣撶郴锛�
+     * <p>
+     * 鐢熶骇绾挎壂鐮佸叆搴撱�侀攢鍞嚭搴撱�佽川妫�杞悎鏍肩瓑娴佺▼锛屽簱瀛樹笌娴佹按缁熶竴鎸傚湪浠撳簱鎵规锛坢es_wm_batch锛変笂锛�
+     * 閫氳繃鐢熶骇鎵规鍙凤紙mes_pro_batch.code锛夋ˉ鎺ワ紝宸插瓨鍦ㄥ垯鐩存帴澶嶇敤锛屽惁鍒欐柊寤恒��
+     *
+     * @param proBatch 鐢熶骇鎵规
+     * @return 浠撳簱鎵规锛堝惈 id锛�
+     */
+    MesWmBatchDO getOrCreateByProBatch(MesProBatchDO proBatch);
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchServiceImpl.java
index ea88533..313063f 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchServiceImpl.java
@@ -10,12 +10,14 @@
 import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchPurchaseSourceRespVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchSalesTargetRespVO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.sn.MesWmSnDO;
 import cn.iocoder.yudao.module.mes.dal.mysql.wm.batch.MesWmBatchMapper;
 import cn.iocoder.yudao.module.mes.dal.mysql.wm.sn.MesWmSnMapper;
 import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
 import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodeRuleCodeEnum;
+import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum;
 import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService;
 import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService;
 import cn.iocoder.yudao.module.mes.service.wm.barcode.MesWmBarcodeService;
@@ -415,4 +417,28 @@
         return batchMapper.selectByCode(snDO.getBatchCode());
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public MesWmBatchDO getOrCreateByProBatch(MesProBatchDO proBatch) {
+        if (proBatch == null || StrUtil.isBlank(proBatch.getCode())) {
+            return null;
+        }
+        // 1. 澶嶇敤宸叉湁浠撳簱鎵规锛堝悓涓�鐢熶骇鎵规鍙凤級
+        MesWmBatchDO existing = batchMapper.selectByCode(proBatch.getCode());
+        if (existing != null) {
+            return existing;
+        }
+        // 2. 鏂板缓浠撳簱鎵规锛氫互鐢熶骇鎵规鍙蜂綔涓轰粨搴撴壒娆″彿锛岃川閲忕姸鎬佺疆涓哄緟妫�楠�
+        MesWmBatchDO batch = MesWmBatchDO.builder()
+                .code(proBatch.getCode())
+                .itemId(proBatch.getItemId())
+                .produceDate(proBatch.getProduceDate())
+                .receiptDate(java.time.LocalDateTime.now())
+                .qualityStatus(MesWmQualityStatusEnum.PENDING.getStatus())
+                .remark("鐢熶骇鎵规鑷姩妗ユ帴")
+                .build();
+        batchMapper.insert(batch);
+        return batch;
+    }
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockService.java
index 50c9faa..2e6a51f 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockService.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockService.java
@@ -6,6 +6,7 @@
 import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockListReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockMoveReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmStockSummaryRespVO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO;
 import jakarta.validation.Valid;
 
@@ -142,6 +143,40 @@
     void updateMaterialStockState(Long id, Integer stockState);
 
     /**
+     * 鎸夊搧绉嶇淮搴︽眹鎬诲簱瀛樻暟閲忥紙鍐呭瓨鑱氬悎锛屾敮鎸� stockState 绛夎繃婊ゆ潯浠讹級
+     *
+     * @param reqVO 杩囨护鏉′欢
+     * @return 鍝佺姹囨�诲垪琛�
+     */
+    List<MesWmStockSummaryRespVO> getStockSummaryByItem(MesWmMaterialStockListReqVO reqVO);
+
+    /**
+     * 鎸夋壒娆$淮搴︽眹鎬诲簱瀛樻暟閲忥紙鍐呭瓨鑱氬悎锛屾敮鎸� stockState 绛夎繃婊ゆ潯浠讹級
+     *
+     * @param reqVO 杩囨护鏉′欢
+     * @return 鎵规姹囨�诲垪琛�
+     */
+    List<MesWmStockSummaryRespVO> getStockSummaryByBatch(MesWmMaterialStockListReqVO reqVO);
+
+    /**
+     * 鎸変粨搴撶淮搴︽眹鎬诲簱瀛樻暟閲忥紙鍐呭瓨鑱氬悎锛屾敮鎸� stockState 绛夎繃婊ゆ潯浠讹級
+     *
+     * @param reqVO 杩囨护鏉′欢
+     * @return 浠撳簱姹囨�诲垪琛�
+     */
+    List<MesWmStockSummaryRespVO> getStockSummaryByWarehouse(MesWmMaterialStockListReqVO reqVO);
+
+    /**
+     * 鎸変粨搴撴壒娆℃洿鏂板簱瀛樼姸鎬侊紙鐢ㄤ簬绉嶅瓙璐ㄦ鍚堟牸鍚庯紝灏嗘殏瀛樺簱鏁存壒杞负鍚堟牸搴擄級
+     *
+     * @param batchId   浠撳簱鎵规ID
+     * @param toState   鐩爣搴撳瓨鐘舵��
+     * @param fromState 鍘熷簱瀛樼姸鎬侊紙闄愬畾鍙洿鏂拌鐘舵�佺殑璁板綍锛�
+     * @return 褰卞搷琛屾暟
+     */
+    int updateStockStateByBatchId(Long batchId, Integer toState, Integer fromState);
+
+    /**
      * 搴撲綅娣锋斁瑙勫垯鏍¢獙
      *
      * 鏍规嵁搴撲綅閰嶇疆鐨勭墿鏂欏拰鎵规娣锋斁瑙勫垯锛屾鏌ユ槸鍚﹀厑璁稿湪璇ュ簱浣嶅瓨鏀炬寚瀹氱墿鏂�/鎵规
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java
index d609a27..4318153 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java
@@ -10,6 +10,7 @@
 import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockListReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockMoveReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmStockSummaryRespVO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemTypeDO;
@@ -39,6 +40,8 @@
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
 import java.util.*;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
@@ -170,6 +173,86 @@
     }
 
     @Override
+    public List<MesWmStockSummaryRespVO> getStockSummaryByItem(MesWmMaterialStockListReqVO reqVO) {
+        List<MesWmMaterialStockDO> stocks = getMaterialStockList(reqVO);
+        List<MesWmStockSummaryRespVO> list = buildStockSummary(stocks,
+                stock -> String.valueOf(stock.getItemId()),
+                (vo, stock) -> vo.setItemId(stock.getItemId()));
+        Map<Long, MesMdmItemDO> itemMap = mdmItemService.getItemMap(
+                convertSet(stocks, MesWmMaterialStockDO::getItemId));
+        list.forEach(vo -> {
+            MesMdmItemDO item = itemMap.get(vo.getItemId());
+            if (item != null) {
+                vo.setItemCode(item.getCode()).setItemName(item.getName());
+            }
+        });
+        return list;
+    }
+
+    @Override
+    public List<MesWmStockSummaryRespVO> getStockSummaryByBatch(MesWmMaterialStockListReqVO reqVO) {
+        List<MesWmMaterialStockDO> stocks = getMaterialStockList(reqVO);
+        return buildStockSummary(stocks,
+                stock -> stock.getBatchId() + "|" + (stock.getBatchCode() != null ? stock.getBatchCode() : ""),
+                (vo, stock) -> vo.setBatchId(stock.getBatchId()).setBatchCode(stock.getBatchCode()));
+    }
+
+    @Override
+    public List<MesWmStockSummaryRespVO> getStockSummaryByWarehouse(MesWmMaterialStockListReqVO reqVO) {
+        List<MesWmMaterialStockDO> stocks = getMaterialStockList(reqVO);
+        List<MesWmStockSummaryRespVO> list = buildStockSummary(stocks,
+                stock -> String.valueOf(stock.getWarehouseId()),
+                (vo, stock) -> vo.setWarehouseId(stock.getWarehouseId()));
+        Map<Long, MesWmWarehouseDO> warehouseMap = warehouseService.getWarehouseMap(
+                convertSet(stocks, MesWmMaterialStockDO::getWarehouseId));
+        list.forEach(vo -> {
+            MesWmWarehouseDO warehouse = warehouseMap.get(vo.getWarehouseId());
+            if (warehouse != null) {
+                vo.setWarehouseName(warehouse.getName());
+            }
+        });
+        return list;
+    }
+
+    /**
+     * 鍐呭瓨鑱氬悎搴撳瓨鏁伴噺锛氭寜缁村害閿� + 搴撳瓨鐘舵�佸垎缁勶紝绱姞鍦ㄥ簱鏁伴噺
+     */
+    private List<MesWmStockSummaryRespVO> buildStockSummary(List<MesWmMaterialStockDO> stocks,
+                                                            Function<MesWmMaterialStockDO, String> dimKeyFn,
+                                                            BiConsumer<MesWmStockSummaryRespVO, MesWmMaterialStockDO> dimSetter) {
+        if (CollUtil.isEmpty(stocks)) {
+            return Collections.emptyList();
+        }
+        Map<String, MesWmStockSummaryRespVO> map = new LinkedHashMap<>();
+        for (MesWmMaterialStockDO stock : stocks) {
+            String key = dimKeyFn.apply(stock) + "|" + stock.getStockState();
+            MesWmStockSummaryRespVO vo = map.computeIfAbsent(key, k -> {
+                MesWmStockSummaryRespVO newVo = new MesWmStockSummaryRespVO();
+                dimSetter.accept(newVo, stock);
+                newVo.setStockState(stock.getStockState());
+                newVo.setStockStateName(stockStateName(stock.getStockState()));
+                newVo.setTotalQuantity(BigDecimal.ZERO);
+                return newVo;
+            });
+            BigDecimal qty = stock.getQuantity() != null ? stock.getQuantity() : BigDecimal.ZERO;
+            vo.setTotalQuantity(vo.getTotalQuantity().add(qty));
+        }
+        return new ArrayList<>(map.values());
+    }
+
+    private String stockStateName(Integer state) {
+        if (state == null) {
+            return "";
+        }
+        for (MesWmStockStateEnum e : MesWmStockStateEnum.values()) {
+            if (e.getState().equals(state)) {
+                return e.getName();
+            }
+        }
+        return String.valueOf(state);
+    }
+
+    @Override
     public MesWmMaterialStockDO getOrCreateMaterialStock(Long itemId, Long warehouseId, Long locationId, Long areaId,
                                                          Long batchId, String batchCode, Long vendorId, LocalDateTime receiptTime) {
         // 1. 鏌ユ壘宸叉湁搴撳瓨璁板綍锛坴endorId 鍙備笌鍞竴閿級
@@ -222,6 +305,11 @@
     }
 
     @Override
+    public int updateStockStateByBatchId(Long batchId, Integer toState, Integer fromState) {
+        return materialStockMapper.updateStockStateByBatchId(batchId, toState, fromState);
+    }
+
+    @Override
     public void checkAreaMixingRule(Long areaId, Long itemId, Long batchId) {
         // 1.1 鏍¢獙搴撲綅蹇呴』瀛樺湪
         MesWmWarehouseAreaDO area = areaService.validateWarehouseAreaExists(areaId);
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesServiceImpl.java
index 00652fd..bfe7e4d 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesServiceImpl.java
@@ -14,6 +14,7 @@
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.pallet.MesProPalletDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO;
 import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesDetailMapper;
 import cn.iocoder.yudao.module.mes.service.pro.bagcode.MesProBagCodeService;
@@ -34,6 +35,7 @@
 import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService;
 import cn.iocoder.yudao.module.mes.service.wm.salesnotice.MesWmSalesNoticeService;
 import cn.iocoder.yudao.module.mes.service.pro.batch.MesProBatchService;
+import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
 import cn.iocoder.yudao.module.mes.service.wm.stockreserve.MesWmStockReserveService;
 import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService;
 import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
@@ -75,7 +77,9 @@
     @Resource
     private MesProPalletService palletService;
     @Resource
-    private MesProBatchService wmBatchService;
+    private MesProBatchService proBatchService;
+    @Resource
+    private MesWmBatchService mesWmBatchService;
 
     @Resource
     private MesWmProductSalesLineService productSalesLineService;
@@ -241,9 +245,14 @@
                     .setCodeId(codeId).setScanContent(reqVO.getScanContent()).setAlreadyScanned(true)
                     .setDetailId(existed.getId()).setOutboundQuantity(existed.getQuantity()).setMessage("璇ョ爜宸叉壂鎻�");
         }
-        Long batchId = pallet != null ? pallet.getBatchId() : bag.getBatchId();
-        MesProBatchDO batch = wmBatchService.getBatch(batchId);
+        Long proBatchId = pallet != null ? pallet.getBatchId() : bag.getBatchId();
+        MesProBatchDO batch = proBatchService.getBatch(proBatchId);
         if (batch == null) {
+            throw exception(WM_TRANSACTION_BATCH_NOT_EXISTS);
+        }
+        // 鐢熶骇鎵规妗ユ帴浠撳簱鎵规锛氭壂鐮佸叆搴撳啓鍏ョ殑鏄粨搴撴壒娆★紙mes_wm_batch锛夛紝姝ゅ蹇呴』鐢ㄤ粨搴撴壒娆� id 鏌ュ簱瀛�
+        MesWmBatchDO wmBatch = mesWmBatchService.getOrCreateByProBatch(batch);
+        if (wmBatch == null) {
             throw exception(WM_TRANSACTION_BATCH_NOT_EXISTS);
         }
         List<MesWmProductSalesLineDO> lines = productSalesLineService.getProductSalesLineListBySalesId(reqVO.getSalesId());
@@ -255,7 +264,7 @@
         if (quantity == null) {
             quantity = line.getQuantity();
         }
-        List<MesWmMaterialStockDO> stocks = materialStockService.getMaterialStockListByItemAndBatch(batch.getItemId(), batchId);
+        List<MesWmMaterialStockDO> stocks = materialStockService.getMaterialStockListByItemAndBatch(batch.getItemId(), wmBatch.getId());
         BigDecimal outboundQuantity = quantity;
         MesWmMaterialStockDO stock = stocks.stream().filter(item -> item.getQuantity().compareTo(outboundQuantity) >= 0).findFirst().orElse(null);
         if (stock == null) {
@@ -268,13 +277,13 @@
         }
         MesWmProductSalesDetailDO detail = new MesWmProductSalesDetailDO().setLineId(line.getId())
                 .setSalesId(reqVO.getSalesId()).setItemId(batch.getItemId()).setQuantity(quantity)
-                .setMaterialStockId(stock.getId()).setBatchId(batchId).setBatchCode(batch.getCode())
+                .setMaterialStockId(stock.getId()).setBatchId(wmBatch.getId()).setBatchCode(wmBatch.getCode())
                 .setWarehouseId(stock.getWarehouseId()).setLocationId(stock.getLocationId()).setAreaId(stock.getAreaId())
                 .setBagCodeId(pallet == null ? codeId : null).setPalletCodeId(pallet != null ? codeId : null);
         productSalesDetailMapper.insert(detail);
         return new MesWmProductSalesScanRespVO().setScanType(pallet != null ? "PALLET" : "BAG")
                 .setCodeId(codeId).setScanContent(reqVO.getScanContent()).setItemId(batch.getItemId())
-                .setBatchId(batchId).setBatchCode(batch.getCode()).setMaterialStockId(stock.getId())
+                .setBatchId(wmBatch.getId()).setBatchCode(wmBatch.getCode()).setMaterialStockId(stock.getId())
                 .setAvailableQuantity(stock.getQuantity()).setScanQuantity(quantity).setOutboundQuantity(quantity)
                 .setAlreadyScanned(false).setDetailId(detail.getId()).setWarehouseId(stock.getWarehouseId())
                 .setLocationId(stock.getLocationId()).setAreaId(stock.getAreaId()).setMessage("鎵爜鎴愬姛");
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskService.java
index 25feac0..cd8a965 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskService.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskService.java
@@ -3,6 +3,7 @@
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.MesWmStockTakingTaskPageReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.MesWmStockTakingTaskSaveReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.MesWmStockTakingTaskScanReqVO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.stocktaking.task.MesWmStockTakingTaskDO;
 import jakarta.validation.Valid;
 
@@ -97,6 +98,14 @@
     PageResult<MesWmStockTakingTaskDO> getStockTakingTaskPage(MesWmStockTakingTaskPageReqVO pageReqVO);
 
     /**
+     * PDA 鎵爜鐩樼偣锛氳В鏋愯拷婧爜锛堣鐮�/鎵樼洏鐮�/鎵规鍙凤級瀹氫綅鎵规锛屾洿鏂扮洏鐐圭粨鏋滀笌浠诲姟琛�
+     *
+     * @param scanReqVO 鎵爜鐩樼偣璇锋眰
+     * @return 鐩樼偣缁撴灉缂栧彿
+     */
+    Long scanStockTakingTask(MesWmStockTakingTaskScanReqVO scanReqVO);
+
+    /**
      * 澶勭悊鐩樼偣宸紓锛岃皟鏁村簱瀛�
      *
      * 璇ユ柟娉曚細姣旇緝鐩樼偣缁撴灉涓庡簱瀛樺彴璐︾殑宸紓锛�
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskServiceImpl.java
index a192e7a..f1e8aa1 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskServiceImpl.java
@@ -2,17 +2,29 @@
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.MesWmStockTakingTaskPageReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.MesWmStockTakingTaskSaveReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.MesWmStockTakingTaskScanReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.result.MesWmStockTakingTaskResultSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.pallet.MesProPalletDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.stocktaking.task.MesWmStockTakingTaskDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.stocktaking.task.MesWmStockTakingTaskLineDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.stocktaking.task.MesWmStockTakingTaskResultDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.pro.bagcode.MesProBagCodeMapper;
+import cn.iocoder.yudao.module.mes.dal.mysql.pro.pallet.MesProPalletMapper;
+import cn.iocoder.yudao.module.mes.dal.mysql.wm.stocktaking.task.MesWmStockTakingTaskLineMapper;
 import cn.iocoder.yudao.module.mes.dal.mysql.wm.stocktaking.task.MesWmStockTakingTaskMapper;
 import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
 import cn.iocoder.yudao.module.mes.enums.wm.MesWmStockTakingTaskStatusEnum;
 import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
+import cn.iocoder.yudao.module.mes.service.pro.batch.MesProBatchService;
+import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
 import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService;
 import cn.iocoder.yudao.module.mes.service.wm.stocktaking.plan.MesWmStockTakingPlanService;
 import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService;
@@ -56,6 +68,17 @@
 
     @Resource
     private MesWmTransactionService wmTransactionService;
+
+    @Resource
+    private MesWmStockTakingTaskLineMapper stockTakingTaskLineMapper;
+    @Resource
+    private MesWmBatchService mesWmBatchService;
+    @Resource
+    private MesProBatchService proBatchService;
+    @Resource
+    private MesProBagCodeMapper bagCodeMapper;
+    @Resource
+    private MesProPalletMapper palletMapper;
 
     @Resource
     private AdminUserApi adminUserApi;
@@ -231,6 +254,69 @@
 
     @Override
     @Transactional(rollbackFor = Exception.class)
+    public Long scanStockTakingTask(MesWmStockTakingTaskScanReqVO scanReqVO) {
+        // 1. 鏍¢獙浠诲姟瀛樺湪涓斿浜庣洏鐐逛腑鐘舵��
+        validateStockTakingTaskExistsAndApproving(scanReqVO.getTaskId());
+
+        // 2. 瑙f瀽杩芥函鐮佸畾浣嶄粨搴撴壒娆★紙鏀寔鎵规鍙�/琚嬬爜/鎵樼洏鐮侊紝璧扮敓浜ф壒娆℃ˉ鎺ワ級
+        MesWmBatchDO wmBatch = resolveWmBatchByScanCode(scanReqVO.getScanCode());
+        if (wmBatch == null) {
+            throw exception(WM_STOCK_TAKING_TASK_SCAN_BATCH_NOT_FOUND);
+        }
+
+        // 3. 鍦ㄤ换鍔¤寖鍥村唴瀹氫綅浠诲姟琛�
+        MesWmStockTakingTaskLineDO line = stockTakingTaskLineMapper
+                .selectByTaskIdAndBatchId(scanReqVO.getTaskId(), wmBatch.getId());
+        if (line == null) {
+            throw exception(WM_STOCK_TAKING_TASK_SCAN_BATCH_NOT_IN_TASK);
+        }
+
+        // 4. 鍐欏叆鐩樼偣缁撴灉锛堝鐢� createStockTakingTaskResult锛氬惈閲嶅鐩樼偣鏍¢獙锛岃仈鍔ㄦ洿鏂颁换鍔¤鐩樼偣鏁伴噺涓庣泩浜忕姸鎬侊級
+        MesWmStockTakingTaskResultSaveReqVO resultReqVO = new MesWmStockTakingTaskResultSaveReqVO()
+                .setTaskId(line.getTaskId()).setLineId(line.getId())
+                .setMaterialStockId(line.getMaterialStockId())
+                .setItemId(line.getItemId()).setBatchId(line.getBatchId()).setBatchCode(line.getBatchCode())
+                .setWarehouseId(line.getWarehouseId()).setLocationId(line.getLocationId()).setAreaId(line.getAreaId())
+                .setTakingQuantity(scanReqVO.getTakingQuantity())
+                .setRemark(scanReqVO.getRemark());
+        return stockTakingTaskResultService.createStockTakingTaskResult(resultReqVO);
+    }
+
+    /**
+     * 瑙f瀽杩芥函鐮佸畾浣嶄粨搴撴壒娆★細渚濇鍖归厤鎵规鍙枫�佽鐮併�佹墭鐩樼爜锛岀敓浜ф壒娆$粡妗ユ帴杩斿洖浠撳簱鎵规
+     */
+    private MesWmBatchDO resolveWmBatchByScanCode(String scanCode) {
+        if (StrUtil.isBlank(scanCode)) {
+            return null;
+        }
+        // 1. 鐩存帴鍛戒腑浠撳簱鎵规鍙�
+        MesWmBatchDO wmBatch = mesWmBatchService.getBatchByCode(scanCode);
+        if (wmBatch != null) {
+            return wmBatch;
+        }
+        // 2. 鍛戒腑琚嬬爜
+        MesProBagCodeDO bag = bagCodeMapper.selectByCode(scanCode);
+        if (bag != null && bag.getBatchId() != null) {
+            return bridgeProBatch(bag.getBatchId());
+        }
+        // 3. 鍛戒腑鎵樼洏鐮�
+        MesProPalletDO pallet = palletMapper.selectByCode(scanCode);
+        if (pallet != null && pallet.getBatchId() != null) {
+            return bridgeProBatch(pallet.getBatchId());
+        }
+        return null;
+    }
+
+    private MesWmBatchDO bridgeProBatch(Long proBatchId) {
+        MesProBatchDO proBatch = proBatchService.getBatch(proBatchId);
+        if (proBatch == null) {
+            return null;
+        }
+        return mesWmBatchService.getOrCreateByProBatch(proBatch);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
     public void processStockTakingDifference(Long id) {
         // 1. 鏍¢獙浠诲姟瀛樺湪涓斿凡瀹屾垚
         MesWmStockTakingTaskDO task = validateStockTakingTaskExists(id);
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/MesWmTransactionServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/MesWmTransactionServiceImpl.java
index 98c781b..fd75003 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/MesWmTransactionServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/MesWmTransactionServiceImpl.java
@@ -127,8 +127,13 @@
         materialStockService.updateMaterialStockQuantity(materialStock.getId(), reqDTO.getQuantity(), checkFlag);
         MesWmMaterialStockDO updatedStock = materialStockService.getMaterialStock(materialStock.getId());
         BigDecimal newQuantity = updatedStock.getQuantity();
-        materialStockService.updateMaterialStockState(updatedStock.getId(), newQuantity.signum() <= 0
-                ? MesWmStockStateEnum.OUT.getState() : MesWmStockStateEnum.QUALIFIED.getState());
+        // 搴撳瓨鐘舵�侊細鏄惧紡鎸囧畾浼樺厛锛堝鎵爜鍏ユ殏瀛樺簱锛夛紝鍚﹀垯鎸夋暟閲忚嚜鍔ㄥ垽瀹氾紙鈮�0 宸插嚭搴擄紝>0 鍚堟牸搴擄級
+        Integer stockState = reqDTO.getStockState();
+        if (stockState == null) {
+            stockState = newQuantity.signum() <= 0
+                    ? MesWmStockStateEnum.OUT.getState() : MesWmStockStateEnum.QUALIFIED.getState();
+        }
+        materialStockService.updateMaterialStockState(updatedStock.getId(), stockState);
 
         // 3. 鎻掑叆浜嬪姟娴佹按
         MesWmTransactionDO transaction = MesWmTransactionDO.builder()
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/dto/MesWmTransactionSaveReqDTO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/dto/MesWmTransactionSaveReqDTO.java
index d0ae46a..39a7693 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/dto/MesWmTransactionSaveReqDTO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/dto/MesWmTransactionSaveReqDTO.java
@@ -118,4 +118,14 @@
      */
     private Boolean checkFlag;
 
+    // ===== 搴撳瓨鐘舵�佹帶鍒讹紙闈炲瓨鍌ㄥ瓧娈碉級 =====
+
+    /**
+     * 鎸囧畾搴撳瓨鐘舵�侊紙鍏ュ簱鏃舵樉寮忔寚瀹氾紱涓虹┖鏃舵寜鏁伴噺鑷姩鍒ゅ畾锛氣墹0 宸插嚭搴擄紝>0 鍚堟牸搴擄級
+     * <p>
+     * 鐢熶骇绾挎壂鐮佸叆搴撲紶 {@link cn.iocoder.yudao.module.mes.enums.wm.MesWmStockStateEnum#TEMP}锛�
+     * 鍏ュ簱鍏堝叆鏆傚瓨搴撳緟璐ㄦ銆�
+     */
+    private Integer stockState;
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/warning/MesWmStockWarningService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/warning/MesWmStockWarningService.java
new file mode 100644
index 0000000..afa19cc
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/warning/MesWmStockWarningService.java
@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.mes.service.wm.warning;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.warning.vo.MesWmStockWarningHandleReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.warning.vo.MesWmStockWarningPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.warning.vo.MesWmStockWarningRespVO;
+
+import java.util.List;
+
+/**
+ * MES 搴撳瓨棰勮 Service 鎺ュ彛
+ * <p>
+ * 棰勮鍙岄�氶亾锛氬疄鏃惰绠楁煡璇� + 瀹氭椂浠诲姟钀藉簱銆�
+ */
+public interface MesWmStockWarningService {
+
+    /**
+     * 鑾峰緱搴撳瓨棰勮鍒嗛〉锛堣惤搴撹褰曪級
+     *
+     * @param pageReqVO 鍒嗛〉鏌ヨ
+     * @return 棰勮鍒嗛〉
+     */
+    PageResult<MesWmStockWarningRespVO> getWarningPage(MesWmStockWarningPageReqVO pageReqVO);
+
+    /**
+     * 瀹炴椂璁$畻褰撳墠搴撳瓨棰勮锛堜綆搴撳瓨 / 涓存湡 / 搴撻緞锛夛紝涓嶈惤搴�
+     *
+     * @return 褰撳墠棰勮鍒楄〃
+     */
+    List<MesWmStockWarningRespVO> getCurrentWarnings();
+
+    /**
+     * 钀藉簱褰撳墠棰勮锛堝畾鏃朵换鍔¤皟鐢級锛氬垹闄ゆ棫鐨勬湭澶勭悊棰勮锛屽啓鍏ュ綋鍓嶉璀︼紝淇濈暀宸插鐞嗗巻鍙�
+     *
+     * @return 鏈钀藉簱鏉℃暟
+     */
+    int persistCurrentWarnings();
+
+    /**
+     * 澶勭悊棰勮锛堟爣璁板凡澶勭悊锛�
+     *
+     * @param reqVO 澶勭悊璇锋眰
+     */
+    void handle(MesWmStockWarningHandleReqVO reqVO);
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/warning/MesWmStockWarningServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/warning/MesWmStockWarningServiceImpl.java
new file mode 100644
index 0000000..a793735
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/warning/MesWmStockWarningServiceImpl.java
@@ -0,0 +1,237 @@
+package cn.iocoder.yudao.module.mes.service.wm.warning;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockListReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.warning.vo.MesWmStockWarningHandleReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.warning.vo.MesWmStockWarningPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.warning.vo.MesWmStockWarningRespVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warning.MesWmStockWarningDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.wm.warning.MesWmStockWarningMapper;
+import cn.iocoder.yudao.module.mes.enums.wm.MesWmStockStateEnum;
+import cn.iocoder.yudao.module.mes.enums.wm.MesWmStockWarningTypeEnum;
+import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService;
+import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
+import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService;
+import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseService;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_STOCK_WARNING_NOT_EXISTS;
+
+/**
+ * MES 搴撳瓨棰勮 Service 瀹炵幇绫�
+ * <p>
+ * 浣庡簱瀛樸�佷复鏈熴�佸簱榫勪笁绫婚璀︺�傚彧閽堝鍚堟牸搴擄紙姝e紡搴撳瓨锛夛紝鏆傚瓨搴擄紙寰呰川妫�锛変笌宸插嚭搴撲笉璁″叆棰勮銆�
+ */
+@Service
+@Validated
+@Slf4j
+public class MesWmStockWarningServiceImpl implements MesWmStockWarningService {
+
+    /**
+     * 鏈鐞�
+     */
+    private static final Integer STATUS_UNHANDLED = 10;
+    /**
+     * 宸插鐞�
+     */
+    private static final Integer STATUS_HANDLED = 20;
+    /**
+     * 搴撻緞棰勮闃堝�硷紙澶╋級
+     */
+    private static final int AGING_DAYS = 365;
+
+    @Resource
+    private MesWmStockWarningMapper warningMapper;
+    @Resource
+    private MesWmMaterialStockService materialStockService;
+    @Resource
+    private MesMdmItemService mdmItemService;
+    @Resource
+    private MesWmWarehouseService warehouseService;
+    @Resource
+    private MesWmBatchService batchService;
+
+    @Override
+    public PageResult<MesWmStockWarningRespVO> getWarningPage(MesWmStockWarningPageReqVO pageReqVO) {
+        PageResult<MesWmStockWarningDO> pageResult = warningMapper.selectPage(pageReqVO);
+        Map<Long, MesMdmItemDO> itemMap = mdmItemService.getItemMap(
+                convertSet(pageResult.getList(), MesWmStockWarningDO::getItemId));
+        return new PageResult<>(convertList(pageResult.getList(),
+                warning -> toRespVO(warning, itemMap.get(warning.getItemId()))), pageResult.getTotal());
+    }
+
+    @Override
+    public List<MesWmStockWarningRespVO> getCurrentWarnings() {
+        List<MesWmStockWarningDO> warnings = computeCurrentWarnings();
+        if (CollUtil.isEmpty(warnings)) {
+            return List.of();
+        }
+        Map<Long, MesMdmItemDO> itemMap = mdmItemService.getItemMap(
+                convertSet(warnings, MesWmStockWarningDO::getItemId));
+        return convertList(warnings, warning -> toRespVO(warning, itemMap.get(warning.getItemId())));
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int persistCurrentWarnings() {
+        // 1. 瀹炴椂璁$畻褰撳墠棰勮
+        List<MesWmStockWarningDO> current = computeCurrentWarnings();
+        // 2. 鍒犻櫎鏃х殑鏈鐞嗛璀︼紙宸插鐞嗚褰曚綔涓哄巻鍙蹭繚鐣欙級
+        warningMapper.delete(new LambdaQueryWrapperX<MesWmStockWarningDO>()
+                .eq(MesWmStockWarningDO::getStatus, STATUS_UNHANDLED));
+        // 3. 鍐欏叆褰撳墠棰勮
+        if (CollUtil.isNotEmpty(current)) {
+            current.forEach(warningMapper::insert);
+        }
+        log.info("[persistCurrentWarnings][搴撳瓨棰勮钀藉簱 {} 鏉", current.size());
+        return current.size();
+    }
+
+    @Override
+    public void handle(MesWmStockWarningHandleReqVO reqVO) {
+        MesWmStockWarningDO warning = warningMapper.selectById(reqVO.getId());
+        if (warning == null) {
+            throw exception(WM_STOCK_WARNING_NOT_EXISTS);
+        }
+        warningMapper.updateById(new MesWmStockWarningDO()
+                .setId(warning.getId())
+                .setStatus(STATUS_HANDLED)
+                .setHandleUserId(SecurityFrameworkUtils.getLoginUserId())
+                .setHandleTime(LocalDateTime.now())
+                .setHandleRemark(reqVO.getHandleRemark()));
+    }
+
+    // ==================== 绉佹湁鏂规硶 ====================
+
+    /**
+     * 瀹炴椂璁$畻褰撳墠棰勮锛堜綆搴撳瓨 / 涓存湡 / 搴撻緞锛夛紝閽堝鍚堟牸搴撳瓨
+     */
+    private List<MesWmStockWarningDO> computeCurrentWarnings() {
+        List<MesWmStockWarningDO> result = new ArrayList<>();
+        // 浠呮壂鎻忓悎鏍煎簱锛堟寮忓簱瀛橈級
+        List<MesWmMaterialStockDO> stocks = materialStockService.getMaterialStockList(
+                new MesWmMaterialStockListReqVO().setStockState(MesWmStockStateEnum.QUALIFIED.getState()));
+        if (CollUtil.isEmpty(stocks)) {
+            return result;
+        }
+        LocalDateTime now = LocalDateTime.now();
+        Set<Long> itemIds = convertSet(stocks, MesWmMaterialStockDO::getItemId);
+        Map<Long, MesMdmItemDO> itemMap = mdmItemService.getItemMap(itemIds);
+        Set<Long> warehouseIds = convertSet(stocks, MesWmMaterialStockDO::getWarehouseId);
+        Map<Long, MesWmWarehouseDO> warehouseMap = warehouseService.getWarehouseMap(warehouseIds);
+
+        for (MesWmMaterialStockDO stock : stocks) {
+            MesMdmItemDO item = itemMap.get(stock.getItemId());
+            MesWmWarehouseDO warehouse = warehouseMap.get(stock.getWarehouseId());
+            if (item == null) {
+                continue;
+            }
+            MesWmBatchDO batch = stock.getBatchId() != null ? batchService.getBatch(stock.getBatchId()) : null;
+            BigDecimal quantity = stock.getQuantity() != null ? stock.getQuantity() : BigDecimal.ZERO;
+
+            // 1. 浣庡簱瀛橈細鍦ㄥ簱鏁伴噺 <= 鏈�浣庡簱瀛�
+            if (item.getMinStock() != null && quantity.compareTo(item.getMinStock()) <= 0) {
+                result.add(buildWarning(stock, item, warehouse, MesWmStockWarningTypeEnum.LOW_STOCK,
+                        item.getMinStock(), quantity, batch != null ? batch.getExpireDate() : null));
+            }
+            // 2. 涓存湡锛氭壒娆℃湁鏈夋晥鏈燂紝涓斿墿浣欏ぉ鏁� <= 鐗╂枡閰嶇疆鐨勬湁鏁堟湡澶╂暟
+            if (item.getExpiryDay() != null && batch != null && batch.getExpireDate() != null) {
+                long remainDays = ChronoUnit.DAYS.between(now.toLocalDate(), batch.getExpireDate().toLocalDate());
+                if (remainDays <= item.getExpiryDay()) {
+                    result.add(buildWarning(stock, item, warehouse, MesWmStockWarningTypeEnum.EXPIRING,
+                            BigDecimal.valueOf(item.getExpiryDay()), BigDecimal.valueOf(Math.max(remainDays, 0)),
+                            batch.getExpireDate()));
+                }
+            }
+            // 3. 搴撻緞锛氬叆搴撴椂闂磋秴杩囬槇鍊�
+            if (stock.getReceiptTime() != null) {
+                long ageDays = ChronoUnit.DAYS.between(stock.getReceiptTime().toLocalDate(), now.toLocalDate());
+                if (ageDays >= AGING_DAYS) {
+                    result.add(buildWarning(stock, item, warehouse, MesWmStockWarningTypeEnum.AGING,
+                            BigDecimal.valueOf(AGING_DAYS), BigDecimal.valueOf(ageDays), null));
+                }
+            }
+        }
+        return result;
+    }
+
+    private MesWmStockWarningDO buildWarning(MesWmMaterialStockDO stock, MesMdmItemDO item, MesWmWarehouseDO warehouse,
+                                             MesWmStockWarningTypeEnum type, BigDecimal threshold, BigDecimal current,
+                                             LocalDateTime expireDate) {
+        return MesWmStockWarningDO.builder()
+                .itemId(item.getId()).itemCode(item.getCode())
+                .batchId(stock.getBatchId()).batchCode(stock.getBatchCode())
+                .warehouseId(stock.getWarehouseId())
+                .warehouseName(warehouse != null ? warehouse.getName() : null)
+                .warningType(type.getType()).thresholdValue(threshold).currentValue(current)
+                .expireDate(expireDate).receiptTime(stock.getReceiptTime())
+                .status(STATUS_UNHANDLED)
+                .build();
+    }
+
+    private MesWmStockWarningRespVO toRespVO(MesWmStockWarningDO warning, MesMdmItemDO item) {
+        MesWmStockWarningRespVO vo = new MesWmStockWarningRespVO();
+        vo.setId(warning.getId());
+        vo.setItemId(warning.getItemId());
+        vo.setItemCode(warning.getItemCode());
+        vo.setItemName(item != null ? item.getName() : null);
+        vo.setBatchId(warning.getBatchId());
+        vo.setBatchCode(warning.getBatchCode());
+        vo.setWarehouseId(warning.getWarehouseId());
+        vo.setWarehouseName(warning.getWarehouseName());
+        vo.setWarningType(warning.getWarningType());
+        vo.setWarningTypeName(warningTypeName(warning.getWarningType()));
+        vo.setThresholdValue(warning.getThresholdValue());
+        vo.setCurrentValue(warning.getCurrentValue());
+        vo.setExpireDate(warning.getExpireDate());
+        vo.setReceiptTime(warning.getReceiptTime());
+        vo.setStatus(warning.getStatus());
+        vo.setStatusName(statusName(warning.getStatus()));
+        vo.setHandleUserId(warning.getHandleUserId());
+        vo.setHandleTime(warning.getHandleTime());
+        vo.setHandleRemark(warning.getHandleRemark());
+        return vo;
+    }
+
+    private String warningTypeName(Integer type) {
+        if (type == null) {
+            return "";
+        }
+        for (MesWmStockWarningTypeEnum e : MesWmStockWarningTypeEnum.values()) {
+            if (e.getType().equals(type)) {
+                return e.getName();
+            }
+        }
+        return String.valueOf(type);
+    }
+
+    private String statusName(Integer status) {
+        if (status == null) {
+            return "";
+        }
+        return status.equals(STATUS_HANDLED) ? "宸插鐞�" : "鏈鐞�";
+    }
+
+}
diff --git a/yudao-server/src/main/resources/application-dev.yaml b/yudao-server/src/main/resources/application-dev.yaml
index d3a4ab3..cd691e6 100644
--- a/yudao-server/src/main/resources/application-dev.yaml
+++ b/yudao-server/src/main/resources/application-dev.yaml
@@ -1,6 +1,10 @@
 server:
   port: 48080
 
+# H5 婧簮椤甸潰鍩虹鍦板潃锛堜簩缁寸爜鍐呭鍓嶇紑锛夛紝闇�涓哄叕浼楁壂鐮佸彲璁块棶鐨勫湴鍧�
+mes:
+  trace-base-url: https://dev.your-domain.com/trace.html
+
 --- #################### 鏁版嵁搴撶浉鍏抽厤缃� ####################
 
 spring:
diff --git a/yudao-server/src/main/resources/application-jhhg.yaml b/yudao-server/src/main/resources/application-jhhg.yaml
index 27b1afe..769a10a 100644
--- a/yudao-server/src/main/resources/application-jhhg.yaml
+++ b/yudao-server/src/main/resources/application-jhhg.yaml
@@ -1,6 +1,10 @@
 server:
   port: 48080
 
+# H5 婧簮椤甸潰鍩虹鍦板潃锛堜簩缁寸爜鍐呭鍓嶇紑锛夛紝闇�涓哄叕浼楁壂鐮佸彲璁块棶鐨勫湴鍧�
+mes:
+  trace-base-url: http://42.63.70.90:9003/trace.html
+
 --- #################### 鏁版嵁搴撶浉鍏抽厤缃� ####################
 spring:
   autoconfigure:
diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml
index b4c990d..28d2c2b 100644
--- a/yudao-server/src/main/resources/application-local.yaml
+++ b/yudao-server/src/main/resources/application-local.yaml
@@ -1,6 +1,10 @@
 server:
   port: 48080
 
+# H5 婧簮椤甸潰鍩虹鍦板潃锛堜簩缁寸爜鍐呭鍓嶇紑锛夛紝闇�涓哄叕浼楁壂鐮佸彲璁块棶鐨勫湴鍧�
+mes:
+  trace-base-url: http://192.168.0.10:48080/trace.html
+
 --- #################### 鏁版嵁搴撶浉鍏抽厤缃� ####################
 spring:
   autoconfigure:
diff --git a/yudao-server/src/main/resources/application-test.yaml b/yudao-server/src/main/resources/application-test.yaml
index f0e1db9..74cb6da 100644
--- a/yudao-server/src/main/resources/application-test.yaml
+++ b/yudao-server/src/main/resources/application-test.yaml
@@ -1,6 +1,10 @@
 server:
   port: 48080
 
+# H5 婧簮椤甸潰鍩虹鍦板潃锛堜簩缁寸爜鍐呭鍓嶇紑锛夛紝闇�涓哄叕浼楁壂鐮佸彲璁块棶鐨勫湴鍧�
+mes:
+  trace-base-url: http://36.213.180.217:9500/trace.html
+
 --- #################### 鏁版嵁搴撶浉鍏抽厤缃� ####################
 spring:
   autoconfigure:
diff --git a/yudao-server/src/main/resources/application-thzy.yaml b/yudao-server/src/main/resources/application-thzy.yaml
index 0626831..5862482 100644
--- a/yudao-server/src/main/resources/application-thzy.yaml
+++ b/yudao-server/src/main/resources/application-thzy.yaml
@@ -1,6 +1,10 @@
 server:
   port: 48080
 
+# H5 婧簮椤甸潰鍩虹鍦板潃锛堜簩缁寸爜鍐呭鍓嶇紑锛夛紝闇�涓哄叕浼楁壂鐮佸彲璁块棶鐨勫湴鍧�
+mes:
+  trace-base-url: http://47.97.181.254:9001/trace.html
+
 --- #################### 鏁版嵁搴撶浉鍏抽厤缃� ####################
 spring:
   autoconfigure:
@@ -187,7 +191,7 @@
   storage: # 鏂囦欢涓婁紶閰嶇疆
     path: /home/mom/javaWork/file # 鏂囦欢瀛樺偍鏍圭洰褰�
     url-prefix: /admin-api/system/storage-blob # URL璺緞鍓嶇紑
-    domain: http://47.97.181.254:9002 # 鍩熷悕鍓嶇紑锛堟寜瀹為檯鍩熷悕閰嶇疆锛夛紝鍓嶇閰嶇疆濂藉儚涓嶅お瀵圭幇鍦ㄦ槸鐩磋繛鍚庣锛屾墍鏈夎鍚庣绔彛
+    domain: http://47.97.181.254:9001 # 鍩熷悕鍓嶇紑锛堟寜瀹為檯鍩熷悕閰嶇疆锛夛紝鍓嶇閰嶇疆濂藉儚涓嶅お瀵圭幇鍦ㄦ槸鐩磋繛鍚庣锛屾墍鏈夎鍚庣绔彛
     expired: 120 # token杩囨湡鏃堕棿锛堝垎閽燂級
     use-limit: 10 # token浣跨敤娆℃暟涓婇檺
     jwt-secret: abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG # JWT瀵嗛挜
diff --git a/yudao-server/src/main/resources/application-yzfx.yaml b/yudao-server/src/main/resources/application-yzfx.yaml
index 0ea0e29..f5b73ef 100644
--- a/yudao-server/src/main/resources/application-yzfx.yaml
+++ b/yudao-server/src/main/resources/application-yzfx.yaml
@@ -1,6 +1,10 @@
 server:
   port: 48080
 
+# H5 婧簮椤甸潰鍩虹鍦板潃锛堜簩缁寸爜鍐呭鍓嶇紑锛夛紝闇�涓哄叕浼楁壂鐮佸彲璁块棶鐨勫湴鍧�
+mes:
+  trace-base-url: http://42.63.70.231:9001/trace.html
+
 --- #################### 鏁版嵁搴撶浉鍏抽厤缃� ####################
 spring:
   autoconfigure:
diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml
index 2a0be3d..4c9c073 100644
--- a/yudao-server/src/main/resources/application.yaml
+++ b/yudao-server/src/main/resources/application.yaml
@@ -1,3 +1,8 @@
+# H5 婧簮椤甸潰鍩虹鍦板潃锛堜簩缁寸爜鍐呭鍓嶇紑锛屽舰濡� http://鍩熷悕:绔彛/trace.html锛夛紝
+# 闇�涓哄叕浼楁壂鐮佸彲璁块棶鐨勫湴鍧�锛涘悇 profile 鍙鐩栥�傜暀绌哄垯浜岀淮鐮佺紪鐮佺函鐮佸�硷紙鍏煎鏃х爜锛夈��
+mes:
+  trace-base-url: http://localhost:48080/trace.html
+
 spring:
   application:
     name: yudao-server
diff --git a/yudao-server/src/main/resources/static/trace.html b/yudao-server/src/main/resources/static/trace.html
new file mode 100644
index 0000000..be5c7f5
--- /dev/null
+++ b/yudao-server/src/main/resources/static/trace.html
@@ -0,0 +1,433 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
+<meta name="format-detection" content="telephone=no">
+<title>浜у搧婧簮</title>
+<style>
+  :root {
+    --primary: #2e7d32;
+    --primary-light: #43a047;
+    --primary-bg: #e8f5e9;
+    --text: #1f2933;
+    --text-sub: #6b7280;
+    --border: #e5e7eb;
+    --bg: #f5f7f4;
+    --card-shadow: 0 1px 4px rgba(0,0,0,.06);
+    --green: #2e7d32;
+    --red: #d32f2f;
+    --blue: #1565c0;
+    --gray: #6b7280;
+  }
+  * { box-sizing: border-box; margin: 0; padding: 0; -webkit-tap-highlight-color: transparent; }
+  html, body { height: 100%; }
+  body {
+    font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", "Microsoft YaHei", sans-serif;
+    background: var(--bg);
+    color: var(--text);
+    line-height: 1.5;
+    -webkit-font-smoothing: antialiased;
+  }
+  .wrap { max-width: 480px; margin: 0 auto; padding: 0 12px 32px; }
+
+  /* 椤堕儴鍝佺墝鍖� */
+  .brand {
+    max-width: 480px; margin: 0 auto;
+    padding: 20px 16px 14px;
+    text-align: center;
+  }
+  .brand .logo {
+    display: inline-flex; align-items: center; gap: 8px;
+    font-size: 20px; font-weight: 700; color: var(--primary);
+  }
+  .brand .logo .mark {
+    width: 26px; height: 26px; border-radius: 50%;
+    background: linear-gradient(135deg, var(--primary-light), var(--primary));
+    position: relative;
+  }
+  .brand .logo .mark::after {
+    content: ""; position: absolute; left: 50%; top: 50%;
+    width: 10px; height: 10px; border: 2px solid #fff; border-radius: 50%;
+    transform: translate(-50%, -50%);
+  }
+  .brand .sub { margin-top: 4px; font-size: 12px; color: var(--text-sub); }
+
+  /* 鏌ヨ鏍� */
+  .query-bar { display: flex; gap: 8px; margin-bottom: 14px; }
+  .query-bar input {
+    flex: 1; height: 42px; padding: 0 12px;
+    border: 1px solid var(--border); border-radius: 8px;
+    font-size: 14px; background: #fff; outline: none;
+  }
+  .query-bar input:focus { border-color: var(--primary); }
+  .query-bar button {
+    height: 42px; padding: 0 18px;
+    border: none; border-radius: 8px;
+    background: var(--primary); color: #fff; font-size: 14px;
+    cursor: pointer;
+  }
+  .query-bar button:active { background: var(--primary-light); }
+
+  /* 鐘舵�� */
+  .state { padding-top: 8px; }
+  .loading { text-align: center; padding: 48px 0; color: var(--text-sub); }
+  .spinner {
+    width: 32px; height: 32px; margin: 0 auto 12px;
+    border: 3px solid var(--primary-bg); border-top-color: var(--primary);
+    border-radius: 50%; animation: spin .8s linear infinite;
+  }
+  @keyframes spin { to { transform: rotate(360deg); } }
+
+  .empty-tip {
+    padding: 12px 16px; border-radius: 10px; background: #fff;
+    box-shadow: var(--card-shadow); font-size: 14px; color: var(--text-sub);
+    text-align: center;
+  }
+  .empty-tip.error { color: var(--red); background: #fdecec; }
+  .empty-tip.primary { color: var(--primary); background: var(--primary-bg); }
+
+  /* 鍗$墖 */
+  .card {
+    background: #fff; border-radius: 12px; box-shadow: var(--card-shadow);
+    margin-bottom: 12px; overflow: hidden;
+  }
+  .card .card-hd {
+    display: flex; align-items: center; gap: 8px;
+    padding: 12px 16px; border-bottom: 1px solid var(--border);
+    font-size: 15px; font-weight: 600;
+  }
+  .card .card-hd .bar { width: 4px; height: 16px; border-radius: 2px; background: var(--primary); }
+  .card .card-hd .count { margin-left: auto; font-size: 12px; font-weight: 400; color: var(--text-sub); }
+  .card .card-bd { padding: 12px 16px; }
+
+  /* 淇℃伅琛� */
+  .kv { display: flex; padding: 6px 0; font-size: 14px; }
+  .kv .k { flex: 0 0 88px; color: var(--text-sub); }
+  .kv .v { flex: 1; word-break: break-all; }
+  .kv .v.dim { color: var(--text-sub); }
+
+  /* 寰界珷 */
+  .badge {
+    display: inline-block; padding: 2px 8px; border-radius: 4px;
+    font-size: 12px; line-height: 18px;
+  }
+  .badge.green { color: var(--green); background: #e8f5e9; }
+  .badge.red { color: var(--red); background: #fdecec; }
+  .badge.blue { color: var(--blue); background: #e3f2fd; }
+  .badge.gray { color: var(--gray); background: #f1f2f4; }
+
+  .badge-big { display: inline-block; padding: 4px 12px; border-radius: 16px; font-size: 14px; }
+  .badge-big.green { color: #fff; background: var(--green); }
+  .badge-big.red { color: #fff; background: var(--red); }
+  .badge-big.gray { color: #fff; background: var(--gray); }
+
+  /* 鍒楄〃 */
+  .mini-table { width: 100%; border-collapse: collapse; font-size: 13px; }
+  .mini-table th, .mini-table td { text-align: left; padding: 8px 6px; border-bottom: 1px solid var(--border); }
+  .mini-table th { color: var(--text-sub); font-weight: 400; background: #fafbf9; white-space: nowrap; }
+  .mini-table tr:last-child td { border-bottom: none; }
+  .mini-table td { word-break: break-all; }
+
+  .pill-list { display: flex; flex-wrap: wrap; gap: 8px; }
+  .pill {
+    display: inline-flex; align-items: center; gap: 6px;
+    padding: 6px 12px; border-radius: 16px; font-size: 13px;
+    background: #f3f4f6;
+  }
+  .pill .dot { width: 6px; height: 6px; border-radius: 50%; }
+
+  .scroll-list { max-height: 300px; overflow-y: auto; }
+  .scroll-list::-webkit-scrollbar { width: 4px; }
+  .scroll-list::-webkit-scrollbar-thumb { background: #d1d5db; border-radius: 2px; }
+
+  footer {
+    max-width: 480px; margin: 0 auto;
+    padding: 20px 16px 28px; text-align: center;
+    font-size: 12px; color: #9ca3af;
+  }
+</style>
+</head>
+<body>
+
+<div class="brand">
+  <div class="logo"><span class="mark"></span>绉嶅瓙浜у搧婧簮</div>
+  <div class="sub">鏉ユ簮鍙煡 路 鍘诲悜鍙拷 路 璐d换鍙┒</div>
+</div>
+
+<div class="wrap">
+  <!-- 鎵嬪姩鏌ヨ鏍忥細鏃� code 鍙傛暟鏃舵彁绀鸿緭鍏ワ紱鏈夊弬鏁版椂鑷姩鏌ヨ -->
+  <div class="query-bar">
+    <input id="codeInput" type="text" placeholder="璇疯緭鍏ヨ拷婧爜锛堣鐮� / 鎵樼洏鐮� / 鎵规鐮侊級" autocomplete="off">
+    <button id="searchBtn" type="button">鏌ヨ</button>
+  </div>
+
+  <section id="state" class="state"></section>
+</div>
+
+<footer>鏈郴缁熸暟鎹粎鐢ㄤ簬浜у搧婧簮锛岃閫氳繃姝h娓犻亾璐拱銆�</footer>
+
+<script>
+(function () {
+  'use strict';
+
+  var STATE = document.getElementById('state');
+  var CODE_INPUT = document.getElementById('codeInput');
+  var SEARCH_BTN = document.getElementById('searchBtn');
+
+  var BATCH_STATUS = { 10: ['鑽夌', 'gray'], 20: ['鐢熶骇涓�', 'blue'], 30: ['宸插畬鎴�', 'green'], 40: ['宸蹭綔搴�', 'red'] };
+  var BAG_STATUS = { 10: ['宸茬敓鎴�', 'gray'], 20: ['宸插鍑哄嵃鍒�', 'blue'], 30: ['宸蹭綔搴�', 'red'] };
+  var QUALITY_RESULT = { 0: ['澶勭悊涓�', 'gray'], 1: ['鍚堟牸', 'green'], 2: ['涓嶅悎鏍�', 'red'] };
+  var WORKER_TYPE = { 10: '缁勯暱', 20: '鎿嶄綔宸�', 30: '鍖呰宸�', 40: '璐ㄦ鍛�', 50: '鍏朵粬' };
+
+  // ========== 宸ュ叿鍑芥暟 ==========
+  function esc(v) {
+    if (v === null || v === undefined) return '';
+    return String(v).replace(/[&<>"']/g, function (c) {
+      return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
+    });
+  }
+
+  function text(v) {
+    return (v === null || v === undefined || v === '') ? '-' : esc(v);
+  }
+
+  function fmtDate(s) {
+    if (!s) return '-';
+    return esc(String(s).replace('T', ' ').replace(/\.\d+$/, '').slice(0, 16));
+  }
+
+  function fmtNum(v) {
+    return (v === null || v === undefined) ? '-' : esc(v);
+  }
+
+  function statusBadge(map, key) {
+    var item = map[key];
+    if (!item) return '<span class="badge gray">' + (key === null || key === undefined ? '' : esc(key)) + '</span>';
+    return '<span class="badge ' + item[1] + '">' + esc(item[0]) + '</span>';
+  }
+
+  function card(title, countHtml, bodyHtml) {
+    return '<div class="card">' +
+      '<div class="card-hd"><span class="bar"></span>' + esc(title) +
+      (countHtml ? '<span class="count">' + countHtml + '</span>' : '') +
+      '</div>' + '<div class="card-bd">' + bodyHtml + '</div>' + '</div>';
+  }
+
+  function kv(k, v, dim) {
+    return '<div class="kv"><span class="k">' + esc(k) + '</span><span class="v' + (dim ? ' dim' : '') + '">' + v + '</span></div>';
+  }
+
+  function isEmptyList(list) {
+    return !list || list.length === 0;
+  }
+
+  // ========== 鏌ヨ ==========
+  function query(code) {
+    var value = (code || '').trim();
+    if (!value) {
+      renderState('<div class="empty-tip primary">璇疯緭鍏ヨ拷婧爜鍚庡啀鏌ヨ</div>');
+      return;
+    }
+    renderState('<div class="loading"><div class="spinner"></div>姝e湪鏌ヨ杩芥函淇℃伅...</div>');
+    fetch('/public/mes/trace/query?code=' + encodeURIComponent(value))
+      .then(function (resp) { return resp.json(); })
+      .then(function (res) {
+        if (res.code === 0) {
+          renderResult(res.data);
+        } else {
+          var msg = res.msg || '鏌ヨ澶辫触锛岃绋嶅悗閲嶈瘯';
+          renderState('<div class="empty-tip error">' + esc(msg) + '</div>');
+        }
+      })
+      .catch(function () {
+        renderState('<div class="empty-tip error">缃戠粶寮傚父锛岃妫�鏌ョ綉缁滃悗閲嶈瘯</div>');
+      });
+  }
+
+  function renderState(html) {
+    STATE.innerHTML = html;
+    window.scrollTo({ top: 0, behavior: 'smooth' });
+  }
+
+  // ========== 缁撴灉娓叉煋锛堜俊鎭尯鍧楁ā鍧楀寲锛屽悗缁柊鏁版嵁婧愬湪姝よ拷鍔犲尯鍧楋級 ==========
+  function renderResult(d) {
+    if (!d) {
+      renderState('<div class="empty-tip error">鏈煡璇㈠埌璇ヨ拷婧爜锛岃鏍稿浜岀淮鐮佹垨鑱旂郴瀹樻柟</div>');
+      return;
+    }
+    var html = '';
+    html += buildItemCard(d);
+    html += buildBatchCard(d);
+    html += buildQualityCard(d);
+    html += buildWorkerCard(d);
+    html += buildInboundCard(d);
+    html += buildPalletCard(d);
+    html += buildBagCard(d);
+    html += buildOptionalCard(d);
+    STATE.innerHTML = html || '<div class="empty-tip">鏆傛棤杩芥函鏁版嵁</div>';
+    window.scrollTo({ top: 0, behavior: 'smooth' });
+  }
+
+  // 鍖哄潡1锛氬搧绉嶄俊鎭�
+  function buildItemCard(d) {
+    if (!d.itemCode && !d.itemName && !d.itemSpecification) return '';
+    return card('鍝佺淇℃伅', null,
+      kv('鍝佺鍚嶇О', text(d.itemName)) +
+      kv('瑙勬牸', text(d.itemSpecification)) +
+      kv('鍝佺缂栫爜', text(d.itemCode))
+    );
+  }
+
+  // 鍖哄潡2锛氱敓浜ф壒娆�
+  function buildBatchCard(d) {
+    if (!d.batchCode && !d.tempCode) return '';
+    var status = statusBadge(BATCH_STATUS, d.batchStatus);
+    return card('鐢熶骇鎵规', status,
+      kv('鎵规鍙�', text(d.batchCode)) +
+      kv('鐘舵��', status) +
+      kv('鐢熶骇鏃ユ湡', fmtDate(d.produceDate)) +
+      kv('浜х嚎', text(d.lineName ? (d.lineName + (d.lineCode ? ' / ' + d.lineCode : '')) : d.lineCode)) +
+      kv('璐d换浜�', text(d.ownerUserName)) +
+      kv('璁″垝鏁伴噺', fmtNum(d.planQuantity) === '-' ? '-' : fmtNum(d.planQuantity)) +
+      kv('宸茬敓鎴愯鐮�', fmtNum(d.bagQuantity)) +
+      kv('宸茬敓鎴愭墭鐩�', fmtNum(d.palletQuantity)) +
+      kv('鍏ュ簱鏃堕棿', fmtDate(d.inboundTime)) +
+      (d.remark ? kv('澶囨敞', text(d.remark)) : '')
+    );
+  }
+
+  // 鍖哄潡3锛氱瀛愯川妫�
+  function buildQualityCard(d) {
+    var q = d.quality;
+    if (!q) return '';
+    var result = QUALITY_RESULT[q.result];
+    var badge = result
+      ? '<span class="badge-big ' + result[1] + '">' + esc(result[0]) + '</span>'
+      : '<span class="badge-big gray">鏈煡</span>';
+
+    var rows = '';
+    if (q.indicators && q.indicators.length) {
+      rows = '<table class="mini-table"><thead><tr><th>鎸囨爣</th><th>妫�娴嬪��</th><th>鏍囧噯鑼冨洿</th><th>缁撴灉</th></tr></thead><tbody>';
+      q.indicators.forEach(function (i) {
+        var range = (i.minValue !== null && i.minValue !== undefined && i.maxValue !== null && i.maxValue !== undefined)
+          ? esc(i.minValue) + ' ~ ' + esc(i.maxValue) : '-';
+        var pass = (i.pass === true)
+          ? '<span class="badge green">鍚堟牸</span>'
+          : (i.pass === false ? '<span class="badge red">涓嶅悎鏍�</span>' : '<span class="badge gray">--</span>');
+        rows += '<tr><td>' + text(i.indicatorName) + '</td><td>' + text(i.value) + '</td><td>' + range + '</td><td>' + pass + '</td></tr>';
+      });
+      rows += '</tbody></table>';
+    }
+
+    return card('绉嶅瓙璐ㄦ', badge,
+      kv('妫�楠岀粨璁�', badge) +
+      kv('妫�楠屽憳', text(q.inspectorName)) +
+      kv('妫�楠屾椂闂�', fmtDate(q.inspectedAt)) +
+      (q.reason ? kv('涓嶅悎鏍煎師鍥�', text(q.reason)) : '') +
+      (rows ? '<div style="margin-top:8px">' + rows + '</div>' : '')
+    );
+  }
+
+  // 鍖哄潡4锛氱敓浜т汉鍛�
+  function buildWorkerCard(d) {
+    if (isEmptyList(d.workers)) return '';
+    var html = '<div class="pill-list">';
+    d.workers.forEach(function (w) {
+      var name = w.userName || '-';
+      var type = w.workerTypeName || (WORKER_TYPE[w.workerType] || '');
+      var color = { 10: 'var(--blue)', 20: 'var(--green)', 30: 'var(--primary-light)', 40: 'var(--red)', 50: 'var(--gray)' }[w.workerType] || 'var(--gray)';
+      html += '<span class="pill"><span class="dot" style="background:' + color + '"></span>' +
+        esc(type) + '锛�' + esc(name) + '</span>';
+    });
+    html += '</div>';
+    return card('鐢熶骇浜哄憳', null, html);
+  }
+
+  // 鍖哄潡5锛氭壂鐮佸叆搴撲簨浠�
+  function buildInboundCard(d) {
+    var e = d.inbound;
+    if (!e) return '';
+    return card('鎵爜鍏ュ簱', null,
+      kv('浜嬩欢鍙�', text(e.eventId)) +
+      kv('鍏ュ簱鏁伴噺', fmtNum(e.quantity)) +
+      kv('璁惧缂栫爜', text(e.deviceCode)) +
+      kv('浜х嚎', text(e.lineCode)) +
+      kv('鍏ュ簱鏃堕棿', fmtDate(e.eventTime))
+    );
+  }
+
+  // 鍖哄潡6锛氭墭鐩�
+  function buildPalletCard(d) {
+    if (isEmptyList(d.pallets)) return '';
+    var countHtml = '鍏� ' + esc(d.palletCount || d.pallets.length) + ' 涓�';
+    var rows = '<table class="mini-table"><thead><tr><th>鎵樼洏鐮�</th><th>搴忓彿</th><th>宸茶/鍙</th></tr></thead><tbody>';
+    d.pallets.forEach(function (p) {
+      rows += '<tr><td>' + text(p.code) + '</td><td>' + fmtNum(p.palletNo) + '</td><td>' +
+        fmtNum(p.bagCount) + ' / ' + fmtNum(p.capacity) + '</td></tr>';
+    });
+    rows += '</tbody></table>';
+    return card('鎵樼洏淇℃伅', countHtml, rows);
+  }
+
+  // 鍖哄潡7锛氳鐮侊紙涓�琚嬩竴鐮侊級
+  function buildBagCard(d) {
+    if (d.bagTotal === null || d.bagTotal === undefined || d.bagTotal === 0) return '';
+    var countHtml = '鍏� ' + esc(d.bagTotal) + ' 涓�' + (d.bags && d.bags.length < d.bagTotal ? '锛堝睍绀哄墠 ' + esc(d.bags.length) + ' 涓級' : '');
+    var rows = '<table class="mini-table"><thead><tr><th>琚嬬爜</th><th>琚嬪彿</th><th>鎵樼洏</th><th>鐘舵��</th></tr></thead><tbody>';
+    (d.bags || []).forEach(function (b) {
+      rows += '<tr><td>' + text(b.code) + '</td><td>' + fmtNum(b.bagNo) + '</td><td>' + text(b.palletCode) + '</td><td>' +
+        statusBadge(BAG_STATUS, b.status) + '</td></tr>';
+    });
+    rows += '</tbody></table>';
+    return card('琚嬬爜淇℃伅', countHtml,
+      '<div class="scroll-list">' + rows + '</div>'
+    );
+  }
+
+  // 鍖哄潡8锛氬彲閫夋墿灞曪紙閲囪喘鏉ユ簮 / 閿�鍞幓鍚� / 鍔犲伐娴佽浆锛夛紝鏈夋暟鎹墠灞曠ず
+  function buildOptionalCard(d) {
+    var html = '';
+    if (!isEmptyList(d.purchaseSources)) {
+      var rows = '<table class="mini-table"><thead><tr><th>閲囪喘鍏ュ簱鍗�</th><th>渚涘簲鍟�</th><th>鏁伴噺</th><th>鏃ユ湡</th></tr></thead><tbody>';
+      d.purchaseSources.forEach(function (s) {
+        rows += '<tr><td>' + text(s.receiptCode) + '</td><td>' + text(s.vendorName) + '</td><td>' + fmtNum(s.receivedQuantity) + '</td><td>' + fmtDate(s.receiptDate) + '</td></tr>';
+      });
+      rows += '</tbody></table>';
+      html += card('閲囪喘鏉ユ簮', null, rows);
+    }
+    if (!isEmptyList(d.salesTargets)) {
+      var rows2 = '<table class="mini-table"><thead><tr><th>閿�鍞嚭搴撳崟</th><th>瀹㈡埛</th><th>鏁伴噺</th><th>鏃ユ湡</th></tr></thead><tbody>';
+      d.salesTargets.forEach(function (s) {
+        rows2 += '<tr><td>' + text(s.outCode || s.salesOrderCode || '') + '</td><td>' + text(s.customerName) + '</td><td>' + fmtNum(s.quantity) + '</td><td>' + fmtDate(s.outTime) + '</td></tr>';
+      });
+      rows2 += '</tbody></table>';
+      html += card('閿�鍞幓鍚�', null, rows2);
+    }
+    if (!isEmptyList(d.processTraces)) {
+      var rows3 = '<table class="mini-table"><thead><tr><th>鑺傜偣</th><th>璇存槑</th><th>鏃堕棿</th></tr></thead><tbody>';
+      d.processTraces.forEach(function (t) {
+        rows3 += '<tr><td>' + text(t.nodeName) + '</td><td>' + text(t.description) + '</td><td>' + fmtDate(t.processTime) + '</td></tr>';
+      });
+      rows3 += '</tbody></table>';
+      html += card('鍔犲伐娴佽浆', null, rows3);
+    }
+    return html;
+  }
+
+  // ========== 鍒濆鍖� ==========
+  SEARCH_BTN.addEventListener('click', function () { query(CODE_INPUT.value); });
+  CODE_INPUT.addEventListener('keydown', function (e) { if (e.key === 'Enter') query(CODE_INPUT.value); });
+
+  var initialCode = new URLSearchParams(location.search).get('code') || '';
+  CODE_INPUT.value = initialCode;
+  if (initialCode) {
+    query(initialCode);
+  } else {
+    renderState('<div class="empty-tip">鎵爜鎴栬緭鍏ヨ拷婧爜锛屽嵆鍙煡璇骇鍝佸叏绋嬫函婧愪俊鎭�</div>');
+    CODE_INPUT.focus();
+  }
+})();
+</script>
+</body>
+</html>

--
Gitblit v1.9.3