From ac2545f3cd01f3061a107e4980fc6fa59eaeaa6b Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期五, 21 八月 2026 13:43:38 +0800
Subject: [PATCH] feat(mes): 新增袋码破损替换功能

---
 .playwright-mcp/page-2026-08-21T03-59-08-154Z.yml                                                                              |    1 
 .playwright-mcp/page-2026-08-21T04-00-37-255Z.yml                                                                              |    1 
 .playwright-mcp/page-2026-08-21T04-00-18-943Z.yml                                                                              |    1 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/bagcode/MesProBagCodeReplaceDO.java              |   37 ++
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/bagcode/vo/MesProBagCodeReplaceReqVO.java      |   30 ++
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/bagcode/MesProBagCodeController.java           |   22 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/bagcode/vo/MesProBagCodeReplaceItemRespVO.java |   51 +++
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/bagcode/vo/MesProBagCodeRespVO.java            |    8 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/bagcode/MesProBagCodeService.java                       |   14 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/bagcode/MesProBagCodeServiceImpl.java                   |  137 +++++++++
 .playwright-mcp/page-2026-08-21T04-01-18-956Z.yml                                                                              |  106 +++++++
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/bagcode/MesProBagCodeReplaceMapper.java               |   27 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/bagcode/vo/MesProBagCodeReplaceRespVO.java     |   27 +
 sql/mysql/mes_pro_bag_code_replacement.sql                                                                                     |   59 +++
 sql/mysql/mes_pro_batch_bag_code_init.sql                                                                                      |   42 ++
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/bagcode/MesProBagCodeMapper.java                      |   18 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/bagcode/vo/MesProBagCodeTraceRespVO.java       |   15 +
 docs/mes_pro_bag_code_replacement_integration.md                                                                               |  150 ++++++++++
 .playwright-mcp/page-2026-08-21T04-00-59-933Z.yml                                                                              |  100 ++++++
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/batch/MesProBatchMapper.java                          |    4 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java                                       |    5 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/bagcode/MesProBagCodeDO.java                     |    8 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/pallet/MesProPalletMapper.java                        |    3 
 .playwright-mcp/page-2026-08-21T03-59-21-224Z.yml                                                                              |    1 
 24 files changed, 863 insertions(+), 4 deletions(-)

diff --git a/.playwright-mcp/page-2026-08-21T03-59-08-154Z.yml b/.playwright-mcp/page-2026-08-21T03-59-08-154Z.yml
new file mode 100644
index 0000000..6adf37e
--- /dev/null
+++ b/.playwright-mcp/page-2026-08-21T03-59-08-154Z.yml
@@ -0,0 +1 @@
+- generic [active]: 宸ヤ笟AI鏅洪�犲钩鍙�
\ No newline at end of file
diff --git a/.playwright-mcp/page-2026-08-21T03-59-21-224Z.yml b/.playwright-mcp/page-2026-08-21T03-59-21-224Z.yml
new file mode 100644
index 0000000..6adf37e
--- /dev/null
+++ b/.playwright-mcp/page-2026-08-21T03-59-21-224Z.yml
@@ -0,0 +1 @@
+- generic [active]: 宸ヤ笟AI鏅洪�犲钩鍙�
\ No newline at end of file
diff --git a/.playwright-mcp/page-2026-08-21T04-00-18-943Z.yml b/.playwright-mcp/page-2026-08-21T04-00-18-943Z.yml
new file mode 100644
index 0000000..6adf37e
--- /dev/null
+++ b/.playwright-mcp/page-2026-08-21T04-00-18-943Z.yml
@@ -0,0 +1 @@
+- generic [active]: 宸ヤ笟AI鏅洪�犲钩鍙�
\ No newline at end of file
diff --git a/.playwright-mcp/page-2026-08-21T04-00-37-255Z.yml b/.playwright-mcp/page-2026-08-21T04-00-37-255Z.yml
new file mode 100644
index 0000000..6adf37e
--- /dev/null
+++ b/.playwright-mcp/page-2026-08-21T04-00-37-255Z.yml
@@ -0,0 +1 @@
+- generic [active]: 宸ヤ笟AI鏅洪�犲钩鍙�
\ No newline at end of file
diff --git a/.playwright-mcp/page-2026-08-21T04-00-59-933Z.yml b/.playwright-mcp/page-2026-08-21T04-00-59-933Z.yml
new file mode 100644
index 0000000..a93d8dc
--- /dev/null
+++ b/.playwright-mcp/page-2026-08-21T04-00-59-933Z.yml
@@ -0,0 +1,100 @@
+- generic [ref=f3e7]:
+  - complementary [ref=f3e9]:
+    - generic [ref=f3e10]:
+      - generic [ref=f3e11] [cursor=pointer]
+      - link "宸ヤ笟AI鏅洪�犲钩鍙� 宸ヤ笟AI鏅洪�犲钩鍙�" [ref=f3e16] [cursor=pointer]:
+        - /url: javascript:void 0
+        - img "宸ヤ笟AI鏅洪�犲钩鍙�" [ref=f3e19]
+        - generic [ref=f3e20]: 宸ヤ笟AI鏅洪�犲钩鍙�
+      - menu [ref=f3e23]:
+        - menuitem "棣栭〉" [ref=f3e24] [cursor=pointer]
+        - listitem [ref=f3e29]:
+          - generic [ref=f3e30] [cursor=pointer]: 鍩虹鏁版嵁
+        - listitem [ref=f3e38]:
+          - generic [ref=f3e39] [cursor=pointer]: 涓�琚嬩竴鐮佺鐞�
+          - list [ref=f3e349]:
+            - menuitem "鎵规鐢熶骇绠$悊" [ref=f3e350] [cursor=pointer]
+            - menuitem "璧嬬爜绠$悊" [ref=f3e355] [cursor=pointer]
+            - menuitem "鎵樼洏鐮佺鐞�" [ref=f3e361] [cursor=pointer]
+            - menuitem "浜х嚎缁存姢" [ref=f3e368] [cursor=pointer]
+        - listitem [ref=f3e45]:
+          - generic [ref=f3e46] [cursor=pointer]: 绯荤粺绠$悊
+      - generic [ref=f3e53] [cursor=pointer]
+  - generic [ref=f3e58]:
+    - generic [ref=f3e59]:
+      - banner [ref=f3e60]:
+        - button [ref=f3e61] [cursor=pointer]
+        - button [ref=f3e64] [cursor=pointer]
+        - navigation "breadcrumb" [ref=f3e69]:
+          - list [ref=f3e70]:
+            - listitem [ref=f3e71]:
+              - link "棣栭〉" [disabled] [ref=f3e72]
+        - generic [ref=f3e77]:
+          - generic [ref=f3e79] [cursor=pointer]:
+            - generic [ref=f3e83]: 鎼滅储
+            - generic [ref=f3e84]: Ctrl K
+          - button [ref=f3e86] [cursor=pointer]
+          - button "dark" [ref=f3e91] [cursor=pointer]
+          - button [ref=f3e100] [cursor=pointer]
+          - button [ref=f3e106] [cursor=pointer]
+          - button [ref=f3e109] [cursor=pointer]
+          - button [ref=f3e115] [cursor=pointer]:
+            - button [ref=f3e117]
+          - button "宸ヤ綔鍙�" [ref=f3e121] [cursor=pointer]
+          - button [ref=f3e125] [cursor=pointer]:
+            - img "瓒呯骇绠$悊鍛�" [ref=f3e130]
+      - generic [ref=f3e132]:
+        - generic [ref=f3e133]: 棣栭〉
+        - generic [ref=f3e152]:
+          - button [ref=f3e153] [cursor=pointer]
+          - generic [ref=f3e160] [cursor=pointer]
+          - generic [ref=f3e164] [cursor=pointer]
+    - main [ref=f3e171]:
+      - generic [ref=f3e174]:
+        - generic [ref=f3e175]:
+          - generic [ref=f3e185] [cursor=pointer]:
+            - generic [ref=f3e186]: 鍝佺鎬绘暟
+            - generic [ref=f3e187]: "1"
+          - generic [ref=f3e198] [cursor=pointer]:
+            - generic [ref=f3e199]: MES 鍝佺
+            - generic [ref=f3e200]: "0"
+          - generic [ref=f3e211] [cursor=pointer]:
+            - generic [ref=f3e212]: 鍝佺鍒嗙被
+            - generic [ref=f3e213]: "1"
+          - generic [ref=f3e222] [cursor=pointer]:
+            - generic [ref=f3e223]: 璁¢噺鍗曚綅
+            - generic [ref=f3e224]: "1"
+        - generic [ref=f3e226]:
+          - generic [ref=f3e234] [cursor=pointer]:
+            - generic [ref=f3e235]: 浜х嚎
+            - generic [ref=f3e236]: "1"
+          - generic [ref=f3e248] [cursor=pointer]:
+            - generic [ref=f3e249]: 鐢熶骇鎵规
+            - generic [ref=f3e250]: "1"
+          - generic [ref=f3e259] [cursor=pointer]:
+            - generic [ref=f3e260]: 琚嬬爜
+            - generic [ref=f3e261]: "100"
+          - generic [ref=f3e272] [cursor=pointer]:
+            - generic [ref=f3e273]: 鎵樼洏鐮�
+            - generic [ref=f3e274]: "10"
+        - generic [ref=f3e276]:
+          - generic [ref=f3e280]:
+            - generic [ref=f3e281]: 琚嬬爜鐢熸垚瓒嬪娍
+            - generic [ref=f3e283]:
+              - generic [ref=f3e284] [cursor=pointer]:
+                - generic [ref=f3e285]:
+                  - radio "杩� 7 澶�" [checked]
+                - generic [ref=f3e286]: 杩� 7 澶�
+              - generic [ref=f3e287] [cursor=pointer]:
+                - generic [ref=f3e288]:
+                  - radio "杩� 30 澶�"
+                - generic [ref=f3e289]: 杩� 30 澶�
+          - generic [ref=f3e294]: 鎵规鐘舵�佸垎甯�
+          - generic [ref=f3e303]: 琚嬬爜鐘舵�佸垎甯�
+        - generic [ref=f3e312]:
+          - generic [ref=f3e313]: 蹇嵎鍏ュ彛
+          - generic [ref=f3e317]:
+            - generic [ref=f3e318]: 鍝佺绠$悊
+            - generic [ref=f3e326]: 鍝佺鍒嗙被
+            - generic [ref=f3e334]: 鎵规鐢熶骇
+            - generic [ref=f3e343]: 璧嬬爜绠$悊
\ No newline at end of file
diff --git a/.playwright-mcp/page-2026-08-21T04-01-18-956Z.yml b/.playwright-mcp/page-2026-08-21T04-01-18-956Z.yml
new file mode 100644
index 0000000..c98ec4f
--- /dev/null
+++ b/.playwright-mcp/page-2026-08-21T04-01-18-956Z.yml
@@ -0,0 +1,106 @@
+- generic [ref=f3e7]:
+  - complementary [ref=f3e9]:
+    - generic [ref=f3e10]:
+      - generic [ref=f3e11] [cursor=pointer]
+      - link "宸ヤ笟AI鏅洪�犲钩鍙� 宸ヤ笟AI鏅洪�犲钩鍙�" [ref=f3e16] [cursor=pointer]:
+        - /url: javascript:void 0
+        - img "宸ヤ笟AI鏅洪�犲钩鍙�" [ref=f3e19]
+        - generic [ref=f3e20]: 宸ヤ笟AI鏅洪�犲钩鍙�
+      - menu [ref=f3e23]:
+        - menuitem "棣栭〉" [ref=f3e24] [cursor=pointer]
+        - listitem [ref=f3e29]:
+          - generic [ref=f3e30] [cursor=pointer]: 鍩虹鏁版嵁
+        - listitem [ref=f3e38]:
+          - generic [ref=f3e39] [cursor=pointer]: 涓�琚嬩竴鐮佺鐞�
+          - list [ref=f3e349]:
+            - menuitem "鎵规鐢熶骇绠$悊" [ref=f3e350] [cursor=pointer]
+            - menuitem "璧嬬爜绠$悊" [active] [ref=f3e355] [cursor=pointer]
+            - menuitem "鎵樼洏鐮佺鐞�" [ref=f3e361] [cursor=pointer]
+            - menuitem "浜х嚎缁存姢" [ref=f3e368] [cursor=pointer]
+        - listitem [ref=f3e45]:
+          - generic [ref=f3e46] [cursor=pointer]: 绯荤粺绠$悊
+      - generic [ref=f3e53] [cursor=pointer]
+  - generic [ref=f3e58]:
+    - generic [ref=f3e59]:
+      - banner [ref=f3e60]:
+        - button [ref=f3e61] [cursor=pointer]
+        - button [ref=f3e64] [cursor=pointer]
+        - navigation "breadcrumb" [ref=f3e69]:
+          - list [ref=f3e70]:
+            - listitem [ref=f3e375]:
+              - link "涓�琚嬩竴鐮佺鐞�" [ref=f3e376] [cursor=pointer]:
+                - /url: javascript:void 0
+              - listitem [ref=f3e380]
+            - listitem [ref=f3e383]:
+              - link "璧嬬爜绠$悊" [disabled] [ref=f3e384]
+        - generic [ref=f3e77]:
+          - generic [ref=f3e79] [cursor=pointer]:
+            - generic [ref=f3e83]: 鎼滅储
+            - generic [ref=f3e84]: Ctrl K
+          - button [ref=f3e86] [cursor=pointer]
+          - button "dark" [ref=f3e91] [cursor=pointer]
+          - button [ref=f3e100] [cursor=pointer]
+          - button [ref=f3e106] [cursor=pointer]
+          - button [ref=f3e109] [cursor=pointer]
+          - button [ref=f3e115] [cursor=pointer]:
+            - button [ref=f3e117]
+          - button "宸ヤ綔鍙�" [ref=f3e121] [cursor=pointer]
+          - button [ref=f3e125] [cursor=pointer]:
+            - img "瓒呯骇绠$悊鍛�" [ref=f3e130]
+      - generic [ref=f3e132]:
+        - generic [ref=f3e137]:
+          - generic [ref=f3e138] [cursor=pointer]: 棣栭〉
+          - generic [ref=f3e392] [cursor=pointer]: 璧嬬爜绠$悊
+        - generic [ref=f3e152]:
+          - button [ref=f3e153] [cursor=pointer]
+          - generic [ref=f3e160] [cursor=pointer]
+          - generic [ref=f3e164] [cursor=pointer]
+    - main [ref=f3e171]:
+      - generic [ref=f3e174]:
+        - generic [ref=f3e175]:
+          - generic [ref=f3e185] [cursor=pointer]:
+            - generic [ref=f3e186]: 鍝佺鎬绘暟
+            - generic [ref=f3e187]: "1"
+          - generic [ref=f3e198] [cursor=pointer]:
+            - generic [ref=f3e199]: MES 鍝佺
+            - generic [ref=f3e200]: "0"
+          - generic [ref=f3e211] [cursor=pointer]:
+            - generic [ref=f3e212]: 鍝佺鍒嗙被
+            - generic [ref=f3e213]: "1"
+          - generic [ref=f3e222] [cursor=pointer]:
+            - generic [ref=f3e223]: 璁¢噺鍗曚綅
+            - generic [ref=f3e224]: "1"
+        - generic [ref=f3e226]:
+          - generic [ref=f3e234] [cursor=pointer]:
+            - generic [ref=f3e235]: 浜х嚎
+            - generic [ref=f3e236]: "1"
+          - generic [ref=f3e248] [cursor=pointer]:
+            - generic [ref=f3e249]: 鐢熶骇鎵规
+            - generic [ref=f3e250]: "1"
+          - generic [ref=f3e259] [cursor=pointer]:
+            - generic [ref=f3e260]: 琚嬬爜
+            - generic [ref=f3e261]: "100"
+          - generic [ref=f3e272] [cursor=pointer]:
+            - generic [ref=f3e273]: 鎵樼洏鐮�
+            - generic [ref=f3e274]: "10"
+        - generic [ref=f3e276]:
+          - generic [ref=f3e280]:
+            - generic [ref=f3e281]: 琚嬬爜鐢熸垚瓒嬪娍
+            - generic [ref=f3e283]:
+              - generic [ref=f3e284] [cursor=pointer]:
+                - generic [ref=f3e285]:
+                  - radio "杩� 7 澶�" [checked]
+                - generic [ref=f3e286]: 杩� 7 澶�
+              - generic [ref=f3e287] [cursor=pointer]:
+                - generic [ref=f3e288]:
+                  - radio "杩� 30 澶�"
+                - generic [ref=f3e289]: 杩� 30 澶�
+          - generic [ref=f3e294]: 鎵规鐘舵�佸垎甯�
+          - generic [ref=f3e303]: 琚嬬爜鐘舵�佸垎甯�
+        - generic [ref=f3e312]:
+          - generic [ref=f3e313]: 蹇嵎鍏ュ彛
+          - generic [ref=f3e317]:
+            - generic [ref=f3e318]: 鍝佺绠$悊
+            - generic [ref=f3e326]: 鍝佺鍒嗙被
+            - generic [ref=f3e334]: 鎵规鐢熶骇
+            - generic [ref=f3e343]: 璧嬬爜绠$悊
\ No newline at end of file
diff --git a/docs/mes_pro_bag_code_replacement_integration.md b/docs/mes_pro_bag_code_replacement_integration.md
new file mode 100644
index 0000000..9816c76
--- /dev/null
+++ b/docs/mes_pro_bag_code_replacement_integration.md
@@ -0,0 +1,150 @@
+# 涓�琚嬩竴鐮佺牬鎹熻鐮佹浛鎹� - 鍓嶇鑱旇皟鏂规
+
+## 娑夊強椤甸潰
+
+- 璧嬬爜绠$悊锛氳鐮佸垪琛ㄣ�佽鐮佽鎯呫�佹壂鐮佽拷婧�
+- 鎵规鐢熶骇绠$悊锛氭壒娆¤鎯呭強鏈夋晥琚嬬爜鏁伴噺灞曠ず
+- 鎵樼洏鐮佺鐞嗭細鎵樼洏璇︽儏鍙婄粦瀹氳鐮佸垪琛�
+
+鏈鍙柊澧炲悗绔帴鍙e拰瀛楁绾﹀畾锛屼笉淇敼鍓嶇椤圭洰浠g爜銆�
+
+## 涓氬姟娴佺▼涓庢暟鎹甫鍏�
+
+1. 鐢熶骇浜哄憳鍦ㄨ鐮佸垪琛ㄤ腑閫夋嫨鍚屼竴鎵规涓嬪寘瑁呯牬鎹熺殑琚嬬爜锛岃褰曞叾鏁版嵁搴撶紪鍙枫��
+2. 鍓嶇璋冪敤鐮存崯鏇挎崲鎺ュ彛锛屼紶鍏ユ壒娆$紪鍙枫�佹棫琚嬬爜缂栧彿鍒楄〃鍜岀牬鎹熷師鍥犮��
+3. 鍚庣鍦ㄤ竴涓簨鍔″唴灏嗘棫琚嬬爜鏀逛负鈥滃凡浣滃簾鈥濓紝鐢熸垚绛夐噺鏂拌鐮侊紝骞惰繑鍥炴棫鐮佷笌鏂扮爜涓�瀵逛竴鏄犲皠銆�
+4. 濡傛灉鏃ц鐮佸凡缁戝畾鍚敤鎵樼洏锛屾柊琚嬬爜鑷姩缁ф壙鍘熸墭鐩橈紱鏃х爜浠庢墭鐩樺綋鍓嶆湁鏁堣鐮佷腑绉婚櫎锛屾墭鐩樺凡缁戝畾鏁伴噺淇濇寔涓嶅彉銆�
+5. 鍓嶇鏍规嵁鍝嶅簲涓殑 `replaceUuid` 鎴栨柊琚嬬爜鍒楄〃鍒锋柊琚嬬爜鍒楄〃锛屽苟璋冪敤鐜版湁琚嬬爜瀵煎嚭鎺ュ彛瀹屾垚鏂版爣绛炬墦鍗般��
+6. 鏂拌鐮佸鍑哄悗鐘舵�佺敱鈥滃凡鐢熸垚鈥濆彉涓衡�滃凡瀵煎嚭鍗板埛鈥濓紱鏃ц鐮佷笉鍙備笌鎵撳嵃銆�
+7. 鎵弿鏃х爜浠嶅彲鏌ヨ鍘熸壒娆″拰浣滃簾淇℃伅锛涙壂鎻忔柊鐮佸彲鏌ヨ褰撳墠鎵规銆佹墭鐩樺拰鏇挎崲鏉ユ簮銆�
+
+## API
+
+### 1. 鐮存崯琚嬬爜鏇挎崲
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|---|---|---|
+| POST | `/mes/pro/bag-code/replace` | 浣滃簾鐮存崯鏃ц鐮佸苟鍦ㄥ師鎵规鐢熸垚绛夐噺鏂拌鐮� |
+
+鏉冮檺锛歚mes:pro-bag-code:replace`
+
+**璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|---|---|---|---|
+| `batchId` | Long | 鏄� | 鐢熶骇鎵规缂栧彿 |
+| `oldIds` | Array<Long> | 鏄� | 寰呬綔搴熻鐮佺紪鍙凤紝鍘婚噸鍚庤嚦灏� 1 涓紝鍗曟鏈�澶� 200 涓� |
+| `reason` | String | 鏄� | 鏇挎崲鍘熷洜锛屼緥濡傗�滃寘瑁呯牬鎹熲�濓紝鏈�闀� 255 涓瓧绗� |
+
+**璇锋眰绀轰緥锛�**
+
+```json
+{
+  "batchId": 1001,
+  "oldIds": [5001, 5002],
+  "reason": "鍖呰鐮存崯"
+}
+```
+
+**鍝嶅簲瀛楁锛�**
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|---|---|---|
+| `replaceUuid` | String | 鏈鏇挎崲缁� UUID锛屽彲鐢ㄤ簬璇嗗埆鏈鏂扮爜闆嗗悎 |
+| `batchId` | Long | 鐢熶骇鎵规缂栧彿 |
+| `batchCode` | String | 鐢熶骇鎵规鍙� |
+| `count` | Integer | 鏈鏇挎崲鏁伴噺 |
+| `items` | Array | 鏂版棫琚嬬爜鏄犲皠 |
+| `items.oldBagCodeId` | Long | 鏃ц鐮佺紪鍙� |
+| `items.oldCode` | String | 鏃ц鐮� |
+| `items.oldBagNo` | Integer | 鏃ц搴忓彿 |
+| `items.oldStatus` | Integer | 浣滃簾鍓嶇姸鎬� |
+| `items.oldPalletId` | Long | 鏃ц鐮佸師鎵樼洏缂栧彿锛屽彲涓虹┖ |
+| `items.oldPalletCode` | String | 鏃ц鐮佸師鎵樼洏鐮侊紝鍙负绌� |
+| `items.newBagCodeId` | Long | 鏂拌鐮佺紪鍙� |
+| `items.newCode` | String | 鏂拌鐮� |
+| `items.newBagNo` | Integer | 鏂拌搴忓彿 |
+| `items.newPalletId` | Long | 鏂拌鐮佸綋鍓嶆墭鐩樼紪鍙凤紝鍙负绌� |
+| `items.newPalletCode` | String | 鏂拌鐮佸綋鍓嶆墭鐩樼爜锛屽彲涓虹┖ |
+| `items.reason` | String | 鏇挎崲鍘熷洜 |
+| `items.replaceTime` | String | 鏇挎崲鏃堕棿 |
+
+**鍝嶅簲绀轰緥锛�**
+
+```json
+{
+  "code": 0,
+  "data": {
+    "replaceUuid": "9f6e4c4b2b2c4d5b8a8a8c0a0b0c0d0e",
+    "batchId": 1001,
+    "batchCode": "PC20260821L01ITEM001",
+    "count": 2,
+    "items": [
+      {
+        "oldBagCodeId": 5001,
+        "oldCode": "PC20260821L01ITEM001-0001",
+        "oldBagNo": 1,
+        "oldStatus": 20,
+        "oldPalletId": 3001,
+        "oldPalletCode": "PC20260821L01ITEM001-P01",
+        "newBagCodeId": 6001,
+        "newCode": "PC20260821L01ITEM001-1201",
+        "newBagNo": 1201,
+        "newPalletId": 3001,
+        "newPalletCode": "PC20260821L01ITEM001-P01",
+        "reason": "鍖呰鐮存崯",
+        "replaceTime": "2026-08-21 15:30:00"
+      }
+    ]
+  }
+}
+```
+
+## 瀛楁灞曠ず瑙勫垯
+
+| 瀛楁 | 灞曠ず浣嶇疆 | 璇存槑 |
+|---|---|---|
+| `status` | 琚嬬爜鍒楄〃銆佽鐮佽鎯呫�佹壂鐮佽拷婧� | 10 宸茬敓鎴愩��20 宸插鍑哄嵃鍒枫��30 宸蹭綔搴� |
+| `cancelTime` | 琚嬬爜璇︽儏銆佹壂鐮佽拷婧� | 浠呬綔搴熻鐮佽繑鍥� |
+| `cancelReason` | 琚嬬爜璇︽儏銆佹壂鐮佽拷婧� | 灞曠ず鐮存崯鎴栧叾浠栨浛鎹㈠師鍥� |
+| `replacementUuid` | 琚嬬爜璇︽儏銆佹壂鐮佽拷婧� | 鏍囪瘑鏇挎崲鎿嶄綔 |
+| `replacedByCode` | 浣滃簾鏃х爜杩芥函 | 灞曠ず鏇挎崲鍚庣殑鏂拌鐮� |
+| `replacementOfCode` | 鏂扮爜杩芥函 | 灞曠ず琚浛鎹㈢殑鏃ц鐮� |
+| `bagQuantity` | 鎵规鍒楄〃銆佹壒娆¤鎯� | 鎸夊綋鍓嶆湁鏁堣鐮佹暟閲忕粺璁★紝涓嶅寘鍚凡浣滃簾琚嬬爜 |
+| `palletCode` | 琚嬬爜鍒楄〃銆佹墭鐩樿鎯呫�佹壂鐮佽拷婧� | 宸茬粦瀹氬惎鐢ㄦ墭鐩樼殑鏇挎崲鏂扮爜缁ф壙鍘熸墭鐩� |
+
+## 涓氬姟瑙勫垯璇存槑
+
+| 鍦烘櫙 | 瑙勫垯 |
+|---|---|
+| 鏅�氱敓鎴� | 浠嶄粎鍏佽鐢熶骇涓壒娆★紝浣跨敤 `/mes/pro/bag-code/generate` |
+| 鐮存崯鏇挎崲 | 鍏佽鐢熶骇涓�佸凡瀹屾垚鎵规锛涜崏绋垮拰宸蹭綔搴熸壒娆′笉鍏佽 |
+| 鏃х爜鐘舵�� | 浠呭凡鐢熸垚銆佸凡瀵煎嚭鍗板埛鍙互鏇挎崲锛涘凡浣滃簾涓嶈兘閲嶅鏇挎崲 |
+| 鎵规褰掑睘 | `oldIds` 蹇呴』鍏ㄩ儴灞炰簬璇锋眰涓殑 `batchId` |
+| 鏂扮爜搴忓彿 | 浠庢壒娆″巻鍙叉渶澶ц搴忓彿缁х画閫掑锛屽凡浣滃簾鍜岄�昏緫鍒犻櫎搴忓彿涔熶笉澶嶇敤 |
+| 鏁伴噺 | 浣滃簾 N 涓苟鐢熸垚 N 涓紝鎵规鏈夋晥琚嬬爜鏁伴噺淇濇寔涓嶅彉 |
+| 鏈鎵樼洏 | 鏃х爜浣滃簾锛屾柊鐮佷繚鎸佹湭缁戝畾 |
+| 宸茶鎵樼洏 | 鍘熸墭鐩樺繀椤诲瓨鍦ㄤ笖鍚敤锛涙棫鐮佽В缁戝苟浣滃簾锛屾柊鐮佽嚜鍔ㄧ户鎵垮師鎵樼洏 |
+| 鎵樼洏鏁伴噺 | 鏇挎崲鍓嶅悗鎵樼洏鏈夋晥缁戝畾鏁伴噺淇濇寔涓嶅彉 |
+| 鎵撳嵃 | 鏂扮爜鍒濆涓哄凡鐢熸垚锛涘鍑哄悗鍙樹负宸插鍑哄嵃鍒凤紱鏃х爜涓嶅緱瀵煎嚭鎵撳嵃 |
+| 杩芥函 | 鏃х爜璁板綍淇濈暀骞舵樉绀哄凡浣滃簾锛屼笉杩斿洖琚嬬爜涓嶅瓨鍦紱鏂版棫鐮佸彲浜掔浉杩芥函 |
+| 鍘熷瓙鎬� | 浣滃簾銆佺敓鎴愩�佹墭鐩樼户鎵裤�佹浛鎹㈣褰曞啓鍏ヤ换涓�姝ュけ璐ワ紝鍏ㄩ儴浜嬪姟鍥炴粴 |
+
+## 鎺ュ彛璋冪敤鏃跺簭
+
+1. 鑾峰彇琚嬬爜鍒嗛〉锛屾寜鎵规鍜岀姸鎬佺瓫閫夋湁鏁堣鐮併��
+2. 鐢ㄦ埛鍕鹃�夌牬鎹熻鐮佸苟濉啓鍘熷洜銆�
+3. 璋冪敤 `/mes/pro/bag-code/replace`銆�
+4. 鎴愬姛鍚庡埛鏂拌鐮佸垪琛ㄥ拰鎵规鏁伴噺銆�
+5. 渚濇嵁鍝嶅簲鐨勬柊琚嬬爜闆嗗悎锛屾寜 `replaceUuid` 鏌ヨ鎴栧鍑烘柊鐮佽繘琛屾墦鍗般��
+6. 鎵撳嵃鎴愬姛鍚庡埛鏂扮姸鎬侊紱濡傞渶鎵爜纭锛屽垎鍒壂鎻忔棫鐮佸拰鏂扮爜鏍稿鏇挎崲鍏崇郴銆�
+
+## 娉ㄦ剰浜嬮」
+
+- `oldIds` 涓嶈娣峰叆涓嶅悓鎵规鐨勮鐮侊紱鏈嶅姟绔細鍐嶆鏍¢獙銆�
+- 涓嶈璋冪敤鏅�� `/generate` 瀹炵幇鐮存崯琛ョ爜锛屾櫘閫氱敓鎴愪細澧炲姞琚嬬爜鏁伴噺涓斾笉淇濆瓨鏇挎崲鍏崇郴銆�
+- 鏂拌鐮佷笉澶嶇敤鏃ц鐮佺殑 code 鎴栬搴忓彿锛�1200 琚嬫浛鎹� 2 琚嬫椂鏂扮爜閫氬父浠� 1201銆�1202 缁х画鐢熸垚銆�
+- 宸蹭綔搴熸棫鐮佷粛鍙拷婧紝浣嗕笉鍙粦瀹氭墭鐩樸�佷笉鍙啀娆℃浛鎹€�佷笉鍙墦鍗般��
+- 鍘熸墭鐩樺凡浣滃簾鎴栦笉瀛樺湪鏃讹紝宸茬粦瀹氳鐮佺殑鏇挎崲浼氬け璐ワ紝闇�瑕佸厛澶勭悊鎵樼洏鐘舵�併��
+- 鎵规杩芥函涓殑琚嬬爜瀵硅薄鍖呭惈鐘舵�佸瓧娈碉紱灞曠ず浣滃簾鏍囪瘑鏃朵笉瑕佸彧鏄剧ず `code` 瀛楃涓层��
+- 鏇挎崲鎺ュ彛杩斿洖鐨勬柊鐮佸垵濮嬫湭鎵撳嵃锛屽鍑烘垚鍔熷悗鎵嶅彉涓衡�滃凡瀵煎嚭鍗板埛鈥濄��
diff --git a/sql/mysql/mes_pro_bag_code_replacement.sql b/sql/mysql/mes_pro_bag_code_replacement.sql
new file mode 100644
index 0000000..af0dd54
--- /dev/null
+++ b/sql/mysql/mes_pro_bag_code_replacement.sql
@@ -0,0 +1,59 @@
+-- 涓�琚嬩竴鐮侊細鐮存崯琚嬬爜鏇挎崲杩佺Щ
+-- 璇存槑锛氭棫琚嬬爜淇濈暀骞舵爣璁板凡浣滃簾锛屾柊琚嬬爜涓庢棫琚嬬爜閫氳繃鏇挎崲璁板綍鍏宠仈銆�
+
+SET @cancel_time_exists = (
+    SELECT COUNT(*) FROM information_schema.columns
+    WHERE table_schema = DATABASE() AND table_name = 'mes_pro_bag_code' AND column_name = 'cancel_time'
+);
+SET @sql = IF(@cancel_time_exists = 0,
+    'ALTER TABLE mes_pro_bag_code ADD COLUMN cancel_time DATETIME DEFAULT NULL COMMENT ''浣滃簾鏃堕棿'' AFTER print_time',
+    'SELECT 1');
+PREPARE stmt FROM @sql;
+EXECUTE stmt;
+DEALLOCATE PREPARE stmt;
+
+SET @cancel_reason_exists = (
+    SELECT COUNT(*) FROM information_schema.columns
+    WHERE table_schema = DATABASE() AND table_name = 'mes_pro_bag_code' AND column_name = 'cancel_reason'
+);
+SET @sql = IF(@cancel_reason_exists = 0,
+    'ALTER TABLE mes_pro_bag_code ADD COLUMN cancel_reason VARCHAR(255) DEFAULT NULL COMMENT ''浣滃簾鍘熷洜'' AFTER cancel_time',
+    'SELECT 1');
+PREPARE stmt FROM @sql;
+EXECUTE stmt;
+DEALLOCATE PREPARE stmt;
+
+CREATE TABLE IF NOT EXISTS `mes_pro_bag_code_replace` (
+    `id`              BIGINT       NOT NULL AUTO_INCREMENT COMMENT '缂栧彿',
+    `replace_uuid`    VARCHAR(32)  NOT NULL COMMENT '鏇挎崲缁� UUID',
+    `batch_id`        BIGINT       NOT NULL COMMENT '鐢熶骇鎵规缂栧彿',
+    `batch_code`      VARCHAR(64)  DEFAULT NULL COMMENT '鐢熶骇鎵规鍙�',
+    `old_bag_code_id` BIGINT       NOT NULL COMMENT '鏃ц鐮佺紪鍙�',
+    `old_code`        VARCHAR(64)  NOT NULL COMMENT '鏃ц鐮�',
+    `old_bag_no`      INT          NOT NULL COMMENT '鏃ц搴忓彿',
+    `old_status`      TINYINT      NOT NULL COMMENT '鏃ц鐮佷綔搴熷墠鐘舵��',
+    `old_pallet_id`   BIGINT       DEFAULT NULL COMMENT '鏃ц鐮佸師鎵樼洏缂栧彿',
+    `old_pallet_code` VARCHAR(64)  DEFAULT NULL COMMENT '鏃ц鐮佸師鎵樼洏鐮�',
+    `new_bag_code_id` BIGINT       NOT NULL COMMENT '鏂拌鐮佺紪鍙�',
+    `new_code`        VARCHAR(64)  NOT NULL COMMENT '鏂拌鐮�',
+    `new_bag_no`      INT          NOT NULL COMMENT '鏂拌搴忓彿',
+    `reason`          VARCHAR(255) NOT NULL COMMENT '鏇挎崲鍘熷洜',
+    `replace_time`    DATETIME     NOT NULL COMMENT '鏇挎崲鏃堕棿',
+    `creator`         VARCHAR(64)  DEFAULT '' COMMENT '鍒涘缓鑰�',
+    `create_time`     DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+    `updater`         VARCHAR(64)  DEFAULT '' COMMENT '鏇存柊鑰�',
+    `update_time`     DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+    `deleted`         BIT(1)       NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_old_bag_code_id` (`old_bag_code_id`),
+    UNIQUE KEY `uk_new_bag_code_id` (`new_bag_code_id`),
+    KEY `idx_replace_uuid` (`replace_uuid`),
+    KEY `idx_batch_id` (`batch_id`),
+    KEY `idx_old_code` (`old_code`),
+    KEY `idx_new_code` (`new_code`)
+) ENGINE = InnoDB COMMENT = 'MES 琚嬬爜鏇挎崲璁板綍';
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+SELECT 602629, '琚嬬爜鐮存崯鏇挎崲', 'mes:pro-bag-code:replace', 3, 6, 602618, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
+FROM DUAL
+WHERE NOT EXISTS (SELECT 1 FROM `system_menu` WHERE `id` = 602629 OR (`permission` = 'mes:pro-bag-code:replace' AND `deleted` = b'0'));
diff --git a/sql/mysql/mes_pro_batch_bag_code_init.sql b/sql/mysql/mes_pro_batch_bag_code_init.sql
index a3a1a03..8c7a86f 100644
--- a/sql/mysql/mes_pro_batch_bag_code_init.sql
+++ b/sql/mysql/mes_pro_batch_bag_code_init.sql
@@ -94,6 +94,8 @@
     `pallet_code` VARCHAR(64)  DEFAULT NULL COMMENT '鎵樼洏鐮侊紙鍐椾綑锛屼究浜庡睍绀猴級',
     `status`      TINYINT      NOT NULL DEFAULT 10 COMMENT '鐘舵�侊紙瀛楀吀 mes_pro_bag_code_status锛�10 宸茬敓鎴� 20 宸插鍑哄嵃鍒� 30 宸蹭綔搴燂級',
     `print_time`  DATETIME     DEFAULT NULL COMMENT '瀵煎嚭锛堜氦浠樺嵃鍒凤級鏃堕棿',
+    `cancel_time` DATETIME     DEFAULT NULL COMMENT '浣滃簾鏃堕棿',
+    `cancel_reason` VARCHAR(255) DEFAULT NULL COMMENT '浣滃簾鍘熷洜',
     `remark`      VARCHAR(255) DEFAULT NULL COMMENT '澶囨敞',
     `creator`     VARCHAR(64)  DEFAULT '' COMMENT '鍒涘缓鑰�',
     `create_time` DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
@@ -105,10 +107,43 @@
     KEY `idx_batch_id` (`batch_id`),
     KEY `idx_uuid` (`uuid`),
     KEY `idx_pallet_id` (`pallet_id`)
-) ENGINE = InnoDB COMMENT = 'MES 琚嬬爜璧嬬爜锛堜竴琚嬩竴鐮侊級';
+ ) ENGINE = InnoDB COMMENT = 'MES 琚嬬爜璧嬬爜锛堜竴琚嬩竴鐮侊級';
 
 -- ----------------------------
--- 5. 鎵规鍙疯嚜鍔ㄧ紪鐮佽鍒欙細PRO_BATCH_CODE
+-- 5. 琚嬬爜鏇挎崲璁板綍琛�
+-- ----------------------------
+CREATE TABLE IF NOT EXISTS `mes_pro_bag_code_replace` (
+    `id`              BIGINT       NOT NULL AUTO_INCREMENT COMMENT '缂栧彿',
+    `replace_uuid`    VARCHAR(32)  NOT NULL COMMENT '鏇挎崲缁� UUID',
+    `batch_id`        BIGINT       NOT NULL COMMENT '鐢熶骇鎵规缂栧彿',
+    `batch_code`      VARCHAR(64)  DEFAULT NULL COMMENT '鐢熶骇鎵规鍙�',
+    `old_bag_code_id` BIGINT       NOT NULL COMMENT '鏃ц鐮佺紪鍙�',
+    `old_code`        VARCHAR(64)  NOT NULL COMMENT '鏃ц鐮�',
+    `old_bag_no`      INT          NOT NULL COMMENT '鏃ц搴忓彿',
+    `old_status`      TINYINT      NOT NULL COMMENT '鏃ц鐮佷綔搴熷墠鐘舵��',
+    `old_pallet_id`   BIGINT       DEFAULT NULL COMMENT '鏃ц鐮佸師鎵樼洏缂栧彿',
+    `old_pallet_code` VARCHAR(64)  DEFAULT NULL COMMENT '鏃ц鐮佸師鎵樼洏鐮�',
+    `new_bag_code_id` BIGINT       NOT NULL COMMENT '鏂拌鐮佺紪鍙�',
+    `new_code`        VARCHAR(64)  NOT NULL COMMENT '鏂拌鐮�',
+    `new_bag_no`      INT          NOT NULL COMMENT '鏂拌搴忓彿',
+    `reason`          VARCHAR(255) NOT NULL COMMENT '鏇挎崲鍘熷洜',
+    `replace_time`    DATETIME     NOT NULL COMMENT '鏇挎崲鏃堕棿',
+    `creator`         VARCHAR(64)  DEFAULT '' COMMENT '鍒涘缓鑰�',
+    `create_time`     DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+    `updater`         VARCHAR(64)  DEFAULT '' COMMENT '鏇存柊鑰�',
+    `update_time`     DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+    `deleted`         BIT(1)       NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_old_bag_code_id` (`old_bag_code_id`),
+    UNIQUE KEY `uk_new_bag_code_id` (`new_bag_code_id`),
+    KEY `idx_replace_uuid` (`replace_uuid`),
+    KEY `idx_batch_id` (`batch_id`),
+    KEY `idx_old_code` (`old_code`),
+    KEY `idx_new_code` (`new_code`)
+) ENGINE = InnoDB COMMENT = 'MES 琚嬬爜鏇挎崲璁板綍';
+
+-- ----------------------------
+-- 6. 鎵规鍙疯嚜鍔ㄧ紪鐮佽鍒欙細PRO_BATCH_CODE
 --    缁撴瀯锛氬浐瀹氬瓧绗�(PC) + 鏃ユ湡(yyyyMMdd) + 杈撳叆瀛楃(浜х嚎缂栫爜+鍝佺缂栫爜) + 娴佹按鍙�(3浣嶏紝鎸夋棩寰幆)
 --    绀轰緥锛歅C20260818L01NY001
 -- ----------------------------
@@ -189,7 +224,8 @@
 (602620, '鐢熸垚浜岀淮鐮佹暟鎹寘', 'mes:pro-bag-code:generate', 3, 2, 602618, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
 (602621, '琚嬬爜鍒犻櫎', 'mes:pro-bag-code:delete', 3, 3, 602618, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
 (602622, '琚嬬爜瀵煎嚭', 'mes:pro-bag-code:export', 3, 4, 602618, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
-(602623, '琚嬬爜缁戝畾鎵樼洏', 'mes:pro-bag-code:bind', 3, 5, 602618, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+(602623, '琚嬬爜缁戝畾鎵樼洏', 'mes:pro-bag-code:bind', 3, 5, 602618, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+(602629, '琚嬬爜鐮存崯鏇挎崲', 'mes:pro-bag-code:replace', 3, 6, 602618, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
 
 -- 鎵樼洏鐮佺鐞�
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/bagcode/MesProBagCodeController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/bagcode/MesProBagCodeController.java
index 61cb109..49cb688 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/bagcode/MesProBagCodeController.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/bagcode/MesProBagCodeController.java
@@ -12,6 +12,8 @@
 import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeBindReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeGenerateReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodePageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeReplaceReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeReplaceRespVO;
 import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeRespVO;
 import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeTraceRespVO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO;
@@ -73,6 +75,14 @@
     @PreAuthorize("@ss.hasPermission('mes:pro-bag-code:create')")
     public CommonResult<String> generateBagCodes(@Valid @RequestBody MesProBagCodeGenerateReqVO generateReqVO) {
         return success(bagCodeService.generateBagCodes(generateReqVO));
+    }
+
+    @PostMapping("/replace")
+    @Operation(summary = "浣滃簾鐮存崯琚嬬爜骞剁敓鎴愮瓑閲忔浛鎹㈣鐮�")
+    @PreAuthorize("@ss.hasPermission('mes:pro-bag-code:replace')")
+    public CommonResult<MesProBagCodeReplaceRespVO> replaceBagCodes(
+            @Valid @RequestBody MesProBagCodeReplaceReqVO replaceReqVO) {
+        return success(bagCodeService.replaceBagCodes(replaceReqVO));
     }
 
     @DeleteMapping("/delete-by-uuid")
@@ -203,6 +213,18 @@
 
     private MesProBagCodeTraceRespVO buildBagCodeTraceRespVO(MesProBagCodeDO bagCode) {
         MesProBagCodeTraceRespVO respVO = BeanUtils.toBean(bagCode, MesProBagCodeTraceRespVO.class);
+        var replacement = bagCode.getStatus() != null
+                && bagCode.getStatus().equals(cn.iocoder.yudao.module.mes.enums.pro.MesProBagCodeStatusEnum.CANCELED.getStatus())
+                ? bagCodeService.getReplacementByOldBagCodeId(bagCode.getId())
+                : bagCodeService.getReplacementByNewBagCodeId(bagCode.getId());
+        if (replacement != null) {
+            respVO.setReplacementUuid(replacement.getReplaceUuid());
+            if (bagCode.getId().equals(replacement.getOldBagCodeId())) {
+                respVO.setReplacedByCode(replacement.getNewCode());
+            } else {
+                respVO.setReplacementOfCode(replacement.getOldCode());
+            }
+        }
         // 鎵规淇℃伅
         MesProBatchDO batch = batchService.getBatch(bagCode.getBatchId());
         if (batch != null) {
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/bagcode/vo/MesProBagCodeReplaceItemRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/bagcode/vo/MesProBagCodeReplaceItemRespVO.java
new file mode 100644
index 0000000..4e38dc8
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/bagcode/vo/MesProBagCodeReplaceItemRespVO.java
@@ -0,0 +1,51 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 琚嬬爜鏇挎崲鏄庣粏 Response VO")
+@Data
+public class MesProBagCodeReplaceItemRespVO {
+
+    @Schema(description = "鏃ц鐮佺紪鍙�")
+    private Long oldBagCodeId;
+
+    @Schema(description = "鏃ц鐮�")
+    private String oldCode;
+
+    @Schema(description = "鏃ц搴忓彿")
+    private Integer oldBagNo;
+
+    @Schema(description = "鏃ц鐮佷綔搴熷墠鐘舵��")
+    private Integer oldStatus;
+
+    @Schema(description = "鏃ц鐮佸師鎵樼洏缂栧彿")
+    private Long oldPalletId;
+
+    @Schema(description = "鏃ц鐮佸師鎵樼洏鐮�")
+    private String oldPalletCode;
+
+    @Schema(description = "鏂拌鐮佺紪鍙�")
+    private Long newBagCodeId;
+
+    @Schema(description = "鏂拌鐮�")
+    private String newCode;
+
+    @Schema(description = "鏂拌搴忓彿")
+    private Integer newBagNo;
+
+    @Schema(description = "鏂拌鐮佸綋鍓嶆墭鐩樼紪鍙�")
+    private Long newPalletId;
+
+    @Schema(description = "鏂拌鐮佸綋鍓嶆墭鐩樼爜")
+    private String newPalletCode;
+
+    @Schema(description = "鏇挎崲鍘熷洜")
+    private String reason;
+
+    @Schema(description = "鏇挎崲鏃堕棿")
+    private LocalDateTime replaceTime;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/bagcode/vo/MesProBagCodeReplaceReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/bagcode/vo/MesProBagCodeReplaceReqVO.java
new file mode 100644
index 0000000..fd9cf6e
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/bagcode/vo/MesProBagCodeReplaceReqVO.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 鐮存崯琚嬬爜鏇挎崲 Request VO")
+@Data
+public class MesProBagCodeReplaceReqVO {
+
+    @Schema(description = "鐢熶骇鎵规缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotNull(message = "鐢熶骇鎵规缂栧彿涓嶈兘涓虹┖")
+    private Long batchId;
+
+    @Schema(description = "寰呬綔搴熺殑琚嬬爜缂栧彿鍒楄〃", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "寰呬綔搴熻鐮佷笉鑳戒负绌�")
+    @Size(max = 200, message = "鍗曟鏈�澶氭浛鎹� 200 涓鐮�")
+    private List<Long> oldIds;
+
+    @Schema(description = "鏇挎崲鍘熷洜", requiredMode = Schema.RequiredMode.REQUIRED, example = "鍖呰鐮存崯")
+    @NotBlank(message = "鏇挎崲鍘熷洜涓嶈兘涓虹┖")
+    @Size(max = 255, message = "鏇挎崲鍘熷洜闀垮害涓嶈兘瓒呰繃 255 涓瓧绗�")
+    private String reason;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/bagcode/vo/MesProBagCodeReplaceRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/bagcode/vo/MesProBagCodeReplaceRespVO.java
new file mode 100644
index 0000000..24210fc
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/bagcode/vo/MesProBagCodeReplaceRespVO.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 鐮存崯琚嬬爜鏇挎崲 Response VO")
+@Data
+public class MesProBagCodeReplaceRespVO {
+
+    @Schema(description = "鏈鏇挎崲缁� UUID")
+    private String replaceUuid;
+
+    @Schema(description = "鐢熶骇鎵规缂栧彿")
+    private Long batchId;
+
+    @Schema(description = "鐢熶骇鎵规鍙�")
+    private String batchCode;
+
+    @Schema(description = "鏇挎崲鏁伴噺")
+    private Integer count;
+
+    @Schema(description = "鏇挎崲鏄庣粏")
+    private List<MesProBagCodeReplaceItemRespVO> items;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/bagcode/vo/MesProBagCodeRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/bagcode/vo/MesProBagCodeRespVO.java
index 900d5c5..07be109 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/bagcode/vo/MesProBagCodeRespVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/bagcode/vo/MesProBagCodeRespVO.java
@@ -68,6 +68,14 @@
     @ColumnWidth(20)
     private LocalDateTime printTime;
 
+    @Schema(description = "浣滃簾鏃堕棿")
+    @ExcelIgnore
+    private LocalDateTime cancelTime;
+
+    @Schema(description = "浣滃簾鍘熷洜")
+    @ExcelIgnore
+    private String cancelReason;
+
     @Schema(description = "澶囨敞")
     @ExcelIgnore
     private String remark;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/bagcode/vo/MesProBagCodeTraceRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/bagcode/vo/MesProBagCodeTraceRespVO.java
index d049c46..4272372 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/bagcode/vo/MesProBagCodeTraceRespVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/bagcode/vo/MesProBagCodeTraceRespVO.java
@@ -26,6 +26,21 @@
     @Schema(description = "瀵煎嚭锛堜氦浠樺嵃鍒凤級鏃堕棿")
     private LocalDateTime printTime;
 
+    @Schema(description = "浣滃簾鏃堕棿")
+    private LocalDateTime cancelTime;
+
+    @Schema(description = "浣滃簾鍘熷洜")
+    private String cancelReason;
+
+    @Schema(description = "鏇挎崲缁� UUID")
+    private String replacementUuid;
+
+    @Schema(description = "鏇挎崲鍚庣殑鏂拌鐮�")
+    private String replacedByCode;
+
+    @Schema(description = "琚浛鎹㈢殑鏃ц鐮�")
+    private String replacementOfCode;
+
     // ========== 鎵规淇℃伅 ==========
 
     @Schema(description = "鐢熶骇鎵规缂栧彿", example = "1024")
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/bagcode/MesProBagCodeDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/bagcode/MesProBagCodeDO.java
index feb8e98..be7f025 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/bagcode/MesProBagCodeDO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/bagcode/MesProBagCodeDO.java
@@ -79,6 +79,14 @@
      */
     private LocalDateTime printTime;
     /**
+     * 浣滃簾鏃堕棿
+     */
+    private LocalDateTime cancelTime;
+    /**
+     * 浣滃簾鍘熷洜
+     */
+    private String cancelReason;
+    /**
      * 澶囨敞
      */
     private String remark;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/bagcode/MesProBagCodeReplaceDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/bagcode/MesProBagCodeReplaceDO.java
new file mode 100644
index 0000000..5e0749b
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/bagcode/MesProBagCodeReplaceDO.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.time.LocalDateTime;
+
+@TableName("mes_pro_bag_code_replace")
+@KeySequence("mes_pro_bag_code_replace_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MesProBagCodeReplaceDO extends BaseDO {
+
+    @TableId
+    private Long id;
+    private String replaceUuid;
+    private Long batchId;
+    private String batchCode;
+    private Long oldBagCodeId;
+    private String oldCode;
+    private Integer oldBagNo;
+    private Integer oldStatus;
+    private Long oldPalletId;
+    private String oldPalletCode;
+    private Long newBagCodeId;
+    private String newCode;
+    private Integer newBagNo;
+    private String reason;
+    private LocalDateTime replaceTime;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/bagcode/MesProBagCodeMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/bagcode/MesProBagCodeMapper.java
index 4a4906d..ca31481 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/bagcode/MesProBagCodeMapper.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/bagcode/MesProBagCodeMapper.java
@@ -98,6 +98,24 @@
     @Select("SELECT IFNULL(MAX(bag_no), 0) FROM mes_pro_bag_code WHERE batch_id = #{batchId}")
     Integer selectMaxBagNo(@Param("batchId") Long batchId);
 
+    default List<MesProBagCodeDO> selectListByIdsForUpdate(Collection<Long> ids) {
+        return selectList(new LambdaQueryWrapperX<MesProBagCodeDO>()
+                .in(MesProBagCodeDO::getId, ids)
+                .orderByAsc(MesProBagCodeDO::getId)
+                .last("FOR UPDATE"));
+    }
+
+    @Update("<script>UPDATE mes_pro_bag_code SET status = #{toStatus}, cancel_time = #{cancelTime}, " +
+            "cancel_reason = #{cancelReason}, pallet_id = NULL, pallet_code = NULL, update_time = NOW() " +
+            "WHERE deleted = 0 AND id = #{id} AND status = #{fromStatus}</script>")
+    int cancelById(@Param("id") Long id, @Param("fromStatus") Integer fromStatus,
+                   @Param("toStatus") Integer toStatus, @Param("cancelTime") LocalDateTime cancelTime,
+                   @Param("cancelReason") String cancelReason);
+
+    @Select("SELECT COUNT(*) FROM mes_pro_bag_code WHERE batch_id = #{batchId} " +
+            "AND deleted = 0 AND status <> 30")
+    int selectEffectiveCountByBatchId(@Param("batchId") Long batchId);
+
     /**
      * 鎵归噺鏍囪涓哄凡瀵煎嚭鍗板埛锛堜粎鏇存柊澶勪簬鐩爣鍘熺姸鎬佺殑璁板綍锛�
      *
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/bagcode/MesProBagCodeReplaceMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/bagcode/MesProBagCodeReplaceMapper.java
new file mode 100644
index 0000000..5fce030
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/bagcode/MesProBagCodeReplaceMapper.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.mes.dal.mysql.pro.bagcode;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeReplaceDO;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+@Mapper
+public interface MesProBagCodeReplaceMapper extends BaseMapperX<MesProBagCodeReplaceDO> {
+
+    default List<MesProBagCodeReplaceDO> selectListByOldBagCodeIds(Collection<Long> oldBagCodeIds) {
+        return selectList(new LambdaQueryWrapper<MesProBagCodeReplaceDO>()
+                .in(MesProBagCodeReplaceDO::getOldBagCodeId, oldBagCodeIds));
+    }
+
+    default MesProBagCodeReplaceDO selectByOldBagCodeId(Long oldBagCodeId) {
+        return selectOne(MesProBagCodeReplaceDO::getOldBagCodeId, oldBagCodeId);
+    }
+
+    default MesProBagCodeReplaceDO selectByNewBagCodeId(Long newBagCodeId) {
+        return selectOne(MesProBagCodeReplaceDO::getNewBagCodeId, newBagCodeId);
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/batch/MesProBatchMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/batch/MesProBatchMapper.java
index 64d0121..f35a1e4 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/batch/MesProBatchMapper.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/batch/MesProBatchMapper.java
@@ -6,6 +6,7 @@
 import cn.iocoder.yudao.module.mes.controller.admin.pro.batch.vo.MesProBatchPageReqVO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchDO;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Select;
 
 /**
  * MES 鐢熶骇鎵规 Mapper
@@ -23,6 +24,9 @@
         return selectCount(MesProBatchDO::getLineId, lineId);
     }
 
+    @Select("SELECT * FROM mes_pro_batch WHERE id = #{id} AND deleted = 0 FOR UPDATE")
+    MesProBatchDO selectByIdForUpdate(@org.apache.ibatis.annotations.Param("id") Long id);
+
     default PageResult<MesProBatchDO> selectPage(MesProBatchPageReqVO reqVO) {
         return selectPage(reqVO, new LambdaQueryWrapperX<MesProBatchDO>()
                 .likeIfPresent(MesProBatchDO::getCode, reqVO.getCode())
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/pallet/MesProPalletMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/pallet/MesProPalletMapper.java
index b2b6512..8e2b856 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/pallet/MesProPalletMapper.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/pallet/MesProPalletMapper.java
@@ -23,6 +23,9 @@
         return selectOne(MesProPalletDO::getCode, code);
     }
 
+    @Select("SELECT * FROM mes_pro_pallet WHERE id = #{id} AND deleted = 0 FOR UPDATE")
+    MesProPalletDO selectByIdForUpdate(@Param("id") Long id);
+
     default List<MesProPalletDO> selectListByBatchId(Long batchId) {
         return selectList(MesProPalletDO::getBatchId, batchId);
     }
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 41c8c20..02e4dab 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
@@ -818,6 +818,11 @@
     ErrorCode PRO_BAG_CODE_BIND_PALLET_BOUND = new ErrorCode(1_040_752_004, "瀛樺湪宸茬粦瀹氭墭鐩樼殑琚嬬爜锛岃鍏堣В缁�");
     ErrorCode PRO_BAG_CODE_UUID_NOT_EXISTS = new ErrorCode(1_040_752_005, "琚嬬爜鐢熸垚缁勪笉瀛樺湪");
     ErrorCode PRO_BAG_CODE_BIND_PALLET_OVER_CAPACITY = new ErrorCode(1_040_752_006, "缁戝畾鍚庤鐮佹暟閲忚秴杩囨墭鐩樺彲瑁呰鏁�");
+    ErrorCode PRO_BAG_CODE_REPLACE_BATCH_STATUS_INVALID = new ErrorCode(1_040_752_007, "鐢熶骇鎵规鐘舵�佷笉鍏佽鐮存崯琚嬬爜鏇挎崲");
+    ErrorCode PRO_BAG_CODE_REPLACE_BATCH_MISMATCH = new ErrorCode(1_040_752_008, "寰呮浛鎹㈣鐮佷笉灞炰簬鎸囧畾鐢熶骇鎵规");
+    ErrorCode PRO_BAG_CODE_REPLACE_ALREADY_REPLACED = new ErrorCode(1_040_752_009, "琚嬬爜宸插畬鎴愭浛鎹紝涓嶅厑璁搁噸澶嶆浛鎹�");
+    ErrorCode PRO_BAG_CODE_REPLACE_STATUS_INVALID = new ErrorCode(1_040_752_010, "琚嬬爜鐘舵�佷笉鍏佽鐮存崯鏇挎崲");
+    ErrorCode PRO_BAG_CODE_REPLACE_PALLET_INVALID = new ErrorCode(1_040_752_011, "琚嬬爜鍘熸墭鐩樹笉瀛樺湪鎴栧凡浣滃簾锛屼笉鍏佽缁ф壙");
 
     // ========== MES 鐢熶骇绠$悊-鎵樼洏鐮侊紙1-040-753-000锛� ==========
     ErrorCode PRO_PALLET_NOT_EXISTS = new ErrorCode(1_040_753_000, "鎵樼洏鐮佷笉瀛樺湪");
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/bagcode/MesProBagCodeService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/bagcode/MesProBagCodeService.java
index 5608218..c75dadf 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/bagcode/MesProBagCodeService.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/bagcode/MesProBagCodeService.java
@@ -4,6 +4,8 @@
 import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeBindReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeGenerateReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodePageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeReplaceReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeReplaceRespVO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeDO;
 import jakarta.validation.Valid;
 
@@ -26,6 +28,11 @@
      * @return 鐢熸垚缁� UUID锛堟爣璁板悓涓�娆$敓鎴愮殑鏁版嵁鍖咃級
      */
     String generateBagCodes(@Valid MesProBagCodeGenerateReqVO generateReqVO);
+
+    /**
+     * 浣滃簾鐮存崯琚嬬爜骞跺湪鍘熸壒娆$敓鎴愮瓑閲忔浛鎹㈣鐮�
+     */
+    MesProBagCodeReplaceRespVO replaceBagCodes(@Valid MesProBagCodeReplaceReqVO replaceReqVO);
 
     /**
      * 鍒犻櫎鐢熸垚缁勶紙浠�"宸茬敓鎴�"鐘舵�佷笖鏈粦瀹氭墭鐩樼殑琚嬬爜鍏佽鍒犻櫎锛�
@@ -80,6 +87,13 @@
     MesProBagCodeDO getBagCodeByCode(String code);
 
     /**
+     * 鏌ヨ琚嬬爜鏇挎崲璁板綍
+     */
+    cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeReplaceDO getReplacementByOldBagCodeId(Long oldBagCodeId);
+
+    cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeReplaceDO getReplacementByNewBagCodeId(Long newBagCodeId);
+
+    /**
      * 鑾峰緱琚嬬爜鍒嗛〉
      *
      * @param pageReqVO 鍒嗛〉鏌ヨ
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/bagcode/MesProBagCodeServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/bagcode/MesProBagCodeServiceImpl.java
index 3ac2f9d..d346513 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/bagcode/MesProBagCodeServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/bagcode/MesProBagCodeServiceImpl.java
@@ -7,13 +7,20 @@
 import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeBindReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeGenerateReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodePageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeReplaceItemRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeReplaceReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeReplaceRespVO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeReplaceDO;
 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.mysql.pro.bagcode.MesProBagCodeMapper;
+import cn.iocoder.yudao.module.mes.dal.mysql.pro.bagcode.MesProBagCodeReplaceMapper;
 import cn.iocoder.yudao.module.mes.dal.mysql.pro.batch.MesProBatchMapper;
+import cn.iocoder.yudao.module.mes.dal.mysql.pro.pallet.MesProPalletMapper;
 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.MesProPalletStatusEnum;
 import cn.iocoder.yudao.module.mes.service.pro.pallet.MesProPalletService;
 import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
@@ -24,7 +31,9 @@
 import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
@@ -43,6 +52,10 @@
     private MesProBagCodeMapper bagCodeMapper;
     @Resource
     private MesProBatchMapper batchMapper;
+    @Resource
+    private MesProBagCodeReplaceMapper replaceMapper;
+    @Resource
+    private MesProPalletMapper palletMapper;
 
     @Resource
     private MesProPalletService palletService;
@@ -51,7 +64,7 @@
     @Transactional(rollbackFor = Exception.class)
     public String generateBagCodes(MesProBagCodeGenerateReqVO generateReqVO) {
         // 1. 鏍¢獙鎵规瀛樺湪涓斿浜庣敓浜т腑鐘舵��
-        MesProBatchDO batch = batchMapper.selectById(generateReqVO.getBatchId());
+        MesProBatchDO batch = batchMapper.selectByIdForUpdate(generateReqVO.getBatchId());
         if (batch == null) {
             throw exception(PRO_BATCH_NOT_EXISTS);
         }
@@ -86,6 +99,118 @@
                 .setBagQuantity((batch.getBagQuantity() == null ? 0 : batch.getBagQuantity())
                         + generateReqVO.getCount()));
         return uuid;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public MesProBagCodeReplaceRespVO replaceBagCodes(MesProBagCodeReplaceReqVO replaceReqVO) {
+        MesProBatchDO batch = batchMapper.selectByIdForUpdate(replaceReqVO.getBatchId());
+        if (batch == null) {
+            throw exception(PRO_BATCH_NOT_EXISTS);
+        }
+        if (!MesProBatchStatusEnum.PRODUCING.getStatus().equals(batch.getStatus())
+                && !MesProBatchStatusEnum.FINISHED.getStatus().equals(batch.getStatus())) {
+            throw exception(PRO_BAG_CODE_REPLACE_BATCH_STATUS_INVALID);
+        }
+
+        List<Long> oldIds = replaceReqVO.getOldIds().stream()
+                .distinct().sorted().toList();
+        List<MesProBagCodeDO> oldBagCodes = bagCodeMapper.selectListByIdsForUpdate(oldIds);
+        if (oldBagCodes.size() != oldIds.size()) {
+            throw exception(PRO_BAG_CODE_NOT_EXISTS);
+        }
+        for (MesProBagCodeDO oldBagCode : oldBagCodes) {
+            if (!batch.getId().equals(oldBagCode.getBatchId())) {
+                throw exception(PRO_BAG_CODE_REPLACE_BATCH_MISMATCH);
+            }
+            if (!MesProBagCodeStatusEnum.GENERATED.getStatus().equals(oldBagCode.getStatus())
+                    && !MesProBagCodeStatusEnum.PRINTED.getStatus().equals(oldBagCode.getStatus())) {
+                throw exception(PRO_BAG_CODE_REPLACE_STATUS_INVALID);
+            }
+            if (replaceMapper.selectByOldBagCodeId(oldBagCode.getId()) != null) {
+                throw exception(PRO_BAG_CODE_REPLACE_ALREADY_REPLACED);
+            }
+        }
+
+        Map<Long, MesProPalletDO> palletMap = new LinkedHashMap<>();
+        oldBagCodes.stream().map(MesProBagCodeDO::getPalletId).filter(id -> id != null)
+                .distinct().sorted().forEach(palletId -> {
+                    MesProPalletDO pallet = palletMapper.selectByIdForUpdate(palletId);
+                    if (pallet == null || !MesProPalletStatusEnum.IN_USE.getStatus().equals(pallet.getStatus())) {
+                        throw exception(PRO_BAG_CODE_REPLACE_PALLET_INVALID);
+                    }
+                    palletMap.put(palletId, pallet);
+                });
+
+        String replaceUuid = IdUtil.fastSimpleUUID();
+        LocalDateTime replaceTime = LocalDateTime.now();
+        int startNo = bagCodeMapper.selectMaxBagNo(batch.getId()) + 1;
+        List<MesProBagCodeDO> newBagCodes = new ArrayList<>(oldBagCodes.size());
+        List<MesProBagCodeReplaceDO> replacements = new ArrayList<>(oldBagCodes.size());
+        List<MesProBagCodeReplaceItemRespVO> items = new ArrayList<>(oldBagCodes.size());
+        for (int i = 0; i < oldBagCodes.size(); i++) {
+            MesProBagCodeDO oldBagCode = oldBagCodes.get(i);
+            if (bagCodeMapper.cancelById(oldBagCode.getId(), oldBagCode.getStatus(),
+                    MesProBagCodeStatusEnum.CANCELED.getStatus(), replaceTime, replaceReqVO.getReason()) != 1) {
+                throw exception(PRO_BAG_CODE_REPLACE_STATUS_INVALID);
+            }
+            int newBagNo = startNo + i;
+            MesProPalletDO pallet = oldBagCode.getPalletId() == null ? null : palletMap.get(oldBagCode.getPalletId());
+            MesProBagCodeDO newBagCode = new MesProBagCodeDO()
+                    .setUuid(replaceUuid)
+                    .setCode(batch.getCode() + "-" + StrUtil.padPre(String.valueOf(newBagNo), 4, '0'))
+                    .setBagNo(newBagNo)
+                    .setBatchId(batch.getId())
+                    .setBatchCode(batch.getCode())
+                    .setPalletId(pallet == null ? null : pallet.getId())
+                    .setPalletCode(pallet == null ? null : pallet.getCode())
+                    .setStatus(MesProBagCodeStatusEnum.GENERATED.getStatus())
+                    .setRemark(replaceReqVO.getReason());
+            newBagCodes.add(newBagCode);
+
+            replacements.add(new MesProBagCodeReplaceDO()
+                    .setReplaceUuid(replaceUuid)
+                    .setBatchId(batch.getId())
+                    .setBatchCode(batch.getCode())
+                    .setOldBagCodeId(oldBagCode.getId())
+                    .setOldCode(oldBagCode.getCode())
+                    .setOldBagNo(oldBagCode.getBagNo())
+                    .setOldStatus(oldBagCode.getStatus())
+                    .setOldPalletId(oldBagCode.getPalletId())
+                    .setOldPalletCode(oldBagCode.getPalletCode())
+                    .setNewCode(newBagCode.getCode())
+                    .setNewBagNo(newBagNo)
+                    .setReason(replaceReqVO.getReason())
+                    .setReplaceTime(replaceTime));
+            items.add(new MesProBagCodeReplaceItemRespVO()
+                    .setOldBagCodeId(oldBagCode.getId())
+                    .setOldCode(oldBagCode.getCode())
+                    .setOldBagNo(oldBagCode.getBagNo())
+                    .setOldStatus(oldBagCode.getStatus())
+                    .setOldPalletId(oldBagCode.getPalletId())
+                    .setOldPalletCode(oldBagCode.getPalletCode())
+                    .setNewCode(newBagCode.getCode())
+                    .setNewBagNo(newBagNo)
+                    .setNewPalletId(newBagCode.getPalletId())
+                    .setNewPalletCode(newBagCode.getPalletCode())
+                    .setReason(replaceReqVO.getReason())
+                    .setReplaceTime(replaceTime));
+        }
+        bagCodeMapper.insertBatch(newBagCodes);
+        for (int i = 0; i < replacements.size(); i++) {
+            replacements.get(i).setNewBagCodeId(newBagCodes.get(i).getId());
+            items.get(i).setNewBagCodeId(newBagCodes.get(i).getId());
+        }
+        replaceMapper.insertBatch(replacements);
+        batchMapper.updateById(new MesProBatchDO().setId(batch.getId())
+                .setBagQuantity(bagCodeMapper.selectEffectiveCountByBatchId(batch.getId())));
+
+        return new MesProBagCodeReplaceRespVO()
+                .setReplaceUuid(replaceUuid)
+                .setBatchId(batch.getId())
+                .setBatchCode(batch.getCode())
+                .setCount(newBagCodes.size())
+                .setItems(items);
     }
 
     @Override
@@ -180,6 +305,16 @@
     }
 
     @Override
+    public MesProBagCodeReplaceDO getReplacementByOldBagCodeId(Long oldBagCodeId) {
+        return replaceMapper.selectByOldBagCodeId(oldBagCodeId);
+    }
+
+    @Override
+    public MesProBagCodeReplaceDO getReplacementByNewBagCodeId(Long newBagCodeId) {
+        return replaceMapper.selectByNewBagCodeId(newBagCodeId);
+    }
+
+    @Override
     public PageResult<MesProBagCodeDO> getBagCodePage(MesProBagCodePageReqVO pageReqVO) {
         return bagCodeMapper.selectPage(pageReqVO);
     }

--
Gitblit v1.9.3