From 3edf8c4fc07f9784e2d04ee7a98692bb457749eb Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期一, 26 十月 2026 16:28:10 +0800
Subject: [PATCH] feat(mes): 新增工艺路线完整导入功能并优化相关验证

---
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/route/MesProRouteProcessServiceImpl.java |  195 +++++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 180 insertions(+), 15 deletions(-)

diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/route/MesProRouteProcessServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/route/MesProRouteProcessServiceImpl.java
index 4957169..f999ae7 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/route/MesProRouteProcessServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/route/MesProRouteProcessServiceImpl.java
@@ -1,12 +1,15 @@
 package cn.iocoder.yudao.module.mes.service.pro.route;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.NumberUtil;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.mes.controller.admin.pro.route.vo.process.MesProRouteProcessSaveReqVO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessOutputDO;
 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.mysql.pro.route.MesProRouteProcessMapper;
+import cn.iocoder.yudao.module.mes.dal.mysql.pro.route.MesProRouteProcessOutputMapper;
 import cn.iocoder.yudao.module.mes.service.pro.process.MesProProcessService;
 import jakarta.annotation.Resource;
 import org.springframework.context.annotation.Lazy;
@@ -14,11 +17,16 @@
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -35,6 +43,8 @@
 
     @Resource
     private MesProRouteProcessMapper routeProcessMapper;
+    @Resource
+    private MesProRouteProcessOutputMapper routeProcessOutputMapper;
 
     @Resource
     @Lazy
@@ -63,13 +73,10 @@
 
         // 2. 鎻掑叆
         MesProRouteProcessDO routeProcess = BeanUtils.toBean(createReqVO, MesProRouteProcessDO.class);
-        if (CollUtil.isNotEmpty(createReqVO.getOutputItemIds())) {
-            routeProcess.setOutputItemId(
-                createReqVO.getOutputItemIds().stream()
-                    .map(String::valueOf)
-                    .collect(Collectors.joining(",")));
-        }
+        routeProcess.setOutputItemId(resolveOutputItemId(createReqVO));
         routeProcessMapper.insert(routeProcess);
+        // 2.1 淇濆瓨浜у嚭褰掑睘閰嶇疆
+        saveRouteProcessOutputs(routeProcess, createReqVO);
 
         // 3. 鏇存柊宸ュ簭閾�
         rebuildProcessChain(createReqVO.getRouteId());
@@ -81,7 +88,7 @@
     public void updateRouteProcess(MesProRouteProcessSaveReqVO updateReqVO) {
         // 1.0 宸插惎鐢ㄧ殑宸ヨ壓璺嚎锛屼笉鍏佽鎿嶄綔
         routeService.validateRouteNotEnable(updateReqVO.getRouteId());
-        // 1.1 鏍¢獙瀛樺湪锛涢儴鍒嗘洿鏂帮紙濡備粎璋冩暣浜у嚭浜у搧锛夋椂鏈紶鐨勫繀濉瓧娈电敤鐜版湁鍊煎洖濉�
+        // 1.1 鏍¢獙瀛樺湪锛涘吋瀹瑰彧璋冩暣浜у嚭閰嶇疆鐨勯儴鍒嗘洿鏂拌姹�
         MesProRouteProcessDO existing = validateRouteProcessExists(updateReqVO.getId());
         if (updateReqVO.getProcessId() == null) {
             updateReqVO.setProcessId(existing.getProcessId());
@@ -98,13 +105,11 @@
 
         // 2. 鏇存柊
         MesProRouteProcessDO updateObj = BeanUtils.toBean(updateReqVO, MesProRouteProcessDO.class);
-        if (CollUtil.isNotEmpty(updateReqVO.getOutputItemIds())) {
-            updateObj.setOutputItemId(
-                updateReqVO.getOutputItemIds().stream()
-                    .map(String::valueOf)
-                    .collect(Collectors.joining(",")));
-        }
+        // 绌烘暟缁勪篃瑕佹樉寮忓啓鍏ョ┖涓诧紝鍚﹀垯 MyBatis-Plus 榛樿蹇界暐 null锛岀敤鎴锋竻绌轰骇鍑哄搧鍚庢棫閰嶇疆浠嶄細娈嬬暀銆�
+        updateObj.setOutputItemId(resolveOutputItemId(updateReqVO));
         routeProcessMapper.updateById(updateObj);
+        // 2.1 瑕嗙洊浜у嚭褰掑睘閰嶇疆
+        saveRouteProcessOutputs(updateObj, updateReqVO);
 
         // 3. 閲嶅缓宸ュ簭閾�
         rebuildProcessChain(updateReqVO.getRouteId());
@@ -118,8 +123,9 @@
         // 1.2 宸插惎鐢ㄧ殑宸ヨ壓璺嚎锛屼笉鍏佽鎿嶄綔
         routeService.validateRouteNotEnable(routeProcess.getRouteId());
 
-        // 2. 鍒犻櫎
+        // 2. 鍒犻櫎锛堝惈浜у嚭褰掑睘閰嶇疆锛�
         routeProcessMapper.deleteById(id);
+        routeProcessOutputMapper.deleteByRouteProcessId(id);
 
         // 3. 閲嶅缓宸ュ簭閾�
         rebuildProcessChain(routeProcess.getRouteId());
@@ -218,7 +224,165 @@
             return Collections.emptyList();
         }
         // 2. 杩斿洖璇ュ伐鑹鸿矾绾跨殑宸ュ簭鍒楄〃
-        return routeProcessMapper.selectListByRouteId(routeProduct.getRouteId());
+        List<MesProRouteProcessDO> list = routeProcessMapper.selectListByRouteId(routeProduct.getRouteId());
+        // 3. 鎸変骇鍑哄綊灞炶繃婊わ細閫氱敤浜у嚭 + 褰撳墠浜у搧涓撳睘浜у嚭
+        Map<Long, List<MesProRouteProcessOutputDO>> outputMap = routeProcessOutputMapper
+                .selectListByRouteId(routeProduct.getRouteId()).stream()
+                .collect(Collectors.groupingBy(MesProRouteProcessOutputDO::getRouteProcessId));
+        for (MesProRouteProcessDO process : list) {
+            List<MesProRouteProcessOutputDO> outputs = outputMap.get(process.getId());
+            if (CollUtil.isEmpty(outputs)) {
+                // 瀛橀噺鏁版嵁鏈媶鍒嗗綊灞炴椂锛岄��鍥炴棫鐨勨�滃墧闄ゅ悓璺嚎鍏跺畠浜у搧鈥濋�昏緫
+                filterOtherProductOutputs(Collections.singletonList(process), routeProduct);
+                continue;
+            }
+            String filtered = outputs.stream()
+                    .filter(output -> output.getProductId() == null || output.getProductId().equals(productId))
+                    .map(MesProRouteProcessOutputDO::getItemId)
+                    .filter(Objects::nonNull)
+                    .distinct()
+                    .map(String::valueOf)
+                    .collect(Collectors.joining(","));
+            process.setOutputItemId(filtered.isEmpty() ? null : filtered);
+        }
+        return list;
+    }
+
+    @Override
+    public List<Long> getOutputItemIdsByRouteProcessId(Long routeProcessId, Long productId) {
+        List<MesProRouteProcessOutputDO> outputs = routeProcessOutputMapper.selectListByRouteProcessId(routeProcessId);
+        if (CollUtil.isEmpty(outputs)) {
+            // 鏈厤缃骇鍑哄綊灞烇紙瀛橀噺鏁版嵁锛夎繑鍥� null锛屼究浜庤皟鐢ㄦ柟鍥為��鑱氬悎浜у嚭
+            return null;
+        }
+        return outputs.stream()
+                .filter(output -> output.getProductId() == null || output.getProductId().equals(productId))
+                .map(MesProRouteProcessOutputDO::getItemId)
+                .filter(Objects::nonNull)
+                .distinct()
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public List<MesProRouteProcessOutputDO> getRouteProcessOutputListByRouteProcessIds(Collection<Long> routeProcessIds) {
+        if (CollUtil.isEmpty(routeProcessIds)) {
+            return Collections.emptyList();
+        }
+        return routeProcessOutputMapper.selectListByRouteProcessIds(routeProcessIds);
+    }
+
+    /**
+     * 璁$畻宸ュ簭浜у嚭鐗╂枡鐨勮仛鍚堜覆锛堥�楀彿鍒嗛殧锛屽幓閲嶏級
+     *
+     * 浼樺厛浣跨敤浜у嚭褰掑睘閰嶇疆锛坥utputs锛夛紝鏈紶鏃跺吋瀹� outputItemIds銆�
+     */
+    private String resolveOutputItemId(MesProRouteProcessSaveReqVO reqVO) {
+        List<Long> itemIds;
+        if (CollUtil.isNotEmpty(reqVO.getOutputs())) {
+            itemIds = reqVO.getOutputs().stream()
+                    .filter(Objects::nonNull)
+                    .sorted(Comparator.comparing(MesProRouteProcessSaveReqVO.OutputItem::getSort,
+                            Comparator.nullsLast(Comparator.naturalOrder())))
+                    .map(MesProRouteProcessSaveReqVO.OutputItem::getItemId)
+                    .filter(Objects::nonNull)
+                    .distinct()
+                    .collect(Collectors.toList());
+        } else if (CollUtil.isNotEmpty(reqVO.getOutputItemIds())) {
+            itemIds = reqVO.getOutputItemIds().stream()
+                    .filter(Objects::nonNull)
+                    .distinct()
+                    .collect(Collectors.toList());
+        } else {
+            itemIds = Collections.emptyList();
+        }
+        return itemIds.stream().map(String::valueOf).collect(Collectors.joining(","));
+    }
+
+    /**
+     * 瑕嗙洊淇濆瓨宸ュ簭浜у嚭褰掑睘閰嶇疆
+     */
+    private void saveRouteProcessOutputs(MesProRouteProcessDO routeProcess,
+                                         MesProRouteProcessSaveReqVO reqVO) {
+        routeProcessOutputMapper.deleteByRouteProcessId(routeProcess.getId());
+        if (CollUtil.isEmpty(reqVO.getOutputs()) && CollUtil.isEmpty(reqVO.getOutputItemIds())) {
+            return;
+        }
+        Set<Long> routeProductIds = routeProductService.getRouteProductListByRouteId(routeProcess.getRouteId())
+                .stream().map(MesProRouteProductDO::getItemId).filter(Objects::nonNull).collect(Collectors.toSet());
+        List<MesProRouteProcessOutputDO> outputs = new ArrayList<>();
+        Set<String> keys = new HashSet<>();
+        int index = 0;
+        if (CollUtil.isNotEmpty(reqVO.getOutputs())) {
+            for (MesProRouteProcessSaveReqVO.OutputItem output : reqVO.getOutputs()) {
+                if (output == null || output.getItemId() == null) {
+                    continue;
+                }
+                index++;
+                if (!keys.add(output.getProductId() + "_" + output.getItemId())) {
+                    continue;
+                }
+                outputs.add(buildOutput(routeProcess, output.getProductId(), output.getItemId(),
+                        output.getSort() != null ? output.getSort() : index, output.getRemark()));
+            }
+        } else {
+            // 鍏煎鍙紶 outputItemIds 鐨勮皟鐢ㄦ柟锛氫骇鍑烘槸璺嚎浜у搧鏃跺綊灞炲埌鑷韩锛屽惁鍒欒涓洪�氱敤
+            for (Long itemId : reqVO.getOutputItemIds()) {
+                if (itemId == null) {
+                    continue;
+                }
+                index++;
+                Long productId = routeProductIds.contains(itemId) ? itemId : null;
+                if (!keys.add(productId + "_" + itemId)) {
+                    continue;
+                }
+                outputs.add(buildOutput(routeProcess, productId, itemId, index, null));
+            }
+        }
+        if (CollUtil.isNotEmpty(outputs)) {
+            routeProcessOutputMapper.insertBatch(outputs);
+        }
+    }
+
+    private MesProRouteProcessOutputDO buildOutput(MesProRouteProcessDO routeProcess, Long productId,
+                                                   Long itemId, Integer sort, String remark) {
+        return MesProRouteProcessOutputDO.builder()
+                .routeProcessId(routeProcess.getId())
+                .routeId(routeProcess.getRouteId())
+                .processId(routeProcess.getProcessId())
+                .productId(productId)
+                .itemId(itemId)
+                .sort(sort)
+                .remark(remark)
+                .build();
+    }
+
+    /**
+     * 杩囨护浜у嚭鐗╂枡锛氬墧闄ゅ悓涓�宸ヨ壓璺嚎涓嬪叾瀹冧骇鍝佺殑浜у嚭锛屽彧淇濈暀褰撳墠浜у搧鐨勪骇鍑哄拰鍗婃垚鍝佷骇鍑�
+     */
+    private void filterOtherProductOutputs(List<MesProRouteProcessDO> list, MesProRouteProductDO routeProduct) {
+        if (CollUtil.isEmpty(list) || routeProduct.getItemId() == null) {
+            return;
+        }
+        Set<Long> otherProductIds = routeProductService.getRouteProductListByRouteId(routeProduct.getRouteId())
+                .stream()
+                .map(MesProRouteProductDO::getItemId)
+                .filter(itemId -> itemId != null && !itemId.equals(routeProduct.getItemId()))
+                .collect(Collectors.toSet());
+        if (otherProductIds.isEmpty()) {
+            return;
+        }
+        for (MesProRouteProcessDO process : list) {
+            String outputItemId = process.getOutputItemId();
+            if (outputItemId == null || outputItemId.isBlank()) {
+                continue;
+            }
+            String filtered = Arrays.stream(outputItemId.split(","))
+                    .map(String::trim)
+                    .filter(s -> !s.isEmpty())
+                    .filter(s -> !otherProductIds.contains(NumberUtil.parseLong(s, -1L)))
+                    .collect(Collectors.joining(","));
+            process.setOutputItemId(filtered.isEmpty() ? null : filtered);
+        }
     }
 
     @Override
@@ -243,6 +407,7 @@
     @Override
     public void deleteRouteProcessByRouteId(Long routeId) {
         routeProcessMapper.deleteByRouteId(routeId);
+        routeProcessOutputMapper.deleteByRouteId(routeId);
     }
 
 }

--
Gitblit v1.9.3