From cccbfadcb6e96c23a33c40c0687a27c2f6efa1db Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期一, 09 十一月 2026 13:16:42 +0800
Subject: [PATCH] feat(seal): 添加印章详情及图片预览功能

---
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorServiceImpl.java |  458 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 435 insertions(+), 23 deletions(-)

diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorServiceImpl.java
index ae6d530..387146e 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorServiceImpl.java
@@ -2,10 +2,15 @@
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.exception.ServiceException;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
+import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
+import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
+import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.returnvendor.vo.MesWmReturnVendorPageReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.returnvendor.vo.MesWmReturnVendorSaveReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.returnvendor.vo.line.MesWmReturnVendorLineSaveReqVO;
@@ -14,22 +19,31 @@
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnvendor.MesWmReturnVendorLineDO;
 import cn.iocoder.yudao.module.mes.dal.mysql.wm.returnvendor.MesWmReturnVendorMapper;
 import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
+import cn.iocoder.yudao.module.mes.enums.wm.MesWmReturnVendorHandleTypeEnum;
 import cn.iocoder.yudao.module.mes.enums.wm.MesWmReturnVendorStatusEnum;
 import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
+import cn.iocoder.yudao.module.erp.api.finance.ErpFinancePaymentApi;
+import cn.iocoder.yudao.module.erp.api.finance.dto.ErpFinancePaymentRedFlushCreateReqDTO;
 import cn.iocoder.yudao.module.erp.api.purchase.ErpPurchaseOrderApi;
 import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderItemRespDTO;
 import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderRespDTO;
 import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
+import cn.iocoder.yudao.module.mes.service.wm.arrivalnotice.MesWmArrivalNoticeService;
 import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService;
 import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
+import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
 import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
 import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -41,7 +55,14 @@
  */
 @Service
 @Validated
+@Slf4j
 public class MesWmReturnVendorServiceImpl implements MesWmReturnVendorService {
+
+    /**
+     * 闄勪欢涓氬姟绫诲瀷锛歁ES 渚涘簲鍟嗛��璐у崟
+     */
+    public static final String RETURN_VENDOR_RECORD_TYPE = "mes_wm_return_vendor";
+    private static final String RETURN_VENDOR_APPROVAL_KEY = "purchase_return_vendor_approve";
 
     @Resource
     private MesWmReturnVendorMapper returnVendorMapper;
@@ -56,49 +77,83 @@
     private MesWmTransactionService wmTransactionService;
     @Resource
     private ErpPurchaseOrderApi purchaseOrderApi;
+    @Resource
+    private ErpFinancePaymentApi financePaymentApi;
+    @Resource
+    private MesWmArrivalNoticeService arrivalNoticeService;
+    @Resource
+    private StorageAttachmentApi storageAttachmentApi;
+    @Resource
+    private BpmProcessInstanceApi processInstanceApi;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
     public Long createReturnVendor(MesWmReturnVendorSaveReqVO createReqVO) {
         // 1. 鏍¢獙鏁版嵁
         validateReturnVendorSaveData(createReqVO);
-        // 1.1. 鑻ラ噰璐鍗曞彿涓虹┖浣嗗叧鑱斾簡閲囪喘璁㈠崟锛屽垯浠� ERP 閲囪喘璁㈠崟琛ラ綈璁㈠崟鍙�
-        fillPurchaseOrderCode(createReqVO);
+        // 1.1. 鑻ラ噰璐鍗曞彿涓虹┖浣嗗叧鑱斾簡閲囪喘璁㈠崟锛屽垯浠� ERP 閲囪喘璁㈠崟琛ラ綈璁㈠崟鍙枫�佸悎鍚屽彿
+        fillFromPurchaseOrder(createReqVO);
 
         // 2. 鎻掑叆涓昏〃
         MesWmReturnVendorDO returnVendor = BeanUtils.toBean(createReqVO, MesWmReturnVendorDO.class);
         returnVendor.setStatus(MesWmReturnVendorStatusEnum.PREPARE.getStatus());
         returnVendorMapper.insert(returnVendor);
+
+        // 2.1. 鍏宠仈浜嗛噰璐鍗曪細鑷姩鎶婅鍗曠墿鏂欏甫鍏ラ��璐ц锛堟棤鍙��鐗╂枡鏃堕潤榛橈紝涓嶅奖鍝嶅崟鎹垱寤猴級
+        if (returnVendor.getPurchaseOrderId() != null) {
+            autoImportLinesFromLinkedOrder(returnVendor.getId(), false);
+        }
+        // 2.2. 缁戝畾闄勪欢锛堣川閲忛儴鍙戣捣閫�璐ф椂涓婁紶鐨勬楠屾姤鍛娿�佺収鐗囩瓑锛�
+        storageAttachmentApi.bindAttachments("file", RETURN_VENDOR_RECORD_TYPE,
+                returnVendor.getId(), createReqVO.getBlobIds());
         return returnVendor.getId();
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void updateReturnVendor(MesWmReturnVendorSaveReqVO updateReqVO) {
-        // 1. 鏍¢獙鏁版嵁
-        validateReturnVendorSaveData(updateReqVO);
-        // 1.1. 鑻ラ噰璐鍗曞彿涓虹┖浣嗗叧鑱斾簡閲囪喘璁㈠崟锛屽垯浠� ERP 閲囪喘璁㈠崟琛ラ綈璁㈠崟鍙�
-        fillPurchaseOrderCode(updateReqVO);
+        // 1. 鏍¢獙鏁版嵁锛堝惈瀛樺湪 + 鑽夌鐘舵�侊級
+        MesWmReturnVendorDO oldReturnVendor = validateReturnVendorSaveData(updateReqVO);
+        // 1.1. 鑻ラ噰璐鍗曞彿涓虹┖浣嗗叧鑱斾簡閲囪喘璁㈠崟锛屽垯浠� ERP 閲囪喘璁㈠崟琛ラ綈璁㈠崟鍙枫�佸悎鍚屽彿
+        fillFromPurchaseOrder(updateReqVO);
+
+        Long oldPurchaseOrderId = oldReturnVendor != null ? oldReturnVendor.getPurchaseOrderId() : null;
+        Long newPurchaseOrderId = updateReqVO.getPurchaseOrderId();
+        boolean purchaseOrderChanged = !ObjUtil.equal(oldPurchaseOrderId, newPurchaseOrderId);
 
         // 2. 鏇存柊涓昏〃
         MesWmReturnVendorDO updateObj = BeanUtils.toBean(updateReqVO, MesWmReturnVendorDO.class);
         returnVendorMapper.updateById(updateObj);
+
+        // 3. 鍏宠仈浜嗛噰璐鍗曪細鑷姩鍚屾鐗╂枡琛岋紝淇濊瘉琛岀墿鏂欎笌璇ラ噰璐鍗曚竴鑷�
+        //    3.1 鏇存崲浜嗛噰璐鍗� 鈫� 鍏堟竻绌烘棫璁㈠崟甯﹀叆鐨勮锛屽啀鎸夋柊璁㈠崟閲嶆柊甯﹀叆
+        //    3.2 閲囪喘璁㈠崟鏈彉浣嗗綋鍓嶆棤琛� 鈫� 鍏滃簳鑷姩甯﹀叆锛堝巻鍙叉暟鎹垨甯︽枡琚腑鏂殑鍦烘櫙锛�
+        if (newPurchaseOrderId != null) {
+            autoImportLinesFromLinkedOrder(updateReqVO.getId(), purchaseOrderChanged);
+        }
+
+        // 4. 澧為噺鏇存柊闄勪欢锛堝彧澶勭悊鏂板/鍒犻櫎锛屾湭浼� blobIds 鏃朵笉鍔ㄧ幇鏈夐檮浠讹級
+        storageAttachmentApi.updateAttachments("file", RETURN_VENDOR_RECORD_TYPE,
+                updateReqVO.getId(), updateReqVO.getBlobIds());
     }
 
     /**
-     * 褰撻噰璐鍗曞彿涓虹┖銆佷絾鍏宠仈浜嗛噰璐鍗曟椂锛屼粠 ERP 閲囪喘璁㈠崟琛ラ綈閲囪喘璁㈠崟鍙�
+     * 鍏宠仈浜嗛噰璐鍗曟椂锛屼粠 ERP 閲囪喘璁㈠崟琛ラ綈閲囪喘璁㈠崟鍙枫�佸悎鍚屽彿锛堝悎鍚屽彿鍙仛蹇収锛屼笉鍏佽鎵嬪伐鐢熸垚锛�
      */
-    private void fillPurchaseOrderCode(MesWmReturnVendorSaveReqVO reqVO) {
+    private void fillFromPurchaseOrder(MesWmReturnVendorSaveReqVO reqVO) {
         if (reqVO == null || reqVO.getPurchaseOrderId() == null) {
-            return;
-        }
-        if (cn.hutool.core.util.StrUtil.isNotBlank(reqVO.getPurchaseOrderCode())) {
             return;
         }
         ErpPurchaseOrderRespDTO purchaseOrder =
                 purchaseOrderApi.getPurchaseOrder(reqVO.getPurchaseOrderId()).getCheckedData();
-        if (purchaseOrder != null) {
+        if (purchaseOrder == null) {
+            return;
+        }
+        if (StrUtil.isBlank(reqVO.getPurchaseOrderCode())) {
             reqVO.setPurchaseOrderCode(purchaseOrder.getNo());
+        }
+        if (StrUtil.isBlank(reqVO.getContractNo())) {
+            reqVO.setContractNo(purchaseOrder.getContractNo());
         }
     }
 
@@ -112,7 +167,9 @@
         returnVendorDetailService.deleteReturnVendorDetailByReturnId(id);
         // 2.2 绾ц仈鍒犻櫎琛�
         returnVendorLineService.deleteReturnVendorLineByReturnId(id);
-        // 2.3 鍒犻櫎涓昏〃
+        // 2.3 鍒犻櫎闄勪欢
+        storageAttachmentApi.deleteAttachmentsByRecord(RETURN_VENDOR_RECORD_TYPE, id);
+        // 2.4 鍒犻櫎涓昏〃
         returnVendorMapper.deleteById(id);
     }
 
@@ -129,17 +186,94 @@
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void submitReturnVendor(Long id) {
-        // 鏍¢獙瀛樺湪 + 鑽夌鐘舵��
-        validateReturnVendorExistsAndPrepare(id);
-        // 鏍¢獙鑷冲皯鏈変竴鏉¤
-        List<MesWmReturnVendorLineDO> lines = returnVendorLineService.getReturnVendorLineListByReturnId(id);
-        if (CollUtil.isEmpty(lines)) {
+        submitReturnVendorApproval(id, RETURN_VENDOR_APPROVAL_KEY);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void submitReturnVendorApproval(Long id, String processDefinitionKey) {
+        if (!RETURN_VENDOR_APPROVAL_KEY.equals(processDefinitionKey)) {
+            throw exception(WM_RETURN_VENDOR_APPROVAL_PROCESS_INVALID, RETURN_VENDOR_APPROVAL_KEY);
+        }
+        // 1. 鏍¢獙瀛樺湪 + 鑽夌鐘舵�� + 鑷冲皯涓�鏉¤
+        MesWmReturnVendorDO returnVendor = validateReturnVendorExistsAndPrepare(id);
+        if (CollUtil.isEmpty(returnVendorLineService.getReturnVendorLineListByReturnId(id))) {
             throw exception(WM_RETURN_VENDOR_NO_LINE);
         }
+        validateReturnVendorOrderQuantity(returnVendor);
+        // 鎷h揣鍙湪瀹℃壒閫氳繃鍚庤繘琛岋紱濡傚凡鎻愬墠褰曞叆鏄庣粏锛屽垯鎻愪氦鍓嶅繀椤绘暟閲忓畧鎭掋��
+        if (CollUtil.isNotEmpty(returnVendorDetailService.getReturnVendorDetailListByReturnId(id))
+                && !checkReturnVendorQuantity(id)) {
+            throw exception(WM_RETURN_VENDOR_DETAIL_QUANTITY_MISMATCH);
+        }
+        // 1.1. 璐ㄩ噺閮ㄥ彂璧烽��璐у繀椤讳笂浼犻檮浠讹紙璐ㄦ鎶ュ憡銆佷笉鍚堟牸鐓х墖绛夛級锛屽惁鍒欏鎵逛汉鏃犳硶鍒ゆ柇閫�璐т緷鎹�
+        if (CollUtil.isEmpty(storageAttachmentApi.listAttachments(RETURN_VENDOR_RECORD_TYPE, id))) {
+            throw exception(WM_RETURN_VENDOR_ATTACHMENT_REQUIRED, returnVendor.getCode());
+        }
 
-        // 鎻愪氦锛堣崏绋� -> 寰呮嫞璐э級
+        // 2. 鍏ㄥ眬鍏嶅鎵癸細鐩存帴缃负銆屽緟鎷h揣銆嶏紝鏃犻渶娴佺▼妯″瀷
+        if (processInstanceApi.isGlobalAutoApprove()) {
+            returnVendorMapper.updateById(new MesWmReturnVendorDO()
+                    .setId(id)
+                    .setStatus(MesWmReturnVendorStatusEnum.APPROVING.getStatus())
+                    .setProcessDefinitionKey(processDefinitionKey)
+                    .setProcessInstanceId(""));
+            return;
+        }
+
+        // 3. 鍒涘缓 BPM 娴佺▼瀹炰緥锛涙祦绋嬫湭鍙戝竷鏃剁粰鍑烘槑纭彁绀�
+        String processInstanceId;
+        try {
+            processInstanceId = processInstanceApi.createProcessInstance(getCurrentUserId(),
+                    new BpmProcessInstanceCreateReqDTO()
+                            .setProcessDefinitionKey(processDefinitionKey)
+                            .setBusinessKey(String.valueOf(id)));
+        } catch (ServiceException ex) {
+            if (cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.PROCESS_DEFINITION_NOT_EXISTS
+                    .getCode().equals(ex.getCode())) {
+                throw exception(WM_RETURN_VENDOR_APPROVE_PROCESS_NOT_PUBLISHED, processDefinitionKey);
+            }
+            throw ex;
+        }
+
+        // 4. 鏇存柊閫�璐у崟鐘舵�佷负瀹℃壒涓�
         returnVendorMapper.updateById(new MesWmReturnVendorDO()
-                .setId(id).setStatus(MesWmReturnVendorStatusEnum.APPROVING.getStatus()));
+                .setId(id)
+                .setStatus(MesWmReturnVendorStatusEnum.BPM_APPROVING.getStatus())
+                .setProcessDefinitionKey(processDefinitionKey)
+                .setProcessInstanceId(processInstanceId));
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateReturnVendorAuditStatus(Long id, Integer bpmResult) {
+        // 1. 鏍¢獙瀛樺湪 + 瀹℃壒涓�
+        MesWmReturnVendorDO returnVendor = validateReturnVendorExists(id);
+        if (!MesWmReturnVendorStatusEnum.BPM_APPROVING.getStatus().equals(returnVendor.getStatus())) {
+            return;
+        }
+
+        // 2. 鏍规嵁瀹℃壒缁撴灉鏇存柊鐘舵�侊細閫氳繃 -> 寰呮嫞璐э紱椹冲洖 -> 鑽夌锛涘彇娑� -> 宸插彇娑�
+        Integer newStatus = convertBpmResultToReturnVendorStatus(bpmResult);
+        if (newStatus != null) {
+            returnVendorMapper.updateById(new MesWmReturnVendorDO().setId(id).setStatus(newStatus));
+        }
+    }
+
+    /**
+     * 杞崲 BPM 瀹℃壒缁撴灉涓轰緵搴斿晢閫�璐у崟鐘舵��
+     */
+    private Integer convertBpmResultToReturnVendorStatus(Integer bpmResult) {
+        if (cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum.APPROVE.getStatus().equals(bpmResult)) {
+            return MesWmReturnVendorStatusEnum.APPROVING.getStatus();
+        }
+        if (cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum.REJECT.getStatus().equals(bpmResult)) {
+            return MesWmReturnVendorStatusEnum.PREPARE.getStatus();
+        }
+        if (cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum.CANCEL.getStatus().equals(bpmResult)) {
+            return MesWmReturnVendorStatusEnum.CANCELED.getStatus();
+        }
+        return null;
     }
 
     @Override
@@ -150,6 +284,11 @@
         if (ObjUtil.notEqual(MesWmReturnVendorStatusEnum.APPROVING.getStatus(), returnVendor.getStatus())) {
             throw exception(WM_RETURN_VENDOR_STATUS_INVALID);
         }
+        validateReturnVendorApproval(returnVendor);
+        if (!checkReturnVendorQuantity(id)) {
+            throw exception(WM_RETURN_VENDOR_DETAIL_QUANTITY_MISMATCH);
+        }
+        validateReturnVendorOrderQuantity(returnVendor);
         // 鎵ц鎷h揣锛堝緟鎷h揣 -> 寰呮墽琛岄��璐э級
         returnVendorMapper.updateById(new MesWmReturnVendorDO()
                 .setId(id).setStatus(MesWmReturnVendorStatusEnum.APPROVED.getStatus()));
@@ -163,23 +302,250 @@
         if (ObjUtil.notEqual(MesWmReturnVendorStatusEnum.APPROVED.getStatus(), returnVendor.getStatus())) {
             throw exception(WM_RETURN_VENDOR_STATUS_INVALID);
         }
+        validateReturnVendorApproval(returnVendor);
         // 1.2 鏍¢獙鏄庣粏涓嶄负绌�
         List<MesWmReturnVendorDetailDO> details = returnVendorDetailService.getReturnVendorDetailListByReturnId(id);
         if (CollUtil.isEmpty(details)) {
             throw exception(WM_RETURN_VENDOR_NO_DETAIL);
         }
+        if (!checkReturnVendorQuantity(id)) {
+            throw exception(WM_RETURN_VENDOR_DETAIL_QUANTITY_MISMATCH);
+        }
+        validateReturnVendorOrderQuantity(returnVendor);
+
+        // 1.3 鏍¢獙澶勭悊鏂瑰紡锛堥��娆� / 鎹㈣揣锛�
+        if (!MesWmReturnVendorHandleTypeEnum.isValid(returnVendor.getHandleType())) {
+            throw exception(WM_RETURN_VENDOR_HANDLE_TYPE_INVALID, returnVendor.getHandleType());
+        }
 
         // 2. 閬嶅巻鎵�鏈夋槑缁嗭紝鍒涘缓搴撳瓨浜嬪姟锛堟墸鍑忓簱瀛� + 璁板綍娴佹按锛�
         createTransactionList(returnVendor, details);
 
-        // 3. 鏇存柊閫�璐у崟鐘舵��
+        // 3. 鍥炲啓閲囪喘璁㈠崟琛岀殑宸查��璐ф暟閲忥紙鎸夌墿鏂欏尮閰嶉噰璐鍗曟槑缁嗭級
+        writeBackPurchaseOrderReturnCount(returnVendor);
+
+        // 4. 鍏堢疆涓哄凡瀹屾垚锛屼繚璇侀噸澶嶈皟鐢ㄤ細琚姸鎬佹牎楠屾嫤鎴紙骞傜瓑锛�
         returnVendorMapper.updateById(new MesWmReturnVendorDO()
                 .setId(id).setStatus(MesWmReturnVendorStatusEnum.FINISHED.getStatus()));
+
+        // 5. 璐圭敤澶勭悊锛氶��娆� -> 璐熸暟鍐茬孩浠樻鍗曪紱鎹㈣揣 -> 鎹㈣揣鍒拌揣閫氱煡鍗曪紙閲嶆柊鍏ュ簱 + 閲嶆柊璐ㄦ锛�
+        if (MesWmReturnVendorHandleTypeEnum.REFUND.getType().equals(returnVendor.getHandleType())) {
+            createRedFlushPayment(id);
+        } else if (MesWmReturnVendorHandleTypeEnum.REPLACEMENT.getType().equals(returnVendor.getHandleType())) {
+            createReplacementNotice(id);
+        }
+    }
+
+    private void validateReturnVendorApproval(MesWmReturnVendorDO returnVendor) {
+        if (!RETURN_VENDOR_APPROVAL_KEY.equals(returnVendor.getProcessDefinitionKey())
+                || returnVendor.getProcessInstanceId() == null) {
+            throw exception(WM_RETURN_VENDOR_APPROVAL_REQUIRED, returnVendor.getCode());
+        }
+    }
+
+    /**
+     * 鎶婇��璐ф暟閲忓閲忓洖鍐欏埌鍏宠仈閲囪喘璁㈠崟鐨勬槑缁嗚锛屽苟鍚屾璁㈠崟绾ч��璐ф暟閲�
+     */
+    private void writeBackPurchaseOrderReturnCount(MesWmReturnVendorDO returnVendor) {
+        if (returnVendor.getPurchaseOrderId() == null) {
+            return;
+        }
+        List<MesWmReturnVendorLineDO> lines = returnVendorLineService.getReturnVendorLineListByReturnId(returnVendor.getId());
+        if (CollUtil.isEmpty(lines)) {
+            return;
+        }
+        List<ErpPurchaseOrderItemRespDTO> orderItems = purchaseOrderApi
+                .getPurchaseOrderItemList(returnVendor.getPurchaseOrderId()).getCheckedData();
+        if (CollUtil.isEmpty(orderItems)) {
+            return;
+        }
+        Map<Long, Long> purchaseOrderItemIdMap = new HashMap<>();
+        Set<Long> ambiguousProductIds = new HashSet<>();
+        orderItems.stream()
+                .filter(item -> item.getProductId() != null)
+                .forEach(item -> {
+                    if (purchaseOrderItemIdMap.putIfAbsent(item.getProductId(), item.getId()) != null) {
+                        ambiguousProductIds.add(item.getProductId());
+                    }
+                });
+        for (MesWmReturnVendorLineDO line : lines) {
+            if (ambiguousProductIds.contains(line.getItemId())) {
+                throw exception(WM_RETURN_VENDOR_PURCHASE_ITEM_AMBIGUOUS, line.getItemId());
+            }
+            Long purchaseOrderItemId = purchaseOrderItemIdMap.get(line.getItemId());
+            if (purchaseOrderItemId == null || line.getQuantity() == null) {
+                continue;
+            }
+            if (!Boolean.TRUE.equals(purchaseOrderApi.updateOrderItemReturnCount(purchaseOrderItemId, line.getQuantity()).getCheckedData())) {
+                throw exception(WM_RETURN_VENDOR_ITEM_NOT_IN_PURCHASE_ORDER);
+            }
+        }
+    }
+
+    private void validateReturnVendorOrderQuantity(MesWmReturnVendorDO returnVendor) {
+        if (returnVendor.getPurchaseOrderId() == null) {
+            return;
+        }
+        List<ErpPurchaseOrderItemRespDTO> items = purchaseOrderApi
+                .getPurchaseOrderItemList(returnVendor.getPurchaseOrderId()).getCheckedData();
+        Map<Long, ErpPurchaseOrderItemRespDTO> byProduct = new HashMap<>();
+        Set<Long> ambiguousProductIds = new HashSet<>();
+        for (ErpPurchaseOrderItemRespDTO item : items) {
+            if (item.getProductId() != null && byProduct.putIfAbsent(item.getProductId(), item) != null) {
+                ambiguousProductIds.add(item.getProductId());
+            }
+        }
+        Map<Long, BigDecimal> requested = new HashMap<>();
+        for (MesWmReturnVendorLineDO line : returnVendorLineService.getReturnVendorLineListByReturnId(returnVendor.getId())) {
+            requested.merge(line.getItemId(), line.getQuantity(), BigDecimal::add);
+        }
+        for (Map.Entry<Long, BigDecimal> entry : requested.entrySet()) {
+            if (ambiguousProductIds.contains(entry.getKey())) {
+                throw exception(WM_RETURN_VENDOR_PURCHASE_ITEM_AMBIGUOUS, entry.getKey());
+            }
+            ErpPurchaseOrderItemRespDTO item = byProduct.get(entry.getKey());
+            if (item == null) {
+                throw exception(WM_RETURN_VENDOR_ITEM_NOT_IN_PURCHASE_ORDER);
+            }
+            BigDecimal available = (item.getInCount() == null ? BigDecimal.ZERO : item.getInCount())
+                    .subtract(item.getReturnCount() == null ? BigDecimal.ZERO : item.getReturnCount());
+            if (entry.getValue().compareTo(available) > 0) {
+                throw exception(WM_RETURN_VENDOR_QUANTITY_EXCEED, entry.getKey(), entry.getValue(), available);
+            }
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createRedFlushPayment(Long id) {
+        // 1. 鏍¢獙瀛樺湪 + 宸插畬鎴� + 澶勭悊鏂瑰紡涓洪��娆�
+        MesWmReturnVendorDO returnVendor = validateReturnVendorFinished(id);
+        if (!MesWmReturnVendorHandleTypeEnum.REFUND.getType().equals(returnVendor.getHandleType())) {
+            throw exception(WM_RETURN_VENDOR_HANDLE_TYPE_INVALID, returnVendor.getHandleType());
+        }
+        // 2. 骞傜瓑锛氬凡鐢熸垚杩囧啿绾㈠崟鐩存帴杩斿洖
+        if (returnVendor.getRedFlushPaymentId() != null) {
+            return returnVendor.getRedFlushPaymentId();
+        }
+        // 3. 璁$畻閫�娆鹃噾棰濓紙鏈~鍐欐椂鎸夐噰璐崟浠� 脳 閫�璐ф暟閲忥級
+        BigDecimal refundAmount = resolveRefundAmount(returnVendor);
+
+        // 4. 鐢熸垚璐熸暟鍐茬孩浠樻鍗�
+        Long paymentId = financePaymentApi.createRedFlushPayment(new ErpFinancePaymentRedFlushCreateReqDTO()
+                .setSupplierId(returnVendor.getVendorId())
+                .setRefundAmount(refundAmount)
+                .setSourceId(returnVendor.getId())
+                .setSourceNo(returnVendor.getCode())
+                .setFinanceUserId(getCurrentUserId())
+                .setRemark("渚涘簲鍟嗛��璐у崟[" + returnVendor.getCode() + "]閫�娆惧啿绾紝鍚堝悓鍙穂" + returnVendor.getContractNo() + "]"))
+                .getCheckedData();
+
+        // 5. 鍥炲啓鍐茬孩鍗曞彿涓庢渶缁堥��娆鹃噾棰�
+        returnVendorMapper.updateById(new MesWmReturnVendorDO()
+                .setId(id).setRefundAmount(refundAmount).setRedFlushPaymentId(paymentId));
+        return paymentId;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createReplacementNotice(Long id) {
+        // 1. 鏍¢獙瀛樺湪 + 宸插畬鎴� + 澶勭悊鏂瑰紡涓烘崲璐�
+        MesWmReturnVendorDO returnVendor = validateReturnVendorFinished(id);
+        if (!MesWmReturnVendorHandleTypeEnum.REPLACEMENT.getType().equals(returnVendor.getHandleType())) {
+            throw exception(WM_RETURN_VENDOR_HANDLE_TYPE_INVALID, returnVendor.getHandleType());
+        }
+        // 2. 骞傜瓑锛氬凡鐢熸垚杩囨崲璐у埌璐у崟鐩存帴杩斿洖
+        if (returnVendor.getReplacementNoticeId() != null) {
+            return returnVendor.getReplacementNoticeId();
+        }
+
+        // 3. 鐢熸垚鎹㈣揣鍒拌揣閫氱煡鍗曪紙寰呰川妫�锛岃鑷姩鍒涘缓 IQC锛�
+        Long noticeId = arrivalNoticeService.createReplacementArrivalNotice(id);
+
+        // 4. 鍥炲啓鍒拌揣閫氱煡鍗曠紪鍙�
+        returnVendorMapper.updateById(new MesWmReturnVendorDO()
+                .setId(id).setReplacementNoticeId(noticeId));
+        return noticeId;
+    }
+
+    /**
+     * 瑙f瀽閫�娆鹃噾棰濓細浼樺厛鍙栧崟鎹~鍐欏�硷紱鏈~鍐欐椂鎸夈�岄噰璐崟浠� 脳 閫�璐ф暟閲忋�嶉�愯绱姞
+     */
+    private BigDecimal resolveRefundAmount(MesWmReturnVendorDO returnVendor) {
+        if (returnVendor.getRefundAmount() != null
+                && returnVendor.getRefundAmount().compareTo(BigDecimal.ZERO) > 0) {
+            return returnVendor.getRefundAmount();
+        }
+        if (returnVendor.getPurchaseOrderId() == null) {
+            throw exception(WM_RETURN_VENDOR_REFUND_AMOUNT_REQUIRED,
+                    returnVendor.getCode(), "鏈叧鑱旈噰璐鍗�");
+        }
+        List<ErpPurchaseOrderItemRespDTO> orderItems = purchaseOrderApi
+                .getPurchaseOrderItemList(returnVendor.getPurchaseOrderId()).getCheckedData();
+        Map<Long, BigDecimal> priceMap = new HashMap<>();
+        if (CollUtil.isNotEmpty(orderItems)) {
+            orderItems.stream().filter(item -> item.getProductId() != null)
+                    .forEach(item -> priceMap.putIfAbsent(item.getProductId(), item.getProductPrice()));
+        }
+        List<MesWmReturnVendorLineDO> lines = returnVendorLineService.getReturnVendorLineListByReturnId(returnVendor.getId());
+        if (CollUtil.isEmpty(lines)) {
+            throw exception(WM_RETURN_VENDOR_NO_LINE);
+        }
+        BigDecimal total = BigDecimal.ZERO;
+        for (MesWmReturnVendorLineDO line : lines) {
+            BigDecimal price = priceMap.get(line.getItemId());
+            if (price == null) {
+                throw exception(WM_RETURN_VENDOR_REFUND_AMOUNT_REQUIRED,
+                        returnVendor.getCode(), "鐗╂枡ID=" + line.getItemId() + " 鏈尮閰嶅埌閲囪喘鍗曚环");
+            }
+            BigDecimal quantity = line.getQuantity() != null ? line.getQuantity() : BigDecimal.ZERO;
+            total = total.add(price.multiply(quantity));
+        }
+        if (total.compareTo(BigDecimal.ZERO) <= 0) {
+            throw exception(WM_RETURN_VENDOR_REFUND_AMOUNT_REQUIRED,
+                    returnVendor.getCode(), "閲囪喘鍗曚环 脳 閫�璐ф暟閲忓悎璁′负 0");
+        }
+        return total;
+    }
+
+    /**
+     * 鏍¢獙閫�璐у崟瀛樺湪涓斿凡瀹屾垚锛堣垂鐢ㄧ被鎿嶄綔浠呭湪瀹屾垚鍚庨��璐у崟涓婃墽琛岋級
+     */
+    private MesWmReturnVendorDO validateReturnVendorFinished(Long id) {
+        MesWmReturnVendorDO returnVendor = validateReturnVendorExists(id);
+        if (!MesWmReturnVendorStatusEnum.FINISHED.getStatus().equals(returnVendor.getStatus())) {
+            throw exception(WM_RETURN_VENDOR_FINISHED_REQUIRED, returnVendor.getCode(),
+                    resolveStatusName(returnVendor.getStatus()));
+        }
+        return returnVendor;
+    }
+
+    /**
+     * 鐘舵�佸�艰浆鍚嶇О锛岃В鏋愬け璐ュ洖閫�涓� ID 瀛楃涓�
+     */
+    private String resolveStatusName(Integer status) {
+        for (MesWmReturnVendorStatusEnum value : MesWmReturnVendorStatusEnum.values()) {
+            if (value.getStatus().equals(status)) {
+                return value.getName();
+            }
+        }
+        return "ID=" + status;
+    }
+
+    /**
+     * 鑾峰彇褰撳墠鐢ㄦ埛缂栧彿锛圔PM 娴佺▼鍙戣捣浜恒�佽储鍔′汉鍛橈級
+     */
+    private Long getCurrentUserId() {
+        // 缁熶竴浠庡畨鍏ㄦ鏋跺彇鐧诲綍鐢ㄦ埛锛汼pring Security 鐨� principal 鏄� LoginUser 鑰岄潪 Long锛�
+        // 鐩存帴寮鸿浆浼氬彇鍒� null锛屽鑷� BPM 娴佺▼瀹炰緥鐨勫彂璧蜂汉涓哄瓧绗︿覆 "null"
+        return SecurityFrameworkUtils.getLoginUserId();
     }
 
     private void createTransactionList(MesWmReturnVendorDO returnVendor, List<MesWmReturnVendorDetailDO> details) {
         wmTransactionService.createTransactionList(convertList(details, detail -> new MesWmTransactionSaveReqDTO()
                 .setType(MesWmTransactionTypeEnum.OUT.getType()).setItemId(detail.getItemId())
+                // 鎸夋嫞璐ф椂閫変腑鐨勫簱瀛樿褰曟墸鍑忥細鐢熶骇鎵规鍙兘娌℃湁 vendor_id锛屼粎闈� 鐗╂枡+搴撲綅+鎵规+渚涘簲鍟� 缁勫悎閿細鎵句笉鍒板簱瀛�
+                .setMaterialStockId(detail.getMaterialStockId())
                 .setQuantity(detail.getQuantity().negate()) // 鍑哄簱鏁伴噺涓鸿礋鏁�
                 .setBatchId(detail.getBatchId()).setBatchCode(detail.getBatchCode())
                 .setWarehouseId(detail.getWarehouseId()).setLocationId(detail.getLocationId()).setAreaId(detail.getAreaId())
@@ -198,6 +564,15 @@
                 MesWmReturnVendorStatusEnum.FINISHED.getStatus(),
                 MesWmReturnVendorStatusEnum.CANCELED.getStatus())) {
             throw exception(WM_RETURN_VENDOR_CANCEL_NOT_ALLOWED);
+        }
+
+        // 瀹℃壒涓厛鎾ら攢 BPM 瀹炰緥锛屾挙閿�澶辫触鏃朵繚鐣欏崟鎹師鐘舵�佸拰寰呭姙銆�
+        if (MesWmReturnVendorStatusEnum.BPM_APPROVING.getStatus().equals(returnVendor.getStatus())) {
+            if (StrUtil.isBlank(returnVendor.getProcessInstanceId())) {
+                throw exception(WM_RETURN_VENDOR_APPROVAL_REQUIRED, returnVendor.getCode());
+            }
+            processInstanceApi.cancelProcessInstanceByStartUser(getCurrentUserId(),
+                    returnVendor.getProcessInstanceId(), "渚涘簲鍟嗛��璐у崟鍙栨秷");
         }
 
         // 鍙栨秷
@@ -239,16 +614,53 @@
 
     /**
      * 鏍¢獙閫�璐у崟鍒涘缓/淇敼鐨勫叕鍏辨暟鎹�
+     *
+     * @return 淇敼鏃惰繑鍥炴棫鍗曟嵁锛堢敤浜庢瘮杈冮噰璐鍗曟槸鍚﹀彉鏇达級锛涘垱寤烘椂杩斿洖 null
      */
-    private void validateReturnVendorSaveData(MesWmReturnVendorSaveReqVO reqVO) {
+    private MesWmReturnVendorDO validateReturnVendorSaveData(MesWmReturnVendorSaveReqVO reqVO) {
         // 鏍¢獙瀛樺湪 + 鑽夌鐘舵�侊紙浠呬慨鏀规椂锛�
+        MesWmReturnVendorDO oldReturnVendor = null;
         if (reqVO.getId() != null) {
-            validateReturnVendorExistsAndPrepare(reqVO.getId());
+            oldReturnVendor = validateReturnVendorExistsAndPrepare(reqVO.getId());
         }
         // 鏍¢獙渚涘簲鍟嗗瓨鍦�
         srmSupplierApi.getSupplier(reqVO.getVendorId());
         // 鏍¢獙缂栫爜鍞竴
         validateCodeUnique(reqVO.getId(), reqVO.getCode());
+        return oldReturnVendor;
+    }
+
+    /**
+     * 鎶婂叧鑱旈噰璐鍗曠殑鐗╂枡鑷姩甯﹀叆閫�璐у崟琛岋紙浠呰崏绋跨姸鎬佷笖宸插叧鑱旈噰璐鍗曟椂鐢熸晥锛夈��
+     *
+     * @param id         閫�璐у崟缂栧彿
+     * @param clearFirst 鏄惁鍏堟竻绌哄凡鏈夎鍐嶅甫鍏ワ紱鏇存崲閲囪喘璁㈠崟鏃朵紶 true
+     */
+    private void autoImportLinesFromLinkedOrder(Long id, boolean clearFirst) {
+        MesWmReturnVendorDO returnVendor = returnVendorMapper.selectById(id);
+        if (returnVendor == null || returnVendor.getPurchaseOrderId() == null) {
+            return;
+        }
+        List<MesWmReturnVendorLineDO> existingLines =
+                returnVendorLineService.getReturnVendorLineListByReturnId(id);
+        if (clearFirst) {
+            if (CollUtil.isNotEmpty(existingLines)) {
+                // 绾ц仈鍒犻櫎鏃ф槑缁嗕笌琛�
+                returnVendorDetailService.deleteReturnVendorDetailByReturnId(id);
+                returnVendorLineService.deleteReturnVendorLineByReturnId(id);
+            }
+        } else if (CollUtil.isNotEmpty(existingLines)) {
+            // 宸叉湁琛屼笖鏈洿鎹㈤噰璐鍗曪細涓嶉噸澶嶅甫鍏ワ紝閬垮厤瑕嗙洊鐢ㄦ埛宸茶皟鏁寸殑鏁伴噺
+            return;
+        }
+        try {
+            importPurchaseOrderItems(id);
+        } catch (ServiceException ex) {
+            // 閲囪喘璁㈠崟鏃犳槑缁� / 鏃犲彲閫�鐗╂枡绛変笟鍔″紓甯革細鍗曟嵁涓讳綋浠嶆甯镐繚瀛橈紝鍙湪鐗╂枡鍖虹偣銆岄噸鏂板甫鍏ヨ鍗曠墿鏂欍�嶈ˉ甯�
+            // 鎵撴棩蹇椾究浜庢帓鏌ワ紙渚嬪锛氫繚瀛樺悗鎵嶅叆搴擄紝姝ゆ椂璁㈠崟杩樻病鏈夊彲閫�鏁伴噺锛�
+            log.warn("[autoImportLinesFromLinkedOrder][閫�璐у崟({}) 鍏宠仈閲囪喘璁㈠崟({}) 鑷姩甯﹀叆鐗╂枡澶辫触锛歿}]",
+                    id, returnVendor.getPurchaseOrderId(), ex.getMessage());
+        }
     }
 
     /**

--
Gitblit v1.9.3