From 1eb432710b7a3a2e820be10b17c0138b377ee1b8 Mon Sep 17 00:00:00 2001
From: liu <2021943741@qq.com>
Date: 星期四, 20 八月 2026 16:50:12 +0800
Subject: [PATCH] 仓储物流-采购退货新增关联采购订单及带入物料功能

---
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsServiceImpl.java |  144 +++++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 135 insertions(+), 9 deletions(-)

diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsServiceImpl.java
index 2ddacaf..1fcd8f1 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsServiceImpl.java
@@ -13,13 +13,23 @@
 import cn.iocoder.yudao.module.mes.dal.mysql.pro.mps.MesProMpsMapper;
 import cn.iocoder.yudao.module.mes.dal.mysql.pro.mps.MesProMpsMergeItemMapper;
 import cn.iocoder.yudao.module.mes.dal.mysql.pro.feedback.MesProFeedbackMapper;
+import cn.iocoder.yudao.module.mes.dal.mysql.pro.workorder.MesProWorkOrderMapper;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProductBomDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProductDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
 import cn.iocoder.yudao.module.mes.enums.pro.MesProMpsStatusEnum;
 import cn.iocoder.yudao.module.mes.enums.pro.MesProFeedbackStatusEnum;
 import cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderSourceTypeEnum;
+import cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderStatusEnum;
 import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService;
 import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
+import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProductService;
+import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProcessService;
 import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
+import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderProcessService;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.process.MesProWorkOrderProcessSaveReqVO;
 import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi;
 import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
@@ -30,9 +40,13 @@
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
@@ -40,7 +54,7 @@
 /**
  * MES 鐢熶骇涓昏鍒掞紙MPS锛塖ervice 瀹炵幇绫�
  *
- * @author 鑺嬮亾婧愮爜
+ * @author 瓒呯骇绠$悊鍛�
  */
 @Service
 @Validated
@@ -66,6 +80,14 @@
     private CrmCustomerApi customerApi;
     @Resource
     private MesProFeedbackMapper feedbackMapper;
+    @Resource
+    private MesProWorkOrderMapper workOrderMapper;
+    @Resource
+    private MesProRouteProductService routeProductService;
+    @Resource
+    private MesProRouteProcessService routeProcessService;
+    @Resource
+    private MesProWorkOrderProcessService workOrderProcessService;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -261,14 +283,26 @@
             throw exception(PRO_MPS_NOT_ISSUED);
         }
 
-        // 绾ц仈鍙栨秷鍏宠仈鐨勭敓浜у伐鍗�
+        // 绾ц仈鍙栨秷鍏宠仈鐨勭敓浜у伐鍗曪紙璺熼殢宸ュ崟褰撳墠鐘舵�侊紝涓斾笉璇激鍚堝苟涓嬪彂鐨勫叡浜伐鍗曪級
         if (mps.getWorkOrderId() != null) {
-            try {
-                workOrderService.cancelWorkOrder(mps.getWorkOrderId());
-                log.info("[cancelMps] 绾ц仈鍙栨秷鐢熶骇宸ュ崟锛寃orkOrderId={}", mps.getWorkOrderId());
-            } catch (Exception e) {
-                // 宸ュ崟鍙兘宸插畬鎴愭垨宸插彇娑堬紝璁板綍鏃ュ織浣嗕笉闃绘 MPS 鍙栨秷
-                log.warn("[cancelMps] 鍙栨秷鐢熶骇宸ュ崟澶辫触锛寃orkOrderId={}锛屽師鍥狅細{}", mps.getWorkOrderId(), e.getMessage());
+            // 1. 缁熻璇ュ伐鍗曡澶氬皯鏉� MPS 寮曠敤锛氬悎骞朵笅鍙戠殑澶氭潯 MPS 鍏变韩鍚屼竴涓伐鍗�
+            Long refCount = mpsMapper.selectCount(MesProMpsDO::getWorkOrderId, mps.getWorkOrderId());
+            if (refCount != null && refCount > 1) {
+                // 鍏变韩宸ュ崟锛氬彧鍙栨秷褰撳墠 MPS锛屼笉鍔ㄥ伐鍗�
+                log.info("[cancelMps] 鐢熶骇宸ュ崟({}) 琚� {} 鏉� MPS 鍏辩敤锛岃烦杩囩骇鑱斿彇娑堝伐鍗�",
+                        mps.getWorkOrderId(), refCount);
+            } else {
+                MesProWorkOrderDO workOrder = workOrderService.getWorkOrder(mps.getWorkOrderId());
+                if (workOrder != null
+                        && MesProWorkOrderStatusEnum.CONFIRMED.getStatus().equals(workOrder.getStatus())) {
+                    // 鍙湁"宸茬‘璁�"鐘舵�佺殑宸ュ崟鎵嶉渶瑕佺骇鑱斿彇娑�
+                    workOrderService.cancelWorkOrder(mps.getWorkOrderId());
+                    log.info("[cancelMps] 绾ц仈鍙栨秷鐢熶骇宸ュ崟锛寃orkOrderId={}", mps.getWorkOrderId());
+                } else {
+                    // 宸ュ崟宸叉槸鑽夌/宸插畬鎴�/宸插彇娑堬紝鏃犻渶绾ц仈鍙栨秷锛岀洿鎺ュ彇娑� MPS
+                    log.info("[cancelMps] 鐢熶骇宸ュ崟({}) 鐘舵�佷负 {}锛屾棤闇�绾ц仈鍙栨秷",
+                            mps.getWorkOrderId(), workOrder != null ? workOrder.getStatus() : "null");
+                }
             }
         }
 
@@ -365,13 +399,53 @@
         return mpsMapper.selectOne(MesProMpsDO::getWorkOrderId, workOrderId);
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean checkAndFinishMps(Long mpsId) {
+        if (mpsId == null) {
+            log.warn("[checkAndFinishMps] mpsId is null");
+            return false;
+        }
+        MesProMpsDO mps = mpsMapper.selectById(mpsId);
+        if (mps == null) {
+            log.warn("[checkAndFinishMps] MPS not found, mpsId={}", mpsId);
+            return false;
+        }
+        // 宸插畬鎴愭垨宸插彇娑堬紝鐩存帴杩斿洖 true
+        if (MesProMpsStatusEnum.FINISHED.getStatus().equals(mps.getStatus())
+                || MesProMpsStatusEnum.CANCELED.getStatus().equals(mps.getStatus())) {
+            log.info("[checkAndFinishMps] MPS {} already finished or canceled, status={}", mpsId, mps.getStatus());
+            return true;
+        }
+        // 妫�鏌ユ槸鍚︽弧瓒冲畬鎴愭潯浠�
+        boolean finished = isMpsFinished(mps);
+        log.info("[checkAndFinishMps] MPS {} check result: {}, quantity={}, produced={}, workOrderId={}",
+                mpsId, finished, mps.getQuantity(), mps.getQuantityProduced(), mps.getWorkOrderId());
+        if (finished) {
+            // 鑷姩瀹屾垚 MPS
+            mpsMapper.updateById(new MesProMpsDO()
+                    .setId(mpsId)
+                    .setStatus(MesProMpsStatusEnum.FINISHED.getStatus())
+                    .setFinishDate(LocalDateTime.now()));
+            log.info("[checkAndFinishMps] MPS {} auto finished", mpsId);
+            return true;
+        }
+        return false;
+    }
+
     /**
      * 鍒ゆ柇鍗曚釜 MPS 鏄惁瀹屾垚
      */
     private boolean isMpsFinished(MesProMpsDO mps) {
+        // 宸插彇娑堢殑 MPS 瑙嗕负缁堟�侊紝涓嶅啀闃诲鐢熶骇瀹屾垚鍒ゆ柇锛堜笌 checkCanCreateSalesNotice 鐨勫彛寰勪繚鎸佷竴鑷达級
+        if (MesProMpsStatusEnum.CANCELED.getStatus().equals(mps.getStatus())) {
+            return true;
+        }
         // 1. 宸茬敓浜ф暟閲� >= 璁″垝鏁伴噺
         BigDecimal quantityProduced = ObjUtil.defaultIfNull(mps.getQuantityProduced(), BigDecimal.ZERO);
         if (quantityProduced.compareTo(mps.getQuantity()) < 0) {
+            log.debug("[isMpsFinished] MPS {} quantity not enough: produced={}, required={}",
+                    mps.getId(), quantityProduced, mps.getQuantity());
             return false;
         }
         // 2. 妫�鏌ュ伐鍗曚笅鎵�鏈夋姤宸ユ槸鍚﹀畬鎴愶紙纭繚璐ㄦ瀹屾垚锛�
@@ -380,6 +454,7 @@
                     new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MesProFeedbackDO>()
                             .eq(MesProFeedbackDO::getWorkOrderId, mps.getWorkOrderId())
                             .ne(MesProFeedbackDO::getStatus, MesProFeedbackStatusEnum.FINISHED.getStatus()));
+            log.debug("[isMpsFinished] MPS {} unfinished feedback count: {}", mps.getId(), unfinishedCount);
             if (unfinishedCount > 0) {
                 return false;
             }
@@ -454,6 +529,20 @@
      * 鏍规嵁 MPS 鍒涘缓鐢熶骇宸ュ崟
      */
     private Long createWorkOrderFromMps(MesProMpsDO mps, BigDecimal quantity) {
+        // 1. 鏍¢獙浜у搧鏄惁瀛樺湪宸ヨ壓璺嚎
+        MesProRouteProductDO routeProduct = routeProductService.getRouteProductByItemId(mps.getProductId());
+        if (routeProduct == null) {
+            throw exception(PRO_ROUTE_NOT_EXISTS);
+        }
+        Long routeId = routeProduct.getRouteId();
+
+        // 2. 鏍¢獙宸ヨ壓璺嚎鏄惁鏈夊伐搴�
+        List<MesProRouteProcessDO> routeProcesses = routeProcessService.getRouteProcessListByRouteId(routeId);
+        if (CollUtil.isEmpty(routeProcesses)) {
+            throw exception(PRO_ROUTE_ENABLE_NO_PROCESS);
+        }
+
+        // 3. 鍒涘缓鐢熶骇宸ュ崟
         MesProWorkOrderSaveReqVO workOrderReq = new MesProWorkOrderSaveReqVO();
         workOrderReq.setCode(generateWorkOrderCode());
         workOrderReq.setName("鐢熶骇宸ュ崟-" + mps.getCode());
@@ -468,7 +557,44 @@
 
         Long workOrderId = workOrderService.createWorkOrder(workOrderReq);
 
-        // 宸ュ崟鐩存帴纭涓哄凡纭鐘舵�侊紙璺宠繃鑽夌锛�
+        // 4. 鏇存柊宸ュ崟宸ヨ壓璺嚎
+        workOrderMapper.updateById(new MesProWorkOrderDO().setId(workOrderId).setRouteId(routeId));
+
+        // 5. 淇濆瓨宸ュ崟宸ュ簭锛堜粠宸ヨ壓璺嚎宸ュ簭甯﹀叆锛屽惈鎶曟枡BOM锛�
+        Map<Long, List<MesProRouteProductBomDO>> bomMap = routeProcessService.getRouteProcessBomMapByProductId(mps.getProductId());
+        List<MesProWorkOrderProcessSaveReqVO> processList = new ArrayList<>();
+        for (MesProRouteProcessDO rp : routeProcesses) {
+            MesProWorkOrderProcessSaveReqVO process = new MesProWorkOrderProcessSaveReqVO();
+            process.setWorkOrderId(workOrderId);
+            process.setProcessId(rp.getProcessId());
+            process.setSort(rp.getSort());
+            process.setKeyFlag(rp.getKeyFlag());
+            process.setCheckFlag(rp.getCheckFlag());
+            process.setBackflushFlag(rp.getBackflushFlag());
+            if (rp.getOutputItemId() != null && !rp.getOutputItemId().isBlank()) {
+                process.setOutputItemIds(Arrays.stream(rp.getOutputItemId().split(","))
+                        .map(String::trim).filter(s -> !s.isEmpty())
+                        .map(Long::valueOf).collect(Collectors.toList()));
+            }
+            process.setColorCode(rp.getColorCode());
+            // 甯﹀叆宸ュ簭鎶曟枡BOM
+            List<MesProRouteProductBomDO> routeBomItems = bomMap != null ? bomMap.get(rp.getProcessId()) : null;
+            if (CollUtil.isNotEmpty(routeBomItems)) {
+                List<MesProWorkOrderProcessSaveReqVO.BomItem> bomItems = new ArrayList<>();
+                for (MesProRouteProductBomDO bomDO : routeBomItems) {
+                    MesProWorkOrderProcessSaveReqVO.BomItem bomItem = new MesProWorkOrderProcessSaveReqVO.BomItem();
+                    bomItem.setItemId(bomDO.getItemId());
+                    bomItem.setQuantity(bomDO.getQuantity());
+                    bomItem.setRemark(bomDO.getRemark());
+                    bomItems.add(bomItem);
+                }
+                process.setBomItems(bomItems);
+            }
+            processList.add(process);
+        }
+        workOrderProcessService.batchSaveWorkOrderProcess(processList);
+
+        // 6. 宸ュ崟鐩存帴纭涓哄凡纭鐘舵��
         workOrderService.confirmWorkOrder(workOrderId);
 
         return workOrderId;

--
Gitblit v1.9.3