From ffe7921a138331185186fd100846fe50dcaf841c Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期四, 16 七月 2026 10:08:02 +0800
Subject: [PATCH] 1.采购管理-采购申请恢复

---
 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/dal/dataobject/purchase/ErpPurchaseRequestDO.java                     |  127 ++++
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java                                         |    1 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderItemDO.java                   |    6 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestItemDO.java                 |   81 ++
 yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java               |    3 
 yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/dto/ErpPurchaseOrderItemRespDTO.java                 |    3 
 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-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestRespVO.java    |  118 ++++
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderRespVO.java        |    9 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcService.java                                   |   12 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/redis/no/ErpNoRedisDAO.java                                       |    5 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeServiceImpl.java           |   30 
 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                     |  132 ++++
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestSaveReqVO.java |   86 ++
 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                   |  430 ++++++++++++++
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcServiceImpl.java                               |   37 +
 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-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java                         |   24 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeLineService.java           |    8 
 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-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java             |   18 
 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/wm/arrivalnotice/MesWmArrivalNoticeLineServiceImpl.java       |    6 
 29 files changed, 1,689 insertions(+), 10 deletions(-)

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/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/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/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..f14cbc0 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
@@ -80,6 +81,23 @@
         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")
     @Operation(summary = "鍒犻櫎閲囪喘璁㈠崟")
     @Parameter(name = "ids", description = "缂栧彿鏁扮粍", required = true)
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..20563c6 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
@@ -145,8 +145,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..b62323e
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestRespVO.java
@@ -0,0 +1,118 @@
+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 = "鍚堣鏁伴噺", 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 = "闄勪欢鍦板潃", 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;
+
+    }
+
+}
\ 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..ae5b693
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestSaveReqVO.java
@@ -0,0 +1,86 @@
+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 = "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 = "浜у搧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..717fd49 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
@@ -89,4 +89,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..ee52968
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestDO.java
@@ -0,0 +1,127 @@
+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 String fileUrl;
+
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+
+    /**
+     * BPM 娴佺▼瀹炰緥缂栧彿
+     */
+    private String processInstanceId;
+
+    /**
+     * 鐢熸垚鐨勯噰璐鍗曠紪鍙�
+     */
+    private Long orderId;
+
+    /**
+     * 鐢熸垚鐨勯噰璐鍗曞彿
+     */
+    private String orderNo;
+
+}
\ 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..1b88736
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestItemDO.java
@@ -0,0 +1,81 @@
+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;
+
+}
\ 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..f965eed 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,7 @@
     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, "鎻愪氦瀹℃壒澶辫触锛屽彧鏈夋湭瀹℃牳鐘舵�佺殑閲囪喘璁㈠崟鎵嶈兘鎻愪氦");
 
     // ========== 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..a5fd3ad 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 缂栧彿
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..e013f25 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,6 +5,13 @@
 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;
@@ -16,6 +23,8 @@
 import cn.iocoder.yudao.module.erp.enums.ErpPurchaseOrderInStatusEnum;
 import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService;
 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;
@@ -34,7 +43,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 +63,13 @@
     private ErpSupplierService supplierService;
     @Resource
     private ErpAccountService accountService;
+
+    @Resource
+    private BpmProcessInstanceApi processInstanceApi;
+    @Resource
+    private BpmCategoryService bpmCategoryService;
+    @Resource
+    private BpmProcessDefinitionService bpmProcessDefinitionService;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -66,9 +88,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 +142,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);
@@ -147,6 +168,109 @@
         }
     }
 
+    @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) {
         // 杞寲涓� ErpPurchaseOrderItemDO 鍒楄〃锛堜骇鍝侀獙璇佸凡绉婚櫎锛�
         return convertList(list, o -> BeanUtils.toBean(o, ErpPurchaseOrderItemDO.class, item -> {
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..1811e98
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseRequestServiceImpl.java
@@ -0,0 +1,430 @@
+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));
+        }
+    }
+
+    @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. 鑾峰彇鐢宠鏄庣粏
+        List<ErpPurchaseRequestItemDO> requestItems = purchaseRequestItemMapper.selectListByRequestId(id);
+        if (CollUtil.isEmpty(requestItems)) {
+            throw exception(PURCHASE_REQUEST_NOT_EXISTS);
+        }
+
+        // 5. 鐢熸垚閲囪喘璁㈠崟鍙�
+        String orderNo = noRedisDAO.generate(ErpNoRedisDAO.PURCHASE_ORDER_NO_PREFIX);
+        if (purchaseOrderMapper.selectByNo(orderNo) != null) {
+            throw exception(PURCHASE_ORDER_NO_EXISTS);
+        }
+
+        // 6. 鍒涘缓閲囪喘璁㈠崟
+        ErpPurchaseOrderDO purchaseOrder = new ErpPurchaseOrderDO();
+        purchaseOrder.setNo(orderNo);
+        purchaseOrder.setStatus(ErpAuditStatus.APPROVE.getStatus()); // 閲囪喘璁㈠崟鐩存帴涓哄凡瀹℃牳鐘舵��
+        purchaseOrder.setSupplierId(supplierId);
+        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.setRemark(purchaseRequest.getRemark());
+        purchaseOrder.setInCount(BigDecimal.ZERO);
+        purchaseOrder.setInStatus(0);
+        purchaseOrderMapper.insert(purchaseOrder);
+
+        // 7. 鍒涘缓閲囪喘璁㈠崟鏄庣粏
+        List<ErpPurchaseOrderItemDO> orderItems = convertList(requestItems, item -> {
+            ErpPurchaseOrderItemDO orderItem = new ErpPurchaseOrderItemDO();
+            orderItem.setOrderId(purchaseOrder.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(false); // 榛樿涓嶉渶瑕佽川妫�
+            return orderItem;
+        });
+        purchaseOrderItemMapper.insertBatch(orderItems);
+
+        // 8. 鏇存柊閲囪喘鐢宠鐨勮鍗曚俊鎭�
+        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-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..2b7ddd6 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;
@@ -308,4 +309,40 @@
         return iqcMapper.selectCountByVendorId(vendorId);
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createIqcFromArrivalNotice(Long noticeId, MesWmArrivalNoticeLineDO line, String noticeCode, Long vendorId) {
+        // 1. 鏍¢獙鐗╂枡鏄惁閰嶇疆浜� IQC 妫�楠屾ā鏉�
+        MesQcTemplateItemDO templateItem = templateItemService.getRequiredTemplateByItemIdAndType(
+                line.getItemId(), MesQcTypeEnum.IQC.getType());
+        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();
+    }
+
 }
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..fb87c62 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
@@ -100,4 +100,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..b90ea8c 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
@@ -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/MesWmArrivalNoticeServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeServiceImpl.java
index 29db064..2f5f920 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
@@ -15,8 +15,10 @@
 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 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;
@@ -50,6 +52,10 @@
 
     @Resource
     private MesMdmItemService mdmItemService;
+
+    @Resource
+    @Lazy
+    private MesQcIqcService iqcService;
 
     @Override
     public Long createArrivalNotice(MesWmArrivalNoticeSaveReqVO createReqVO) {
@@ -115,7 +121,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 +132,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;
         }
         // 涓嶉渶瑕佹楠岋紝鐩存帴杩涘叆寰呭叆搴�
@@ -295,7 +318,8 @@
             // 娉ㄦ剰锛氳繖閲岄渶瑕佹牴鎹� 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.setIqcCheckFlag(Boolean.TRUE.equals(item.getQcCheckFlag()));
             arrivalNoticeLineService.createArrivalNoticeLineFromPurchaseOrderItem(line);
         }
 

--
Gitblit v1.9.3