From 3be684af887afe4c0e45f281cc53adfef6f12b78 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期一, 20 七月 2026 09:48:44 +0800
Subject: [PATCH] feat(erp): 添加销售订单生产状态跟踪功能

---
 sql/mes_item_id_migration.sql                                                                                     |  303 +++++++++++++++++++++++++
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/mps/MesProMpsApiImpl.java                          |   11 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemApiImpl.java                         |   24 +
 docs/sale_order_production_status.md                                                                              |  118 +++++++++
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemServiceImpl.java              |   10 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsServiceImpl.java              |   56 ++++
 yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/mps/MesProMpsApi.java                          |   20 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsService.java                  |   20 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java    |   27 ++
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderDO.java                |   12 +
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/listener/ProductionFinishedListener.java  |   40 +++
 yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/event/ProductionFinishedEvent.java             |   31 ++
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderRespVO.java |    8 
 13 files changed, 676 insertions(+), 4 deletions(-)

diff --git a/docs/sale_order_production_status.md b/docs/sale_order_production_status.md
new file mode 100644
index 0000000..a6c9d0b
--- /dev/null
+++ b/docs/sale_order_production_status.md
@@ -0,0 +1,118 @@
+# 閿�鍞鍗曠敓浜х姸鎬� - 鍓嶇鑱旇皟鏂规
+
+## 娑夊強椤甸潰
+
+- 閿�鍞鍗曞垪琛ㄩ〉
+- 閿�鍞鍗曡鎯呴〉
+
+## 涓氬姟娴佺▼
+
+```
+閿�鍞鍗曞鏍搁�氳繃 鈫� 鍒涘缓 MPS 鈫� 涓嬪彂 MPS 鐢熸垚鐢熶骇宸ュ崟 鈫� 鐢熶骇鎶ュ伐 鈫� 璐ㄦ(濡傞渶瑕�) 鈫� 鐢熶骇瀹屾垚 鈫� 鏇存柊閿�鍞鍗曠敓浜х姸鎬�
+```
+
+**鐢熶骇瀹屾垚鍒ゆ柇鏉′欢**锛�
+1. 宸茬敓浜ф暟閲� >= 璁″垝鏁伴噺
+2. 鎵�鏈夋姤宸ュ崟鐘舵�佷负 FINISHED锛堢‘淇濊川妫�瀹屾垚锛�
+
+## API
+
+### 閿�鍞鍗曟煡璇㈡帴鍙o紙宸叉湁锛屽搷搴旀柊澧炲瓧娈碉級
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /erp/sale-order/page | 閿�鍞鍗曞垎椤垫煡璇� |
+| GET | /erp/sale-order/get | 閿�鍞鍗曡鎯呮煡璇� |
+
+**鍝嶅簲瀛楁鏂板锛�**
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| productionStatus | Integer | 鐢熶骇鐘舵�侊細0-鏈畬鎴愶紝1-宸插畬鎴� |
+| productionFinishTime | DateTime | 鐢熶骇瀹屾垚鏃堕棿 |
+
+**鍝嶅簲绀轰緥锛�**
+
+```json
+{
+  "code": 0,
+  "data": {
+    "id": 1024,
+    "no": "XS20260718001",
+    "status": 2,
+    "customerId": 1001,
+    "customerName": "娴嬭瘯瀹㈡埛",
+    "productionStatus": 1,
+    "productionFinishTime": "2026-07-18 16:30:00",
+    "needProduction": 1,
+    "canCreateSalesNotice": true,
+    "items": [...]
+  }
+}
+```
+
+## 瀛楁灞曠ず瑙勫垯
+
+### 鍒楄〃椤�
+
+| 瀛楁 | 灞曠ず浣嶇疆 | 璇存槑 |
+|------|----------|------|
+| productionStatus | 鍒楄〃 | 鏍囩褰㈠紡锛�0=鏈畬鎴�(鐏拌壊)锛�1=宸插畬鎴�(缁胯壊) |
+| productionFinishTime | 鍒楄〃锛堝彲閫夛級 | 浠呭綋 productionStatus=1 鏃跺睍绀� |
+
+### 璇︽儏椤�
+
+| 瀛楁 | 灞曠ず浣嶇疆 | 璇存槑 |
+|------|----------|------|
+| productionStatus | 鍩烘湰淇℃伅鍖哄煙 | 鏍囩褰㈠紡灞曠ず |
+| productionFinishTime | 鍩烘湰淇℃伅鍖哄煙 | 浠呭綋 productionStatus=1 鏃跺睍绀� |
+
+## 涓氬姟瑙勫垯璇存槑
+
+| 鍦烘櫙 | 瑙勫垯 |
+|------|------|
+| 鐢熶骇鐘舵�佸垵濮嬪�� | 閿�鍞鍗曞鏍搁�氳繃鏃讹紝productionStatus 榛樿涓� 0锛堟湭瀹屾垚锛� |
+| 鐢熶骇鐘舵�佹洿鏂版椂鏈� | 褰撴墍鏈夊叧鑱旂殑鐢熶骇璁㈠崟瀹屾垚鏃讹紝绯荤粺鑷姩鏇存柊涓� 1锛堝凡瀹屾垚锛� |
+| 鐢熶骇瀹屾垚鍒ゆ柇 | 宸茬敓浜ф暟閲� >= 璁″垝鏁伴噺 涓� 鎵�鏈夋姤宸ュ崟宸插畬鎴愶紙鍚川妫�瀹屾垚锛� |
+| 鍙戣揣閫氱煡鍗曠敓鎴� | 褰� productionStatus=1 鏃讹紝canCreateSalesNotice 涓� true |
+
+## 灞曠ず閫昏緫璇存槑
+
+| 鏉′欢 | 灞曠ず鍐呭 |
+|------|----------|
+| needProduction !== 1锛堟棤闇�鐢熶骇锛� | 鏄剧ず"鏃犻渶鐢熶骇"鎴�"-" |
+| productionStatus === 0 | 鏄剧ず鐏拌壊鏍囩"鏈畬鎴�" |
+| productionStatus === 1 | 鏄剧ず缁胯壊鏍囩"宸插畬鎴�"锛屽苟灞曠ず瀹屾垚鏃堕棿 |
+
+## 鍒楅『搴忓缓璁�
+
+鐢熶骇鐘舵�佸垪寤鸿鏀惧湪"鍑哄簱鐘舵��"鍒椾箣鍚庛��
+
+## 绛涢�夋潯浠讹紙鍙�夛級
+
+濡傞渶鎸夌敓浜х姸鎬佺瓫閫夛紝鍙柊澧炰笅鎷夌瓫閫夛細
+- 鍏ㄩ儴
+- 鏈畬鎴�
+- 宸插畬鎴�
+
+娉ㄦ剰锛氱瓫閫夋椂浠呮煡璇� `needProduction=1` 鐨勮鍗曘��
+
+## 娉ㄦ剰浜嬮」
+
+1. **鐘舵�佽仈鍔�**锛歚productionStatus` 涓� `canCreateSalesNotice` 鍏宠仈锛岀敓浜ф湭瀹屾垚鏃舵棤娉曠敓鎴愬彂璐ч�氱煡鍗�
+
+2. **鏃犻渶鐢熶骇鐨勮鍗�**锛歚needProduction !== 1` 鐨勮鍗曪紝`productionStatus` 瀛楁涓� null锛屽墠绔簲鏄剧ず"鏃犻渶鐢熶骇"鎴栦笉鏄剧ず
+
+3. **瀹炴椂鎬�**锛氱敓浜х姸鎬佺敱鍚庣鑷姩鏇存柊锛屽墠绔埛鏂伴〉闈㈠嵆鍙湅鍒版渶鏂扮姸鎬�
+
+## 瀹屾暣娴佺▼绀轰緥
+
+1. 鐢ㄦ埛鍒涘缓閿�鍞鍗曪紙鏄庣粏鍕鹃��"闇�瑕佺敓浜�"锛�
+2. 閿�鍞鍗曞鏍搁�氳繃 鈫� 绯荤粺鑷姩鍒涘缓 MPS
+3. MPS 涓嬪彂 鈫� 鐢熸垚鐢熶骇宸ュ崟
+4. 鐢熶骇鎶ュ伐 鈫� 鎶ュ伐瀹℃壒
+   - 鍏抽敭宸ュ簭+闇�妫�楠岋細鎶ュ伐鐘舵�佲啋寰呮楠屸啋璐ㄦ瀹屾垚鈫掑凡瀹屾垚
+   - 鍏朵粬宸ュ簭锛氭姤宸ュ鎵瑰悗鐩存帴鈫掑凡瀹屾垚
+5. 鎵�鏈夋姤宸ュ畬鎴� 鈫� 绯荤粺鑷姩鏇存柊閿�鍞鍗� productionStatus=1
+6. 鐢ㄦ埛鍒锋柊閿�鍞鍗曢〉闈紝鐪嬪埌"鐢熶骇瀹屾垚"鐘舵��
+7. 鐢ㄦ埛鍙敓鎴愬彂璐ч�氱煡鍗�
\ No newline at end of file
diff --git a/sql/mes_item_id_migration.sql b/sql/mes_item_id_migration.sql
new file mode 100644
index 0000000..4b257a1
--- /dev/null
+++ b/sql/mes_item_id_migration.sql
@@ -0,0 +1,303 @@
+-- ============================================================
+-- MES 鐗╂枡 ID 杩佺Щ鑴氭湰
+-- 鐩殑锛氬皢 mes_md_item.id 鏇存柊涓轰笌 mdm_item.id 涓�鑷�
+-- 鎵ц鍓嶈澶囦唤鏁版嵁搴擄紒
+-- ============================================================
+
+-- 1. 鍒涘缓涓存椂鏄犲皠琛�
+DROP TEMPORARY TABLE IF EXISTS tmp_item_id_mapping;
+CREATE TEMPORARY TABLE tmp_item_id_mapping (
+    old_id BIGINT NOT NULL,
+    new_id BIGINT NOT NULL,
+    code VARCHAR(64),
+    PRIMARY KEY (old_id)
+);
+
+-- 2. 鎻掑叆闇�瑕佽縼绉荤殑鐗╂枡鏄犲皠鏁版嵁
+INSERT INTO tmp_item_id_mapping (old_id, new_id, code)
+SELECT id, mdm_item_id, code
+FROM mes_md_item
+WHERE mdm_item_id IS NOT NULL AND id != mdm_item_id;
+
+-- 鏌ョ湅闇�瑕佽縼绉荤殑鏁版嵁
+SELECT * FROM tmp_item_id_mapping;
+
+-- 3. 绂佺敤澶栭敭妫�鏌ワ紙閬垮厤澶栭敭绾︽潫闃绘鏇存柊锛�
+SET FOREIGN_KEY_CHECKS = 0;
+
+-- 4. 鏇存柊鎵�鏈夊紩鐢� mes_md_item.id 鐨勮〃
+-- 娉ㄦ剰锛氫互涓嬭鍙ヤ細鏇存柊鎵�鏈� item_id 瀛楁
+
+-- 4.1 鍩虹鏁版嵁琛�
+UPDATE mes_md_item_batch_config t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_md_product_bom t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_md_product_sip t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_md_product_sop t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+-- 4.2 鐢熶骇鐩稿叧琛�
+UPDATE mes_pro_card t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_pro_feedback t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_pro_route_product t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_pro_route_product_bom t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_pro_task t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_pro_task_issue t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_pro_work_order_bom t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+-- 4.3 璐ㄩ噺鐩稿叧琛�
+UPDATE mes_qc_indicator_result t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_qc_ipqc t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_qc_iqc t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_qc_oqc t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_qc_rqc t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_qc_template_item t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+-- 4.4 浠撳簱绠$悊琛� - 鍏ュ簱
+UPDATE mes_wm_arrival_notice_line t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_wm_item_receipt_detail t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_wm_item_receipt_line t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_wm_misc_receipt_detail t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_wm_misc_receipt_line t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_wm_outsource_receipt_detail t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_wm_outsource_receipt_line t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_wm_product_receipt_detail t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_wm_product_receipt_line t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+-- 4.5 浠撳簱绠$悊琛� - 鍑哄簱
+UPDATE mes_wm_item_consume_detail t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_wm_item_consume_line t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_wm_misc_issue_detail t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_wm_misc_issue_line t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_wm_outsource_issue_detail t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_wm_outsource_issue_line t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_wm_product_issue_detail t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_wm_product_issue_line t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_wm_return_issue_detail t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_wm_return_issue_line t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+-- 4.6 浠撳簱绠$悊琛� - 閿�鍞�/閫�璐�
+UPDATE mes_wm_product_sales_detail t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_wm_product_sales_line t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_wm_return_sales_detail t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_wm_return_sales_line t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_wm_return_vendor_detail t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_wm_return_vendor_line t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_wm_sales_notice_line t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+-- 4.7 浠撳簱绠$悊琛� - 鍏朵粬
+UPDATE mes_wm_batch t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_wm_material_stock t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_wm_package_line t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_wm_sn t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_wm_stock_reserve t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_wm_stock_taking_plan_param t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_wm_stock_taking_task_line t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_wm_stock_taking_task_result t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_wm_transaction t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_wm_transfer_detail t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_wm_transfer_line t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+-- 4.8 鐢熶骇浜у嚭琛�
+UPDATE mes_wm_product_produce_detail t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+UPDATE mes_wm_product_produce_line t
+JOIN tmp_item_id_mapping m ON t.item_id = m.old_id
+SET t.item_id = m.new_id;
+
+-- 5. 鏇存柊涓昏〃 mes_md_item 鐨� id
+-- 鐢变簬 id 鏄富閿紝闇�瑕佸厛鍒犻櫎鏃ц褰曞啀鎻掑叆鏂拌褰�
+-- 鍒涘缓涓存椂琛ㄤ繚瀛樺畬鏁存暟鎹�
+DROP TEMPORARY TABLE IF EXISTS tmp_mes_md_item;
+CREATE TEMPORARY TABLE tmp_mes_md_item AS
+SELECT * FROM mes_md_item;
+
+-- 鍒犻櫎闇�瑕佽縼绉荤殑璁板綍
+DELETE FROM mes_md_item WHERE id IN (SELECT old_id FROM tmp_item_id_mapping);
+
+-- 浣跨敤鏂� ID 閲嶆柊鎻掑叆
+INSERT INTO mes_md_item (
+    id, mdm_item_id, code, name, specification, unit_measure_id, item_type_id,
+    status, safe_stock_flag, min_stock, max_stock, high_value, batch_flag, remark,
+    creator, create_time, updater, update_time, deleted
+)
+SELECT
+    m.new_id AS id,  -- 浣跨敤 MDM 鐗╂枡 ID
+    t.mdm_item_id,
+    t.code, t.name, t.specification, t.unit_measure_id, t.item_type_id,
+    t.status, t.safe_stock_flag, t.min_stock, t.max_stock, t.high_value, t.batch_flag, t.remark,
+    t.creator, t.create_time, t.updater, t.update_time, t.deleted
+FROM tmp_mes_md_item t
+JOIN tmp_item_id_mapping m ON t.id = m.old_id;
+
+-- 6. 閲嶆柊鍚敤澶栭敭妫�鏌�
+SET FOREIGN_KEY_CHECKS = 1;
+
+-- 7. 娓呯悊涓存椂琛�
+DROP TEMPORARY TABLE IF EXISTS tmp_item_id_mapping;
+DROP TEMPORARY TABLE IF EXISTS tmp_mes_md_item;
+
+-- 8. 楠岃瘉杩佺Щ缁撴灉
+SELECT id, mdm_item_id, code, name
+FROM mes_md_item
+WHERE mdm_item_id IS NOT NULL;
+
+-- 妫�鏌ユ槸鍚﹁繕鏈� id != mdm_item_id 鐨勮褰�
+SELECT COUNT(*) AS need_migration_count
+FROM mes_md_item
+WHERE mdm_item_id IS NOT NULL AND id != mdm_item_id;
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderRespVO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderRespVO.java
index 63d2389..f545a7b 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderRespVO.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderRespVO.java
@@ -115,6 +115,14 @@
     @Schema(description = "鏄惁鏈夐渶瑕佺敓浜х殑鏄庣粏锛�0=鍚� 1=鏄級", example = "1")
     private Integer needProduction;
 
+    // ========== 鐢熶骇鍏宠仈 ==========
+
+    @Schema(description = "鐢熶骇鐘舵�侊細0-鏈畬鎴愶紝1-宸插畬鎴�", example = "1")
+    private Integer productionStatus;
+
+    @Schema(description = "鐢熶骇瀹屾垚鏃堕棿")
+    private LocalDateTime productionFinishTime;
+
     @Data
     public static class Item {
 
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderDO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderDO.java
index 1b05462..be719e0 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderDO.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderDO.java
@@ -128,4 +128,16 @@
      */
     private Integer outStatus;
 
+    // ========== 鐢熶骇鍏宠仈 ==========
+
+    /**
+     * 鐢熶骇鐘舵�侊細0-鏈畬鎴愶紝1-宸插畬鎴�
+     */
+    private Integer productionStatus;
+
+    /**
+     * 鐢熶骇瀹屾垚鏃堕棿
+     */
+    private LocalDateTime productionFinishTime;
+
 }
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/listener/ProductionFinishedListener.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/listener/ProductionFinishedListener.java
new file mode 100644
index 0000000..613b038
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/listener/ProductionFinishedListener.java
@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.module.erp.service.sale.listener;
+
+import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderDO;
+import cn.iocoder.yudao.module.erp.dal.mysql.sale.ErpSaleOrderMapper;
+import cn.iocoder.yudao.module.mes.api.event.ProductionFinishedEvent;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.event.EventListener;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Component;
+
+/**
+ * 鐢熶骇瀹屾垚浜嬩欢鐩戝惉鍣�
+ *
+ * 鐩戝惉 MES 妯″潡鍙戝竷鐨� ProductionFinishedEvent锛屾洿鏂伴攢鍞鍗曠殑鐢熶骇鐘舵��
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Component
+@Slf4j
+public class ProductionFinishedListener {
+
+    @Resource
+    private ErpSaleOrderMapper saleOrderMapper;
+
+    @EventListener
+    @Async
+    public void onProductionFinished(ProductionFinishedEvent event) {
+        log.info("[onProductionFinished] 鏀跺埌鐢熶骇瀹屾垚浜嬩欢锛岄攢鍞鍗旾D: {}", event.getSaleOrderId());
+
+        // 鏇存柊閿�鍞鍗曠殑鐢熶骇鐘舵��
+        saleOrderMapper.updateById(new ErpSaleOrderDO()
+                .setId(event.getSaleOrderId())
+                .setProductionStatus(1)
+                .setProductionFinishTime(event.getFinishTime()));
+
+        log.info("[onProductionFinished] 閿�鍞鍗� {} 鐢熶骇鐘舵�佸凡鏇存柊涓哄畬鎴�", event.getSaleOrderId());
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/event/ProductionFinishedEvent.java b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/event/ProductionFinishedEvent.java
new file mode 100644
index 0000000..a6747d5
--- /dev/null
+++ b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/event/ProductionFinishedEvent.java
@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.mes.api.event;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.time.LocalDateTime;
+
+/**
+ * 鐢熶骇瀹屾垚浜嬩欢
+ *
+ * 褰撻攢鍞鍗曞叧鑱旂殑鎵�鏈夌敓浜ц鍗曞畬鎴愭椂鍙戝竷姝や簨浠�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class ProductionFinishedEvent {
+
+    /**
+     * 閿�鍞鍗旾D
+     */
+    private Long saleOrderId;
+
+    /**
+     * 瀹屾垚鏃堕棿
+     */
+    private LocalDateTime finishTime;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/mps/MesProMpsApi.java b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/mps/MesProMpsApi.java
index 55de681..b98ad92 100644
--- a/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/mps/MesProMpsApi.java
+++ b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/mps/MesProMpsApi.java
@@ -56,4 +56,24 @@
      */
     Boolean checkCanCreateSalesNotice(List<Long> saleOrderItemIds);
 
+    /**
+     * 妫�鏌ラ攢鍞鍗曠殑鎵�鏈� MPS 鏄惁宸插畬鎴愮敓浜�
+     *
+     * 瀹屾垚鏉′欢锛�
+     * 1. 宸茬敓浜ф暟閲� >= 璁″垝鏁伴噺
+     * 2. 鎵�鏈夋姤宸ュ崟鐘舵�佷负 FINISHED锛堢‘淇濊川妫�瀹屾垚锛�
+     *
+     * @param saleOrderId 閿�鍞鍗旾D
+     * @return true=鍏ㄩ儴瀹屾垚锛宖alse=鏈叏閮ㄥ畬鎴�
+     */
+    Boolean checkSaleOrderProductionFinished(Long saleOrderId);
+
+    /**
+     * 鏍规嵁宸ュ崟ID鑾峰彇 MPS
+     *
+     * @param workOrderId 宸ュ崟ID
+     * @return MPS 淇℃伅
+     */
+    MesProMpsRespDTO getMpsByWorkOrderId(Long workOrderId);
+
 }
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemApiImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemApiImpl.java
index 8ff44ef..fe63450 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemApiImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemApiImpl.java
@@ -43,8 +43,12 @@
 
     @Override
     public Long createItem(MesMdItemSyncReqDTO syncReqDTO) {
-        log.info("[createItem] 鍚屾鍒涘缓 MES 鐗╂枡浜у搧锛宑ode={}", syncReqDTO.getCode());
+        log.info("[createItem] 鍚屾鍒涘缓 MES 鐗╂枡浜у搧锛宑ode={}, mdmItemId={}", syncReqDTO.getCode(), syncReqDTO.getMdmItemId());
         MesMdItemSaveReqVO saveReqVO = buildSaveReqVO(syncReqDTO);
+        // 浣跨敤 MDM 鐗╂枡 ID 浣滀负 MES 鐗╂枡 ID
+        if (syncReqDTO.getMdmItemId() != null) {
+            saveReqVO.setId(syncReqDTO.getMdmItemId());
+        }
         Long itemId = itemService.createItem(saveReqVO);
         // 鍚屾鎵规閰嶇疆
 //        saveBatchConfig(itemId, syncReqDTO);
@@ -53,10 +57,22 @@
 
     @Override
     public void updateItem(MesMdItemSyncReqDTO syncReqDTO) {
-        log.info("[updateItem] 鍚屾鏇存柊 MES 鐗╂枡浜у搧锛宑ode={}", syncReqDTO.getCode());
+        log.info("[updateItem] 鍚屾鏇存柊 MES 鐗╂枡浜у搧锛宑ode={}, mdmItemId={}", syncReqDTO.getCode(), syncReqDTO.getMdmItemId());
         MesMdItemSaveReqVO saveReqVO = buildSaveReqVO(syncReqDTO);
-        // 鎸� code 鍖归厤锛氬瓨鍦ㄥ垯鏇存柊锛屼笉瀛樺湪鍒欏垱寤猴紙upsert锛�
-        MesMdItemDO existItem = itemService.getItemByCode(syncReqDTO.getCode());
+        // 浣跨敤 MDM 鐗╂枡 ID 浣滀负 MES 鐗╂枡 ID
+        if (syncReqDTO.getMdmItemId() != null) {
+            saveReqVO.setId(syncReqDTO.getMdmItemId());
+        }
+        // 鎸� ID 鎴� code 鍖归厤锛氬瓨鍦ㄥ垯鏇存柊锛屼笉瀛樺湪鍒欏垱寤猴紙upsert锛�
+        MesMdItemDO existItem = null;
+        // 浼樺厛鎸� ID 鏌ユ壘
+        if (syncReqDTO.getMdmItemId() != null) {
+            existItem = itemService.getItem(syncReqDTO.getMdmItemId());
+        }
+        // 濡傛灉鎸� ID 鎵句笉鍒帮紝鍐嶆寜 code 鏌ユ壘
+        if (existItem == null) {
+            existItem = itemService.getItemByCode(syncReqDTO.getCode());
+        }
         Long itemId;
         if (existItem == null) {
             itemId = itemService.createItem(saveReqVO);
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/mps/MesProMpsApiImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/mps/MesProMpsApiImpl.java
index 94c009c..30aadce 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/mps/MesProMpsApiImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/mps/MesProMpsApiImpl.java
@@ -95,4 +95,15 @@
         return true;
     }
 
+    @Override
+    public Boolean checkSaleOrderProductionFinished(Long saleOrderId) {
+        return mpsService.checkSaleOrderProductionFinished(saleOrderId);
+    }
+
+    @Override
+    public MesProMpsRespDTO getMpsByWorkOrderId(Long workOrderId) {
+        MesProMpsDO mps = mpsService.getMpsByWorkOrderId(workOrderId);
+        return BeanUtils.toBean(mps, MesProMpsRespDTO.class);
+    }
+
 }
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemServiceImpl.java
index 2300bb2..c8f85a6 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemServiceImpl.java
@@ -77,6 +77,16 @@
         // 鏍¢獙鏁版嵁
         validateItemSaveData(createReqVO);
 
+        // 濡傛灉鎸囧畾浜� ID锛屾鏌ヨ ID 鏄惁宸插瓨鍦紙鐢ㄤ簬 MDM 鍚屾鏃朵繚鎸� ID 涓�鑷达級
+        if (createReqVO.getId() != null) {
+            MesMdItemDO existItem = itemMapper.selectById(createReqVO.getId());
+            if (existItem != null) {
+                // ID 宸插瓨鍦紝杞负鏇存柊
+                updateItem(createReqVO);
+                return createReqVO.getId();
+            }
+        }
+
         // 鎻掑叆
         MesMdItemDO item = BeanUtils.toBean(createReqVO, MesMdItemDO.class);
         item.setStatus(CommonStatusEnum.DISABLE.getStatus()); // 榛樿绂佺敤銆備俊鎭畬鎴愬悗锛屽啀鍚敤
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java
index d704d07..096b440 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java
@@ -26,7 +26,11 @@
 import cn.iocoder.yudao.module.mes.service.wm.itemconsume.MesWmItemConsumeService;
 import cn.iocoder.yudao.module.mes.service.wm.productproduce.MesWmProductProduceLineService;
 import cn.iocoder.yudao.module.mes.service.wm.productproduce.MesWmProductProduceService;
+import cn.iocoder.yudao.module.mes.api.mps.MesProMpsApi;
+import cn.iocoder.yudao.module.mes.api.mps.dto.MesProMpsRespDTO;
+import cn.iocoder.yudao.module.mes.api.event.ProductionFinishedEvent;
 import jakarta.annotation.Resource;
+import org.springframework.context.ApplicationEventPublisher;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -67,6 +71,10 @@
     private MesWmProductProduceService productProduceService;
     @Resource
     private MesWmProductProduceLineService produceLineService;
+    @Resource
+    private MesProMpsApi mpsApi;
+    @Resource
+    private ApplicationEventPublisher eventPublisher;
 
     @Override
     public Long createFeedback(MesProFeedbackSaveReqVO createReqVO) {
@@ -247,6 +255,25 @@
         // 3. 鏇存柊宸ュ崟鐨勫凡鐢熶骇鏁伴噺
         workOrderService.updateProducedQuantity(feedback.getWorkOrderId(),
                 feedback.getFeedbackQuantity());
+
+        // 4. 妫�鏌ュ苟鍙戝竷鐢熶骇瀹屾垚浜嬩欢
+        checkAndPublishProductionFinished(feedback.getWorkOrderId());
+    }
+
+    /**
+     * 妫�鏌ュ苟鍙戝竷鐢熶骇瀹屾垚浜嬩欢
+     */
+    private void checkAndPublishProductionFinished(Long workOrderId) {
+        // 1. 鑾峰彇宸ュ崟鍏宠仈鐨� MPS
+        MesProMpsRespDTO mps = mpsApi.getMpsByWorkOrderId(workOrderId);
+        if (mps == null || mps.getSaleOrderId() == null) {
+            return; // 闈為攢鍞鍗曟潵婧愮殑宸ュ崟锛岃烦杩�
+        }
+
+        // 2. 妫�鏌ラ攢鍞鍗曟墍鏈夌敓浜ф槸鍚﹀畬鎴�
+        if (mpsApi.checkSaleOrderProductionFinished(mps.getSaleOrderId())) {
+            eventPublisher.publishEvent(new ProductionFinishedEvent(mps.getSaleOrderId(), java.time.LocalDateTime.now()));
+        }
     }
 
     // ==================== 鏍¢獙鏂规硶 ====================
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsService.java
index 8c6af1f..e1fa3e4 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsService.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsService.java
@@ -145,4 +145,24 @@
      */
     void updateProducedQuantity(Long id, BigDecimal incrQuantity);
 
+    /**
+     * 妫�鏌ラ攢鍞鍗曠殑鎵�鏈� MPS 鏄惁宸插畬鎴愮敓浜�
+     *
+     * 瀹屾垚鏉′欢锛�
+     * 1. 宸茬敓浜ф暟閲� >= 璁″垝鏁伴噺
+     * 2. 鎵�鏈夋姤宸ュ崟鐘舵�佷负 FINISHED锛堢‘淇濊川妫�瀹屾垚锛�
+     *
+     * @param saleOrderId 閿�鍞鍗旾D
+     * @return true=鍏ㄩ儴瀹屾垚锛宖alse=鏈叏閮ㄥ畬鎴�
+     */
+    Boolean checkSaleOrderProductionFinished(Long saleOrderId);
+
+    /**
+     * 鏍规嵁宸ュ崟ID鑾峰彇 MPS
+     *
+     * @param workOrderId 宸ュ崟ID
+     * @return MPS
+     */
+    MesProMpsDO getMpsByWorkOrderId(Long workOrderId);
+
 }
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsServiceImpl.java
index 34ca046..2ddacaf 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsServiceImpl.java
@@ -12,7 +12,10 @@
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.mps.MesProMpsMergeItemDO;
 import cn.iocoder.yudao.module.mes.dal.mysql.pro.mps.MesProMpsMapper;
 import cn.iocoder.yudao.module.mes.dal.mysql.pro.mps.MesProMpsMergeItemMapper;
+import cn.iocoder.yudao.module.mes.dal.mysql.pro.feedback.MesProFeedbackMapper;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO;
 import cn.iocoder.yudao.module.mes.enums.pro.MesProMpsStatusEnum;
+import cn.iocoder.yudao.module.mes.enums.pro.MesProFeedbackStatusEnum;
 import cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderSourceTypeEnum;
 import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService;
 import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
@@ -61,6 +64,8 @@
     private MesMdAutoCodeRecordService autoCodeRecordService;
     @Resource
     private CrmCustomerApi customerApi;
+    @Resource
+    private MesProFeedbackMapper feedbackMapper;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -331,6 +336,57 @@
         mpsMapper.updateProducedQuantity(id, incrQuantity);
     }
 
+    @Override
+    public Boolean checkSaleOrderProductionFinished(Long saleOrderId) {
+        if (saleOrderId == null) {
+            return false;
+        }
+        // 1. 鏌ヨ璇ラ攢鍞鍗曟墍鏈� MPS锛堥�氳繃 saleOrderId 鍏宠仈锛�
+        List<MesProMpsDO> mpsList = mpsMapper.selectList(
+                MesProMpsDO::getSaleOrderId, saleOrderId);
+        if (CollUtil.isEmpty(mpsList)) {
+            // 鏃� MPS 鍏宠仈锛岃涓哄畬鎴�
+            return true;
+        }
+        // 2. 妫�鏌ユ瘡涓� MPS 鏄惁瀹屾垚
+        for (MesProMpsDO mps : mpsList) {
+            if (!isMpsFinished(mps)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public MesProMpsDO getMpsByWorkOrderId(Long workOrderId) {
+        if (workOrderId == null) {
+            return null;
+        }
+        return mpsMapper.selectOne(MesProMpsDO::getWorkOrderId, workOrderId);
+    }
+
+    /**
+     * 鍒ゆ柇鍗曚釜 MPS 鏄惁瀹屾垚
+     */
+    private boolean isMpsFinished(MesProMpsDO mps) {
+        // 1. 宸茬敓浜ф暟閲� >= 璁″垝鏁伴噺
+        BigDecimal quantityProduced = ObjUtil.defaultIfNull(mps.getQuantityProduced(), BigDecimal.ZERO);
+        if (quantityProduced.compareTo(mps.getQuantity()) < 0) {
+            return false;
+        }
+        // 2. 妫�鏌ュ伐鍗曚笅鎵�鏈夋姤宸ユ槸鍚﹀畬鎴愶紙纭繚璐ㄦ瀹屾垚锛�
+        if (mps.getWorkOrderId() != null) {
+            Long unfinishedCount = feedbackMapper.selectCount(
+                    new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MesProFeedbackDO>()
+                            .eq(MesProFeedbackDO::getWorkOrderId, mps.getWorkOrderId())
+                            .ne(MesProFeedbackDO::getStatus, MesProFeedbackStatusEnum.FINISHED.getStatus()));
+            if (unfinishedCount > 0) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     // ========== 绉佹湁鏂规硶 ==========
 
     /**

--
Gitblit v1.9.3