From 274dd8b724dd4485658e1d2a3f825a007f625bd7 Mon Sep 17 00:00:00 2001
From: liyong <18434998025@163.com>
Date: 星期五, 17 七月 2026 09:10:07 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev_business' into dev_business

---
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpPurchaseRequestStatusEnum.java                               |   38 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java                                         |    2 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/qc/MesQcCheckResultEnum.java                                    |   18 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsInboundStatusListener.java           |   24 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsTransferStatusListener.java          |   24 
 yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/dto/ErpPurchaseOrderItemRespDTO.java                 |    3 
 yudao-module-erp/pom.xml                                                                                                         |    7 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/itemreceipt/MesWmItemReceiptController.java       |   17 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeServiceImpl.java               |   29 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderSaveReqVO.java     |    3 
 docs/erp_purchase_qc_check_flag.md                                                                                               |  103 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/arrivalnotice/MesWmArrivalNoticeController.java   |   17 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeService.java               |    3 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderRespVO.java        |   12 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/redis/no/ErpNoRedisDAO.java                                       |    5 
 docs/confirm_receipt_qc_flow.md                                                                                                  |  117 ++
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseRequestMapper.java                      |   44 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java                     |  230 ++++
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestSaveReqVO.java |   92 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcServiceImpl.java                               |   90 +
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpSupplierApiImpl.java                                  |   45 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemreceipt/MesWmItemReceiptServiceImpl.java               |   82 +
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java                         |   34 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeLineService.java           |   11 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpAuditStatus.java                                             |    3 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestPageReqVO.java |   42 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java                                         |   10 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java             |   26 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderDO.java                       |    5 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsOutboundStatusListener.java          |   24 
 docs/purchase_order_confirm_receipt.md                                                                                           |  160 +++
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/arrivalnotice/MesArrivalNoticeApiImpl.java                        |   21 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestDO.java                     |  141 ++
 docs/purchase_request_in_status.md                                                                                               |   97 +
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderItemDO.java                   |   12 
 yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/arrivalnotice/MesArrivalNoticeApi.java                        |   16 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestItemDO.java                 |   91 +
 yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java               |    3 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineServiceImpl.java         |    2 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestRespVO.java    |  130 ++
 yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/dto/ErpSupplierRespDTO.java                          |   48 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/arrivalnotice/MesWmArrivalNoticeLineDO.java         |    9 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcService.java                                   |   12 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeServiceImpl.java           |  111 +
 docs/purchase_flow_frontend_guide.md                                                                                             |  202 +++
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseRequestService.java                       |  114 ++
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseRequestServiceImpl.java                   |  459 ++++++++
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/itemreceipt/MesWmItemReceiptMapper.java                  |    4 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseRequestController.java           |  182 +++
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseRequestItemMapper.java                  |   29 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/salesnotice/MesWmSalesNoticeMapper.java                  |    4 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemreceipt/MesWmItemReceiptDetailServiceImpl.java         |    3 
 yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpSupplierApi.java                                  |   40 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/productsales/MesWmProductSalesMapper.java                |    4 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/arrivalnotice/MesWmArrivalNoticeMapper.java              |    4 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeLineServiceImpl.java       |   12 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/listener/ErpPurchaseRequestStatusListener.java       |   51 
 57 files changed, 3,009 insertions(+), 112 deletions(-)

diff --git a/docs/confirm_receipt_qc_flow.md b/docs/confirm_receipt_qc_flow.md
new file mode 100644
index 0000000..4556e72
--- /dev/null
+++ b/docs/confirm_receipt_qc_flow.md
@@ -0,0 +1,117 @@
+# 纭鏀惰揣涓庤川妫�娴佺▼ - 鍓嶇鑱旇皟鏂规
+
+## 娑夊強椤甸潰
+
+- 閲囪喘璁㈠崟鍒楄〃椤�
+- 閲囪喘璁㈠崟璇︽儏椤�
+- 鍒拌揣閫氱煡鍗曞垪琛ㄩ〉
+- 鍒拌揣閫氱煡鍗曡鎯呴〉
+- IQC 鏉ユ枡妫�楠屽崟椤甸潰
+
+## 涓氬姟娴佺▼涓庢暟鎹甫鍏�
+
+```
+閲囪喘鐢宠瀹℃壒閫氳繃 鈫� 鐢熸垚閲囪喘璁㈠崟锛堝凡瀹℃牳锛� 鈫� 纭鏀惰揣
+鈫� 鍒ゆ柇鏄惁瀛樺湪闇�璐ㄦ鐗╂枡
+  鈹溾攢鈹� 鏄� 鈫� 鐢熸垚鍒拌揣閫氱煡鍗曪紙寰呰川妫�锛夆啋 鑷姩鍒涘缓 IQC 鍗� 鈫� IQC 鏉ユ枡璐ㄦ
+  鈹�      鈹溾攢鈹� 鍚堟牸 鈫� 绛夊緟鎵�鏈夎川妫�瀹屾垚
+  鈹�      鈹溾攢鈹� 鐗归噰 鈫� 绛夊緟鎵�鏈夎川妫�瀹屾垚
+  鈹�      鈹溾攢鈹� 涓嶅悎鏍硷紙閫�璐э級鈫� 鐘舵�佷繚鎸佸緟璐ㄦ
+  鈹�      鈹斺攢鈹� 涓嶅悎鏍硷紙鎶ュ簾锛夆啋 鐘舵�佷繚鎸佸緟璐ㄦ
+  鈹�      鈫�
+  鈹�  鎵�鏈夐渶妫�鐗╂枡璐ㄦ瀹屾垚涓斿彲鍏ュ簱锛堝悎鏍�/鐗归噰锛夆啋 鍒拌揣閫氱煡鍗曪紙寰呭叆搴擄級
+  鈹斺攢鈹� 鍚� 鈫� 鐢熸垚鍒拌揣閫氱煡鍗曪紙寰呭叆搴擄級
+```
+
+**瀛楁甯﹀叆璇存槑锛�**
+
+| 婧愬崟鎹� | 鐩爣鍗曟嵁 | 甯﹀叆瀛楁 |
+|--------|----------|----------|
+| 閲囪喘璁㈠崟 | 鍒拌揣閫氱煡鍗� | 渚涘簲鍟嗐�侀噰璐鍗曞彿 |
+| 閲囪喘璁㈠崟鏄庣粏 | 鍒拌揣閫氱煡鍗曡 | 鐗╂枡銆佸埌璐ф暟閲忋�佽川妫�鏍囪瘑(qcCheckFlag) |
+| 鍒拌揣閫氱煡鍗曡 | IQC 妫�楠屽崟 | 鐗╂枡銆佸埌璐ф暟閲忋�佷緵搴斿晢 |
+
+## API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /erp/purchase-order/confirm-receipt | 纭鏀惰揣 |
+
+**璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| id | Long | 鏄� | 閲囪喘璁㈠崟缂栧彿 |
+
+**鍝嶅簲锛�**
+
+```json
+{
+  "code": 0,
+  "data": 123,  // 鐢熸垚鐨勫埌璐ч�氱煡鍗曠紪鍙�
+  "msg": "鎿嶄綔鎴愬姛"
+}
+```
+
+## 瀛楁灞曠ず瑙勫垯
+
+### 閲囪喘璁㈠崟鏄庣粏鏂板瀛楁
+
+| 瀛楁 | 绫诲瀷 | 灞曠ず浣嶇疆 | 璇存槑 |
+|------|------|----------|------|
+| qcCheckFlag | Boolean | 鍒楄〃銆佽鎯呫�佽〃鍗� | 鏄惁闇�瑕佹潵鏂欐楠岋紝榛樿 false |
+
+### 鍒拌揣閫氱煡鍗曡鏂板瀛楁
+
+| 瀛楁 | 绫诲瀷 | 灞曠ず浣嶇疆 | 璇存槑 |
+|------|------|----------|------|
+| iqcCheckFlag | Boolean | 鍒楄〃銆佽鎯� | 鏄惁闇�瑕佹潵鏂欐楠岋紙浠庨噰璐鍗曞甫鍏ワ級 |
+| iqcId | Long | 璇︽儏 | 鍏宠仈鐨� IQC 妫�楠屽崟缂栧彿锛堣嚜鍔ㄥ垱寤哄悗鍥炲啓锛� |
+| iqcCheckResult | Integer | 鍒楄〃銆佽鎯� | 璐ㄦ缁撴灉锛�1鍚堟牸 2鐗归噰 3涓嶅悎鏍奸��璐� 4涓嶅悎鏍兼姤搴� |
+| qualifiedQuantity | BigDecimal | 鍒楄〃銆佽鎯� | 鍚堟牸鏁伴噺锛堣川妫�瀹屾垚鍚庡洖鍐欙級 |
+
+### 鍒拌揣閫氱煡鍗曠姸鎬佹祦杞�
+
+| 鐘舵�佸�� | 鐘舵�佸悕 | 璇存槑 |
+|--------|--------|------|
+| 0 | 鑽夌 | 鏂板缓鐘舵�� |
+| 10 | 寰呰川妫� | 瀛樺湪闇�璐ㄦ鐗╂枡锛岀瓑寰� IQC 瀹屾垚 |
+| 20 | 寰呭叆搴� | 鏃犻渶璐ㄦ鎴栨墍鏈夎川妫�瀹屾垚涓斿彲鍏ュ簱 |
+| 30 | 宸插畬鎴� | 鍏ュ簱瀹屾垚 |
+
+## 涓氬姟瑙勫垯璇存槑
+
+### 纭鏀惰揣鎸夐挳灞曠ず
+
+| 鍦烘櫙 | 瑙勫垯 |
+|------|------|
+| 鏄剧ず鏉′欢 | 璁㈠崟鐘舵��=宸插鏍�(20) 涓� 鍏ュ簱鐘舵�佲墵鍏ㄩ儴鍏ュ簱(2) |
+| 鐐瑰嚮鍚� | 璋冪敤 confirmReceipt 鎺ュ彛锛岃繑鍥炲埌璐ч�氱煡鍗曠紪鍙� |
+| 鎴愬姛鍚� | 鎻愮ず"纭鏀惰揣鎴愬姛锛屽凡鐢熸垚鍒拌揣閫氱煡鍗�"锛屽埛鏂板垪琛� |
+
+### 璐ㄦ缁撴灉瀵瑰叆搴撶殑褰卞搷
+
+| 璐ㄦ缁撴灉 | 鍊� | 鏄惁鍙叆搴� | 璇存槑 |
+|----------|-----|------------|------|
+| 鍚堟牸 | 1 | 鉁� 鏄� | 姝e父鍏ュ簱 |
+| 鐗归噰 | 2 | 鉁� 鏄� | 鐗规畩鎵瑰噯鍏ュ簱 |
+| 涓嶅悎鏍奸��璐� | 3 | 鉂� 鍚� | 閫�鍥炰緵搴斿晢锛屼笉鍙叆搴� |
+| 涓嶅悎鏍兼姤搴� | 4 | 鉂� 鍚� | 鎶ュ簾澶勭悊锛屼笉鍙叆搴� |
+
+### 鍒拌揣閫氱煡鍗曠姸鎬佹帹杩涜鍒�
+
+| 褰撳墠鐘舵�� | 鎺ㄨ繘鏉′欢 | 鐩爣鐘舵�� |
+|----------|----------|----------|
+| 鑽夌 | 鎻愪氦鏃跺瓨鍦ㄩ渶璐ㄦ琛� | 寰呰川妫� |
+| 鑽夌 | 鎻愪氦鏃舵棤闇�璐ㄦ琛� | 寰呭叆搴� |
+| 寰呰川妫� | 鎵�鏈夐渶妫�琛屽畬鎴愯川妫�锛屼笖閮戒负鍚堟牸/鐗归噰 | 寰呭叆搴� |
+| 寰呰川妫� | 瀛樺湪閫�璐�/鎶ュ簾缁撴灉鐨勮 | 淇濇寔寰呰川妫� |
+| 寰呭叆搴� | 鎵嬪姩瀹屾垚鎿嶄綔 | 宸插畬鎴� |
+
+## 娉ㄦ剰浜嬮」
+
+1. **鑷姩鍒涘缓 IQC 鍗�**锛氱‘璁ゆ敹璐ф椂锛屽鏋滈噰璐鍗曟槑缁嗚缃簡 `qcCheckFlag=true`锛岀郴缁熶細鑷姩鍒涘缓瀵瑰簲鐨� IQC 妫�楠屽崟锛屾棤闇�鎵嬪姩鍒涘缓銆�
+2. **璐ㄦ缁撴灉鍥炲啓**锛欼QC 妫�楠屽畬鎴愬悗锛屼細鑷姩鍥炲啓鍒拌揣閫氱煡鍗曡鐨� `iqcCheckResult` 鍜� `qualifiedQuantity` 瀛楁銆�
+3. **閮ㄥ垎鏀惰揣**锛氫竴涓噰璐鍗曞彲浠ュ娆$‘璁ゆ敹璐э紙閮ㄥ垎鏀惰揣鍦烘櫙锛夛紝姣忔鐢熸垚鐙珛鐨勫埌璐ч�氱煡鍗曘��
+4. **鍏ュ簱鍓嶆彁鏉′欢**锛氬彧鏈夊埌璐ч�氱煡鍗曠姸鎬佷负"寰呭叆搴�"鏃舵墠鑳芥墽琛岄噰璐叆搴撴搷浣溿��
+5. **璐ㄦ澶辫触澶勭悊**锛氬鏋滆川妫�缁撴灉涓洪��璐ф垨鎶ュ簾锛屽埌璐ч�氱煡鍗曠姸鎬佷繚鎸�"寰呰川妫�"锛岄渶瑕佷汉宸ヤ粙鍏ュ鐞嗭紙濡傞噸鏂板畨鎺掓敹璐э級銆�
diff --git a/docs/erp_purchase_qc_check_flag.md b/docs/erp_purchase_qc_check_flag.md
new file mode 100644
index 0000000..3001efe
--- /dev/null
+++ b/docs/erp_purchase_qc_check_flag.md
@@ -0,0 +1,103 @@
+# ERP 閲囪喘璁㈠崟璐ㄦ瀛楁涓� MES 鏉ユ枡妫�楠岃嚜鍔ㄥ垱寤� - 鍓嶇鑱旇皟鏂规
+
+## 娑夊強椤甸潰
+
+- ERP 閲囪喘璁㈠崟鍒楄〃椤�
+- ERP 閲囪喘璁㈠崟鏂板/缂栬緫椤�
+- ERP 閲囪喘璁㈠崟璇︽儏椤�
+- MES 鍒拌揣閫氱煡鍗曞垪琛ㄩ〉
+- MES 鍒拌揣閫氱煡鍗曡鎯呴〉
+- MES 鏉ユ枡妫�楠岋紙IQC锛夊垪琛ㄩ〉
+
+## 涓氬姟娴佺▼
+
+### 1. 閲囪喘璁㈠崟鏂板璐ㄦ鏍囪瘑
+
+1. 鐢ㄦ埛鍦� ERP 閲囪喘璁㈠崟鏂板/缂栬緫椤甸潰锛屽彲浠ヤ负姣忎釜浜у搧鏄庣粏鍕鹃��"鏄惁闇�瑕佹潵鏂欐楠�"
+2. 淇濆瓨鏃讹紝`qcCheckFlag` 瀛楁闅忚鍗曟槑缁嗕竴璧蜂繚瀛�
+
+### 2. 浠庨噰璐鍗曠敓鎴愬埌璐ч�氱煡鍗�
+
+1. 鐢ㄦ埛鐐瑰嚮"鐢熸垚鍒拌揣閫氱煡鍗�"鎸夐挳
+2. 绯荤粺鑷姩灏嗛噰璐鍗曟槑缁嗙殑 `qcCheckFlag` 甯﹀叆鍒拌揣閫氱煡鍗曡鐨� `iqcCheckFlag`
+3. 鐢ㄦ埛鍙湪鍒拌揣閫氱煡鍗曡鎯呴〉淇敼姣忚鐨勮川妫�鏍囪瘑
+
+### 3. 鍒拌揣閫氱煡鍗曟彁浜よ嚜鍔ㄥ垱寤� IQC
+
+1. 鐢ㄦ埛鎻愪氦鍒拌揣閫氱煡鍗�
+2. 濡傛灉鏈夎鏍囪涓洪渶瑕佽川妫�锛坄iqcCheckFlag = true`锛夛細
+   - 鍒拌揣閫氱煡鍗曠姸鎬佸彉涓�"寰呰川妫�"
+   - 绯荤粺鑷姩涓烘瘡涓渶瑕佽川妫�鐨勮鍒涘缓涓�鏉� IQC 鏉ユ枡妫�楠屽崟
+   - 琛屼笂鐨� `iqcId` 鍏宠仈鍒板垱寤虹殑妫�楠屽崟
+3. 濡傛灉娌℃湁闇�瑕佽川妫�鐨勮锛岀姸鎬佺洿鎺ュ彉涓�"寰呭叆搴�"
+
+## API 鍙樻洿
+
+### ERP 閲囪喘璁㈠崟
+
+#### 璇锋眰鍙傛暟鍙樻洿
+
+**ErpPurchaseOrderSaveReqVO.Item 鏂板瀛楁锛�**
+
+| 瀛楁 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| qcCheckFlag | Boolean | 鍚� | 鏄惁闇�瑕佹潵鏂欐楠� |
+
+#### 鍝嶅簲鍙傛暟鍙樻洿
+
+**ErpPurchaseOrderRespVO.Item 鏂板瀛楁锛�**
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| qcCheckFlag | Boolean | 鏄惁闇�瑕佹潵鏂欐楠� |
+
+### MES 鍒拌揣閫氱煡鍗�
+
+#### 鍝嶅簲鍙傛暟鍙樻洿
+
+**鍒拌揣閫氱煡鍗曡鏂板瀛楁锛堝凡瀛樺湪锛屽己璋冨甫鍏ラ�昏緫锛夛細**
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| iqcCheckFlag | Boolean | 鏄惁闇�瑕佹潵鏂欐楠岋紙浠庨噰璐鍗曞甫鍏ワ級 |
+| iqcId | Long | 鍏宠仈鐨� IQC 妫�楠屽崟 ID锛堟彁浜ゅ悗鑷姩鍒涘缓锛� |
+
+## 瀛楁灞曠ず瑙勫垯
+
+### ERP 閲囪喘璁㈠崟
+
+| 瀛楁 | 灞曠ず浣嶇疆 | 璇存槑 |
+|------|----------|------|
+| 鏄惁璐ㄦ | 璁㈠崟鏄庣粏琛ㄦ牸 | 浣跨敤 Checkbox 鎴� Switch 灞曠ず锛屽彲缂栬緫 |
+
+### MES 鍒拌揣閫氱煡鍗�
+
+| 瀛楁 | 灞曠ず浣嶇疆 | 璇存槑 |
+|------|----------|------|
+| 鏄惁璐ㄦ | 琛屾槑缁嗚〃鏍� | 浣跨敤 Tag 鎴� Checkbox 灞曠ず锛屾彁浜ゅ悗涓嶅彲淇敼 |
+| IQC 妫�楠屽崟 | 琛屾槑缁嗚〃鏍� | 鍏宠仈鐨勬楠屽崟缂栧彿锛岀偣鍑诲彲璺宠浆锛堟彁浜ゅ悗鑷姩鍏宠仈锛� |
+
+### MES 鏉ユ枡妫�楠�
+
+| 瀛楁 | 灞曠ず浣嶇疆 | 璇存槑 |
+|------|----------|------|
+| 鏉ユ簮鍗曟嵁 | 璇︽儏椤� | 鏄剧ず鍒拌揣閫氱煡鍗曠紪鍙凤紝鐐瑰嚮鍙烦杞� |
+| 鏉ユ簮鍗曟嵁琛� | 璇︽儏椤� | 鏄剧ず鍒拌揣閫氱煡鍗曡缂栧彿 |
+
+## 涓氬姟瑙勫垯璇存槑
+
+| 鍦烘櫙 | 瑙勫垯 |
+|------|------|
+| 閲囪喘璁㈠崟淇濆瓨 | `qcCheckFlag` 榛樿涓� false锛堜笉璐ㄦ锛� |
+| 鐢熸垚鍒拌揣閫氱煡鍗� | 浠庨噰璐鍗曟槑缁嗗甫鍏� `qcCheckFlag` 鍒� `iqcCheckFlag` |
+| 鍒拌揣閫氱煡鍗曟彁浜� | 濡傛湁闇�瑕佽川妫�鐨勮锛岃嚜鍔ㄥ垱寤� IQC 妫�楠屽崟骞跺叧鑱� |
+| IQC 鍒涘缓鏉′欢 | 鐗╂枡蹇呴』閰嶇疆浜� IQC 妫�楠屾ā鏉匡紝鍚﹀垯璺宠繃骞惰褰曟棩蹇� |
+| IQC 鍗曟嵁鐘舵�� | 鑷姩鍒涘缓鐨� IQC 涓鸿崏绋跨姸鎬侊紝闇�瑕佹楠屽憳濉啓妫�楠岀粨鏋� |
+
+## 娉ㄦ剰浜嬮」
+
+1. **妫�楠屾ā鏉块厤缃�**锛氱墿鏂欏繀椤诲厛鍦� MES 璐ㄩ噺妯″潡閰嶇疆 IQC 妫�楠屾ā鏉匡紝鍚﹀垯鑷姩鍒涘缓 IQC 浼氬け璐�
+2. **鏁版嵁甯﹀叆**锛氫粠閲囪喘璁㈠崟鐢熸垚鍒拌揣閫氱煡鍗曟椂锛岃川妫�鏍囪瘑浼氳嚜鍔ㄥ甫鍏ワ紝浣嗙敤鎴峰彲鍦ㄥ埌璐ч�氱煡鍗曚腑淇敼
+3. **鐘舵�佹祦杞�**锛氬埌璐ч�氱煡鍗曟彁浜ゅ悗锛岄渶瑕佽川妫�鐨勮浼氳繘鍏�"寰呰川妫�"鐘舵�侊紝鎵�鏈夋楠屽畬鎴愬悗鎵嶅彲鍏ュ簱
+4. **妫�楠屽崟鍏宠仈**锛氭彁浜ゅ悗琛屼笂浼氬叧鑱� IQC 妫�楠屽崟 ID锛岀偣鍑诲彲璺宠浆鏌ョ湅妫�楠岃鎯�
+5. **寮傚父澶勭悊**锛氬鏋滄煇琛屽垱寤� IQC 澶辫触锛堝缂哄皯妯℃澘锛夛紝涓嶄細涓柇鏁翠釜鎻愪氦娴佺▼锛屼細璁板綍鏃ュ織缁х画澶勭悊鍏朵粬琛�
\ No newline at end of file
diff --git a/docs/purchase_flow_frontend_guide.md b/docs/purchase_flow_frontend_guide.md
new file mode 100644
index 0000000..6011c1f
--- /dev/null
+++ b/docs/purchase_flow_frontend_guide.md
@@ -0,0 +1,202 @@
+# 閲囪喘娴佺▼鍓嶇鑱旇皟鏂规
+
+## 娑夊強椤甸潰
+
+- 閲囪喘璁㈠崟鍒楄〃椤�
+- 閲囪喘璁㈠崟璇︽儏椤�
+- 鍒拌揣閫氱煡鍗曞垪琛ㄩ〉
+- 鍒拌揣閫氱煡鍗曡鎯呴〉
+
+## 涓氬姟娴佺▼
+
+```
+閲囪喘鐢宠瀹℃壒閫氳繃 鈫� 鑷姩鐢熸垚閲囪喘璁㈠崟(宸插鏍�)
+    鈫� 鐐瑰嚮"纭鏀惰揣" 鈫� 鐢熸垚鍒拌揣閫氱煡鍗�(寰呭叆搴�/寰呰川妫�)
+    鈫� IQC璐ㄦ(濡傞渶瑕�) 鈫� 閲囪喘鍏ュ簱
+```
+
+---
+
+## 涓�銆侀噰璐鍗曟ā鍧�
+
+### 1. 鏂板鎸夐挳锛氱‘璁ゆ敹璐�
+
+**浣嶇疆锛�** 閲囪喘璁㈠崟鍒楄〃椤点�佽鎯呴〉
+
+**灞曠ず鏉′欢锛�**
+- 璁㈠崟鐘舵�� = 宸插鏍�(20)
+- 鍏ュ簱鐘舵�� 鈮� 鍏ㄩ儴鍏ュ簱(2)
+
+**API锛�**
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /erp/purchase-order/confirm-receipt | 纭鏀惰揣 |
+
+**璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| id | Long | 鏄� | 閲囪喘璁㈠崟缂栧彿 |
+
+**鍝嶅簲锛�**
+
+```json
+{
+  "code": 0,
+  "data": 123,  // 鐢熸垚鐨勫埌璐ч�氱煡鍗曠紪鍙�
+  "msg": "鎿嶄綔鎴愬姛"
+}
+```
+
+### 2. 鏂板瀛楁锛氶噰璐鍗曟槑缁�
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| qcCheckFlag | Boolean | 鏄惁闇�瑕佹潵鏂欐楠岋紝榛樿 false |
+
+**灞曠ず浣嶇疆锛�** 閲囪喘璁㈠崟鏄庣粏琛ㄦ牸
+
+---
+
+## 浜屻�侀噰璐敵璇锋ā鍧�
+
+### 鏂板瀛楁锛氶噰璐敵璇锋槑缁�
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| qcCheckFlag | Boolean | 鏄惁闇�瑕佹潵鏂欐楠岋紝榛樿 false |
+
+**灞曠ず浣嶇疆锛�** 閲囪喘鐢宠鏄庣粏琛ㄦ牸
+
+### 鏂板瀛楁锛氶噰璐敵璇蜂富琛�
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| depositPrice | BigDecimal | 瀹氶噾閲戦锛岄粯璁� 0 |
+
+**灞曠ず浣嶇疆锛�** 閲囪喘鐢宠琛ㄥ崟
+
+---
+
+## 涓夈�佸埌璐ч�氱煡鍗曟ā鍧�
+
+### 1. 鐘舵�佹祦杞鏄�
+
+| 鐘舵�佸�� | 鐘舵�佸悕 | 璇存槑 |
+|--------|--------|------|
+| 0 | 鑽夌 | 鏂板缓鐘舵�� |
+| 2 | 寰呰川妫� | 瀛樺湪闇�璐ㄦ鐗╂枡锛岀瓑寰� IQC 瀹屾垚 |
+| 3 | 寰呭叆搴� | 鏃犻渶璐ㄦ鎴栨墍鏈夎川妫�瀹屾垚涓斿彲鍏ュ簱 |
+| 4 | 宸插畬鎴� | 鍏ュ簱瀹屾垚 |
+
+### 2. 鏂板瀛楁锛氬埌璐ч�氱煡鍗曡
+
+| 瀛楁 | 绫诲瀷 | 灞曠ず浣嶇疆 | 璇存槑 |
+|------|------|----------|------|
+| iqcCheckFlag | Boolean | 鍒楄〃銆佽鎯� | 鏄惁闇�瑕佹潵鏂欐楠� |
+| iqcId | Long | 璇︽儏 | 鍏宠仈鐨� IQC 妫�楠屽崟缂栧彿 |
+| iqcCheckResult | Integer | 鍒楄〃銆佽鎯� | 璐ㄦ缁撴灉 |
+| qualifiedQuantity | BigDecimal | 鍒楄〃銆佽鎯� | 鍚堟牸鏁伴噺 |
+
+### 3. 璐ㄦ缁撴灉鏋氫妇
+
+| 鍊� | 鍚嶇О | 鏄惁鍙叆搴� |
+|-----|------|------------|
+| 1 | 鍚堟牸 | 鉁� 鏄� |
+| 2 | 鐗归噰 | 鉁� 鏄� |
+| 3 | 涓嶅悎鏍奸��璐� | 鉂� 鍚� |
+| 4 | 涓嶅悎鏍兼姤搴� | 鉂� 鍚� |
+
+### 4. 鏂板鎺ュ彛锛氭牴鎹噰璐鍗曠敓鎴愬埌璐ч�氱煡鍗�
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /mes/wm/arrival-notice/generate-from-purchase-order | 鏍规嵁閲囪喘璁㈠崟鐢熸垚鍒拌揣閫氱煡鍗� |
+
+**璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| purchaseOrderId | Long | 鏄� | 閲囪喘璁㈠崟缂栧彿 |
+
+**鍝嶅簲锛�**
+
+```json
+{
+  "code": 0,
+  "data": 123,  // 鐢熸垚鐨勫埌璐ч�氱煡鍗曠紪鍙�
+  "msg": "鎿嶄綔鎴愬姛"
+}
+```
+
+---
+
+## 鍥涖�佸瓧娈靛甫鍏ヨ鏄�
+
+### 鏁版嵁娴佽浆鏄犲皠
+
+| 婧愬崟鎹� | 鐩爣鍗曟嵁 | 甯﹀叆瀛楁 |
+|--------|----------|----------|
+| 閲囪喘鐢宠鏄庣粏 | 閲囪喘璁㈠崟鏄庣粏 | qcCheckFlag锛堣川妫�鏍囪瘑锛� |
+| 閲囪喘鐢宠涓昏〃 | 閲囪喘璁㈠崟涓昏〃 | depositPrice锛堝畾閲戦噾棰濓級 |
+| 閲囪喘璁㈠崟 | 鍒拌揣閫氱煡鍗� | 渚涘簲鍟嗐�侀噰璐鍗曞彿 |
+| 閲囪喘璁㈠崟鏄庣粏 | 鍒拌揣閫氱煡鍗曡 | 鐗╂枡銆佸埌璐ф暟閲忋�乹cCheckFlag |
+| 鍒拌揣閫氱煡鍗曡 | IQC 妫�楠屽崟 | 鐗╂枡銆佸埌璐ф暟閲忋�佷緵搴斿晢 |
+
+---
+
+## 浜斻�佹寜閽睍绀鸿鍒欐眹鎬�
+
+### 閲囪喘璁㈠崟鍒楄〃/璇︽儏椤�
+
+| 鎸夐挳 | 鏄剧ず鏉′欢 |
+|------|----------|
+| 纭鏀惰揣 | 璁㈠崟鐘舵��=宸插鏍�(20) 涓� 鍏ュ簱鐘舵�佲墵鍏ㄩ儴鍏ュ簱(2) |
+
+### 閲囪喘鐢宠鏄庣粏琛ㄦ牸
+
+| 鍒� | 璇存槑 |
+|----|------|
+| 鏄惁璐ㄦ | 寮�鍏崇粍浠讹紝榛樿鍏抽棴 |
+
+### 閲囪喘璁㈠崟鏄庣粏琛ㄦ牸
+
+| 鍒� | 璇存槑 |
+|----|------|
+| 鏄惁璐ㄦ | 鍙灞曠ず锛屼粠閲囪喘鐢宠甯﹀叆 |
+
+### 鍒拌揣閫氱煡鍗曡琛ㄦ牸
+
+| 鍒� | 璇存槑 |
+|----|------|
+| 璐ㄦ鏍囪瘑 | 鍙灞曠ず |
+| 璐ㄦ缁撴灉 | 鍙灞曠ず锛岃川妫�瀹屾垚鍚庡洖鍐� |
+| 鍚堟牸鏁伴噺 | 鍙灞曠ず锛岃川妫�瀹屾垚鍚庡洖鍐� |
+
+---
+
+## 鍏�佹敞鎰忎簨椤�
+
+1. **纭鏀惰揣鎴愬姛鍚�**锛氳繑鍥炲埌璐ч�氱煡鍗曠紪鍙凤紝鍓嶇鍙彁绀哄苟鍒锋柊鍒楄〃锛屾垨璺宠浆鍒拌揣閫氱煡鍗曡鎯�
+2. **涓�涓噰璐鍗曞彲澶氭纭鏀惰揣**锛氭敮鎸侀儴鍒嗘敹璐у満鏅紝姣忔鐢熸垚鐙珛鐨勫埌璐ч�氱煡鍗�
+3. **璐ㄦ鑷姩鍒涘缓**锛氱‘璁ゆ敹璐ф椂锛岀郴缁熻嚜鍔ㄥ垱寤� IQC 妫�楠屽崟锛屾棤闇�鎵嬪姩鍒涘缓
+4. **鍏ュ簱鍓嶆彁鏉′欢**锛氬彧鏈夊埌璐ч�氱煡鍗曠姸鎬佷负"寰呭叆搴�"鏃舵墠鑳芥墽琛岄噰璐叆搴�
+5. **璐ㄦ澶辫触澶勭悊**锛氬鏋滆川妫�缁撴灉涓洪��璐ф垨鎶ュ簾锛屽埌璐ч�氱煡鍗曠姸鎬佷繚鎸�"寰呰川妫�"锛岄渶浜哄伐澶勭悊
+
+---
+
+## 涓冦�佹潈闄愰厤缃�
+
+| 鏉冮檺鏍囪瘑 | 璇存槑 |
+|----------|------|
+| erp:purchase-order:confirm-receipt | 纭鏀惰揣鏉冮檺 |
+
+---
+
+## 鍏�丄PI 姹囨��
+
+| 妯″潡 | 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|------|
+| 閲囪喘璁㈠崟 | POST | /erp/purchase-order/confirm-receipt | 纭鏀惰揣 |
+| 鍒拌揣閫氱煡鍗� | POST | /mes/wm/arrival-notice/generate-from-purchase-order | 鏍规嵁閲囪喘璁㈠崟鐢熸垚 |
\ No newline at end of file
diff --git a/docs/purchase_order_confirm_receipt.md b/docs/purchase_order_confirm_receipt.md
new file mode 100644
index 0000000..c29e1ca
--- /dev/null
+++ b/docs/purchase_order_confirm_receipt.md
@@ -0,0 +1,160 @@
+# 閲囪喘璁㈠崟纭鏀惰揣 - 鍓嶇鑱旇皟鏂规
+
+## 娑夊強椤甸潰
+
+- 閲囪喘璁㈠崟鍒楄〃椤�
+- 閲囪喘璁㈠崟璇︽儏椤�
+
+## 涓氬姟娴佺▼
+
+```
+閲囪喘鐢宠瀹℃壒閫氳繃 鈫� 鐢熸垚閲囪喘璁㈠崟(宸插鏍�) 鈫� 纭鏀惰揣 鈫� 鐢熸垚鍒拌揣閫氱煡鍗�(寰呭叆搴�/寰呰川妫�) 鈫� 鎵ц鍏ュ簱
+```
+
+**纭鏀惰揣鍚庣殑鑷姩澶勭悊锛�**
+- 濡傛灉閲囪喘璁㈠崟鏄庣粏璁剧疆浜� `qcCheckFlag=true`锛堥渶瑕佽川妫�锛夛紝鍒拌揣閫氱煡鍗曡嚜鍔ㄨ繘鍏�"寰呰川妫�"鐘舵�侊紝骞跺垱寤� IQC 妫�楠屽崟
+- 濡傛灉閲囪喘璁㈠崟鏄庣粏璁剧疆浜� `qcCheckFlag=false`锛堟棤闇�璐ㄦ锛夛紝鍒拌揣閫氱煡鍗曠洿鎺ヨ繘鍏�"寰呭叆搴�"鐘舵��
+
+## 鏂板 API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /erp/purchase-order/confirm-receipt | 纭鏀惰揣 |
+
+**璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| id | Long | 鏄� | 閲囪喘璁㈠崟缂栧彿 |
+
+**鍝嶅簲锛�**
+
+```json
+{
+  "code": 0,
+  "data": 123,  // 鐢熸垚鐨勫埌璐ч�氱煡鍗曠紪鍙�
+  "msg": "鎿嶄綔鎴愬姛"
+}
+```
+
+**涓氬姟瑙勫垯锛�**
+- 鍙湁宸插鏍哥姸鎬佺殑閲囪喘璁㈠崟鎵嶈兘纭鏀惰揣
+- 宸插叏閮ㄥ叆搴撶殑閲囪喘璁㈠崟涓嶈兘纭鏀惰揣
+- 纭鏀惰揣鍚庤繑鍥炵敓鎴愮殑鍒拌揣閫氱煡鍗曠紪鍙凤紝鍓嶇鍙烦杞埌璐ч�氱煡鍗曡鎯�
+
+## 瀛楁鍙樻洿
+
+### 閲囪喘鐢宠鏄庣粏鏂板瀛楁
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| qcCheckFlag | Boolean | 鏄惁闇�瑕佹潵鏂欐楠岋紝榛樿 false |
+
+### 閲囪喘璁㈠崟鏄庣粏鏂板瀛楁
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| qcCheckFlag | Boolean | 鏄惁闇�瑕佹潵鏂欐楠岋紝浠庨噰璐敵璇峰甫鍏� |
+
+### 閲囪喘鐢宠鏂板瀛楁
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| depositPrice | BigDecimal | 瀹氶噾閲戦锛岄粯璁� 0 |
+
+### 閲囪喘璁㈠崟鏂板瀛楁
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| depositPrice | BigDecimal | 瀹氶噾閲戦锛屼粠閲囪喘鐢宠甯﹀叆 |
+
+## 鎸夐挳灞曠ず瑙勫垯
+
+### 閲囪喘璁㈠崟鍒楄〃/璇︽儏椤�
+
+| 鎸夐挳 | 鏄剧ず鏉′欢 |
+|------|----------|
+| 纭鏀惰揣 | 璁㈠崟鐘舵��=宸插鏍�(20) 涓� 鍏ュ簱鐘舵�佲墵鍏ㄩ儴鍏ュ簱(2) |
+
+## 鍓嶇淇敼鐐�
+
+### 1. 閲囪喘璁㈠崟鍒楄〃椤� - 娣诲姞纭鏀惰揣鎸夐挳
+
+```html
+<el-button 
+  type="success" 
+  v-if="scope.row.status === 20 && scope.row.inStatus !== 2" 
+  @click="handleConfirmReceipt(scope.row)"
+  v-hasPermi="['erp:purchase-order:confirm-receipt']">
+  纭鏀惰揣
+</el-button>
+```
+
+### 2. 鏂规硶瀹炵幇
+
+```js
+// 纭鏀惰揣
+async handleConfirmReceipt(row) {
+  try {
+    await this.$modal.confirm(`纭瀵归噰璐鍗曘��${row.no}銆戣繘琛屾敹璐ф搷浣滐紵`);
+    const res = await confirmReceipt(row.id);
+    this.$modal.msgSuccess('纭鏀惰揣鎴愬姛锛屽凡鐢熸垚鍒拌揣閫氱煡鍗�');
+    // 鍙�夛細璺宠浆鍒拌揣閫氱煡鍗曡鎯�
+    // this.$router.push({ path: '/mes/wm/arrival-notice/detail', query: { id: res.data } });
+    this.getList();
+  } catch (e) {
+    // 鍙栨秷鎴栧け璐�
+  }
+}
+```
+
+### 3. API 鎺ュ彛
+
+```js
+// api/erp/purchase/order.js
+export function confirmReceipt(id) {
+  return request({
+    url: '/erp/purchase-order/confirm-receipt',
+    method: 'post',
+    params: { id }
+  })
+}
+```
+
+### 4. 閲囪喘鐢宠琛ㄥ崟 - 娣诲姞璐ㄦ鏍囪瘑瀛楁
+
+鍦ㄩ噰璐敵璇锋槑缁嗚〃鏍间腑娣诲姞"鏄惁璐ㄦ"鍒楋細
+
+```html
+<el-table-column label="鏄惁璐ㄦ" prop="qcCheckFlag" width="100">
+  <template #default="scope">
+    <el-switch v-model="scope.row.qcCheckFlag" />
+  </template>
+</el-table-column>
+```
+
+### 5. 閲囪喘鐢宠琛ㄥ崟 - 娣诲姞瀹氶噾瀛楁
+
+```html
+<el-form-item label="瀹氶噾閲戦" prop="depositPrice">
+  <el-input-number 
+    v-model="form.depositPrice" 
+    :min="0" 
+    :precision="2" 
+    :controls="false"
+    placeholder="璇疯緭鍏ュ畾閲戦噾棰�" />
+</el-form-item>
+```
+
+## 鏉冮檺閰嶇疆
+
+| 鏉冮檺鏍囪瘑 | 璇存槑 |
+|----------|------|
+| erp:purchase-order:confirm-receipt | 纭鏀惰揣鏉冮檺 |
+
+## 娉ㄦ剰浜嬮」
+
+1. 纭鏀惰揣鎿嶄綔浼氳嚜鍔ㄧ敓鎴愬埌璐ч�氱煡鍗曪紝鏃犻渶鎵嬪姩鍒涘缓
+2. 璐ㄦ鏍囪瘑浠庨噰璐敵璇� 鈫� 閲囪喘璁㈠崟 鈫� 鍒拌揣閫氱煡鍗曞叏绋嬩紶閫�
+3. 瀹氶噾閲戦浠庨噰璐敵璇疯嚜鍔ㄥ甫鍏ラ噰璐鍗�
+4. 涓�涓噰璐鍗曞彲浠ュ娆$‘璁ゆ敹璐э紙閮ㄥ垎鏀惰揣鍦烘櫙锛�
\ No newline at end of file
diff --git a/docs/purchase_request_in_status.md b/docs/purchase_request_in_status.md
new file mode 100644
index 0000000..f51a432
--- /dev/null
+++ b/docs/purchase_request_in_status.md
@@ -0,0 +1,97 @@
+# 閲囪喘鍏ュ簱鐘舵�佸洖鍐� - 鍓嶇鑱旇皟鏂规
+
+## 娑夊強椤甸潰
+
+- 閲囪喘鐢宠鍒楄〃椤�
+- 閲囪喘鐢宠璇︽儏椤�
+- 閲囪喘璁㈠崟鍒楄〃椤�
+- 閲囪喘璁㈠崟璇︽儏椤�
+
+## 涓氬姟娴佺▼涓庢暟鎹甫鍏�
+
+```
+閲囪喘鐢宠瀹℃壒閫氳繃 鈫� 鐢熸垚閲囪喘璁㈠崟(宸插鏍�) 鈫� 纭鏀惰揣 鈫� 鐢熸垚鍒拌揣閫氱煡鍗�(寰呭叆搴�) 鈫� 鎵ц鍏ュ簱
+       鈫�                    鈫�                                           鈫�
+   order_id/order_no   request_item_id 鍏宠仈                         in_status 鍥炲啓
+```
+
+**鐘舵�佸洖鍐欐祦绋嬶細**
+1. 鍏ュ簱鍗曞畬鎴� 鈫� 鍥炲啓閲囪喘璁㈠崟鏄庣粏鍏ュ簱鏁伴噺锛坕n_count锛�
+2. 鍏ュ簱鍗曞畬鎴� 鈫� 鍥炲啓閲囪喘璁㈠崟鍏ュ簱鐘舵�侊紙in_status锛�
+3. 鍏ュ簱鍗曞畬鎴� 鈫� 鍥炲啓閲囪喘鐢宠鏄庣粏鍏ュ簱鏁伴噺锛坕n_count锛�
+4. 鍏ュ簱鍗曞畬鎴� 鈫� 鍥炲啓閲囪喘鐢宠鍏ュ簱鐘舵�侊紙in_status锛�
+
+## 瀛楁鍙樻洿
+
+### 閲囪喘鐢宠鏂板瀛楁
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| inStatus | Integer | 鍏ュ簱鐘舵�侊紙0:鏈叆搴� 1:閮ㄥ垎鍏ュ簱 2:鍏ㄩ儴鍏ュ簱锛� |
+
+### 閲囪喘鐢宠鏄庣粏鏂板瀛楁
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| inCount | BigDecimal | 鍏ュ簱鏁伴噺 |
+
+### 閲囪喘璁㈠崟鏄庣粏鏂板瀛楁
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| requestItemId | Long | 閲囪喘鐢宠鏄庣粏缂栧彿锛堢郴缁熻嚜鍔ㄥ叧鑱旓級 |
+
+## 鍝嶅簲瀛楁鍙樻洿
+
+### 閲囪喘鐢宠璇︽儏鍝嶅簲鏂板
+
+```json
+{
+  "inStatus": 0
+}
+```
+
+### 閲囪喘鐢宠鏄庣粏鍝嶅簲鏂板
+
+```json
+{
+  "items": [
+    {
+      "inCount": 0
+    }
+  ]
+}
+```
+
+### 閲囪喘璁㈠崟璇︽儏鍝嶅簲鏂板
+
+鏃犲彉鍖栵紙`requestItemId` 涓虹郴缁熷唴閮ㄥ叧鑱旓紝鍓嶇鏃犻渶灞曠ず锛�
+
+## 瀛楁灞曠ず瑙勫垯
+
+| 瀛楁 | 灞曠ず浣嶇疆 | 璇存槑 |
+|------|----------|------|
+| 鍏ュ簱鐘舵�� | 閲囪喘鐢宠鍒楄〃銆佽鎯� | 浣跨敤鏍囩灞曠ず锛氭湭鍏ュ簱/閮ㄥ垎鍏ュ簱/鍏ㄩ儴鍏ュ簱 |
+| 鍏ュ簱鏁伴噺 | 閲囪喘鐢宠鏄庣粏琛ㄦ牸 | 鏁板�煎睍绀猴紝淇濈暀6浣嶅皬鏁� |
+
+## 涓氬姟瑙勫垯璇存槑
+
+| 鍦烘櫙 | 瑙勫垯 |
+|------|------|
+| 閲囪喘鐢宠鍏ュ簱鐘舵�� | 鏍规嵁閲囪喘璁㈠崟鍏ュ簱鐘舵�佽嚜鍔ㄦ洿鏂帮紝鏃犻渶鎵嬪姩鎿嶄綔 |
+| 閲囪喘鐢宠鏄庣粏鍏ュ簱鏁伴噺 | 鏍规嵁閲囪喘璁㈠崟鏄庣粏鍏ュ簱鏁伴噺绱姞鏇存柊 |
+| 澶氭鍏ュ簱 | 鏀寔閮ㄥ垎鍏ュ簱锛屽叆搴撶姸鎬佽嚜鍔ㄦ祦杞� |
+
+## 鍏ュ簱鐘舵�佹灇涓�
+
+| 鍊� | 鍚嶇О | 棰滆壊寤鸿 |
+|----|------|----------|
+| 0 | 鏈叆搴� | info锛堢伆鑹诧級 |
+| 1 | 閮ㄥ垎鍏ュ簱 | warning锛堟鑹诧級 |
+| 2 | 鍏ㄩ儴鍏ュ簱 | success锛堢豢鑹诧級 |
+
+## 娉ㄦ剰浜嬮」
+
+1. 鍏ュ簱鐘舵�佺敱绯荤粺鑷姩鍥炲啓锛屾棤闇�鎵嬪姩缁存姢
+2. 涓�涓噰璐敵璇峰搴斾竴涓噰璐鍗曪紝鍏ュ簱鐘舵�佸悓姝�
+3. 濡傛灉閲囪喘鐢宠鏈寚瀹氫緵搴斿晢锛屽鎵归�氳繃鍚庨渶瑕佹墜鍔ㄩ�夋嫨渚涘簲鍟嗙敓鎴愯鍗�
\ No newline at end of file
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java
index 8171fb0..f4e3776 100644
--- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java
@@ -62,6 +62,9 @@
 
     @Override
     public List<ProcessDefinition> getProcessDefinitionList(Set<String> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return emptyList();
+        }
         return repositoryService.createProcessDefinitionQuery().processDefinitionIds(ids).list();
     }
 
diff --git a/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpSupplierApi.java b/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpSupplierApi.java
new file mode 100644
index 0000000..a4d7d13
--- /dev/null
+++ b/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpSupplierApi.java
@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.module.erp.api.purchase;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpSupplierRespDTO;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * ERP 渚涘簲鍟� API 鎺ュ彛
+ */
+public interface ErpSupplierApi {
+
+    String PREFIX = "/rpc-api/erp/supplier";
+
+    /**
+     * 鑾峰彇渚涘簲鍟�
+     *
+     * @param id 渚涘簲鍟嗙紪鍙�
+     * @return 渚涘簲鍟�
+     */
+    CommonResult<ErpSupplierRespDTO> getSupplier(Long id);
+
+    /**
+     * 鏍¢獙渚涘簲鍟嗗瓨鍦�
+     *
+     * @param id 渚涘簲鍟嗙紪鍙�
+     * @return 渚涘簲鍟�
+     */
+    CommonResult<ErpSupplierRespDTO> validateSupplier(Long id);
+
+    /**
+     * 鑾峰彇渚涘簲鍟嗗垪琛�
+     *
+     * @param ids 缂栧彿鍒楄〃
+     * @return 渚涘簲鍟嗗垪琛�
+     */
+    CommonResult<List<ErpSupplierRespDTO>> getSupplierList(Collection<Long> ids);
+
+}
\ No newline at end of file
diff --git a/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/dto/ErpPurchaseOrderItemRespDTO.java b/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/dto/ErpPurchaseOrderItemRespDTO.java
index 7df8499..f100da6 100644
--- a/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/dto/ErpPurchaseOrderItemRespDTO.java
+++ b/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/dto/ErpPurchaseOrderItemRespDTO.java
@@ -36,4 +36,7 @@
     @Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
     private String remark;
 
+    @Schema(description = "鏄惁闇�瑕佹潵鏂欐楠�", example = "true")
+    private Boolean qcCheckFlag;
+
 }
\ No newline at end of file
diff --git a/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/dto/ErpSupplierRespDTO.java b/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/dto/ErpSupplierRespDTO.java
new file mode 100644
index 0000000..31d0d6e
--- /dev/null
+++ b/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/dto/ErpSupplierRespDTO.java
@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.erp.api.purchase.dto;
+
+import lombok.Data;
+
+/**
+ * ERP 渚涘簲鍟� Response DTO
+ */
+@Data
+public class ErpSupplierRespDTO {
+
+    /**
+     * 渚涘簲鍟嗙紪鍙�
+     */
+    private Long id;
+    /**
+     * 渚涘簲鍟嗗悕绉�
+     */
+    private String name;
+    /**
+     * 鑱旂郴浜�
+     */
+    private String contact;
+    /**
+     * 鎵嬫満鍙风爜
+     */
+    private String mobile;
+    /**
+     * 鑱旂郴鐢佃瘽
+     */
+    private String telephone;
+    /**
+     * 鐢靛瓙閭
+     */
+    private String email;
+    /**
+     * 寮�鍚姸鎬�
+     */
+    private Integer status;
+    /**
+     * 绾崇◣浜鸿瘑鍒彿
+     */
+    private String taxNo;
+    /**
+     * 绋庣巼
+     */
+    private java.math.BigDecimal taxPercent;
+
+}
\ No newline at end of file
diff --git a/yudao-module-erp/pom.xml b/yudao-module-erp/pom.xml
index ec7a802..a75b76e 100644
--- a/yudao-module-erp/pom.xml
+++ b/yudao-module-erp/pom.xml
@@ -52,6 +52,13 @@
             <version>${revision}</version>
         </dependency>
 
+        <!-- MES 鍒堕�犳墽琛岀郴缁� API -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-mes-api</artifactId>
+            <version>${revision}</version>
+        </dependency>
+
         <!-- 涓氬姟缁勪欢 -->
 
         <!-- Web 鐩稿叧 -->
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpSupplierApiImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpSupplierApiImpl.java
new file mode 100644
index 0000000..d793a88
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpSupplierApiImpl.java
@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.erp.api.purchase;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpSupplierRespDTO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpSupplierDO;
+import cn.iocoder.yudao.module.erp.service.purchase.ErpSupplierService;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.Collection;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+/**
+ * ERP 渚涘簲鍟� API 瀹炵幇绫�
+ */
+@Service
+@Validated
+public class ErpSupplierApiImpl implements ErpSupplierApi {
+
+    @Resource
+    private ErpSupplierService supplierService;
+
+    @Override
+    public CommonResult<ErpSupplierRespDTO> getSupplier(Long id) {
+        ErpSupplierDO supplier = supplierService.getSupplier(id);
+        return success(BeanUtils.toBean(supplier, ErpSupplierRespDTO.class));
+    }
+
+    @Override
+    public CommonResult<ErpSupplierRespDTO> validateSupplier(Long id) {
+        ErpSupplierDO supplier = supplierService.validateSupplier(id);
+        return success(BeanUtils.toBean(supplier, ErpSupplierRespDTO.class));
+    }
+
+    @Override
+    public CommonResult<List<ErpSupplierRespDTO>> getSupplierList(Collection<Long> ids) {
+        List<ErpSupplierDO> list = supplierService.getSupplierList(ids);
+        return success(BeanUtils.toBean(list, ErpSupplierRespDTO.class));
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java
index bc5cb00..517926d 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java
@@ -39,6 +39,7 @@
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
 
 @Tag(name = "绠$悊鍚庡彴 - ERP 閲囪喘璁㈠崟")
 @RestController
@@ -78,6 +79,23 @@
                                                       @RequestParam("status") Integer status) {
         purchaseOrderService.updatePurchaseOrderStatus(id, status);
         return success(true);
+    }
+
+    @PostMapping("/submit")
+    @Operation(summary = "鎻愪氦閲囪喘璁㈠崟瀹℃壒")
+    @PreAuthorize("@ss.hasPermission('erp:purchase-order:update-status')")
+    public CommonResult<Boolean> submitPurchaseOrder(@RequestParam("id") Long id,
+                                                     @RequestParam("processDefinitionKey") String processDefinitionKey) {
+        Long userId = getLoginUserId();
+        purchaseOrderService.submitPurchaseOrder(id, processDefinitionKey, userId);
+        return success(true);
+    }
+
+    @GetMapping("/approve-process-list")
+    @Operation(summary = "鑾峰彇閲囪喘璁㈠崟瀹℃壒娴佺▼鍒楄〃")
+    @PreAuthorize("@ss.hasPermission('erp:purchase-order:query')")
+    public CommonResult<List<Map<String, Object>>> getApproveProcessList() {
+        return success(purchaseOrderService.getPurchaseOrderApproveProcessDefinitionList());
     }
 
     @DeleteMapping("/delete")
@@ -130,6 +148,14 @@
         ExcelUtils.write(response, "閲囪喘璁㈠崟.xls", "鏁版嵁", ErpPurchaseOrderRespVO.class, list);
     }
 
+    @PostMapping("/confirm-receipt")
+    @Operation(summary = "纭鏀惰揣")
+    @Parameter(name = "id", description = "閲囪喘璁㈠崟缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('erp:purchase-order:confirm-receipt')")
+    public CommonResult<Long> confirmReceipt(@RequestParam("id") Long id) {
+        return success(purchaseOrderService.confirmReceipt(id));
+    }
+
     private PageResult<ErpPurchaseOrderRespVO> buildPurchaseOrderVOPageResult(PageResult<ErpPurchaseOrderDO> pageResult) {
         if (CollUtil.isEmpty(pageResult.getList())) {
             return PageResult.empty(pageResult.getTotal());
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseRequestController.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseRequestController.java
new file mode 100644
index 0000000..814f10e
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseRequestController.java
@@ -0,0 +1,182 @@
+package cn.iocoder.yudao.module.erp.controller.admin.purchase;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request.ErpPurchaseRequestPageReqVO;
+import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request.ErpPurchaseRequestRespVO;
+import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request.ErpPurchaseRequestSaveReqVO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestDO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestItemDO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpSupplierDO;
+import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseRequestService;
+import cn.iocoder.yudao.module.erp.service.purchase.ErpSupplierService;
+import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
+import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+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.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "绠$悊鍚庡彴 - ERP 閲囪喘鐢宠")
+@RestController
+@RequestMapping("/erp/purchase-request")
+@Validated
+public class ErpPurchaseRequestController {
+
+    @Resource
+    private ErpPurchaseRequestService purchaseRequestService;
+    @Resource
+    private ErpSupplierService supplierService;
+
+    @Resource
+    private MdmItemApi mdmItemApi;
+    @Resource
+    private AdminUserApi adminUserApi;
+    @Resource
+    private DeptApi deptApi;
+
+    @PostMapping("/create")
+    @Operation(summary = "鍒涘缓閲囪喘鐢宠")
+    @PreAuthorize("@ss.hasPermission('erp:purchase-request:create')")
+    public CommonResult<Long> createPurchaseRequest(@Valid @RequestBody ErpPurchaseRequestSaveReqVO createReqVO) {
+        return success(purchaseRequestService.createPurchaseRequest(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊閲囪喘鐢宠")
+    @PreAuthorize("@ss.hasPermission('erp:purchase-request:update')")
+    public CommonResult<Boolean> updatePurchaseRequest(@Valid @RequestBody ErpPurchaseRequestSaveReqVO updateReqVO) {
+        purchaseRequestService.updatePurchaseRequest(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎閲囪喘鐢宠")
+    @Parameter(name = "ids", description = "缂栧彿鏁扮粍", required = true)
+    @PreAuthorize("@ss.hasPermission('erp:purchase-request:delete')")
+    public CommonResult<Boolean> deletePurchaseRequest(@RequestParam("ids") List<Long> ids) {
+        purchaseRequestService.deletePurchaseRequest(ids);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰緱閲囪喘鐢宠")
+    @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('erp:purchase-request:query')")
+    public CommonResult<ErpPurchaseRequestRespVO> getPurchaseRequest(@RequestParam("id") Long id) {
+        ErpPurchaseRequestDO purchaseRequest = purchaseRequestService.getPurchaseRequest(id);
+        if (purchaseRequest == null) {
+            return success(null);
+        }
+        List<ErpPurchaseRequestItemDO> purchaseRequestItemList = purchaseRequestService.getPurchaseRequestItemListByRequestId(id);
+        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(
+                convertSet(purchaseRequestItemList, ErpPurchaseRequestItemDO::getProductId));
+        return success(BeanUtils.toBean(purchaseRequest, ErpPurchaseRequestRespVO.class, purchaseRequestVO ->
+                purchaseRequestVO.setItems(BeanUtils.toBean(purchaseRequestItemList, ErpPurchaseRequestRespVO.Item.class, item -> {
+                    MapUtils.findAndThen(itemMap, item.getProductId(), mdmItem -> item.setProductName(mdmItem.getName())
+                            .setProductBarCode(mdmItem.getBarCode()));
+                }))));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰緱閲囪喘鐢宠鍒嗛〉")
+    @PreAuthorize("@ss.hasPermission('erp:purchase-request:query')")
+    public CommonResult<PageResult<ErpPurchaseRequestRespVO>> getPurchaseRequestPage(@Valid ErpPurchaseRequestPageReqVO pageReqVO) {
+        PageResult<ErpPurchaseRequestDO> pageResult = purchaseRequestService.getPurchaseRequestPage(pageReqVO);
+        return success(buildPurchaseRequestVOPageResult(pageResult));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "瀵煎嚭閲囪喘鐢宠 Excel")
+    @PreAuthorize("@ss.hasPermission('erp:purchase-request:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportPurchaseRequestExcel(@Valid ErpPurchaseRequestPageReqVO pageReqVO,
+                                           HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        List<ErpPurchaseRequestRespVO> list = buildPurchaseRequestVOPageResult(purchaseRequestService.getPurchaseRequestPage(pageReqVO)).getList();
+        ExcelUtils.write(response, "閲囪喘鐢宠.xls", "鏁版嵁", ErpPurchaseRequestRespVO.class, list);
+    }
+
+    @PutMapping("/submit")
+    @Operation(summary = "鎻愪氦閲囪喘鐢宠瀹℃壒")
+    @PreAuthorize("@ss.hasPermission('erp:purchase-request:submit')")
+    public CommonResult<Boolean> submitPurchaseRequest(@RequestParam("id") Long id,
+                                                       @RequestParam("processDefinitionKey") String processDefinitionKey) {
+        Long userId = getLoginUserId();
+        purchaseRequestService.submitPurchaseRequest(id, processDefinitionKey, userId);
+        return success(true);
+    }
+
+    @GetMapping("/approve-process-list")
+    @Operation(summary = "鑾峰彇閲囪喘鐢宠瀹℃壒娴佺▼鍒楄〃")
+    @PreAuthorize("@ss.hasPermission('erp:purchase-request:query')")
+    public CommonResult<List<Map<String, Object>>> getApproveProcessList() {
+        return success(purchaseRequestService.getPurchaseRequestApproveProcessDefinitionList());
+    }
+
+    @PostMapping("/generate-order")
+    @Operation(summary = "鐢熸垚閲囪喘璁㈠崟")
+    @PreAuthorize("@ss.hasPermission('erp:purchase-request:generate-order')")
+    public CommonResult<Long> generatePurchaseOrder(@RequestParam("id") Long id,
+                                                    @RequestParam("supplierId") Long supplierId) {
+        return success(purchaseRequestService.generatePurchaseOrder(id, supplierId));
+    }
+
+    private PageResult<ErpPurchaseRequestRespVO> buildPurchaseRequestVOPageResult(PageResult<ErpPurchaseRequestDO> pageResult) {
+        if (CollUtil.isEmpty(pageResult.getList())) {
+            return PageResult.empty(pageResult.getTotal());
+        }
+        // 1.1 鐢宠鏄庣粏
+        List<ErpPurchaseRequestItemDO> purchaseRequestItemList = purchaseRequestService.getPurchaseRequestItemListByRequestIds(
+                convertSet(pageResult.getList(), ErpPurchaseRequestDO::getId));
+        Map<Long, List<ErpPurchaseRequestItemDO>> purchaseRequestItemMap = convertMultiMap(purchaseRequestItemList, ErpPurchaseRequestItemDO::getRequestId);
+        // 1.2 鐗╂枡淇℃伅
+        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(
+                convertSet(purchaseRequestItemList, ErpPurchaseRequestItemDO::getProductId));
+        // 1.3 渚涘簲鍟嗕俊鎭�
+        Map<Long, ErpSupplierDO> supplierMap = supplierService.getSupplierMap(
+                convertSet(pageResult.getList(), ErpPurchaseRequestDO::getSupplierId));
+        // 1.4 鐢ㄦ埛淇℃伅
+        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
+                convertSet(pageResult.getList(), ErpPurchaseRequestDO::getRequestUserId));
+        // 1.5 閮ㄩ棬淇℃伅
+        Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(
+                convertSet(pageResult.getList(), ErpPurchaseRequestDO::getRequestDeptId));
+        // 2. 寮�濮嬫嫾鎺�
+        return BeanUtils.toBean(pageResult, ErpPurchaseRequestRespVO.class, purchaseRequest -> {
+            purchaseRequest.setItems(BeanUtils.toBean(purchaseRequestItemMap.get(purchaseRequest.getId()), ErpPurchaseRequestRespVO.Item.class,
+                    item -> MapUtils.findAndThen(itemMap, item.getProductId(), mdmItem -> item.setProductName(mdmItem.getName())
+                            .setProductBarCode(mdmItem.getBarCode()))));
+            purchaseRequest.setProductNames(CollUtil.join(purchaseRequest.getItems(), "锛�", ErpPurchaseRequestRespVO.Item::getProductName));
+            MapUtils.findAndThen(supplierMap, purchaseRequest.getSupplierId(), supplier -> purchaseRequest.setSupplierName(supplier.getName()));
+            MapUtils.findAndThen(userMap, purchaseRequest.getRequestUserId(), user -> purchaseRequest.setRequestUserName(user.getNickname()));
+            MapUtils.findAndThen(deptMap, purchaseRequest.getRequestDeptId(), dept -> purchaseRequest.setRequestDeptName(dept.getName()));
+        });
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderRespVO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderRespVO.java
index e6a6aa4..dae576f 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderRespVO.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderRespVO.java
@@ -92,6 +92,9 @@
     @Schema(description = "閲囪喘鍏ュ簱鏁伴噺", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
     private BigDecimal inCount;
 
+    @Schema(description = "鍏ュ簱鐘舵�侊紙0:鏈叆搴� 1:閮ㄥ垎鍏ュ簱 2:鍏ㄩ儴鍏ュ簱锛�", example = "0")
+    private Integer inStatus;
+
     // ========== 閲囪喘閫�璐э紙鍑哄簱锛夛級 ==========
 
     @Schema(description = "閲囪喘閫�璐ф暟閲�", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
@@ -145,8 +148,13 @@
         private String productUnitName;
 
         @Schema(description = "搴撳瓨鏁伴噺", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
-        private BigDecimal stockCount; // 璇ュ瓧娈典粎浠呭湪鈥滆鎯呪�濆拰鈥滅紪杈戔�濇椂浣跨敤
+        private BigDecimal stockCount; // 璇ュ瓧娈典粎浠呭湪璇︽儏鍜岀紪杈戞椂浣跨敤
+
+        // ========== 璐ㄦ ==========
+
+        @Schema(description = "鏄惁闇�瑕佹潵鏂欐楠�", example = "true")
+        private Boolean qcCheckFlag;
 
     }
 
-}
+}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderSaveReqVO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderSaveReqVO.java
index 061ed9d..d259001 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderSaveReqVO.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderSaveReqVO.java
@@ -68,6 +68,9 @@
         @Schema(description = "澶囨敞", example = "闅忎究")
         private String remark;
 
+        @Schema(description = "鏄惁闇�瑕佹潵鏂欐楠�", example = "true")
+        private Boolean qcCheckFlag;
+
     }
 
 }
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestPageReqVO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestPageReqVO.java
new file mode 100644
index 0000000..2ee0563
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestPageReqVO.java
@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request;
+
+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;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
+
+@Schema(description = "绠$悊鍚庡彴 - ERP 閲囪喘鐢宠鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ErpPurchaseRequestPageReqVO extends PageParam {
+
+    @Schema(description = "閲囪喘鐢宠鍗曞彿", example = "CGSQ001")
+    private String no;
+
+    @Schema(description = "鐢宠浜虹紪鍙�", example = "1")
+    private Long requestUserId;
+
+    @Schema(description = "鐢宠閮ㄩ棬缂栧彿", example = "1")
+    private Long requestDeptId;
+
+    @Schema(description = "渚涘簲鍟嗙紪鍙�", example = "1")
+    private Long supplierId;
+
+    @Schema(description = "鐘舵��", example = "0")
+    private Integer status;
+
+    @Schema(description = "鐢宠鏃堕棿")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
+    private LocalDateTime[] requestTime;
+
+    @Schema(description = "浜у搧缂栧彿", example = "1")
+    private Long productId;
+
+}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestRespVO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestRespVO.java
new file mode 100644
index 0000000..40fcab4
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestRespVO.java
@@ -0,0 +1,130 @@
+package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - ERP 閲囪喘鐢宠 Response VO")
+@Data
+public class ErpPurchaseRequestRespVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long id;
+
+    @Schema(description = "閲囪喘鐢宠鍗曞彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "CGSQ001")
+    private String no;
+
+    @Schema(description = "鐘舵��", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+    private Integer status;
+
+    @Schema(description = "鐢宠浜虹紪鍙�", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long requestUserId;
+
+    @Schema(description = "鐢宠浜哄鍚�", example = "寮犱笁")
+    private String requestUserName;
+
+    @Schema(description = "鐢宠閮ㄩ棬缂栧彿", example = "1")
+    private Long requestDeptId;
+
+    @Schema(description = "鐢宠閮ㄩ棬鍚嶇О", example = "鐢熶骇閮�")
+    private String requestDeptName;
+
+    @Schema(description = "鐢宠鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime requestTime;
+
+    @Schema(description = "鐢宠鐞嗙敱", example = "鐢熶骇闇�瑕�")
+    private String requestReason;
+
+    @Schema(description = "渚涘簲鍟嗙紪鍙�", example = "1")
+    private Long supplierId;
+
+    @Schema(description = "渚涘簲鍟嗗悕绉�", example = "渚涘簲鍟咥")
+    private String supplierName;
+
+    @Schema(description = "瀹氶噾閲戦", example = "1000.00")
+    private BigDecimal depositPrice;
+
+    @Schema(description = "鍚堣鏁伴噺", requiredMode = Schema.RequiredMode.REQUIRED)
+    private BigDecimal totalCount;
+
+    @Schema(description = "鍚堣浠锋牸", requiredMode = Schema.RequiredMode.REQUIRED)
+    private BigDecimal totalPrice;
+
+    @Schema(description = "浜у搧鍚嶇О鍒楄〃", example = "浜у搧A,浜у搧B")
+    private String productNames;
+
+    @Schema(description = "鐢熸垚鐨勯噰璐鍗曠紪鍙�", example = "1")
+    private Long orderId;
+
+    @Schema(description = "鐢熸垚鐨勯噰璐鍗曞彿", example = "CGDD001")
+    private String orderNo;
+
+    @Schema(description = "鍏ュ簱鐘舵�侊紙0:鏈叆搴� 1:閮ㄥ垎鍏ュ簱 2:鍏ㄩ儴鍏ュ簱锛�", example = "0")
+    private Integer inStatus;
+
+    @Schema(description = "闄勪欢鍦板潃", example = "https://xxx.com/xxx.pdf")
+    private String fileUrl;
+
+    @Schema(description = "澶囨敞", example = "澶囨敞")
+    private String remark;
+
+    @Schema(description = "鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+    @Schema(description = "鐢宠鏄庣粏鍒楄〃")
+    private java.util.List<Item> items;
+
+    @Schema(description = "鐢宠鏄庣粏椤�")
+    @Data
+    public static class Item {
+
+        @Schema(description = "缂栧彿", example = "1")
+        private Long id;
+
+        @Schema(description = "浜у搧缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+        private Long productId;
+
+        @Schema(description = "浜у搧鍚嶇О", example = "浜у搧A")
+        private String productName;
+
+        @Schema(description = "浜у搧鏉$爜", example = "12345678")
+        private String productBarCode;
+
+        @Schema(description = "浜у搧鍗曚綅缂栧彿", example = "1")
+        private Long productUnitId;
+
+        @Schema(description = "浜у搧鍗曚綅鍚嶇О", example = "涓�")
+        private String productUnitName;
+
+        @Schema(description = "浜у搧鍙傝�冨崟浠�", example = "100.00")
+        private BigDecimal productPrice;
+
+        @Schema(description = "鏁伴噺", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+        private BigDecimal count;
+
+        @Schema(description = "鎬讳环", example = "1000.00")
+        private BigDecimal totalPrice;
+
+        @Schema(description = "绋庣巼锛岀櫨鍒嗘瘮", example = "13")
+        private BigDecimal taxPercent;
+
+        @Schema(description = "绋庨", example = "130.00")
+        private BigDecimal taxPrice;
+
+        @Schema(description = "闇�姹傛棩鏈�")
+        private LocalDateTime demandTime;
+
+        @Schema(description = "澶囨敞", example = "澶囨敞")
+        private String remark;
+
+        @Schema(description = "鏄惁闇�瑕佹潵鏂欐楠�", example = "true")
+        private Boolean qcCheckFlag;
+
+        @Schema(description = "鍏ュ簱鏁伴噺", example = "10.00")
+        private BigDecimal inCount;
+
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestSaveReqVO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestSaveReqVO.java
new file mode 100644
index 0000000..97b9f8b
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestSaveReqVO.java
@@ -0,0 +1,92 @@
+package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "绠$悊鍚庡彴 - ERP 閲囪喘鐢宠鏂板/淇敼 Request VO")
+@Data
+public class ErpPurchaseRequestSaveReqVO {
+
+    @Schema(description = "缂栧彿锛堜慨鏀规椂蹇呭~锛�", example = "1")
+    private Long id;
+
+    @Schema(description = "鐢宠鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "鐢宠鏃堕棿涓嶈兘涓虹┖")
+    private LocalDateTime requestTime;
+
+    @Schema(description = "鐢宠鐞嗙敱", example = "鐢熶骇闇�瑕�")
+    private String requestReason;
+
+    @Schema(description = "渚涘簲鍟嗙紪鍙�", example = "1")
+    private Long supplierId;
+
+    @Schema(description = "浼樻儬鐜囷紝鐧惧垎姣�", example = "10")
+    private BigDecimal discountPercent;
+
+    @Schema(description = "瀹氶噾閲戦", example = "1000.00")
+    private BigDecimal depositPrice;
+
+    @Schema(description = "闄勪欢鍦板潃", example = "https://xxx.com/xxx.pdf")
+    private String fileUrl;
+
+    @Schema(description = "澶囨敞", example = "澶囨敞")
+    private String remark;
+
+    @Schema(description = "鐢宠鏄庣粏鍒楄〃", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "鐢宠鏄庣粏涓嶈兘涓虹┖")
+    @Valid
+    private List<Item> items;
+
+    @Schema(description = "鐢宠鏄庣粏椤�")
+    @Data
+    public static class Item {
+
+        @Schema(description = "缂栧彿锛堜慨鏀规椂蹇呭~锛�", example = "1")
+        private Long id;
+
+        @Schema(description = "浜у搧缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+        @NotNull(message = "浜у搧缂栧彿涓嶈兘涓虹┖")
+        private Long productId;
+
+        @Schema(description = "浜у搧鍗曚綅缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+        @NotNull(message = "浜у搧鍗曚綅涓嶈兘涓虹┖")
+        private Long productUnitId;
+
+        @Schema(description = "浜у搧鍙傝�冨崟浠�", example = "100.00")
+        private BigDecimal productPrice;
+
+        @Schema(description = "鏁伴噺", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+        @NotNull(message = "鏁伴噺涓嶈兘涓虹┖")
+        private BigDecimal count;
+
+        @Schema(description = "绋庣巼锛岀櫨鍒嗘瘮", example = "13")
+        private BigDecimal taxPercent;
+
+        @Schema(description = "闇�姹傛棩鏈�")
+        private LocalDateTime demandTime;
+
+        @Schema(description = "澶囨敞", example = "澶囨敞")
+        private String remark;
+
+        @Schema(description = "鏄惁闇�瑕佹潵鏂欐楠�", example = "true")
+        private Boolean qcCheckFlag;
+
+        @Schema(description = "浜у搧鍚嶇О锛堝墠绔睍绀虹敤锛屽悗绔拷鐣ワ級", example = "浜у搧A")
+        private String productName;
+
+        @Schema(description = "浜у搧鏉$爜锛堝墠绔睍绀虹敤锛屽悗绔拷鐣ワ級", example = "12345678")
+        private String productBarCode;
+
+        @Schema(description = "浜у搧鍗曚綅鍚嶇О锛堝墠绔睍绀虹敤锛屽悗绔拷鐣ワ級", example = "涓�")
+        private String productUnitName;
+
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderDO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderDO.java
index 6e6b9d4..88a67a8 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderDO.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderDO.java
@@ -119,4 +119,9 @@
      */
     private Integer inStatus;
 
+    /**
+     * BPM 娴佺▼瀹炰緥缂栧彿
+     */
+    private String processInstanceId;
+
 }
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderItemDO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderItemDO.java
index f20b148..228c74c 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderItemDO.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderItemDO.java
@@ -35,6 +35,12 @@
      */
     private Long orderId;
     /**
+     * 閲囪喘鐢宠鏄庣粏缂栧彿
+     *
+     * 鍏宠仈 {@link ErpPurchaseRequestItemDO#getId()}
+     */
+    private Long requestItemId;
+    /**
      * 鐗╂枡缂栧彿
      *
      * 鍏宠仈 {@link cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO#getId()}
@@ -89,4 +95,10 @@
      */
     private BigDecimal returnCount;
 
+    // ========== 璐ㄦ ==========
+    /**
+     * 鏄惁闇�瑕佹潵鏂欐楠�
+     */
+    private Boolean qcCheckFlag;
+
 }
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestDO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestDO.java
new file mode 100644
index 0000000..ec47b9e
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestDO.java
@@ -0,0 +1,141 @@
+package cn.iocoder.yudao.module.erp.dal.dataobject.purchase;
+
+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.*;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * ERP 閲囪喘鐢宠 DO
+ */
+@TableName("erp_purchase_request")
+@KeySequence("erp_purchase_request_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ErpPurchaseRequestDO extends BaseDO {
+
+    /**
+     * 缂栧彿
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 閲囪喘鐢宠鍗曞彿
+     */
+    private String no;
+
+    /**
+     * 鐘舵��
+     *
+     * 0: 鏈彁浜�
+     * 10: 瀹℃壒涓�
+     * 20: 瀹℃牳閫氳繃
+     * 30: 瀹℃牳涓嶉�氳繃
+     * 40: 宸插彇娑�
+     */
+    private Integer status;
+
+    /**
+     * 鐢宠浜虹紪鍙�
+     */
+    private Long requestUserId;
+
+    /**
+     * 鐢宠閮ㄩ棬缂栧彿
+     */
+    private Long requestDeptId;
+
+    /**
+     * 鐢宠鏃堕棿
+     */
+    private LocalDateTime requestTime;
+
+    /**
+     * 鐢宠鐞嗙敱
+     */
+    private String requestReason;
+
+    /**
+     * 渚涘簲鍟嗙紪鍙凤紙鍙�夛級
+     */
+    private Long supplierId;
+
+    /**
+     * 鍚堣鏁伴噺
+     */
+    private BigDecimal totalCount;
+
+    /**
+     * 鍚堣浠锋牸
+     */
+    private BigDecimal totalPrice;
+
+    /**
+     * 鍚堣浜у搧浠锋牸
+     */
+    private BigDecimal totalProductPrice;
+
+    /**
+     * 鍚堣绋庨
+     */
+    private BigDecimal totalTaxPrice;
+
+    /**
+     * 浼樻儬鐜囷紝鐧惧垎姣�
+     */
+    private BigDecimal discountPercent;
+
+    /**
+     * 浼樻儬閲戦
+     */
+    private BigDecimal discountPrice;
+
+    /**
+     * 瀹氶噾閲戦
+     */
+    private BigDecimal depositPrice;
+
+    /**
+     * 闄勪欢鍦板潃
+     */
+    private String fileUrl;
+
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+
+    /**
+     * BPM 娴佺▼瀹炰緥缂栧彿
+     */
+    private String processInstanceId;
+
+    /**
+     * 鐢熸垚鐨勯噰璐鍗曠紪鍙�
+     */
+    private Long orderId;
+
+    /**
+     * 鐢熸垚鐨勯噰璐鍗曞彿
+     */
+    private String orderNo;
+
+    /**
+     * 鍏ュ簱鐘舵��
+     *
+     * 0: 鏈叆搴�
+     * 1: 閮ㄥ垎鍏ュ簱
+     * 2: 鍏ㄩ儴鍏ュ簱
+     */
+    private Integer inStatus;
+
+}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestItemDO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestItemDO.java
new file mode 100644
index 0000000..40ac455
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestItemDO.java
@@ -0,0 +1,91 @@
+package cn.iocoder.yudao.module.erp.dal.dataobject.purchase;
+
+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.*;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * ERP 閲囪喘鐢宠鏄庣粏 DO
+ */
+@TableName("erp_purchase_request_items")
+@KeySequence("erp_purchase_request_items_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ErpPurchaseRequestItemDO extends BaseDO {
+
+    /**
+     * 缂栧彿
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 閲囪喘鐢宠缂栧彿
+     */
+    private Long requestId;
+
+    /**
+     * 浜у搧缂栧彿
+     */
+    private Long productId;
+
+    /**
+     * 浜у搧鍗曚綅缂栧彿
+     */
+    private Long productUnitId;
+
+    /**
+     * 浜у搧鍙傝�冨崟浠�
+     */
+    private BigDecimal productPrice;
+
+    /**
+     * 鏁伴噺
+     */
+    private BigDecimal count;
+
+    /**
+     * 鎬讳环
+     */
+    private BigDecimal totalPrice;
+
+    /**
+     * 绋庣巼锛岀櫨鍒嗘瘮
+     */
+    private BigDecimal taxPercent;
+
+    /**
+     * 绋庨
+     */
+    private BigDecimal taxPrice;
+
+    /**
+     * 闇�姹傛棩鏈�
+     */
+    private LocalDateTime demandTime;
+
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+
+    /**
+     * 鏄惁闇�瑕佹潵鏂欐楠�
+     */
+    private Boolean qcCheckFlag;
+
+    /**
+     * 鍏ュ簱鏁伴噺
+     */
+    private BigDecimal inCount;
+
+}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseRequestItemMapper.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseRequestItemMapper.java
new file mode 100644
index 0000000..fe00f73
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseRequestItemMapper.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.erp.dal.mysql.purchase;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestItemDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * ERP 閲囪喘鐢宠鏄庣粏 Mapper
+ */
+@Mapper
+public interface ErpPurchaseRequestItemMapper extends BaseMapperX<ErpPurchaseRequestItemDO> {
+
+    default List<ErpPurchaseRequestItemDO> selectListByRequestId(Long requestId) {
+        return selectList(ErpPurchaseRequestItemDO::getRequestId, requestId);
+    }
+
+    default List<ErpPurchaseRequestItemDO> selectListByRequestIds(Collection<Long> requestIds) {
+        return selectList(ErpPurchaseRequestItemDO::getRequestId, requestIds);
+    }
+
+    default void deleteByRequestId(Long requestId) {
+        delete(new LambdaQueryWrapperX<ErpPurchaseRequestItemDO>().eq(ErpPurchaseRequestItemDO::getRequestId, requestId));
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseRequestMapper.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseRequestMapper.java
new file mode 100644
index 0000000..9fbd884
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseRequestMapper.java
@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.erp.dal.mysql.purchase;
+
+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.erp.controller.admin.purchase.vo.request.ErpPurchaseRequestPageReqVO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * ERP 閲囪喘鐢宠 Mapper
+ */
+@Mapper
+public interface ErpPurchaseRequestMapper extends BaseMapperX<ErpPurchaseRequestDO> {
+
+    default ErpPurchaseRequestDO selectByNo(String no) {
+        return selectOne(ErpPurchaseRequestDO::getNo, no);
+    }
+
+    default List<ErpPurchaseRequestDO> selectRequestListByIds(Collection<Long> ids) {
+        return selectList(ErpPurchaseRequestDO::getId, ids);
+    }
+
+    default List<ErpPurchaseRequestDO> selectList(Long supplierId, Integer status) {
+        return selectList(new LambdaQueryWrapperX<ErpPurchaseRequestDO>()
+                .eqIfPresent(ErpPurchaseRequestDO::getSupplierId, supplierId)
+                .eqIfPresent(ErpPurchaseRequestDO::getStatus, status));
+    }
+
+    default PageResult<ErpPurchaseRequestDO> selectPage(ErpPurchaseRequestPageReqVO pageReqVO) {
+        return selectPage(pageReqVO, new LambdaQueryWrapperX<ErpPurchaseRequestDO>()
+                .likeIfPresent(ErpPurchaseRequestDO::getNo, pageReqVO.getNo())
+                .eqIfPresent(ErpPurchaseRequestDO::getRequestUserId, pageReqVO.getRequestUserId())
+                .eqIfPresent(ErpPurchaseRequestDO::getRequestDeptId, pageReqVO.getRequestDeptId())
+                .eqIfPresent(ErpPurchaseRequestDO::getSupplierId, pageReqVO.getSupplierId())
+                .eqIfPresent(ErpPurchaseRequestDO::getStatus, pageReqVO.getStatus())
+                .betweenIfPresent(ErpPurchaseRequestDO::getRequestTime, pageReqVO.getRequestTime())
+                .orderByDesc(ErpPurchaseRequestDO::getId));
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/redis/no/ErpNoRedisDAO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/redis/no/ErpNoRedisDAO.java
index 463b47c..fa36e91 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/redis/no/ErpNoRedisDAO.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/redis/no/ErpNoRedisDAO.java
@@ -30,6 +30,11 @@
     public static final String PURCHASE_ORDER_NO_PREFIX = "CGDD";
 
     /**
+     * 閲囪喘鐢宠 {@link cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestDO}
+     */
+    public static final String PURCHASE_REQUEST_NO_PREFIX = "CGSQ";
+
+    /**
      * 浠樻鍗� {@link cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinancePaymentDO}
      */
     public static final String FINANCE_PAYMENT_NO_PREFIX = "FKD";
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpAuditStatus.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpAuditStatus.java
index 81412f1..32aae4f 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpAuditStatus.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpAuditStatus.java
@@ -17,7 +17,8 @@
 @Getter
 public enum ErpAuditStatus implements ArrayValuable<Integer> {
 
-    PROCESS(10, "鏈鏍�"), // 瀹℃牳涓�
+    PROCESS(10, "鏈鏍�"), // 鏈鏍�/鑽夌
+    APPROVING(15, "瀹℃壒涓�"), // 瀹℃壒涓�
     APPROVE(20, "宸插鏍�"), // 瀹℃牳閫氳繃
     CANCEL(30, "宸蹭綔搴�"); // 宸蹭綔搴�/宸叉挙閿�
 
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpPurchaseRequestStatusEnum.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpPurchaseRequestStatusEnum.java
new file mode 100644
index 0000000..6eb1ad3
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpPurchaseRequestStatusEnum.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.erp.enums;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * ERP 閲囪喘鐢宠鐘舵�佹灇涓�
+ */
+@RequiredArgsConstructor
+@Getter
+public enum ErpPurchaseRequestStatusEnum implements ArrayValuable<Integer> {
+
+    DRAFT(0, "鏈彁浜�"),
+    PROCESS(10, "瀹℃壒涓�"),
+    APPROVE(20, "瀹℃牳閫氳繃"),
+    REJECT(30, "瀹℃牳涓嶉�氳繃"),
+    CANCEL(40, "宸插彇娑�");
+
+    public static final Integer[] ARRAYS = Arrays.stream(values()).map(ErpPurchaseRequestStatusEnum::getStatus).toArray(Integer[]::new);
+
+    /**
+     * 鐘舵��
+     */
+    private final Integer status;
+    /**
+     * 鐘舵�佸悕
+     */
+    private final String name;
+
+    @Override
+    public Integer[] array() {
+        return ARRAYS;
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java
index 7294529..9082005 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java
@@ -27,6 +27,8 @@
     ErrorCode PURCHASE_ORDER_PROCESS_FAIL_EXISTS_RETURN = new ErrorCode(1_030_101_010, "鍙嶅鏍稿け璐ワ紝宸插瓨鍦ㄥ搴旂殑閲囪喘閫�璐у崟");
     ErrorCode PURCHASE_ORDER_BPM_CATEGORY_NOT_EXISTS = new ErrorCode(1_030_101_011, "閲囪喘瀹℃牳鍒嗙被涓嶅瓨鍦紝璇峰厛鍦˙PM妯″潡鍒涘缓閲囪喘瀹℃牳娴佺▼鍒嗙被");
     ErrorCode PURCHASE_ORDER_BPM_PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_030_101_012, "閲囪喘瀹℃牳鍒嗙被涓嬫病鏈夊彲鐢ㄧ殑娴佺▼妯″瀷锛岃鍏堝垱寤哄苟鍙戝竷娴佺▼");
+    ErrorCode PURCHASE_ORDER_SUBMIT_FAIL_STATUS = new ErrorCode(1_030_101_013, "鎻愪氦瀹℃壒澶辫触锛屽彧鏈夋湭瀹℃牳鐘舵�佺殑閲囪喘璁㈠崟鎵嶈兘鎻愪氦");
+    ErrorCode PURCHASE_ORDER_ALL_IN = new ErrorCode(1_030_101_014, "閲囪喘璁㈠崟宸插叏閮ㄥ叆搴擄紝鏃犻渶纭鏀惰揣");
 
     // ========== ERP 閲囪喘鍏ュ簱锛�1-030-102-000锛� ==========
     ErrorCode PURCHASE_IN_NOT_EXISTS = new ErrorCode(1_030_102_000, "閲囪喘鍏ュ簱鍗曚笉瀛樺湪");
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java
index 0fe2151..a7b6fdd 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java
@@ -43,6 +43,30 @@
     void updatePurchaseOrderStatus(Long id, Integer status);
 
     /**
+     * 鎻愪氦閲囪喘璁㈠崟瀹℃壒
+     *
+     * @param id 缂栧彿
+     * @param processDefinitionKey 娴佺▼瀹氫箟 Key
+     * @param userId 鐢ㄦ埛缂栧彿
+     */
+    void submitPurchaseOrder(Long id, String processDefinitionKey, Long userId);
+
+    /**
+     * 鑾峰彇閲囪喘璁㈠崟瀹℃壒娴佺▼鍒楄〃
+     *
+     * @return 娴佺▼瀹氫箟鍒楄〃
+     */
+    List<Map<String, Object>> getPurchaseOrderApproveProcessDefinitionList();
+
+    /**
+     * 鏇存柊閲囪喘璁㈠崟瀹℃壒缁撴灉锛圔PM 鍥炶皟锛�
+     *
+     * @param id 缂栧彿
+     * @param bpmResult BPM 瀹℃壒缁撴灉
+     */
+    void updatePurchaseOrderAuditStatus(Long id, Integer bpmResult);
+
+    /**
      * 鏇存柊閲囪喘璁㈠崟鐨勫叆搴撴暟閲�
      *
      * @param id 缂栧彿
@@ -136,4 +160,14 @@
      */
     void updatePurchaseOrderInStatus(Long orderId);
 
+    /**
+     * 纭鏀惰揣
+     *
+     * 鐢熸垚鍒拌揣閫氱煡鍗曪紝鐘舵�佷负寰呭叆搴�
+     *
+     * @param id 閲囪喘璁㈠崟缂栧彿
+     * @return 鍒拌揣閫氱煡鍗曠紪鍙�
+     */
+    Long confirmReceipt(Long id);
+
 }
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java
index ec5cd18..7e24cea 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java
@@ -5,17 +5,32 @@
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
+import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
+import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
+import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
+import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
 import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderPageReqVO;
 import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderSaveReqVO;
 import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderDO;
 import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderItemDO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestDO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestItemDO;
 import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseOrderItemMapper;
 import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseOrderMapper;
+import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseRequestItemMapper;
+import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseRequestMapper;
 import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO;
 import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
 import cn.iocoder.yudao.module.erp.enums.ErpPurchaseOrderInStatusEnum;
 import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService;
+import cn.iocoder.yudao.module.mes.api.arrivalnotice.MesArrivalNoticeApi;
 import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.flowable.engine.repository.ProcessDefinition;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
@@ -34,7 +49,13 @@
  */
 @Service
 @Validated
+@Slf4j
 public class ErpPurchaseOrderServiceImpl implements ErpPurchaseOrderService {
+
+    /**
+     * BPM 閲囪喘璁㈠崟瀹℃壒鍒嗙被缂栫爜
+     */
+    private static final String PURCHASE_ORDER_APPROVE_CATEGORY_CODE = "purchase_request_approve";
 
     @Resource
     private ErpPurchaseOrderMapper purchaseOrderMapper;
@@ -48,6 +69,22 @@
     private ErpSupplierService supplierService;
     @Resource
     private ErpAccountService accountService;
+
+    @Resource
+    private BpmProcessInstanceApi processInstanceApi;
+    @Resource
+    private BpmCategoryService bpmCategoryService;
+    @Resource
+    private BpmProcessDefinitionService bpmProcessDefinitionService;
+
+    @Resource
+    @Lazy
+    private MesArrivalNoticeApi arrivalNoticeApi;
+
+    @Resource
+    private ErpPurchaseRequestItemMapper purchaseRequestItemMapper;
+    @Resource
+    private ErpPurchaseRequestMapper purchaseRequestMapper;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -66,9 +103,10 @@
             throw exception(PURCHASE_ORDER_NO_EXISTS);
         }
 
-        // 2.1 鎻掑叆璁㈠崟锛堥噰璐鍗曚笉闇�瑕佸鎵癸紝鐩存帴涓哄凡瀹℃牳鐘舵�侊級
+        // 2.1 鎻掑叆璁㈠崟锛堥噰璐鍗曞垱寤哄悗涓烘湭瀹℃牳鐘舵�侊紝闇�瑕佹彁浜ゅ鎵癸級
         ErpPurchaseOrderDO purchaseOrder = BeanUtils.toBean(createReqVO, ErpPurchaseOrderDO.class, in -> in
-                .setNo(no).setStatus(ErpAuditStatus.APPROVE.getStatus()));
+                .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus())
+                .setInCount(BigDecimal.ZERO).setInStatus(ErpPurchaseOrderInStatusEnum.NOT_IN.getStatus()));
         calculateTotalPrice(purchaseOrder, purchaseOrderItems);
         purchaseOrderMapper.insert(purchaseOrder);
         // 2.2 鎻掑叆璁㈠崟椤�
@@ -119,8 +157,6 @@
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void updatePurchaseOrderStatus(Long id, Integer status) {
-        // 閲囪喘璁㈠崟涓嶅啀鏀寔鐘舵�佹祦杞紙涓嶉渶瑕佸鎵癸級
-        // 姝ゆ柟娉曚繚鐣欑敤浜庣壒娈婃儏鍐碉紙濡傚弽瀹℃牳鍚庨噸鏂板鏍革級
         boolean approve = ErpAuditStatus.APPROVE.getStatus().equals(status);
         // 1.1 鏍¢獙瀛樺湪
         ErpPurchaseOrderDO purchaseOrder = validatePurchaseOrderExists(id);
@@ -145,6 +181,109 @@
         if (updateCount == 0) {
             throw exception(approve ? PURCHASE_ORDER_APPROVE_FAIL : PURCHASE_ORDER_PROCESS_FAIL);
         }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void submitPurchaseOrder(Long id, String processDefinitionKey, Long userId) {
+        // 1. 鏍¢獙瀛樺湪 + 鏈鏍哥姸鎬�
+        ErpPurchaseOrderDO purchaseOrder = validatePurchaseOrderExists(id);
+        if (!ErpAuditStatus.PROCESS.getStatus().equals(purchaseOrder.getStatus())) {
+            throw exception(PURCHASE_ORDER_SUBMIT_FAIL_STATUS);
+        }
+
+        // 2. 鍒涘缓 BPM 娴佺▼瀹炰緥
+        String processInstanceId = processInstanceApi.createProcessInstance(userId,
+                new BpmProcessInstanceCreateReqDTO()
+                        .setProcessDefinitionKey(processDefinitionKey)
+                        .setBusinessKey(String.valueOf(id)));
+
+        // 3. 鏇存柊閲囪喘璁㈠崟鐘舵�佷负瀹℃壒涓�
+        purchaseOrderMapper.updateById(new ErpPurchaseOrderDO()
+                .setId(id)
+                .setStatus(ErpAuditStatus.APPROVING.getStatus())
+                .setProcessInstanceId(processInstanceId));
+    }
+
+    @Override
+    public List<Map<String, Object>> getPurchaseOrderApproveProcessDefinitionList() {
+        // 1. 鏍¢獙鍒嗙被鍜屾祦绋嬪畾涔夋槸鍚﹀瓨鍦�
+        validatePurchaseOrderApproveCategoryAndProcessDefinition();
+
+        // 2. 鑾峰彇鍒嗙被涓嬬殑娴佺▼瀹氫箟淇℃伅
+        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
+                .getProcessDefinitionInfoListByCategory(PURCHASE_ORDER_APPROVE_CATEGORY_CODE);
+        if (CollUtil.isEmpty(definitionInfoList)) {
+            return Collections.emptyList();
+        }
+
+        // 3. 閬嶅巻鑾峰彇娴佺▼瀹氫箟璇︽儏锛岃繃婊ゆ縺娲荤姸鎬侊紝淇濈暀鏈�鏂扮増鏈�
+        Map<String, ProcessDefinition> latestVersionMap = new HashMap<>();
+        for (BpmProcessDefinitionInfoDO info : definitionInfoList) {
+            ProcessDefinition pd = bpmProcessDefinitionService.getProcessDefinition(info.getProcessDefinitionId());
+            if (pd == null || pd.isSuspended()) {
+                continue;
+            }
+            ProcessDefinition existing = latestVersionMap.get(pd.getKey());
+            if (existing == null || pd.getVersion() > existing.getVersion()) {
+                latestVersionMap.put(pd.getKey(), pd);
+            }
+        }
+
+        // 4. 杩斿洖娴佺▼瀹氫箟鍒楄〃
+        List<Map<String, Object>> result = new ArrayList<>();
+        for (ProcessDefinition pd : latestVersionMap.values()) {
+            Map<String, Object> item = new HashMap<>();
+            item.put("id", pd.getId());
+            item.put("key", pd.getKey());
+            item.put("name", pd.getName());
+            result.add(item);
+        }
+        return result;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updatePurchaseOrderAuditStatus(Long id, Integer bpmResult) {
+        // 1. 鏍¢獙瀛樺湪
+        ErpPurchaseOrderDO purchaseOrder = validatePurchaseOrderExists(id);
+        if (!ErpAuditStatus.APPROVING.getStatus().equals(purchaseOrder.getStatus())) {
+            log.warn("[updatePurchaseOrderAuditStatus] 閲囪喘璁㈠崟({}) 涓嶅浜庡鎵逛腑鐘舵��", id);
+            return;
+        }
+
+        // 2. 鏍规嵁瀹℃壒缁撴灉鏇存柊鐘舵��
+        Integer newStatus = convertBpmResultToAuditStatus(bpmResult);
+        if (newStatus != null) {
+            purchaseOrderMapper.updateById(new ErpPurchaseOrderDO()
+                    .setId(id).setStatus(newStatus));
+        }
+    }
+
+    /**
+     * 鏍¢獙閲囪喘璁㈠崟瀹℃壒鍒嗙被鍜屾祦绋嬪畾涔夋槸鍚﹀瓨鍦�
+     */
+    private void validatePurchaseOrderApproveCategoryAndProcessDefinition() {
+        // 1. 鏍¢獙鍒嗙被鏄惁瀛樺湪
+        List<BpmCategoryDO> categories = bpmCategoryService.getCategoryListByCode(
+                Collections.singletonList(PURCHASE_ORDER_APPROVE_CATEGORY_CODE));
+        if (CollUtil.isEmpty(categories)) {
+            throw exception(PURCHASE_ORDER_BPM_CATEGORY_NOT_EXISTS);
+        }
+    }
+
+    /**
+     * 杞崲 BPM 瀹℃壒缁撴灉涓哄鏍哥姸鎬�
+     */
+    private Integer convertBpmResultToAuditStatus(Integer bpmResult) {
+        if (BpmTaskStatusEnum.APPROVE.getStatus().equals(bpmResult)) {
+            return ErpAuditStatus.APPROVE.getStatus();
+        } else if (BpmTaskStatusEnum.REJECT.getStatus().equals(bpmResult)) {
+            return ErpAuditStatus.PROCESS.getStatus();
+        } else if (BpmTaskStatusEnum.CANCEL.getStatus().equals(bpmResult)) {
+            return ErpAuditStatus.CANCEL.getStatus();
+        }
+        return null;
     }
 
     private List<ErpPurchaseOrderItemDO> validatePurchaseOrderItems(List<ErpPurchaseOrderSaveReqVO.Item> list) {
@@ -307,11 +446,70 @@
         if (CollUtil.isEmpty(items)) {
             return;
         }
-        // 3. 璁$畻鍏ュ簱鐘舵��
+        // 3. 璁$畻鍏ュ簱鐘舵�佸拰鎬诲叆搴撴暟閲�
         int inStatus = calculateInStatus(items);
-        // 4. 鏇存柊閲囪喘璁㈠崟鍏ュ簱鐘舵��
+        BigDecimal totalInCount = calculateTotalInCount(items);
+        // 4. 鏇存柊閲囪喘璁㈠崟鍏ュ簱鐘舵�佸拰鍏ュ簱鏁伴噺
         purchaseOrderMapper.updateById(new ErpPurchaseOrderDO()
-                .setId(orderId).setInStatus(inStatus));
+                .setId(orderId).setInStatus(inStatus).setInCount(totalInCount));
+
+        // 5. 鍥炲啓閲囪喘鐢宠鏄庣粏鐨勫叆搴撴暟閲忓拰閲囪喘鐢宠鐨勫叆搴撶姸鎬�
+        updatePurchaseRequestInStatus(items, inStatus);
+    }
+
+    /**
+     * 璁$畻鎬诲叆搴撴暟閲�
+     */
+    private BigDecimal calculateTotalInCount(List<ErpPurchaseOrderItemDO> items) {
+        BigDecimal total = BigDecimal.ZERO;
+        for (ErpPurchaseOrderItemDO item : items) {
+            if (item.getInCount() != null) {
+                total = total.add(item.getInCount());
+            }
+        }
+        return total;
+    }
+
+    /**
+     * 鍥炲啓閲囪喘鐢宠鍏ュ簱鏁伴噺鍜屽叆搴撶姸鎬�
+     *
+     * @param orderItems 閲囪喘璁㈠崟鏄庣粏鍒楄〃
+     * @param orderInStatus 閲囪喘璁㈠崟鍏ュ簱鐘舵��
+     */
+    private void updatePurchaseRequestInStatus(List<ErpPurchaseOrderItemDO> orderItems, int orderInStatus) {
+        // 鑾峰彇鎵�鏈夊叧鑱旂殑閲囪喘鐢宠鏄庣粏 ID
+        Set<Long> requestItemIds = new HashSet<>();
+        for (ErpPurchaseOrderItemDO item : orderItems) {
+            if (item.getRequestItemId() != null) {
+                requestItemIds.add(item.getRequestItemId());
+            }
+        }
+        if (requestItemIds.isEmpty()) {
+            return;
+        }
+
+        // 鏇存柊閲囪喘鐢宠鏄庣粏鐨勫叆搴撴暟閲�
+        for (ErpPurchaseOrderItemDO orderItem : orderItems) {
+            if (orderItem.getRequestItemId() != null && orderItem.getInCount() != null) {
+                purchaseRequestItemMapper.updateById(new ErpPurchaseRequestItemDO()
+                        .setId(orderItem.getRequestItemId())
+                        .setInCount(orderItem.getInCount()));
+            }
+        }
+
+        // 鑾峰彇閲囪喘鐢宠鏄庣粏锛屾壘鍒板搴旂殑閲囪喘鐢宠 ID
+        List<ErpPurchaseRequestItemDO> requestItems = purchaseRequestItemMapper.selectBatchIds(requestItemIds);
+        if (CollUtil.isEmpty(requestItems)) {
+            return;
+        }
+        Set<Long> requestIds = convertSet(requestItems, ErpPurchaseRequestItemDO::getRequestId);
+
+        // 鏇存柊閲囪喘鐢宠鐨勫叆搴撶姸鎬�
+        for (Long requestId : requestIds) {
+            purchaseRequestMapper.updateById(new ErpPurchaseRequestDO()
+                    .setId(requestId)
+                    .setInStatus(orderInStatus));
+        }
     }
 
     /**
@@ -345,4 +543,22 @@
         }
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long confirmReceipt(Long id) {
+        // 1. 鏍¢獙閲囪喘璁㈠崟瀛樺湪涓斿凡瀹℃牳
+        ErpPurchaseOrderDO purchaseOrder = validatePurchaseOrder(id);
+
+        // 2. 鏍¢獙閲囪喘璁㈠崟鏈叏閮ㄥ叆搴�
+        if (ErpPurchaseOrderInStatusEnum.ALL_IN.getStatus().equals(purchaseOrder.getInStatus())) {
+            throw exception(PURCHASE_ORDER_ALL_IN);
+        }
+
+        // 3. 璋冪敤 MES 鎺ュ彛鐢熸垚鍒拌揣閫氱煡鍗�
+        Long arrivalNoticeId = arrivalNoticeApi.generateArrivalNoticeFromPurchaseOrder(id);
+        log.info("[confirmReceipt] 閲囪喘璁㈠崟({}) 纭鏀惰揣锛岀敓鎴愬埌璐ч�氱煡鍗�({})", id, arrivalNoticeId);
+
+        return arrivalNoticeId;
+    }
+
 }
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseRequestService.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseRequestService.java
new file mode 100644
index 0000000..a761923
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseRequestService.java
@@ -0,0 +1,114 @@
+package cn.iocoder.yudao.module.erp.service.purchase;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request.ErpPurchaseRequestPageReqVO;
+import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request.ErpPurchaseRequestSaveReqVO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestDO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestItemDO;
+import jakarta.validation.Valid;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * ERP 閲囪喘鐢宠 Service 鎺ュ彛
+ */
+public interface ErpPurchaseRequestService {
+
+    /**
+     * 鍒涘缓閲囪喘鐢宠
+     *
+     * @param createReqVO 鍒涘缓淇℃伅
+     * @return 缂栧彿
+     */
+    Long createPurchaseRequest(@Valid ErpPurchaseRequestSaveReqVO createReqVO);
+
+    /**
+     * 鏇存柊閲囪喘鐢宠
+     *
+     * @param updateReqVO 鏇存柊淇℃伅
+     */
+    void updatePurchaseRequest(@Valid ErpPurchaseRequestSaveReqVO updateReqVO);
+
+    /**
+     * 鍒犻櫎閲囪喘鐢宠
+     *
+     * @param ids 缂栧彿鏁扮粍
+     */
+    void deletePurchaseRequest(List<Long> ids);
+
+    /**
+     * 鑾峰緱閲囪喘鐢宠
+     *
+     * @param id 缂栧彿
+     * @return 閲囪喘鐢宠
+     */
+    ErpPurchaseRequestDO getPurchaseRequest(Long id);
+
+    /**
+     * 鑾峰緱閲囪喘鐢宠鍒嗛〉
+     *
+     * @param pageReqVO 鍒嗛〉鏌ヨ
+     * @return 閲囪喘鐢宠鍒嗛〉
+     */
+    PageResult<ErpPurchaseRequestDO> getPurchaseRequestPage(ErpPurchaseRequestPageReqVO pageReqVO);
+
+    /**
+     * 鑾峰緱閲囪喘鐢宠椤瑰垪琛�
+     *
+     * @param requestId 閲囪喘鐢宠缂栧彿
+     * @return 閲囪喘鐢宠椤瑰垪琛�
+     */
+    List<ErpPurchaseRequestItemDO> getPurchaseRequestItemListByRequestId(Long requestId);
+
+    /**
+     * 鑾峰緱閲囪喘鐢宠椤� List
+     *
+     * @param requestIds 閲囪喘鐢宠缂栧彿鏁扮粍
+     * @return 閲囪喘鐢宠椤� List
+     */
+    List<ErpPurchaseRequestItemDO> getPurchaseRequestItemListByRequestIds(Collection<Long> requestIds);
+
+    /**
+     * 鎻愪氦閲囪喘鐢宠瀹℃壒
+     *
+     * @param id 缂栧彿
+     * @param processDefinitionKey 娴佺▼瀹氫箟 Key
+     * @param userId 鐢ㄦ埛缂栧彿
+     */
+    void submitPurchaseRequest(Long id, String processDefinitionKey, Long userId);
+
+    /**
+     * 鑾峰彇閲囪喘鐢宠瀹℃壒娴佺▼鍒楄〃
+     *
+     * @return 娴佺▼瀹氫箟鍒楄〃
+     */
+    List<Map<String, Object>> getPurchaseRequestApproveProcessDefinitionList();
+
+    /**
+     * 鏇存柊閲囪喘鐢宠瀹℃壒缁撴灉锛圔PM 鍥炶皟锛�
+     *
+     * @param id 缂栧彿
+     * @param bpmResult BPM 瀹℃壒缁撴灉
+     */
+    void updatePurchaseRequestAuditStatus(Long id, Integer bpmResult);
+
+    /**
+     * 鐢熸垚閲囪喘璁㈠崟
+     *
+     * @param id 閲囪喘鐢宠缂栧彿
+     * @param supplierId 渚涘簲鍟嗙紪鍙�
+     * @return 閲囪喘璁㈠崟缂栧彿
+     */
+    Long generatePurchaseOrder(Long id, Long supplierId);
+
+    /**
+     * 鏍规嵁缂栧彿鍒楄〃鑾峰彇閲囪喘鐢宠鍒楄〃
+     *
+     * @param ids 缂栧彿鍒楄〃
+     * @return 閲囪喘鐢宠鍒楄〃
+     */
+    List<ErpPurchaseRequestDO> getPurchaseRequestList(List<Long> ids);
+
+}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseRequestServiceImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseRequestServiceImpl.java
new file mode 100644
index 0000000..f12da1b
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseRequestServiceImpl.java
@@ -0,0 +1,459 @@
+package cn.iocoder.yudao.module.erp.service.purchase;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
+import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
+import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
+import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
+import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
+import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request.ErpPurchaseRequestPageReqVO;
+import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request.ErpPurchaseRequestSaveReqVO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderDO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderItemDO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestDO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestItemDO;
+import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseOrderItemMapper;
+import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseOrderMapper;
+import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseRequestItemMapper;
+import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseRequestMapper;
+import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO;
+import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
+import cn.iocoder.yudao.module.erp.enums.ErpPurchaseRequestStatusEnum;
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+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.flowable.engine.repository.ProcessDefinition;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.math.BigDecimal;
+import java.util.*;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
+import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*;
+
+/**
+ * ERP 閲囪喘鐢宠 Service 瀹炵幇绫�
+ */
+@Service
+@Validated
+@Slf4j
+public class ErpPurchaseRequestServiceImpl implements ErpPurchaseRequestService {
+
+    /**
+     * BPM 閲囪喘鐢宠瀹℃壒鍒嗙被缂栫爜
+     */
+    private static final String PURCHASE_REQUEST_APPROVE_CATEGORY_CODE = "purchase_request_approve";
+
+    @Resource
+    private ErpPurchaseRequestMapper purchaseRequestMapper;
+    @Resource
+    private ErpPurchaseRequestItemMapper purchaseRequestItemMapper;
+
+    @Resource
+    private ErpPurchaseOrderMapper purchaseOrderMapper;
+    @Resource
+    private ErpPurchaseOrderItemMapper purchaseOrderItemMapper;
+
+    @Resource
+    private ErpNoRedisDAO noRedisDAO;
+
+    @Resource
+    private ErpSupplierService supplierService;
+
+    @Resource
+    private BpmProcessInstanceApi processInstanceApi;
+    @Resource
+    private BpmCategoryService bpmCategoryService;
+    @Resource
+    private BpmProcessDefinitionService bpmProcessDefinitionService;
+
+    @Resource
+    private AdminUserApi adminUserApi;
+    @Resource
+    private DeptApi deptApi;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createPurchaseRequest(ErpPurchaseRequestSaveReqVO createReqVO) {
+        // 1. 鏍¢獙璁㈠崟椤圭殑鏈夋晥鎬�
+        List<ErpPurchaseRequestItemDO> requestItems = validatePurchaseRequestItems(createReqVO.getItems());
+        // 2. 鏍¢獙渚涘簲鍟嗭紙濡傛灉鎸囧畾锛�
+        if (createReqVO.getSupplierId() != null) {
+            supplierService.validateSupplier(createReqVO.getSupplierId());
+        }
+        // 3. 鐢熸垚鐢宠鍗曞彿
+        String no = noRedisDAO.generate(ErpNoRedisDAO.PURCHASE_REQUEST_NO_PREFIX);
+        if (purchaseRequestMapper.selectByNo(no) != null) {
+            throw exception(PURCHASE_REQUEST_NO_EXISTS);
+        }
+
+        // 4. 鑾峰彇褰撳墠鐢ㄦ埛淇℃伅
+        Long userId = Long.parseLong(getCurrentUserId());
+        AdminUserRespDTO user = adminUserApi.getUser(userId);
+        Long deptId = user != null ? user.getDeptId() : null;
+
+        // 5. 鎻掑叆鐢宠鍗�
+        ErpPurchaseRequestDO purchaseRequest = BeanUtils.toBean(createReqVO, ErpPurchaseRequestDO.class, req -> req
+                .setNo(no)
+                .setStatus(ErpPurchaseRequestStatusEnum.DRAFT.getStatus())
+                .setRequestUserId(userId)
+                .setRequestDeptId(deptId));
+        calculateTotalPrice(purchaseRequest, requestItems);
+        purchaseRequestMapper.insert(purchaseRequest);
+
+        // 6. 鎻掑叆鐢宠鏄庣粏
+        requestItems.forEach(o -> o.setRequestId(purchaseRequest.getId()));
+        purchaseRequestItemMapper.insertBatch(requestItems);
+
+        return purchaseRequest.getId();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updatePurchaseRequest(ErpPurchaseRequestSaveReqVO updateReqVO) {
+        // 1. 鏍¢獙瀛樺湪 + 鑽夌鐘舵��
+        ErpPurchaseRequestDO purchaseRequest = validatePurchaseRequestExistsAndDraft(updateReqVO.getId());
+        // 2. 鏍¢獙渚涘簲鍟嗭紙濡傛灉鎸囧畾锛�
+        if (updateReqVO.getSupplierId() != null) {
+            supplierService.validateSupplier(updateReqVO.getSupplierId());
+        }
+        // 3. 鏍¢獙璁㈠崟椤圭殑鏈夋晥鎬�
+        List<ErpPurchaseRequestItemDO> requestItems = validatePurchaseRequestItems(updateReqVO.getItems());
+
+        // 4. 鏇存柊鐢宠鍗�
+        ErpPurchaseRequestDO updateObj = BeanUtils.toBean(updateReqVO, ErpPurchaseRequestDO.class);
+        calculateTotalPrice(updateObj, requestItems);
+        purchaseRequestMapper.updateById(updateObj);
+
+        // 5. 鏇存柊鐢宠鏄庣粏
+        updatePurchaseRequestItemList(updateReqVO.getId(), requestItems);
+    }
+
+    private void calculateTotalPrice(ErpPurchaseRequestDO purchaseRequest, List<ErpPurchaseRequestItemDO> requestItems) {
+        purchaseRequest.setTotalCount(getSumValue(requestItems, ErpPurchaseRequestItemDO::getCount, BigDecimal::add));
+        purchaseRequest.setTotalProductPrice(getSumValue(requestItems, ErpPurchaseRequestItemDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO));
+        purchaseRequest.setTotalTaxPrice(getSumValue(requestItems, ErpPurchaseRequestItemDO::getTaxPrice, BigDecimal::add, BigDecimal.ZERO));
+        purchaseRequest.setTotalPrice(purchaseRequest.getTotalProductPrice().add(purchaseRequest.getTotalTaxPrice()));
+        // 璁$畻浼樻儬浠锋牸
+        if (purchaseRequest.getDiscountPercent() == null) {
+            purchaseRequest.setDiscountPercent(BigDecimal.ZERO);
+        }
+        purchaseRequest.setDiscountPrice(MoneyUtils.priceMultiplyPercent(purchaseRequest.getTotalPrice(), purchaseRequest.getDiscountPercent()));
+        purchaseRequest.setTotalPrice(purchaseRequest.getTotalPrice().subtract(purchaseRequest.getDiscountPrice()));
+    }
+
+    private List<ErpPurchaseRequestItemDO> validatePurchaseRequestItems(List<ErpPurchaseRequestSaveReqVO.Item> list) {
+        return convertList(list, o -> BeanUtils.toBean(o, ErpPurchaseRequestItemDO.class, item -> {
+            if (item.getProductPrice() != null && item.getCount() != null) {
+                item.setTotalPrice(MoneyUtils.priceMultiply(item.getProductPrice(), item.getCount()));
+                if (item.getTaxPercent() != null) {
+                    item.setTaxPrice(MoneyUtils.priceMultiplyPercent(item.getTotalPrice(), item.getTaxPercent()));
+                }
+            }
+        }));
+    }
+
+    private void updatePurchaseRequestItemList(Long id, List<ErpPurchaseRequestItemDO> newList) {
+        List<ErpPurchaseRequestItemDO> oldList = purchaseRequestItemMapper.selectListByRequestId(id);
+        List<List<ErpPurchaseRequestItemDO>> diffList = diffList(oldList, newList,
+                (oldVal, newVal) -> oldVal.getId().equals(newVal.getId()));
+
+        if (CollUtil.isNotEmpty(diffList.get(0))) {
+            diffList.get(0).forEach(o -> o.setRequestId(id));
+            purchaseRequestItemMapper.insertBatch(diffList.get(0));
+        }
+        if (CollUtil.isNotEmpty(diffList.get(1))) {
+            purchaseRequestItemMapper.updateBatch(diffList.get(1));
+        }
+        if (CollUtil.isNotEmpty(diffList.get(2))) {
+            purchaseRequestItemMapper.deleteByIds(convertList(diffList.get(2), ErpPurchaseRequestItemDO::getId));
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deletePurchaseRequest(List<Long> ids) {
+        List<ErpPurchaseRequestDO> purchaseRequests = purchaseRequestMapper.selectRequestListByIds(ids);
+        if (CollUtil.isEmpty(purchaseRequests)) {
+            return;
+        }
+        purchaseRequests.forEach(purchaseRequest -> {
+            // 鍙湁鑽夌鐘舵�佹墠鑳藉垹闄�
+            if (!ErpPurchaseRequestStatusEnum.DRAFT.getStatus().equals(purchaseRequest.getStatus())) {
+                throw exception(PURCHASE_REQUEST_DELETE_FAIL_NOT_DRAFT, purchaseRequest.getNo());
+            }
+        });
+
+        purchaseRequests.forEach(purchaseRequest -> {
+            purchaseRequestMapper.deleteById(purchaseRequest.getId());
+            purchaseRequestItemMapper.deleteByRequestId(purchaseRequest.getId());
+        });
+    }
+
+    @Override
+    public ErpPurchaseRequestDO getPurchaseRequest(Long id) {
+        return purchaseRequestMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<ErpPurchaseRequestDO> getPurchaseRequestPage(ErpPurchaseRequestPageReqVO pageReqVO) {
+        return purchaseRequestMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public List<ErpPurchaseRequestItemDO> getPurchaseRequestItemListByRequestId(Long requestId) {
+        return purchaseRequestItemMapper.selectListByRequestId(requestId);
+    }
+
+    @Override
+    public List<ErpPurchaseRequestItemDO> getPurchaseRequestItemListByRequestIds(Collection<Long> requestIds) {
+        if (CollUtil.isEmpty(requestIds)) {
+            return Collections.emptyList();
+        }
+        return purchaseRequestItemMapper.selectListByRequestIds(requestIds);
+    }
+
+    @Override
+    public List<ErpPurchaseRequestDO> getPurchaseRequestList(List<Long> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return Collections.emptyList();
+        }
+        return purchaseRequestMapper.selectRequestListByIds(ids);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void submitPurchaseRequest(Long id, String processDefinitionKey, Long userId) {
+        // 1. 鏍¢獙瀛樺湪 + 鑽夌鐘舵��
+        ErpPurchaseRequestDO purchaseRequest = validatePurchaseRequestExistsAndDraft(id);
+
+        // 2. 鏍¢獙鏄惁鏈夋槑缁�
+        List<ErpPurchaseRequestItemDO> items = purchaseRequestItemMapper.selectListByRequestId(id);
+        if (CollUtil.isEmpty(items)) {
+            throw exception(PURCHASE_REQUEST_NOT_EXISTS); // TODO: 娣诲姞鏄庣粏涓虹┖鐨勯敊璇爜
+        }
+
+        // 3. 鍒涘缓 BPM 娴佺▼瀹炰緥
+        String processInstanceId = processInstanceApi.createProcessInstance(userId,
+                new BpmProcessInstanceCreateReqDTO()
+                        .setProcessDefinitionKey(processDefinitionKey)
+                        .setBusinessKey(String.valueOf(id)));
+
+        // 4. 鏇存柊閲囪喘鐢宠鐘舵�佷负瀹℃壒涓�
+        purchaseRequestMapper.updateById(new ErpPurchaseRequestDO()
+                .setId(id)
+                .setStatus(ErpPurchaseRequestStatusEnum.PROCESS.getStatus())
+                .setProcessInstanceId(processInstanceId));
+    }
+
+    @Override
+    public List<Map<String, Object>> getPurchaseRequestApproveProcessDefinitionList() {
+        // 1. 鏍¢獙鍒嗙被鍜屾祦绋嬪畾涔夋槸鍚﹀瓨鍦�
+        validatePurchaseRequestApproveCategoryAndProcessDefinition();
+
+        // 2. 鑾峰彇鍒嗙被涓嬬殑娴佺▼瀹氫箟淇℃伅
+        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
+                .getProcessDefinitionInfoListByCategory(PURCHASE_REQUEST_APPROVE_CATEGORY_CODE);
+        if (CollUtil.isEmpty(definitionInfoList)) {
+            return Collections.emptyList();
+        }
+
+        // 3. 閬嶅巻鑾峰彇娴佺▼瀹氫箟璇︽儏锛岃繃婊ゆ縺娲荤姸鎬侊紝淇濈暀鏈�鏂扮増鏈�
+        Map<String, ProcessDefinition> latestVersionMap = new HashMap<>();
+        for (BpmProcessDefinitionInfoDO info : definitionInfoList) {
+            ProcessDefinition pd = bpmProcessDefinitionService.getProcessDefinition(info.getProcessDefinitionId());
+            if (pd == null || pd.isSuspended()) {
+                continue;
+            }
+            ProcessDefinition existing = latestVersionMap.get(pd.getKey());
+            if (existing == null || pd.getVersion() > existing.getVersion()) {
+                latestVersionMap.put(pd.getKey(), pd);
+            }
+        }
+
+        // 4. 杩斿洖娴佺▼瀹氫箟鍒楄〃
+        List<Map<String, Object>> result = new ArrayList<>();
+        for (ProcessDefinition pd : latestVersionMap.values()) {
+            Map<String, Object> item = new HashMap<>();
+            item.put("id", pd.getId());
+            item.put("key", pd.getKey());
+            item.put("name", pd.getName());
+            result.add(item);
+        }
+        return result;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updatePurchaseRequestAuditStatus(Long id, Integer bpmResult) {
+        // 1. 鏍¢獙瀛樺湪
+        ErpPurchaseRequestDO purchaseRequest = validatePurchaseRequestExists(id);
+        if (!ErpPurchaseRequestStatusEnum.PROCESS.getStatus().equals(purchaseRequest.getStatus())) {
+            log.warn("[updatePurchaseRequestAuditStatus] 閲囪喘鐢宠({}) 涓嶅浜庡鎵逛腑鐘舵��", id);
+            return;
+        }
+
+        // 2. 鏍规嵁瀹℃壒缁撴灉鏇存柊鐘舵��
+        Integer newStatus = convertBpmResultToStatus(bpmResult);
+        if (newStatus != null) {
+            purchaseRequestMapper.updateById(new ErpPurchaseRequestDO()
+                    .setId(id).setStatus(newStatus));
+        }
+
+        // 3. 瀹℃壒閫氳繃涓旀湁渚涘簲鍟嗭紝鑷姩鐢熸垚閲囪喘璁㈠崟
+        if (ErpPurchaseRequestStatusEnum.APPROVE.getStatus().equals(newStatus)
+                && purchaseRequest.getSupplierId() != null
+                && purchaseRequest.getOrderId() == null) {
+            try {
+                Long orderId = doGeneratePurchaseOrder(purchaseRequest);
+                log.info("[updatePurchaseRequestAuditStatus] 閲囪喘鐢宠({}) 鑷姩鐢熸垚閲囪喘璁㈠崟({})", id, orderId);
+            } catch (Exception e) {
+                log.error("[updatePurchaseRequestAuditStatus] 閲囪喘鐢宠({}) 鑷姩鐢熸垚閲囪喘璁㈠崟澶辫触", id, e);
+                // 涓嶆姏鍑哄紓甯革紝閬垮厤褰卞搷鐘舵�佹洿鏂�
+            }
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long generatePurchaseOrder(Long id, Long supplierId) {
+        // 1. 鏍¢獙瀛樺湪 + 瀹℃牳閫氳繃鐘舵��
+        ErpPurchaseRequestDO purchaseRequest = validatePurchaseRequestExists(id);
+        if (!ErpPurchaseRequestStatusEnum.APPROVE.getStatus().equals(purchaseRequest.getStatus())) {
+            throw exception(PURCHASE_REQUEST_NOT_APPROVE);
+        }
+        // 2. 鏍¢獙鏄惁宸茬敓鎴愯鍗�
+        if (purchaseRequest.getOrderId() != null) {
+            throw exception(PURCHASE_REQUEST_ORDER_EXISTS);
+        }
+        // 3. 鏍¢獙渚涘簲鍟�
+        supplierService.validateSupplier(supplierId);
+
+        // 4. 璁剧疆渚涘簲鍟嗗苟鐢熸垚璁㈠崟
+        purchaseRequest.setSupplierId(supplierId);
+        return doGeneratePurchaseOrder(purchaseRequest);
+    }
+
+    /**
+     * 鎵ц鐢熸垚閲囪喘璁㈠崟
+     *
+     * @param purchaseRequest 閲囪喘鐢宠锛堥渶鍖呭惈 supplierId锛�
+     * @return 閲囪喘璁㈠崟 ID
+     */
+    private Long doGeneratePurchaseOrder(ErpPurchaseRequestDO purchaseRequest) {
+        Long id = purchaseRequest.getId();
+
+        // 1. 鑾峰彇鐢宠鏄庣粏
+        List<ErpPurchaseRequestItemDO> requestItems = purchaseRequestItemMapper.selectListByRequestId(id);
+        if (CollUtil.isEmpty(requestItems)) {
+            throw exception(PURCHASE_REQUEST_NOT_EXISTS);
+        }
+
+        // 2. 鐢熸垚閲囪喘璁㈠崟鍙�
+        String orderNo = noRedisDAO.generate(ErpNoRedisDAO.PURCHASE_ORDER_NO_PREFIX);
+        if (purchaseOrderMapper.selectByNo(orderNo) != null) {
+            throw exception(PURCHASE_ORDER_NO_EXISTS);
+        }
+
+        // 3. 鍒涘缓閲囪喘璁㈠崟
+        ErpPurchaseOrderDO purchaseOrder = new ErpPurchaseOrderDO();
+        purchaseOrder.setNo(orderNo);
+        purchaseOrder.setStatus(ErpAuditStatus.APPROVE.getStatus()); // 閲囪喘璁㈠崟鐩存帴涓哄凡瀹℃牳鐘舵��
+        purchaseOrder.setSupplierId(purchaseRequest.getSupplierId());
+        purchaseOrder.setOrderTime(purchaseRequest.getRequestTime());
+        purchaseOrder.setTotalCount(purchaseRequest.getTotalCount());
+        purchaseOrder.setTotalPrice(purchaseRequest.getTotalPrice());
+        purchaseOrder.setTotalProductPrice(purchaseRequest.getTotalProductPrice());
+        purchaseOrder.setTotalTaxPrice(purchaseRequest.getTotalTaxPrice());
+        purchaseOrder.setDiscountPercent(purchaseRequest.getDiscountPercent());
+        purchaseOrder.setDiscountPrice(purchaseRequest.getDiscountPrice());
+        purchaseOrder.setDepositPrice(purchaseRequest.getDepositPrice() != null ? purchaseRequest.getDepositPrice() : BigDecimal.ZERO);
+        purchaseOrder.setRemark(purchaseRequest.getRemark());
+        purchaseOrder.setInCount(BigDecimal.ZERO);
+        purchaseOrder.setInStatus(0);
+        purchaseOrderMapper.insert(purchaseOrder);
+
+        // 4. 鍒涘缓閲囪喘璁㈠崟鏄庣粏
+        List<ErpPurchaseOrderItemDO> orderItems = convertList(requestItems, item -> {
+            ErpPurchaseOrderItemDO orderItem = new ErpPurchaseOrderItemDO();
+            orderItem.setOrderId(purchaseOrder.getId());
+            orderItem.setRequestItemId(item.getId()); // 鍏宠仈閲囪喘鐢宠鏄庣粏
+            orderItem.setProductId(item.getProductId());
+            orderItem.setProductUnitId(item.getProductUnitId());
+            orderItem.setProductPrice(item.getProductPrice());
+            orderItem.setCount(item.getCount());
+            orderItem.setTotalPrice(item.getTotalPrice());
+            orderItem.setTaxPercent(item.getTaxPercent());
+            orderItem.setTaxPrice(item.getTaxPrice());
+            orderItem.setRemark(item.getRemark());
+            orderItem.setInCount(BigDecimal.ZERO);
+            orderItem.setQcCheckFlag(item.getQcCheckFlag() != null ? item.getQcCheckFlag() : false);
+            return orderItem;
+        });
+        purchaseOrderItemMapper.insertBatch(orderItems);
+
+        // 5. 鏇存柊閲囪喘鐢宠鐨勮鍗曚俊鎭�
+        purchaseRequestMapper.updateById(new ErpPurchaseRequestDO()
+                .setId(id)
+                .setOrderId(purchaseOrder.getId())
+                .setOrderNo(orderNo));
+
+        return purchaseOrder.getId();
+    }
+
+    /**
+     * 鏍¢獙閲囪喘鐢宠瀹℃壒鍒嗙被鍜屾祦绋嬪畾涔夋槸鍚﹀瓨鍦�
+     */
+    private void validatePurchaseRequestApproveCategoryAndProcessDefinition() {
+        List<BpmCategoryDO> categories = bpmCategoryService.getCategoryListByCode(
+                Collections.singletonList(PURCHASE_REQUEST_APPROVE_CATEGORY_CODE));
+        if (CollUtil.isEmpty(categories)) {
+            throw exception(PURCHASE_REQUEST_BPM_CATEGORY_NOT_EXISTS);
+        }
+    }
+
+    /**
+     * 杞崲 BPM 瀹℃壒缁撴灉涓虹姸鎬�
+     */
+    private Integer convertBpmResultToStatus(Integer bpmResult) {
+        if (BpmTaskStatusEnum.APPROVE.getStatus().equals(bpmResult)) {
+            return ErpPurchaseRequestStatusEnum.APPROVE.getStatus();
+        } else if (BpmTaskStatusEnum.REJECT.getStatus().equals(bpmResult)) {
+            return ErpPurchaseRequestStatusEnum.REJECT.getStatus();
+        } else if (BpmTaskStatusEnum.CANCEL.getStatus().equals(bpmResult)) {
+            return ErpPurchaseRequestStatusEnum.CANCEL.getStatus();
+        }
+        return null;
+    }
+
+    private ErpPurchaseRequestDO validatePurchaseRequestExists(Long id) {
+        ErpPurchaseRequestDO purchaseRequest = purchaseRequestMapper.selectById(id);
+        if (purchaseRequest == null) {
+            throw exception(PURCHASE_REQUEST_NOT_EXISTS);
+        }
+        return purchaseRequest;
+    }
+
+    private ErpPurchaseRequestDO validatePurchaseRequestExistsAndDraft(Long id) {
+        ErpPurchaseRequestDO purchaseRequest = validatePurchaseRequestExists(id);
+        if (!ErpPurchaseRequestStatusEnum.DRAFT.getStatus().equals(purchaseRequest.getStatus())) {
+            throw exception(PURCHASE_REQUEST_UPDATE_FAIL_NOT_DRAFT, purchaseRequest.getNo());
+        }
+        return purchaseRequest;
+    }
+
+    private String getCurrentUserId() {
+        return cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId().toString();
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/listener/ErpPurchaseRequestStatusListener.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/listener/ErpPurchaseRequestStatusListener.java
new file mode 100644
index 0000000..caf124d
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/listener/ErpPurchaseRequestStatusListener.java
@@ -0,0 +1,51 @@
+package cn.iocoder.yudao.module.erp.service.purchase.listener;
+
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
+import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseRequestService;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+/**
+ * 閲囪喘鐢宠瀹℃壒鐘舵�佺洃鍚櫒
+ *
+ * 鐩戝惉 BPM 娴佺▼瀹炰緥鐘舵�佸彉鏇翠簨浠讹紝鏇存柊閲囪喘鐢宠鐨勫鎵圭姸鎬�
+ */
+@Component
+@Slf4j
+public class ErpPurchaseRequestStatusListener extends cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEventListener {
+
+    /**
+     * 閲囪喘鐢宠瀹℃壒娴佺▼瀹氫箟 Key
+     */
+    private static final String PROCESS_DEFINITION_KEY = "purchase_request_approve";
+
+    @Resource
+    private ErpPurchaseRequestService purchaseRequestService;
+
+    @Override
+    protected String getProcessDefinitionKey() {
+        return PROCESS_DEFINITION_KEY;
+    }
+
+    @Override
+    protected void onEvent(BpmProcessInstanceStatusEvent event) {
+        Long requestId;
+        try {
+            requestId = Long.parseLong(event.getBusinessKey());
+        } catch (NumberFormatException e) {
+            log.warn("[onEvent] businessKey({}) 涓嶆槸鏈夋晥鐨勯噰璐敵璇风紪鍙�", event.getBusinessKey());
+            return;
+        }
+
+        log.info("[onEvent] 鏀跺埌BPM瀹℃壒浜嬩欢锛宺equestId={}, status={}", requestId, event.getStatus());
+
+        try {
+            purchaseRequestService.updatePurchaseRequestAuditStatus(requestId, event.getStatus());
+            log.info("[onEvent] 閲囪喘鐢宠({}) 瀹℃壒鐘舵�佹洿鏂板畬鎴�", requestId);
+        } catch (Exception e) {
+            log.error("[onEvent] 鏇存柊閲囪喘鐢宠瀹℃壒鐘舵�佸け璐ワ紝requestId={}", requestId, e);
+        }
+    }
+
+}
diff --git a/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/arrivalnotice/MesArrivalNoticeApi.java b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/arrivalnotice/MesArrivalNoticeApi.java
new file mode 100644
index 0000000..da950e3
--- /dev/null
+++ b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/arrivalnotice/MesArrivalNoticeApi.java
@@ -0,0 +1,16 @@
+package cn.iocoder.yudao.module.mes.api.arrivalnotice;
+
+/**
+ * MES 鍒拌揣閫氱煡鍗� API 鎺ュ彛
+ */
+public interface MesArrivalNoticeApi {
+
+    /**
+     * 鏍规嵁閲囪喘璁㈠崟鐢熸垚鍒拌揣閫氱煡鍗曪紙寰呭叆搴撶姸鎬侊級
+     *
+     * @param purchaseOrderId 閲囪喘璁㈠崟缂栧彿
+     * @return 鐢熸垚鐨勫埌璐ч�氱煡鍗曠紪鍙�
+     */
+    Long generateArrivalNoticeFromPurchaseOrder(Long purchaseOrderId);
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/arrivalnotice/MesArrivalNoticeApiImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/arrivalnotice/MesArrivalNoticeApiImpl.java
new file mode 100644
index 0000000..37e7a0e
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/arrivalnotice/MesArrivalNoticeApiImpl.java
@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.mes.api.arrivalnotice;
+
+import cn.iocoder.yudao.module.mes.service.wm.arrivalnotice.MesWmArrivalNoticeService;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+
+/**
+ * MES 鍒拌揣閫氱煡鍗� API 瀹炵幇绫�
+ */
+@Service
+public class MesArrivalNoticeApiImpl implements MesArrivalNoticeApi {
+
+    @Resource
+    private MesWmArrivalNoticeService arrivalNoticeService;
+
+    @Override
+    public Long generateArrivalNoticeFromPurchaseOrder(Long purchaseOrderId) {
+        return arrivalNoticeService.generateArrivalNoticeFromPurchaseOrder(purchaseOrderId);
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/arrivalnotice/MesWmArrivalNoticeController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/arrivalnotice/MesWmArrivalNoticeController.java
index 38b54fe..bc4256e 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/arrivalnotice/MesWmArrivalNoticeController.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/arrivalnotice/MesWmArrivalNoticeController.java
@@ -11,12 +11,12 @@
 import cn.iocoder.yudao.module.mes.controller.admin.wm.arrivalnotice.vo.MesWmArrivalNoticePageReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.arrivalnotice.vo.MesWmArrivalNoticeRespVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.arrivalnotice.vo.MesWmArrivalNoticeSaveReqVO;
-import cn.iocoder.yudao.module.mes.dal.dataobject.md.vendor.MesMdVendorDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeDO;
-import cn.iocoder.yudao.module.mes.service.md.vendor.MesMdVendorService;
 import cn.iocoder.yudao.module.mes.service.wm.arrivalnotice.MesWmArrivalNoticeService;
 import cn.iocoder.yudao.module.erp.api.purchase.ErpPurchaseOrderApi;
+import cn.iocoder.yudao.module.erp.api.purchase.ErpSupplierApi;
 import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderRespDTO;
+import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpSupplierRespDTO;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -45,7 +45,7 @@
     @Resource
     private MesWmArrivalNoticeService arrivalNoticeService;
     @Resource
-    private MesMdVendorService vendorService;
+    private ErpSupplierApi supplierApi;
     @Resource
     private ErpPurchaseOrderApi purchaseOrderApi;
 
@@ -129,9 +129,10 @@
         if (CollUtil.isEmpty(list)) {
             return Collections.emptyList();
         }
-        // 1. 鑾峰緱鍏宠仈鏁版嵁
-        Map<Long, MesMdVendorDO> vendorMap = vendorService.getVendorMap(
-                convertSet(list, MesWmArrivalNoticeDO::getVendorId));
+        // 1. 鑾峰緱渚涘簲鍟嗕俊鎭紙ERP 閲囪喘渚涘簲鍟嗭級
+        Map<Long, ErpSupplierRespDTO> supplierMap = supplierApi.getSupplierList(
+                convertSet(list, MesWmArrivalNoticeDO::getVendorId)).getCheckedData().stream()
+                .collect(java.util.stream.Collectors.toMap(ErpSupplierRespDTO::getId, v -> v, (a, b) -> a));
         // 2. 鑾峰緱閲囪喘璁㈠崟淇℃伅
         List<Long> purchaseOrderIds = list.stream()
                 .map(MesWmArrivalNoticeDO::getPurchaseOrderId)
@@ -143,8 +144,8 @@
                 .collect(java.util.stream.Collectors.toMap(ErpPurchaseOrderRespDTO::getId, v -> v, (a, b) -> a));
         // 3. 鏋勫缓缁撴灉
         return BeanUtils.toBean(list, MesWmArrivalNoticeRespVO.class, vo -> {
-            MapUtils.findAndThen(vendorMap, vo.getVendorId(), vendor ->
-                    vo.setVendorCode(vendor.getCode()).setVendorName(vendor.getName()));
+            MapUtils.findAndThen(supplierMap, vo.getVendorId(), supplier ->
+                    vo.setVendorName(supplier.getName()));
             MapUtils.findAndThen(purchaseOrderMap, vo.getPurchaseOrderId(), purchaseOrder ->
                     vo.setPurchaseOrderNo(purchaseOrder.getNo()));
         });
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/itemreceipt/MesWmItemReceiptController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/itemreceipt/MesWmItemReceiptController.java
index 247c35b..deb0f72 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/itemreceipt/MesWmItemReceiptController.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/itemreceipt/MesWmItemReceiptController.java
@@ -11,14 +11,14 @@
 import cn.iocoder.yudao.module.mes.controller.admin.wm.itemreceipt.vo.MesWmItemReceiptPageReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.itemreceipt.vo.MesWmItemReceiptRespVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.itemreceipt.vo.MesWmItemReceiptSaveReqVO;
-import cn.iocoder.yudao.module.mes.dal.dataobject.md.vendor.MesMdVendorDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.qc.iqc.MesQcIqcDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemreceipt.MesWmItemReceiptDO;
-import cn.iocoder.yudao.module.mes.service.md.vendor.MesMdVendorService;
 import cn.iocoder.yudao.module.mes.service.qc.iqc.MesQcIqcService;
 import cn.iocoder.yudao.module.mes.service.wm.arrivalnotice.MesWmArrivalNoticeService;
 import cn.iocoder.yudao.module.mes.service.wm.itemreceipt.MesWmItemReceiptService;
+import cn.iocoder.yudao.module.erp.api.purchase.ErpSupplierApi;
+import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpSupplierRespDTO;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -47,7 +47,7 @@
     @Resource
     private MesWmItemReceiptService itemReceiptService;
     @Resource
-    private MesMdVendorService vendorService;
+    private ErpSupplierApi supplierApi;
     @Resource
     private MesQcIqcService iqcService;
     @Resource
@@ -171,17 +171,18 @@
         if (CollUtil.isEmpty(list)) {
             return Collections.emptyList();
         }
-        // 1. 鑾峰緱鍏宠仈鏁版嵁
-        Map<Long, MesMdVendorDO> vendorMap = vendorService.getVendorMap(
-                convertSet(list, MesWmItemReceiptDO::getVendorId));
+        // 1. 鑾峰緱鍏宠仈鏁版嵁锛堥噰璐叆搴撲娇鐢� ERP 渚涘簲鍟嗭級
+        Map<Long, ErpSupplierRespDTO> supplierMap = supplierApi.getSupplierList(
+                convertSet(list, MesWmItemReceiptDO::getVendorId)).getCheckedData().stream()
+                .collect(java.util.stream.Collectors.toMap(ErpSupplierRespDTO::getId, v -> v, (a, b) -> a));
         Map<Long, MesQcIqcDO> iqcMap = iqcService.getIqcMap(
                 convertSet(list, MesWmItemReceiptDO::getIqcId));
         Map<Long, MesWmArrivalNoticeDO> noticeMap = arrivalNoticeService.getArrivalNoticeMap(
                 convertSet(list, MesWmItemReceiptDO::getNoticeId));
         // 2. 鏋勫缓缁撴灉
         return BeanUtils.toBean(list, MesWmItemReceiptRespVO.class, vo -> {
-            MapUtils.findAndThen(vendorMap, vo.getVendorId(),
-                    vendor -> vo.setVendorName(vendor.getName()));
+            MapUtils.findAndThen(supplierMap, vo.getVendorId(),
+                    supplier -> vo.setVendorName(supplier.getName()));
             MapUtils.findAndThen(iqcMap, vo.getIqcId(),
                     iqc -> vo.setIqcCode(iqc.getCode()));
             MapUtils.findAndThen(noticeMap, vo.getNoticeId(), notice ->
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/arrivalnotice/MesWmArrivalNoticeLineDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/arrivalnotice/MesWmArrivalNoticeLineDO.java
index 81eb889..136ebcf 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/arrivalnotice/MesWmArrivalNoticeLineDO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/arrivalnotice/MesWmArrivalNoticeLineDO.java
@@ -64,6 +64,15 @@
      * 鍏宠仈 {@link MesQcIqcDO#getId()}
      */
     private Long iqcId;
+
+    /**
+     * 璐ㄦ缁撴灉
+     *
+     * 鏋氫妇 {@link cn.iocoder.yudao.module.mes.enums.qc.MesQcCheckResultEnum}
+     * 1-鍚堟牸 2-鐗归噰 3-涓嶅悎鏍奸��璐� 4-涓嶅悎鏍兼姤搴�
+     */
+    private Integer iqcCheckResult;
+
     /**
      * 澶囨敞
      */
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/arrivalnotice/MesWmArrivalNoticeMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/arrivalnotice/MesWmArrivalNoticeMapper.java
index f4794dc..a2e6705 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/arrivalnotice/MesWmArrivalNoticeMapper.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/arrivalnotice/MesWmArrivalNoticeMapper.java
@@ -32,4 +32,8 @@
         return selectCount(MesWmArrivalNoticeDO::getVendorId, vendorId);
     }
 
+    default MesWmArrivalNoticeDO selectByPurchaseOrderId(Long purchaseOrderId) {
+        return selectOne(MesWmArrivalNoticeDO::getPurchaseOrderId, purchaseOrderId);
+    }
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/itemreceipt/MesWmItemReceiptMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/itemreceipt/MesWmItemReceiptMapper.java
index 486cee3..65336ff 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/itemreceipt/MesWmItemReceiptMapper.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/itemreceipt/MesWmItemReceiptMapper.java
@@ -36,4 +36,8 @@
         return selectList(MesWmItemReceiptDO::getVendorId, vendorId);
     }
 
+    default MesWmItemReceiptDO selectByNoticeId(Long noticeId) {
+        return selectOne(MesWmItemReceiptDO::getNoticeId, noticeId);
+    }
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/productsales/MesWmProductSalesMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/productsales/MesWmProductSalesMapper.java
index e350ee4..2218ba1 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/productsales/MesWmProductSalesMapper.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/productsales/MesWmProductSalesMapper.java
@@ -31,6 +31,10 @@
         return selectOne(MesWmProductSalesDO::getCode, code);
     }
 
+    default MesWmProductSalesDO selectByNoticeId(Long noticeId) {
+        return selectOne(MesWmProductSalesDO::getNoticeId, noticeId);
+    }
+
     default List<MesWmProductSalesDO> selectListByClientId(Long clientId) {
         return selectList(MesWmProductSalesDO::getClientId, clientId);
     }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/salesnotice/MesWmSalesNoticeMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/salesnotice/MesWmSalesNoticeMapper.java
index 71c4714..3ac2683 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/salesnotice/MesWmSalesNoticeMapper.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/salesnotice/MesWmSalesNoticeMapper.java
@@ -27,4 +27,8 @@
         return selectOne(MesWmSalesNoticeDO::getCode, code);
     }
 
+    default MesWmSalesNoticeDO selectBySaleOrderId(Long saleOrderId) {
+        return selectOne(MesWmSalesNoticeDO::getSaleOrderId, saleOrderId);
+    }
+
 }
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 6b5c000..52c0302 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
@@ -348,6 +348,7 @@
     ErrorCode QC_IQC_NOT_PREPARE = new ErrorCode(1_040_603_002, "鍙湁鑽夌鐘舵�佺殑妫�楠屽崟鎵嶅彲鎿嶄綔");
     ErrorCode QC_IQC_QUANTITY_MISMATCH = new ErrorCode(1_040_603_004, "鍚堟牸鍝佷笌涓嶅悎鏍煎搧鏁伴噺涔嬪拰椤荤瓑浜庢娴嬫暟閲�");
     ErrorCode QC_NO_TEMPLATE = new ErrorCode(1_040_603_005, "褰撳墠浜у搧鏈厤缃娴嬫ā鏉�");
+    ErrorCode QC_NO_TEMPLATE_FOR_ITEM = new ErrorCode(1_040_603_008, "鐗╂枡銆恵}銆戞湭閰嶇疆 IQC 妫�娴嬫ā鏉匡紝璇峰厛鍦ㄨ川閲忕鐞嗕腑閰嶇疆");
     ErrorCode QC_IQC_SOURCE_DOC_PARAMS_MISSING = new ErrorCode(1_040_603_006, "鏉ユ簮鍗曟嵁绫诲瀷闈炵┖鏃讹紝鏉ユ簮鍗曟嵁 ID 鍜屾潵婧愬崟鎹 ID 涓嶈兘涓虹┖");
     ErrorCode QC_IQC_CHECK_RESULT_EMPTY = new ErrorCode(1_040_603_007, "瀹屾垚妫�楠屽崟鍓嶏紝妫�娴嬬粨鏋滃繀椤诲~鍐�");
     // ========== MES 璐ㄩ噺绠$悊-鏉ユ枡妫�楠岃锛�1-040-603-100锛� ==========
@@ -458,6 +459,7 @@
     ErrorCode WM_ARRIVAL_NOTICE_IQC_PENDING = new ErrorCode(1_040_704_005, "瀛樺湪寰呮楠岃锛屾棤娉曞鎵归�氳繃");
     ErrorCode WM_ARRIVAL_NOTICE_NO_LINE = new ErrorCode(1_040_704_006, "鑷冲皯闇�瑕佷竴鏉¤椤圭洰");
     ErrorCode WM_ARRIVAL_NOTICE_VENDOR_MISMATCH = new ErrorCode(1_040_704_007, "鍒拌揣閫氱煡鍗曠殑渚涘簲鍟嗕笌褰撳墠鍗曟嵁涓嶄竴鑷�");
+    ErrorCode WM_ARRIVAL_NOTICE_ALREADY_EXISTS = new ErrorCode(1_040_704_008, "璇ラ噰璐鍗曞凡鐢熸垚鍒拌揣閫氱煡鍗曪紝涓嶈兘閲嶅鐢熸垚");
     ErrorCode WM_ARRIVAL_NOTICE_LINE_NOT_EXISTS = new ErrorCode(1_040_704_100, "鍒拌揣閫氱煡鍗曡涓嶅瓨鍦�");
     ErrorCode WM_ARRIVAL_NOTICE_LINE_NOT_MATCH = new ErrorCode(1_040_704_101, "鍒拌揣閫氱煡鍗曡涓嶅睘浜庢寚瀹氱殑鍒拌揣閫氱煡鍗�");
 
@@ -469,7 +471,8 @@
     ErrorCode WM_ITEM_RECEIPT_DETAIL_QUANTITY_MISMATCH = new ErrorCode(1_040_705_004, "鏄庣粏涓婃灦鎬绘暟涓庤鍏ュ簱鏁伴噺涓嶅尮閰�");
     ErrorCode WM_ITEM_RECEIPT_STATUS_ERROR = new ErrorCode(1_040_705_005, "鍏ュ簱鍗曠姸鎬佷笉姝g‘");
     ErrorCode WM_ITEM_RECEIPT_CANCEL_NOT_ALLOWED = new ErrorCode(1_040_705_006, "宸插畬鎴愭垨宸插彇娑堢殑鍏ュ簱鍗曚笉鍏佽鍙栨秷");
-    ErrorCode WM_PURCHASE_ORDER_NOT_EXISTS = new ErrorCode(1_040_705_007, "閲囪喘璁㈠崟涓嶅瓨鍦�");
+    ErrorCode WM_ITEM_RECEIPT_ALREADY_EXISTS = new ErrorCode(1_040_705_007, "璇ュ埌璐ч�氱煡鍗曞凡鐢熸垚鍏ュ簱鍗曪紝涓嶈兘閲嶅鐢熸垚");
+    ErrorCode WM_PURCHASE_ORDER_NOT_EXISTS = new ErrorCode(1_040_705_008, "閲囪喘璁㈠崟涓嶅瓨鍦�");
     ErrorCode WM_PURCHASE_ORDER_NO_ITEM = new ErrorCode(1_040_705_008, "閲囪喘璁㈠崟鏃犳槑缁嗘暟鎹�");
     ErrorCode WM_ITEM_RECEIPT_LINE_NOT_EXISTS = new ErrorCode(1_040_705_100, "閲囪喘鍏ュ簱鍗曡涓嶅瓨鍦�");
     ErrorCode WM_ITEM_RECEIPT_LINE_ARRIVAL_NOTICE_LINE_REQUIRED = new ErrorCode(1_040_705_101,
@@ -478,6 +481,7 @@
             "鍏ュ簱鍗曟湭鍏宠仈鍒拌揣閫氱煡鍗曪紝涓嶈兘閫夋嫨鍒拌揣閫氱煡鍗曡");
     ErrorCode WM_ITEM_RECEIPT_DETAIL_NOT_EXISTS = new ErrorCode(1_040_705_200, "閲囪喘鍏ュ簱鏄庣粏涓嶅瓨鍦�");
     ErrorCode WM_ITEM_RECEIPT_DETAIL_QUANTITY_EXCEED = new ErrorCode(1_040_705_202, "涓婃灦鏄庣粏鎬绘暟閲忎笉鑳借秴杩囪鍏ュ簱鏁伴噺");
+    ErrorCode WM_ITEM_RECEIPT_BATCH_REQUIRED = new ErrorCode(1_040_705_203, "鐗╂枡銆恵}-{}銆戝凡寮�鍚壒娆$鐞嗭紝涓婃灦鏄庣粏鐨勬壒娆″彿涓嶈兘涓虹┖");
 
     // ========== MES 浠撳簱绠$悊-棰嗘枡鐢宠鍗曪紙1-040-706-000锛� ==========
     ErrorCode WM_MATERIAL_REQUEST_NOT_EXISTS = new ErrorCode(1_040_706_000, "棰嗘枡鐢宠鍗曚笉瀛樺湪");
@@ -643,8 +647,9 @@
     ErrorCode WM_PRODUCT_SALES_CANNOT_FINISH = new ErrorCode(1_040_714_007, "褰撳墠鐘舵�佷笉鍏佽鎵ц鍑哄簱");
     ErrorCode WM_PRODUCT_SALES_CANNOT_CANCEL = new ErrorCode(1_040_714_008, "褰撳墠鐘舵�佷笉鍏佽鍙栨秷");
     ErrorCode WM_PRODUCT_SALES_CANNOT_CONFIRM = new ErrorCode(1_040_714_009, "褰撳墠鐘舵�佷笉鍏佽纭妫�楠岄�氳繃");
+    ErrorCode WM_PRODUCT_SALES_ALREADY_EXISTS = new ErrorCode(1_040_714_010, "璇ュ彂璐ч�氱煡鍗曞凡鐢熸垚閿�鍞嚭搴撳崟锛屼笉鑳介噸澶嶇敓鎴�");
     ErrorCode WM_PRODUCT_SALES_DETAILS_EMPTY = new ErrorCode(1_040_714_020, "鎷h揣鏄庣粏涓嶈兘涓虹┖");
-    ErrorCode WM_PRODUCT_SALES_DETAIL_QUANTITY_MISMATCH = new ErrorCode(1_040_714_010, "鎷h揣鏁伴噺涓庡嚭搴撴暟閲忎笉鍖归厤");
+    ErrorCode WM_PRODUCT_SALES_DETAIL_QUANTITY_MISMATCH = new ErrorCode(1_040_714_011, "鎷h揣鏁伴噺涓庡嚭搴撴暟閲忎笉鍖归厤");
     ErrorCode WM_PRODUCT_SALES_LINE_NOT_EXISTS = new ErrorCode(1_040_714_011, "閿�鍞嚭搴撳崟琛屼笉瀛樺湪");
     ErrorCode WM_PRODUCT_SALES_DETAIL_NOT_EXISTS = new ErrorCode(1_040_714_012, "閿�鍞嚭搴撴槑缁嗕笉瀛樺湪");
     ErrorCode WM_PRODUCT_SALES_STOCK_INSUFFICIENT = new ErrorCode(1_040_714_013, "搴撳瓨涓嶈冻锛屾棤娉曟嫞璐�");
@@ -698,6 +703,7 @@
     ErrorCode WM_SALES_NOTICE_LINE_NOT_MATCH = new ErrorCode(1_040_720_012, "鍙戣揣閫氱煡鍗曡涓嶅睘浜庢寚瀹氱殑鍙戣揣閫氱煡鍗�");
     ErrorCode WM_SALES_NOTICE_SALE_ORDER_NOT_EXISTS = new ErrorCode(1_040_720_013, "閿�鍞鍗曚笉瀛樺湪");
     ErrorCode WM_SALES_NOTICE_SALE_ORDER_ITEM_EMPTY = new ErrorCode(1_040_720_014, "閿�鍞鍗曟槑缁嗕负绌�");
+    ErrorCode WM_SALES_NOTICE_ALREADY_EXISTS = new ErrorCode(1_040_720_015, "璇ラ攢鍞鍗曞凡鐢熸垚鍙戣揣閫氱煡鍗曪紝涓嶈兘閲嶅鐢熸垚");
 
     // ========== MES 浠撳簱绠$悊-鏉$爜閰嶇疆锛�1-040-730-000锛� ==========
     ErrorCode WM_BARCODE_CONFIG_NOT_EXISTS = new ErrorCode(1_040_730_000, "鏉$爜閰嶇疆涓嶅瓨鍦�");
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/qc/MesQcCheckResultEnum.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/qc/MesQcCheckResultEnum.java
index a8ae07c..718f342 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/qc/MesQcCheckResultEnum.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/qc/MesQcCheckResultEnum.java
@@ -8,15 +8,15 @@
 
 /**
  * MES 妫�娴嬬粨鏋滄灇涓�
- *
- * @author 鑺嬮亾婧愮爜
  */
 @Getter
 @AllArgsConstructor
 public enum MesQcCheckResultEnum implements ArrayValuable<Integer> {
 
-    PASS(1, "妫�楠岄�氳繃"),
-    FAIL(2, "妫�楠屼笉閫氳繃");
+    QUALIFIED(1, "鍚堟牸"),
+    SPECIAL_APPROVAL(2, "鐗归噰"),
+    RETURN(3, "涓嶅悎鏍奸��璐�"),
+    SCRAP(4, "涓嶅悎鏍兼姤搴�");
 
     public static final Integer[] ARRAYS = Arrays.stream(values()).map(MesQcCheckResultEnum::getType).toArray(Integer[]::new);
 
@@ -34,4 +34,14 @@
         return ARRAYS;
     }
 
+    /**
+     * 鍒ゆ柇鏄惁鍙互鍏ュ簱
+     *
+     * @param type 妫�娴嬬粨鏋�
+     * @return true 鍙互鍏ュ簱
+     */
+    public static boolean canInbound(Integer type) {
+        return QUALIFIED.getType().equals(type) || SPECIAL_APPROVAL.getType().equals(type);
+    }
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcService.java
index e9c80db..20d33fd 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcService.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcService.java
@@ -5,6 +5,7 @@
 import cn.iocoder.yudao.module.mes.controller.admin.qc.iqc.vo.MesQcIqcSaveReqVO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.qc.defectrecord.MesQcDefectRecordDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.qc.iqc.MesQcIqcDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeLineDO;
 import jakarta.validation.Valid;
 
 import java.util.Collection;
@@ -95,4 +96,15 @@
      */
     Long getIqcCountByVendorId(Long vendorId);
 
+    /**
+     * 鏍规嵁鍒拌揣閫氱煡鍗曡鑷姩鍒涘缓鏉ユ枡妫�楠屽崟
+     *
+     * @param noticeId        鍒拌揣閫氱煡鍗� ID
+     * @param line            鍒拌揣閫氱煡鍗曡
+     * @param noticeCode      鍒拌揣閫氱煡鍗曠紪鍙�
+     * @param vendorId        渚涘簲鍟� ID
+     * @return IQC 妫�楠屽崟 ID
+     */
+    Long createIqcFromArrivalNotice(Long noticeId, MesWmArrivalNoticeLineDO line, String noticeCode, Long vendorId);
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcServiceImpl.java
index b6715ff..6c5f535 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcServiceImpl.java
@@ -10,6 +10,7 @@
 import cn.iocoder.yudao.module.mes.dal.dataobject.qc.defectrecord.MesQcDefectRecordDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.qc.iqc.MesQcIqcDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateItemDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeLineDO;
 import cn.iocoder.yudao.module.mes.dal.mysql.qc.iqc.MesQcIqcMapper;
 import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
 import cn.iocoder.yudao.module.mes.enums.qc.MesQcDefectLevelEnum;
@@ -22,6 +23,8 @@
 import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateItemService;
 import cn.iocoder.yudao.module.mes.service.wm.arrivalnotice.MesWmArrivalNoticeService;
 import cn.iocoder.yudao.module.mes.service.wm.outsourcereceipt.MesWmOutsourceReceiptService;
+import cn.iocoder.yudao.module.erp.api.purchase.ErpSupplierApi;
+import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
 import jakarta.annotation.Resource;
 import org.springframework.context.annotation.Lazy;
@@ -71,6 +74,13 @@
     @Resource
     private AdminUserApi adminUserApi;
 
+    @Resource
+    private MdmItemApi mdmItemApi;
+
+    @Resource
+    @Lazy
+    private ErpSupplierApi supplierApi;
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     public Long createIqc(MesQcIqcSaveReqVO createReqVO) {
@@ -116,9 +126,20 @@
     private void validateIqcSaveData(MesQcIqcSaveReqVO reqVO) {
         // 鏍¢獙缂栧彿鍞竴
         validateIqcCodeUnique(reqVO.getId(), reqVO.getCode());
-        // 鏍¢獙渚涘簲鍟嗐�佺墿鏂欍�佹娴嬩汉鍛樺瓨鍦�
-        vendorService.validateVendorExists(reqVO.getVendorId());
-        itemService.validateItemExists(reqVO.getItemId());
+        // 鏍¢獙渚涘簲鍟嗗瓨鍦紙浼樺厛鏍¢獙 ERP 渚涘簲鍟嗭紝澶辫触鍒欐牎楠� MDM 渚涘簲鍟嗭級
+        if (reqVO.getVendorId() != null) {
+            try {
+                supplierApi.validateSupplier(reqVO.getVendorId());
+            } catch (Exception e) {
+                // ERP 渚涘簲鍟嗘牎楠屽け璐ワ紝灏濊瘯 MDM 渚涘簲鍟�
+                vendorService.validateVendorExists(reqVO.getVendorId());
+            }
+        }
+        // 鏍¢獙鐗╂枡瀛樺湪锛堜娇鐢� MDM 鐗╂枡锛�
+        if (reqVO.getItemId() != null) {
+            mdmItemApi.getItem(reqVO.getItemId()).getCheckedData();
+        }
+        // 鏍¢獙妫�娴嬩汉鍛樺瓨鍦�
         adminUserApi.validateUser(reqVO.getInspectorUserId());
     }
 
@@ -160,7 +181,7 @@
         // 鏍规嵁涓嶅悓鐨勬潵婧愬崟鎹被鍨嬶紝鍥炲啓涓嶅悓鐨勫崟鎹�
         if (Objects.equals(iqc.getSourceDocType(), MesBizTypeConstants.WM_ARRIVAL_NOTICE)) {
             arrivalNoticeService.updateArrivalNoticeWhenIqcFinish(iqc.getSourceDocId(), iqc.getSourceLineId(),
-                    iqc.getId(), iqc.getQualifiedQuantity());
+                    iqc.getId(), iqc.getCheckResult(), iqc.getQualifiedQuantity());
         } else if (Objects.equals(iqc.getSourceDocType(), MesBizTypeConstants.WM_OUTSOURCE_RECPT)) {
             outsourceReceiptService.updateOutsourceReceiptWhenIqcFinish(iqc.getSourceDocId(), iqc.getSourceLineId(),
                     iqc.getId(), iqc.getQualifiedQuantity(), iqc.getUnqualifiedQuantity());
@@ -308,4 +329,65 @@
         return iqcMapper.selectCountByVendorId(vendorId);
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createIqcFromArrivalNotice(Long noticeId, MesWmArrivalNoticeLineDO line, String noticeCode, Long vendorId) {
+        // 1. 鏍¢獙鐗╂枡鏄惁閰嶇疆浜� IQC 妫�楠屾ā鏉�
+        MesQcTemplateItemDO templateItem;
+        try {
+            templateItem = templateItemService.getRequiredTemplateByItemIdAndType(
+                    line.getItemId(), MesQcTypeEnum.IQC.getType());
+        } catch (Exception e) {
+            // 鑾峰彇鐗╂枡淇℃伅锛屾彁渚涙洿鏄庣‘鐨勯敊璇彁绀�
+            String itemInfo = getItemInfo(line.getItemId());
+            throw exception(QC_NO_TEMPLATE_FOR_ITEM, itemInfo);
+        }
+        Long templateId = templateItem.getTemplateId();
+
+        // 2. 鐢熸垚妫�楠屽崟缂栫爜
+        String code = "IQC" + System.currentTimeMillis();
+
+        // 3. 鍒涘缓 IQC 涓昏〃锛堝緟妫�楠岀姸鎬侊紝鍒濆鍚堟牸鏁伴噺涓� 0锛�
+        MesQcIqcDO iqc = new MesQcIqcDO();
+        iqc.setCode(code);
+        iqc.setName(noticeCode + "-鐗╂枡妫�楠�");
+        iqc.setTemplateId(templateId);
+        iqc.setSourceDocType(MesBizTypeConstants.WM_ARRIVAL_NOTICE);
+        iqc.setSourceDocId(noticeId);
+        iqc.setSourceLineId(line.getId());
+        iqc.setSourceDocCode(noticeCode);
+        iqc.setVendorId(vendorId);
+        iqc.setItemId(line.getItemId());
+        iqc.setReceivedQuantity(line.getArrivalQuantity());
+        iqc.setCheckQuantity(BigDecimal.ZERO); // 寰呭~鍐�
+        iqc.setQualifiedQuantity(BigDecimal.ZERO); // 寰呭~鍐�
+        iqc.setUnqualifiedQuantity(BigDecimal.ZERO); // 寰呭~鍐�
+        iqc.setReceiveDate(java.time.LocalDateTime.now());
+        iqc.setStatus(MesQcStatusEnum.DRAFT.getStatus());
+        iqcMapper.insert(iqc);
+
+        // 4. 浠庢ā鏉挎寚鏍囪嚜鍔ㄧ敓鎴愭楠岃
+        iqcLineService.createLinesFromTemplate(iqc.getId(), templateId);
+
+        return iqc.getId();
+    }
+
+    /**
+     * 鑾峰彇鐗╂枡淇℃伅锛堢紪鐮�+鍚嶇О锛�
+     */
+    private String getItemInfo(Long itemId) {
+        if (itemId == null) {
+            return "鏈煡鐗╂枡";
+        }
+        try {
+            cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO item = mdmItemApi.getItem(itemId).getCheckedData();
+            if (item != null) {
+                return item.getCode() + "-" + item.getName();
+            }
+        } catch (Exception e) {
+            // 蹇界暐寮傚父
+        }
+        return "鐗╂枡ID:" + itemId;
+    }
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeLineService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeLineService.java
index 8d68a85..6db9f19 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeLineService.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeLineService.java
@@ -72,9 +72,10 @@
      *
      * @param lineId 琛岀紪鍙�
      * @param iqcId IQC 妫�楠屽崟缂栧彿
+     * @param checkResult 璐ㄦ缁撴灉锛�1鍚堟牸 2鐗归噰 3涓嶅悎鏍奸��璐� 4涓嶅悎鏍兼姤搴燂級
      * @param qualifiedQuantity 鍚堟牸鏁伴噺
      */
-    void updateArrivalNoticeLineWhenIqcFinish(Long lineId, Long iqcId, BigDecimal qualifiedQuantity);
+    void updateArrivalNoticeLineWhenIqcFinish(Long lineId, Long iqcId, Integer checkResult, BigDecimal qualifiedQuantity);
 
     /**
      * 鏍¢獙鍒拌揣閫氱煡鍗曡瀛樺湪
@@ -100,4 +101,12 @@
      */
     void createArrivalNoticeLineFromPurchaseOrderItem(MesWmArrivalNoticeLineDO line);
 
+    /**
+     * 鏇存柊鍒拌揣閫氱煡鍗曡鐨� IQC 妫�楠屽崟 ID
+     *
+     * @param lineId 琛岀紪鍙�
+     * @param iqcId  IQC 妫�楠屽崟缂栧彿
+     */
+    void updateArrivalNoticeLineIqcId(Long lineId, Long iqcId);
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeLineServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeLineServiceImpl.java
index c71717e..ea14127 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeLineServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeLineServiceImpl.java
@@ -112,12 +112,12 @@
     }
 
     @Override
-    public void updateArrivalNoticeLineWhenIqcFinish(Long lineId, Long iqcId, BigDecimal qualifiedQuantity) {
+    public void updateArrivalNoticeLineWhenIqcFinish(Long lineId, Long iqcId, Integer checkResult, BigDecimal qualifiedQuantity) {
         // 鏍¢獙琛屽瓨鍦�
         validateArrivalNoticeLineExists(lineId);
-        // 鏇存柊
+        // 鏇存柊锛氱粦瀹� iqcId + 璐ㄦ缁撴灉 + 鍚堟牸鏁伴噺
         arrivalNoticeLineMapper.updateById(new MesWmArrivalNoticeLineDO()
-                .setId(lineId).setIqcId(iqcId).setQualifiedQuantity(qualifiedQuantity));
+                .setId(lineId).setIqcId(iqcId).setIqcCheckResult(checkResult).setQualifiedQuantity(qualifiedQuantity));
     }
 
     @Override
@@ -146,4 +146,10 @@
         arrivalNoticeLineMapper.insert(line);
     }
 
+    @Override
+    public void updateArrivalNoticeLineIqcId(Long lineId, Long iqcId) {
+        arrivalNoticeLineMapper.updateById(new MesWmArrivalNoticeLineDO()
+                .setId(lineId).setIqcId(iqcId));
+    }
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeService.java
index f7edbfa..5b7a1b6 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeService.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeService.java
@@ -69,9 +69,10 @@
      * @param id 鍒拌揣閫氱煡鍗曠紪鍙�
      * @param lineId 鍒拌揣閫氱煡鍗曡缂栧彿
      * @param iqcId IQC 妫�楠屽崟缂栧彿
+     * @param checkResult 璐ㄦ缁撴灉锛�1鍚堟牸 2鐗归噰 3涓嶅悎鏍奸��璐� 4涓嶅悎鏍兼姤搴燂級
      * @param qualifiedQuantity 鍚堟牸鏁伴噺
      */
-    void updateArrivalNoticeWhenIqcFinish(Long id, Long lineId, Long iqcId, BigDecimal qualifiedQuantity);
+    void updateArrivalNoticeWhenIqcFinish(Long id, Long lineId, Long iqcId, Integer checkResult, BigDecimal qualifiedQuantity);
 
     /**
      * 瀹屾垚鍒拌揣閫氱煡鍗曪紙寰呭叆搴� 鈫� 宸插畬鎴愶級锛屽唴閮ㄨ皟鐢�
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeServiceImpl.java
index 29db064..ea57d40 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeServiceImpl.java
@@ -12,11 +12,15 @@
 import cn.iocoder.yudao.module.mes.dal.mysql.wm.arrivalnotice.MesWmArrivalNoticeMapper;
 import cn.iocoder.yudao.module.mes.enums.wm.MesWmArrivalNoticeStatusEnum;
 import cn.iocoder.yudao.module.erp.api.purchase.ErpPurchaseOrderApi;
+import cn.iocoder.yudao.module.erp.api.purchase.ErpSupplierApi;
 import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderRespDTO;
 import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderItemRespDTO;
 import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService;
+import cn.iocoder.yudao.module.mes.service.qc.iqc.MesQcIqcService;
+import cn.iocoder.yudao.module.mes.enums.qc.MesQcCheckResultEnum;
 import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
@@ -42,14 +46,19 @@
 
     @Resource
     private MesWmArrivalNoticeLineService arrivalNoticeLineService;
+
     @Resource
-    private cn.iocoder.yudao.module.mes.service.md.vendor.MesMdVendorService vendorService;
+    private ErpSupplierApi supplierApi;
 
     @Resource
     private ErpPurchaseOrderApi purchaseOrderApi;
 
     @Resource
     private MesMdmItemService mdmItemService;
+
+    @Resource
+    @Lazy
+    private MesQcIqcService iqcService;
 
     @Override
     public Long createArrivalNotice(MesWmArrivalNoticeSaveReqVO createReqVO) {
@@ -78,8 +87,8 @@
     private void validateArrivalNoticeSaveData(MesWmArrivalNoticeSaveReqVO reqVO) {
         // 鏍¢獙缂栫爜鍞竴
         validateCodeUnique(reqVO.getId(), reqVO.getCode());
-        // 鏍¢獙渚涘簲鍟嗗瓨鍦ㄤ笖鍚敤
-        vendorService.validateVendorExistsAndEnable(reqVO.getVendorId());
+        // 鏍¢獙渚涘簲鍟嗗瓨鍦紙ERP 閲囪喘渚涘簲鍟嗭級
+        supplierApi.validateSupplier(reqVO.getVendorId());
         // 鏍¢獙閲囪喘璁㈠崟瀛樺湪
         if (reqVO.getPurchaseOrderId() != null) {
             ErpPurchaseOrderRespDTO purchaseOrder = purchaseOrderApi.getPurchaseOrder(reqVO.getPurchaseOrderId()).getCheckedData();
@@ -115,7 +124,7 @@
     @Transactional(rollbackFor = Exception.class)
     public void submitArrivalNotice(Long id) {
         // 1.1 鏍¢獙瀛樺湪 + 鑽夌鐘舵��
-        validateArrivalNoticeExistsAndDraft(id);
+        MesWmArrivalNoticeDO notice = validateArrivalNoticeExistsAndDraft(id);
         // 1.2 妫�鏌ユ槸鍚︽湁琛岄」鐩�
         List<MesWmArrivalNoticeLineDO> lines = arrivalNoticeLineService.getArrivalNoticeLineListByNoticeId(id);
         if (CollUtil.isEmpty(lines)) {
@@ -126,9 +135,26 @@
         boolean needCheck = CollectionUtils.anyMatch(lines,
                 line -> Boolean.TRUE.equals(line.getIqcCheckFlag()));
         if (needCheck) {
-            // 闇�瑕佹楠岋紝杩涘叆寰呰川妫�
+            // 2.1 闇�瑕佹楠岋紝杩涘叆寰呰川妫�
             arrivalNoticeMapper.updateById(new MesWmArrivalNoticeDO()
                     .setId(id).setStatus(MesWmArrivalNoticeStatusEnum.PENDING_QC.getStatus()));
+            // 2.2 涓洪渶瑕佽川妫�鐨勮鑷姩鍒涘缓 IQC 妫�楠屽崟
+            for (MesWmArrivalNoticeLineDO line : lines) {
+                if (Boolean.TRUE.equals(line.getIqcCheckFlag()) && line.getIqcId() == null) {
+                    try {
+                        Long iqcId = iqcService.createIqcFromArrivalNotice(
+                                id, line, notice.getCode(), notice.getVendorId());
+                        // 鏇存柊琛屼笂鐨� iqcId
+                        arrivalNoticeLineService.updateArrivalNoticeLineIqcId(line.getId(), iqcId);
+                        log.info("[submitArrivalNotice][鍒拌揣閫氱煡鍗�({}) 琛�({}) 鑷姩鍒涘缓 IQC 妫�楠屽崟({})]",
+                                id, line.getId(), iqcId);
+                    } catch (Exception e) {
+                        log.warn("[submitArrivalNotice][鍒拌揣閫氱煡鍗�({}) 琛�({}) 鍒涘缓 IQC 澶辫触: {}]",
+                                id, line.getId(), e.getMessage());
+                        // 缁х画澶勭悊鍏朵粬琛岋紝涓嶄腑鏂祦绋�
+                    }
+                }
+            }
             return;
         }
         // 涓嶉渶瑕佹楠岋紝鐩存帴杩涘叆寰呭叆搴�
@@ -138,7 +164,7 @@
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public void updateArrivalNoticeWhenIqcFinish(Long id, Long lineId, Long iqcId, BigDecimal qualifiedQuantity) {
+    public void updateArrivalNoticeWhenIqcFinish(Long id, Long lineId, Long iqcId, Integer checkResult, BigDecimal qualifiedQuantity) {
         // 1.1 鏍¢獙鍒拌揣閫氱煡鍗曞瓨鍦�
         MesWmArrivalNoticeDO notice = validateArrivalNoticeExists(id);
         // 1.2 鏍¢獙鐘舵�佷负寰呰川妫�
@@ -146,18 +172,28 @@
             throw exception(WM_ARRIVAL_NOTICE_STATUS_NOT_PENDING_QC);
         }
 
-        // 2. 鏇存柊鍒拌揣閫氱煡鍗曡锛氱粦瀹� iqcId + 鍚堟牸鏁伴噺
-        arrivalNoticeLineService.updateArrivalNoticeLineWhenIqcFinish(lineId, iqcId, qualifiedQuantity);
+        // 2. 鏇存柊鍒拌揣閫氱煡鍗曡锛氱粦瀹� iqcId + 璐ㄦ缁撴灉 + 鍚堟牸鏁伴噺
+        arrivalNoticeLineService.updateArrivalNoticeLineWhenIqcFinish(lineId, iqcId, checkResult, qualifiedQuantity);
 
-        // 3.1 鍒ゆ柇鏄惁鎵�鏈夐渶妫�琛岄兘宸插畬鎴�
+        // 3.1 鍒ゆ柇鏄惁鎵�鏈夐渶妫�琛岄兘宸插畬鎴愯川妫�
         List<MesWmArrivalNoticeLineDO> lines = arrivalNoticeLineService.getArrivalNoticeLineListByNoticeId(id);
         boolean hasUnchecked = CollectionUtils.anyMatch(lines,
-                line -> Boolean.TRUE.equals(line.getIqcCheckFlag()) && line.getIqcId() == null);
+                line -> Boolean.TRUE.equals(line.getIqcCheckFlag()) && line.getIqcCheckResult() == null);
         if (hasUnchecked) {
             log.info("[updateArrivalNoticeWhenIqcFinish][鍒拌揣閫氱煡鍗�({}) 杩樻湁鏈畬鎴愭楠岀殑琛岋紝鏆備笉鏇存柊鐘舵�乚", id);
             return;
         }
-        // 3.2 鎵�鏈夎妫�楠屽畬鎴愶紝鏇存柊涓昏〃鐘舵�� PENDING_QC 鈫� PENDING_RECEIPT
+
+        // 3.2 鍒ゆ柇鏄惁鎵�鏈夐渶妫�琛岄兘鍙互鍏ュ簱锛堝悎鏍兼垨鐗归噰锛�
+        boolean hasNonInbound = CollectionUtils.anyMatch(lines,
+                line -> Boolean.TRUE.equals(line.getIqcCheckFlag())
+                        && !MesQcCheckResultEnum.canInbound(line.getIqcCheckResult()));
+        if (hasNonInbound) {
+            log.info("[updateArrivalNoticeWhenIqcFinish][鍒拌揣閫氱煡鍗�({}) 瀛樺湪涓嶅彲鍏ュ簱鐨勮锛堥��璐�/鎶ュ簾锛夛紝鐘舵�佷繚鎸佸緟璐ㄦ]", id);
+            return;
+        }
+
+        // 3.3 鎵�鏈夎妫�楠屽畬鎴愪笖鍙叆搴擄紝鏇存柊涓昏〃鐘舵�� PENDING_QC 鈫� PENDING_RECEIPT
         arrivalNoticeMapper.updateById(new MesWmArrivalNoticeDO()
                 .setId(id).setStatus(MesWmArrivalNoticeStatusEnum.PENDING_RECEIPT.getStatus()));
     }
@@ -262,43 +298,76 @@
     @Override
     @Transactional(rollbackFor = Exception.class)
     public Long generateArrivalNoticeFromPurchaseOrder(Long purchaseOrderId) {
-        // 1. 鑾峰彇閲囪喘璁㈠崟
+        // 1. 鏍¢獙鏄惁宸茬敓鎴愯繃鍒拌揣閫氱煡鍗曪紙闃叉閲嶅鐢熸垚锛�
+        MesWmArrivalNoticeDO existNotice = arrivalNoticeMapper.selectByPurchaseOrderId(purchaseOrderId);
+        if (existNotice != null) {
+            throw exception(WM_ARRIVAL_NOTICE_ALREADY_EXISTS);
+        }
+
+        // 2. 鑾峰彇閲囪喘璁㈠崟
         ErpPurchaseOrderRespDTO purchaseOrder = purchaseOrderApi.getPurchaseOrder(purchaseOrderId).getCheckedData();
         if (purchaseOrder == null) {
             throw exception(WM_PURCHASE_ORDER_NOT_EXISTS);
         }
 
-        // 2. 鑾峰彇閲囪喘璁㈠崟鏄庣粏
+        // 3. 鑾峰彇閲囪喘璁㈠崟鏄庣粏
         List<ErpPurchaseOrderItemRespDTO> items = purchaseOrderApi.getPurchaseOrderItemList(purchaseOrderId).getCheckedData();
         if (CollUtil.isEmpty(items)) {
             throw exception(WM_PURCHASE_ORDER_NO_ITEM);
         }
 
-        // 3. 鐢熸垚鍒拌揣閫氱煡鍗曠紪鐮侊紙浣跨敤缂栫爜瑙勫垯鏈嶅姟锛�
+        // 4. 鐢熸垚鍒拌揣閫氱煡鍗曠紪鐮�
         String code = generateArrivalNoticeCode();
 
-        // 4. 鍒涘缓鍒拌揣閫氱煡鍗曚富琛�
+        // 5. 鍒涘缓鍒拌揣閫氱煡鍗曚富琛紝鐩存帴璁句负寰呭叆搴撶姸鎬�
         MesWmArrivalNoticeDO notice = new MesWmArrivalNoticeDO();
         notice.setCode(code);
         notice.setName(purchaseOrder.getNo() + "鍒拌揣閫氱煡");
         notice.setPurchaseOrderId(purchaseOrderId);
         notice.setPurchaseOrderCode(purchaseOrder.getNo());
         notice.setVendorId(purchaseOrder.getSupplierId());
-        notice.setStatus(MesWmArrivalNoticeStatusEnum.PREPARE.getStatus());
+        notice.setArrivalDate(java.time.LocalDateTime.now()); // 鍒拌揣鏃堕棿涓哄綋鍓嶆椂闂�
+        notice.setStatus(MesWmArrivalNoticeStatusEnum.PENDING_RECEIPT.getStatus());
         arrivalNoticeMapper.insert(notice);
 
-        // 5. 閬嶅巻鏄庣粏鍒涘缓鍒拌揣閫氱煡鍗曡
+        // 6. 閬嶅巻鏄庣粏鍒涘缓鍒拌揣閫氱煡鍗曡
+        boolean hasNeedQc = false;
         for (ErpPurchaseOrderItemRespDTO item : items) {
             MesWmArrivalNoticeLineDO line = new MesWmArrivalNoticeLineDO();
             line.setNoticeId(notice.getId());
             line.setPurchaseOrderItemId(item.getId());
-            // 娉ㄦ剰锛氳繖閲岄渶瑕佹牴鎹� ERP 浜у搧 ID 鏄犲皠鍒� MES 鐗╂枡 ID
-            // 瀹為檯椤圭洰涓渶瑕侀�氳繃鏄犲皠鏈嶅姟澶勭悊锛岃繖閲屾殏鏃剁暀绌鸿鐢ㄦ埛鎵嬪姩閫夋嫨
-            line.setArrivalQuantity(item.getCount() != null ? item.getCount().subtract(item.getInCount() != null ? item.getInCount() : BigDecimal.ZERO) : BigDecimal.ZERO);
-            line.setIqcCheckFlag(false); // 榛樿涓嶉渶瑕佹楠岋紝鐢ㄦ埛鍙慨鏀�
+            line.setItemId(item.getProductId()); // 浠庨噰璐鍗曟槑缁嗗甫鍏ョ墿鏂欑紪鍙�
+            BigDecimal arrivalQuantity = item.getCount() != null ? item.getCount().subtract(item.getInCount() != null ? item.getInCount() : BigDecimal.ZERO) : BigDecimal.ZERO;
+            line.setArrivalQuantity(arrivalQuantity);
+            // 浠庨噰璐鍗曟槑缁嗗甫鍏ヨ川妫�鏍囪瘑
+            boolean needQc = Boolean.TRUE.equals(item.getQcCheckFlag());
+            line.setIqcCheckFlag(needQc);
+            // 濡傛灉涓嶉渶瑕佽川妫�锛屽悎鏍兼暟閲忕瓑浜庡埌璐ф暟閲�
+            if (!needQc) {
+                line.setQualifiedQuantity(arrivalQuantity);
+            } else {
+                hasNeedQc = true;
+            }
             arrivalNoticeLineService.createArrivalNoticeLineFromPurchaseOrderItem(line);
         }
 
+        // 7. 濡傛灉鏈夐渶瑕佽川妫�鐨勮锛屾洿鏂扮姸鎬佷负寰呰川妫�骞惰嚜鍔ㄥ垱寤� IQC 鍗�
+        if (hasNeedQc) {
+            arrivalNoticeMapper.updateById(new MesWmArrivalNoticeDO()
+                    .setId(notice.getId()).setStatus(MesWmArrivalNoticeStatusEnum.PENDING_QC.getStatus()));
+            // 涓洪渶瑕佽川妫�鐨勮鑷姩鍒涘缓 IQC 妫�楠屽崟
+            List<MesWmArrivalNoticeLineDO> lines = arrivalNoticeLineService.getArrivalNoticeLineListByNoticeId(notice.getId());
+            for (MesWmArrivalNoticeLineDO line : lines) {
+                if (Boolean.TRUE.equals(line.getIqcCheckFlag())) {
+                    Long iqcId = iqcService.createIqcFromArrivalNotice(
+                            notice.getId(), line, notice.getCode(), notice.getVendorId());
+                    arrivalNoticeLineService.updateArrivalNoticeLineIqcId(line.getId(), iqcId);
+                    log.info("[generateArrivalNoticeFromPurchaseOrder][鍒拌揣閫氱煡鍗�({}) 琛�({}) 鑷姩鍒涘缓 IQC 妫�楠屽崟({})]",
+                            notice.getId(), line.getId(), iqcId);
+                }
+            }
+        }
+
         return notice.getId();
     }
 
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemreceipt/MesWmItemReceiptDetailServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemreceipt/MesWmItemReceiptDetailServiceImpl.java
index 2d4f600..651d901 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemreceipt/MesWmItemReceiptDetailServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemreceipt/MesWmItemReceiptDetailServiceImpl.java
@@ -11,6 +11,7 @@
 import jakarta.annotation.Resource;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
 import java.math.BigDecimal;
@@ -42,6 +43,7 @@
     private MesWmMaterialStockService materialStockService;
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public Long createItemReceiptDetail(MesWmItemReceiptDetailSaveReqVO createReqVO) {
         // 1. 鏍¢獙鏁版嵁
         itemReceiptService.validateItemReceiptEditable(createReqVO.getReceiptId());
@@ -57,6 +59,7 @@
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void updateItemReceiptDetail(MesWmItemReceiptDetailSaveReqVO updateReqVO) {
         // 1.1 鏍¢獙瀛樺湪
         MesWmItemReceiptDetailDO detail = validateItemReceiptDetailExists(updateReqVO.getId());
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemreceipt/MesWmItemReceiptServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemreceipt/MesWmItemReceiptServiceImpl.java
index 662cc0a..e474e63 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemreceipt/MesWmItemReceiptServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemreceipt/MesWmItemReceiptServiceImpl.java
@@ -17,7 +17,6 @@
 import cn.iocoder.yudao.module.mes.enums.wm.MesWmItemReceiptStatusEnum;
 import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
 import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
-import cn.iocoder.yudao.module.mes.service.md.vendor.MesMdVendorService;
 import cn.iocoder.yudao.module.mes.service.qc.iqc.MesQcIqcService;
 import cn.iocoder.yudao.module.mes.service.wm.arrivalnotice.MesWmArrivalNoticeService;
 import cn.iocoder.yudao.module.mes.service.wm.arrivalnotice.MesWmArrivalNoticeLineService;
@@ -27,6 +26,9 @@
 import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
 import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
 import cn.iocoder.yudao.module.erp.api.purchase.ErpPurchaseOrderApi;
+import cn.iocoder.yudao.module.erp.api.purchase.ErpSupplierApi;
+import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
+import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.context.SecurityContextHolder;
 import jakarta.annotation.Resource;
@@ -39,6 +41,7 @@
 import java.math.BigDecimal;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
@@ -63,7 +66,8 @@
     @Lazy
     private MesWmArrivalNoticeService arrivalNoticeService;
     @Resource
-    private MesMdVendorService vendorService;
+    @Lazy
+    private ErpSupplierApi supplierApi;
     @Resource
     @Lazy
     private MesQcIqcService iqcService;
@@ -78,6 +82,9 @@
 
     @Resource
     private ErpPurchaseOrderApi purchaseOrderApi;
+
+    @Resource
+    private MdmItemApi mdmItemApi;
 
     @Override
     public Long createItemReceipt(MesWmItemReceiptSaveReqVO createReqVO) {
@@ -106,8 +113,8 @@
     private void validateItemReceiptSaveData(MesWmItemReceiptSaveReqVO reqVO) {
         // 鏍¢獙缂栫爜鍞竴
         validateCodeUnique(reqVO.getId(), reqVO.getCode());
-        // 鏍¢獙渚涘簲鍟嗗瓨鍦�
-        vendorService.validateVendorExistsAndEnable(reqVO.getVendorId());
+        // 鏍¢獙渚涘簲鍟嗗瓨鍦紙閲囪喘鍏ュ簱浣跨敤 ERP 渚涘簲鍟嗭級
+        supplierApi.validateSupplier(reqVO.getVendorId());
         // 鏍¢獙鍒拌揣閫氱煡鍗曞瓨鍦�
         if (reqVO.getNoticeId() != null) {
             MesWmArrivalNoticeDO notice = arrivalNoticeService.validateArrivalNoticeReadyForReceipt(reqVO.getNoticeId());
@@ -155,9 +162,33 @@
             throw exception(WM_ITEM_RECEIPT_NO_LINE);
         }
 
+        // 鏍¢獙鐗╂枡鎵规绠$悊锛氬鏋滅墿鏂欏紑鍚壒娆$鐞嗭紝鍒欐槑缁嗙殑鎵规鍙蜂笉鑳戒负绌�
+        validateBatchManagement(lines);
+
         // 鎻愪氦锛堣崏绋� 鈫� 寰呬笂鏋讹級
         itemReceiptMapper.updateById(new MesWmItemReceiptDO()
                 .setId(id).setStatus(MesWmItemReceiptStatusEnum.APPROVING.getStatus()));
+    }
+
+    /**
+     * 鏍¢獙鐗╂枡鎵规绠$悊锛氬鏋滅墿鏂欏紑鍚壒娆$鐞嗭紝鍒欒鐨勬壒娆″彿涓嶈兘涓虹┖
+     */
+    private void validateBatchManagement(List<MesWmItemReceiptLineDO> lines) {
+        // 鑾峰彇鎵�鏈夌墿鏂橧D
+        List<Long> itemIds = CollectionUtils.convertList(lines, MesWmItemReceiptLineDO::getItemId);
+        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(itemIds);
+
+        // 閬嶅巻姣忎竴琛岋紝妫�鏌ョ墿鏂欐槸鍚﹀紑鍚壒娆$鐞�
+        for (MesWmItemReceiptLineDO line : lines) {
+            MdmItemRespDTO item = itemMap.get(line.getItemId());
+            if (item == null) {
+                continue;
+            }
+            // 濡傛灉鐗╂枡寮�鍚壒娆$鐞嗭紝妫�鏌ヨ涓婄殑鎵规鍙锋槸鍚︿负绌�
+            if (Boolean.TRUE.equals(item.getIsBatchManaged()) && line.getBatchId() == null) {
+                throw exception(WM_ITEM_RECEIPT_BATCH_REQUIRED, item.getCode(), item.getName());
+            }
+        }
     }
 
     @Override
@@ -245,14 +276,26 @@
 
     private void createTransactionList(MesWmItemReceiptDO receipt) {
         List<MesWmItemReceiptDetailDO> details = itemReceiptDetailService.getItemReceiptDetailListByReceiptId(receipt.getId());
-        wmTransactionService.createTransactionList(convertList(details, detail -> new MesWmTransactionSaveReqDTO()
-                .setType(MesWmTransactionTypeEnum.IN.getType()).setItemId(detail.getItemId())
-                .setQuantity(detail.getQuantity()) // 鍏ュ簱鏁伴噺涓烘鏁�
-                .setBatchId(detail.getBatchId())
-                .setWarehouseId(detail.getWarehouseId()).setLocationId(detail.getLocationId()).setAreaId(detail.getAreaId())
-                .setVendorId(receipt.getVendorId()).setReceiptTime(receipt.getReceiptDate())
-                .setBizType(MesBizTypeConstants.WM_ITEM_RECEIPT_IN).setBizId(receipt.getId())
-                .setBizCode(receipt.getCode()).setBizLineId(detail.getLineId())));
+        // 鑾峰彇鎵�鏈夎淇℃伅锛岀敤浜庤幏鍙栨壒娆″彿
+        List<MesWmItemReceiptLineDO> lines = itemReceiptLineService.getItemReceiptLineListByReceiptId(receipt.getId());
+        Map<Long, MesWmItemReceiptLineDO> lineMap = CollectionUtils.convertMap(lines, MesWmItemReceiptLineDO::getId);
+
+        wmTransactionService.createTransactionList(convertList(details, detail -> {
+            // 浠庤涓婅幏鍙栨壒娆′俊鎭紙濡傛灉鏄庣粏娌℃湁鎵规鍙凤級
+            MesWmItemReceiptLineDO line = lineMap.get(detail.getLineId());
+            Long batchId = detail.getBatchId() != null ? detail.getBatchId() :
+                    (line != null ? line.getBatchId() : null);
+            String batchCode = line != null ? line.getBatchCode() : null;
+
+            return new MesWmTransactionSaveReqDTO()
+                    .setType(MesWmTransactionTypeEnum.IN.getType()).setItemId(detail.getItemId())
+                    .setQuantity(detail.getQuantity())
+                    .setBatchId(batchId).setBatchCode(batchCode)
+                    .setWarehouseId(detail.getWarehouseId()).setLocationId(detail.getLocationId()).setAreaId(detail.getAreaId())
+                    .setVendorId(receipt.getVendorId()).setReceiptTime(receipt.getReceiptDate())
+                    .setBizType(MesBizTypeConstants.WM_ITEM_RECEIPT_IN).setBizId(receipt.getId())
+                    .setBizCode(receipt.getCode()).setBizLineId(detail.getLineId());
+        }));
     }
 
     @Override
@@ -331,26 +374,33 @@
         // 1. 鏍¢獙鍒拌揣閫氱煡鍗曞瓨鍦ㄤ笖鐘舵�佷负寰呭叆搴�
         MesWmArrivalNoticeDO notice = arrivalNoticeService.validateArrivalNoticeReadyForReceipt(arrivalNoticeId);
 
-        // 2. 鑾峰彇鍒拌揣閫氱煡鍗曡
+        // 2. 鏍¢獙鏄惁宸茬敓鎴愯繃鍏ュ簱鍗曪紙闃叉閲嶅鐢熸垚锛�
+        MesWmItemReceiptDO existReceipt = itemReceiptMapper.selectByNoticeId(arrivalNoticeId);
+        if (existReceipt != null) {
+            throw exception(WM_ITEM_RECEIPT_ALREADY_EXISTS);
+        }
+
+        // 3. 鑾峰彇鍒拌揣閫氱煡鍗曡
         List<MesWmArrivalNoticeLineDO> lines = arrivalNoticeLineService.getArrivalNoticeLineListByNoticeId(arrivalNoticeId);
         if (CollUtil.isEmpty(lines)) {
             throw exception(WM_ARRIVAL_NOTICE_NO_LINE);
         }
 
-        // 3. 鐢熸垚鍏ュ簱鍗曠紪鐮�
+        // 4. 鐢熸垚鍏ュ簱鍗曠紪鐮�
         String code = generateItemReceiptCode();
 
-        // 4. 鍒涘缓鍏ュ簱鍗曚富琛�
+        // 5. 鍒涘缓鍏ュ簱鍗曚富琛�
         MesWmItemReceiptDO receipt = new MesWmItemReceiptDO();
         receipt.setCode(code);
         receipt.setName(notice.getName() + "鍏ュ簱鍗�");
         receipt.setNoticeId(arrivalNoticeId);
         receipt.setPurchaseOrderCode(notice.getPurchaseOrderCode());
         receipt.setVendorId(notice.getVendorId());
+        receipt.setReceiptDate(java.time.LocalDateTime.now()); // 鍏ュ簱鏃堕棿涓哄綋鍓嶆椂闂�
         receipt.setStatus(MesWmItemReceiptStatusEnum.PREPARE.getStatus());
         itemReceiptMapper.insert(receipt);
 
-        // 5. 閬嶅巻琛屽垱寤哄叆搴撳崟琛�
+        // 6. 閬嶅巻琛屽垱寤哄叆搴撳崟琛�
         for (MesWmArrivalNoticeLineDO noticeLine : lines) {
             MesWmItemReceiptLineDO receiptLine = new MesWmItemReceiptLineDO();
             receiptLine.setReceiptId(receipt.getId());
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineServiceImpl.java
index a1ec2f7..2ba2348 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineServiceImpl.java
@@ -204,7 +204,7 @@
     @Transactional(rollbackFor = Exception.class)
     public void updateProductSalesLineWhenOqcFinish(Long id, Long oqcId, Integer checkResult) {
         // 1. 鏇存柊琛岀殑 oqcId + qualityStatus
-        Integer qualityStatus = Objects.equals(checkResult, MesQcCheckResultEnum.PASS.getType())
+        Integer qualityStatus = Objects.equals(checkResult, MesQcCheckResultEnum.QUALIFIED.getType())
                 ? MesWmQualityStatusEnum.PASS.getStatus() : MesWmQualityStatusEnum.FAIL.getStatus();
         productSalesLineMapper.updateById(new MesWmProductSalesLineDO()
                 .setId(id).setOqcId(oqcId).setQualityStatus(qualityStatus));
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeServiceImpl.java
index eb42a1f..d7124e9 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeServiceImpl.java
@@ -183,22 +183,28 @@
     @Override
     @Transactional(rollbackFor = Exception.class)
     public Long generateSalesNoticeFromSaleOrder(Long saleOrderId) {
-        // 1. 鑾峰彇閿�鍞鍗曚俊鎭�
+        // 1. 鏍¢獙鏄惁宸茬敓鎴愯繃鍙戣揣閫氱煡鍗曪紙闃叉閲嶅鐢熸垚锛�
+        MesWmSalesNoticeDO existNotice = salesNoticeMapper.selectBySaleOrderId(saleOrderId);
+        if (existNotice != null) {
+            throw exception(WM_SALES_NOTICE_ALREADY_EXISTS);
+        }
+
+        // 2. 鑾峰彇閿�鍞鍗曚俊鎭�
         ErpSaleOrderRespDTO saleOrder = saleOrderApi.getSaleOrder(saleOrderId).getCheckedData();
         if (saleOrder == null) {
             throw exception(WM_SALES_NOTICE_SALE_ORDER_NOT_EXISTS);
         }
 
-        // 2. 鑾峰彇閿�鍞鍗曟槑缁�
+        // 3. 鑾峰彇閿�鍞鍗曟槑缁�
         List<ErpSaleOrderItemRespDTO> saleOrderItems = saleOrderApi.getSaleOrderItemList(saleOrderId).getCheckedData();
         if (CollUtil.isEmpty(saleOrderItems)) {
             throw exception(WM_SALES_NOTICE_SALE_ORDER_ITEM_EMPTY);
         }
 
-        // 3. 鐢熸垚鍙戣揣閫氱煡鍗曠紪鐮�
+        // 4. 鐢熸垚鍙戣揣閫氱煡鍗曠紪鐮�
         String code = autoCodeRecordService.generateAutoCode("SALESNOTICE_CODE", null);
 
-        // 4. 鍒涘缓鍙戣揣閫氱煡鍗曚富琛�
+        // 5. 鍒涘缓鍙戣揣閫氱煡鍗曚富琛�
         MesWmSalesNoticeDO notice = new MesWmSalesNoticeDO();
         notice.setCode(code);
         notice.setName("閿�鍞鍗昜" + saleOrder.getNo() + "]鍙戣揣閫氱煡");
@@ -209,7 +215,7 @@
         notice.setStatus(PREPARE.getStatus());
         salesNoticeMapper.insert(notice);
 
-        // 5. 閬嶅巻鏄庣粏鍒涘缓鍙戣揣閫氱煡鍗曡
+        // 6. 閬嶅巻鏄庣粏鍒涘缓鍙戣揣閫氱煡鍗曡
         for (ErpSaleOrderItemRespDTO saleOrderItem : saleOrderItems) {
             MesWmSalesNoticeLineDO line = new MesWmSalesNoticeLineDO();
             line.setNoticeId(notice.getId());
@@ -315,16 +321,21 @@
             throw exception(WM_SALES_NOTICE_STATUS_NOT_APPROVED);
         }
 
-        // 2. 鑾峰彇鍙戣揣閫氱煡鍗曡
+        // 2. 鏍¢獙鏄惁宸茬敓鎴愯繃閿�鍞嚭搴撳崟锛堥槻姝㈤噸澶嶇敓鎴愶級
+        if (productSalesMapper.selectByNoticeId(noticeId) != null) {
+            throw exception(WM_PRODUCT_SALES_ALREADY_EXISTS);
+        }
+
+        // 3. 鑾峰彇鍙戣揣閫氱煡鍗曡
         List<MesWmSalesNoticeLineDO> noticeLines = salesNoticeLineService.getSalesNoticeLineListByNoticeId(noticeId);
         if (CollUtil.isEmpty(noticeLines)) {
             throw exception(WM_SALES_NOTICE_LINE_EMPTY);
         }
 
-        // 3. 鐢熸垚閿�鍞嚭搴撳崟缂栫爜
+        // 4. 鐢熸垚閿�鍞嚭搴撳崟缂栫爜
         String code = autoCodeRecordService.generateAutoCode("WM_PRODUCT_SALES_CODE", null);
 
-        // 4. 鍒涘缓閿�鍞嚭搴撳崟涓昏〃
+        // 5. 鍒涘缓閿�鍞嚭搴撳崟涓昏〃
         MesWmProductSalesDO productSales = new MesWmProductSalesDO();
         productSales.setCode(code);
         productSales.setName("鍙戣揣閫氱煡鍗昜" + notice.getCode() + "]鍑哄簱");
@@ -340,7 +351,7 @@
         productSales.setStatus(MesWmProductSalesStatusEnum.PREPARE.getStatus());
         productSalesMapper.insert(productSales);
 
-        // 5. 閬嶅巻鍙戣揣閫氱煡鍗曡鍒涘缓閿�鍞嚭搴撳崟琛�
+        // 6. 閬嶅巻鍙戣揣閫氱煡鍗曡鍒涘缓閿�鍞嚭搴撳崟琛�
         for (MesWmSalesNoticeLineDO noticeLine : noticeLines) {
             MesWmProductSalesLineDO salesLine = new MesWmProductSalesLineDO();
             salesLine.setSalesId(productSales.getId());
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsInboundStatusListener.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsInboundStatusListener.java
index 99ea2af..7336cce 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsInboundStatusListener.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsInboundStatusListener.java
@@ -4,7 +4,6 @@
 import cn.iocoder.yudao.module.mes.service.wms.approval.WmsApprovalService;
 import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.context.ApplicationListener;
 import org.springframework.stereotype.Component;
 
 /**
@@ -14,29 +13,38 @@
  */
 @Component
 @Slf4j
-public class WmsInboundStatusListener implements ApplicationListener<BpmProcessInstanceStatusEvent> {
+public class WmsInboundStatusListener extends cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEventListener {
+
+    /**
+     * 閲囪喘鍏ュ簱瀹℃壒娴佺▼瀹氫箟 Key
+     */
+    private static final String PROCESS_DEFINITION_KEY = "wms_inbound_approve";
 
     @Resource
     private WmsApprovalService approvalService;
 
     @Override
-    public void onApplicationEvent(BpmProcessInstanceStatusEvent event) {
-        // 閫氳繃 businessKey 鏌ヨ鍏ュ簱鍗�
+    protected String getProcessDefinitionKey() {
+        return PROCESS_DEFINITION_KEY;
+    }
+
+    @Override
+    protected void onEvent(BpmProcessInstanceStatusEvent event) {
         Long orderId;
         try {
             orderId = Long.parseLong(event.getBusinessKey());
         } catch (NumberFormatException e) {
-            return; // businessKey 涓嶆槸鏁板瓧锛屽拷鐣�
+            log.warn("[onEvent] businessKey({}) 涓嶆槸鏈夋晥鐨勬暟瀛�", event.getBusinessKey());
+            return;
         }
 
-        log.info("[onApplicationEvent] 鏀跺埌BPM瀹℃壒浜嬩欢锛宐usinessKey={}, status={}",
+        log.info("[onEvent] 鏀跺埌BPM瀹℃壒浜嬩欢锛宐usinessKey={}, status={}",
                 event.getBusinessKey(), event.getStatus());
 
-        // 璋冪敤瀹℃壒鏈嶅姟鏇存柊鐘舵��
         try {
             approvalService.updateAuditStatus(orderId, "INBOUND", event.getStatus());
         } catch (Exception e) {
-            log.error("[onApplicationEvent] 鏇存柊鍏ュ簱鍗曞鎵圭姸鎬佸け璐ワ紝orderId={}", orderId, e);
+            log.error("[onEvent] 鏇存柊鍏ュ簱鍗曞鎵圭姸鎬佸け璐ワ紝orderId={}", orderId, e);
         }
     }
 
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsOutboundStatusListener.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsOutboundStatusListener.java
index dae7b5e..1586e0c 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsOutboundStatusListener.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsOutboundStatusListener.java
@@ -4,7 +4,6 @@
 import cn.iocoder.yudao.module.mes.service.wms.approval.WmsApprovalService;
 import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.context.ApplicationListener;
 import org.springframework.stereotype.Component;
 
 /**
@@ -14,29 +13,38 @@
  */
 @Component
 @Slf4j
-public class WmsOutboundStatusListener implements ApplicationListener<BpmProcessInstanceStatusEvent> {
+public class WmsOutboundStatusListener extends cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEventListener {
+
+    /**
+     * 閿�鍞嚭搴撳鎵规祦绋嬪畾涔� Key
+     */
+    private static final String PROCESS_DEFINITION_KEY = "wms_outbound_approve";
 
     @Resource
     private WmsApprovalService approvalService;
 
     @Override
-    public void onApplicationEvent(BpmProcessInstanceStatusEvent event) {
-        // 閫氳繃 businessKey 鏌ヨ鍑哄簱鍗�
+    protected String getProcessDefinitionKey() {
+        return PROCESS_DEFINITION_KEY;
+    }
+
+    @Override
+    protected void onEvent(BpmProcessInstanceStatusEvent event) {
         Long orderId;
         try {
             orderId = Long.parseLong(event.getBusinessKey());
         } catch (NumberFormatException e) {
-            return; // businessKey 涓嶆槸鏁板瓧锛屽拷鐣�
+            log.warn("[onEvent] businessKey({}) 涓嶆槸鏈夋晥鐨勬暟瀛�", event.getBusinessKey());
+            return;
         }
 
-        log.info("[onApplicationEvent] 鏀跺埌BPM瀹℃壒浜嬩欢锛宐usinessKey={}, status={}",
+        log.info("[onEvent] 鏀跺埌BPM瀹℃壒浜嬩欢锛宐usinessKey={}, status={}",
                 event.getBusinessKey(), event.getStatus());
 
-        // 璋冪敤瀹℃壒鏈嶅姟鏇存柊鐘舵��
         try {
             approvalService.updateAuditStatus(orderId, "OUTBOUND", event.getStatus());
         } catch (Exception e) {
-            log.error("[onApplicationEvent] 鏇存柊鍑哄簱鍗曞鎵圭姸鎬佸け璐ワ紝orderId={}", orderId, e);
+            log.error("[onEvent] 鏇存柊鍑哄簱鍗曞鎵圭姸鎬佸け璐ワ紝orderId={}", orderId, e);
         }
     }
 
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsTransferStatusListener.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsTransferStatusListener.java
index fffea2c..fb30731 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsTransferStatusListener.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsTransferStatusListener.java
@@ -4,7 +4,6 @@
 import cn.iocoder.yudao.module.mes.service.wms.approval.WmsApprovalService;
 import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.context.ApplicationListener;
 import org.springframework.stereotype.Component;
 
 /**
@@ -14,29 +13,38 @@
  */
 @Component
 @Slf4j
-public class WmsTransferStatusListener implements ApplicationListener<BpmProcessInstanceStatusEvent> {
+public class WmsTransferStatusListener extends cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEventListener {
+
+    /**
+     * 璋冩嫧瀹℃壒娴佺▼瀹氫箟 Key
+     */
+    private static final String PROCESS_DEFINITION_KEY = "wms_transfer_approve";
 
     @Resource
     private WmsApprovalService approvalService;
 
     @Override
-    public void onApplicationEvent(BpmProcessInstanceStatusEvent event) {
-        // 閫氳繃 businessKey 鏌ヨ璋冩嫧鍗�
+    protected String getProcessDefinitionKey() {
+        return PROCESS_DEFINITION_KEY;
+    }
+
+    @Override
+    protected void onEvent(BpmProcessInstanceStatusEvent event) {
         Long orderId;
         try {
             orderId = Long.parseLong(event.getBusinessKey());
         } catch (NumberFormatException e) {
-            return; // businessKey 涓嶆槸鏁板瓧锛屽拷鐣�
+            log.warn("[onEvent] businessKey({}) 涓嶆槸鏈夋晥鐨勬暟瀛�", event.getBusinessKey());
+            return;
         }
 
-        log.info("[onApplicationEvent] 鏀跺埌BPM瀹℃壒浜嬩欢锛宐usinessKey={}, status={}",
+        log.info("[onEvent] 鏀跺埌BPM瀹℃壒浜嬩欢锛宐usinessKey={}, status={}",
                 event.getBusinessKey(), event.getStatus());
 
-        // 璋冪敤瀹℃壒鏈嶅姟鏇存柊鐘舵��
         try {
             approvalService.updateAuditStatus(orderId, "TRANSFER", event.getStatus());
         } catch (Exception e) {
-            log.error("[onApplicationEvent] 鏇存柊璋冩嫧鍗曞鎵圭姸鎬佸け璐ワ紝orderId={}", orderId, e);
+            log.error("[onEvent] 鏇存柊璋冩嫧鍗曞鎵圭姸鎬佸け璐ワ紝orderId={}", orderId, e);
         }
     }
 

--
Gitblit v1.9.3