From eee03bbfea8b15d1a56ab63bd09a35c6c861c4cb Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期一, 09 十一月 2026 13:01:08 +0800
Subject: [PATCH] feat(mes): 不合格品处置联动入库与生产订单

---
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdProductBomServiceImpl.java |  133 ++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 133 insertions(+), 0 deletions(-)

diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdProductBomServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdProductBomServiceImpl.java
index 104fb0a..9db67d1 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdProductBomServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdProductBomServiceImpl.java
@@ -1,10 +1,15 @@
 package cn.iocoder.yudao.module.mes.service.md.item;
 
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
 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.module.mes.controller.admin.md.item.vo.bom.MesMdProductBomImportExcelVO;
+import cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.bom.MesMdProductBomImportRespVO;
 import cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.bom.MesMdProductBomPageReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.bom.MesMdProductBomSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdProductBomDO;
 import cn.iocoder.yudao.module.mes.dal.mysql.md.item.MesMdProductBomMapper;
 import jakarta.annotation.Resource;
@@ -141,4 +146,132 @@
         productBomMapper.deleteByItemId(itemId);
     }
 
+    @Override
+    public MesMdProductBomImportRespVO importProductBomList(List<MesMdProductBomImportExcelVO> importList,
+                                                            Boolean updateSupport) {
+        if (CollUtil.isEmpty(importList)) {
+            throw exception(MD_PRODUCT_BOM_IMPORT_LIST_IS_EMPTY);
+        }
+        MesMdProductBomImportRespVO respVO = MesMdProductBomImportRespVO.builder()
+                .createList(new ArrayList<>()).updateList(new ArrayList<>())
+                .failureList(new LinkedHashMap<>()).build();
+
+        // 1. 鐜版湁 BOM 鐨勭埗瀛愮粍鍚堢储寮� + 閭绘帴琛紙鐢ㄤ簬鍒ら噸鍜岄棴鐜娴嬶級
+        Map<String, MesMdProductBomDO> existingPairMap = new HashMap<>();
+        Map<Long, Set<Long>> graph = new HashMap<>();
+        for (MesMdProductBomDO bom : productBomMapper.selectAll()) {
+            existingPairMap.put(pairKey(bom.getItemId(), bom.getBomItemId()), bom);
+            graph.computeIfAbsent(bom.getItemId(), key -> new HashSet<>()).add(bom.getBomItemId());
+        }
+        Map<String, Optional<MesMdItemDO>> itemCache = new HashMap<>();
+        Set<String> filePairs = new HashSet<>();
+
+        // 2. 閫愯鏍¢獙 + 瀵煎叆
+        int rowNo = 0;
+        for (MesMdProductBomImportExcelVO importVO : importList) {
+            rowNo++;
+            String parentCode = StrUtil.trim(importVO.getParentItemCode());
+            String componentCode = StrUtil.trim(importVO.getBomItemCode());
+            String rowKey = "绗�" + rowNo + "琛� " + StrUtil.blankToDefault(parentCode, "(绌虹埗椤�)")
+                    + "鈫�" + StrUtil.blankToDefault(componentCode, "(绌哄瓙椤�)");
+            // 2.1 鍩虹鏍¢獙
+            if (StrUtil.isBlank(parentCode) || StrUtil.isBlank(componentCode)) {
+                respVO.getFailureList().put("绗�" + rowNo + "琛�",
+                        StrUtil.isBlank(parentCode) && StrUtil.isBlank(componentCode) ? "鐖堕」鐗╂枡缂栫爜鍜屽瓙椤圭墿鏂欑紪鐮侀兘涓嶈兘涓虹┖"
+                                : StrUtil.isBlank(parentCode) ? "鐖堕」鐗╂枡缂栫爜涓嶈兘涓虹┖" : "瀛愰」鐗╂枡缂栫爜涓嶈兘涓虹┖");
+                continue;
+            }
+            if (importVO.getQuantity() == null || importVO.getQuantity().signum() <= 0) {
+                respVO.getFailureList().put(rowKey, "鍗曚綅鐢ㄩ噺蹇呴』澶т簬 0锛屽綋鍓嶄负锛�"
+                        + (importVO.getQuantity() == null ? "绌�" : importVO.getQuantity().toPlainString()));
+                continue;
+            }
+            // 2.2 鐖堕」銆佸瓙椤瑰繀椤诲凡鍦� MES 鐗╂枡涓淮鎶�
+            MesMdItemDO parent = lookupItem(itemCache, parentCode);
+            MesMdItemDO component = lookupItem(itemCache, componentCode);
+            if (parent == null) {
+                respVO.getFailureList().put(rowKey, "鐖堕」鐗╂枡缂栫爜[" + parentCode
+                        + "]鍦ㄧ敓浜х墿鏂欑鐞嗕腑涓嶅瓨鍦紝璇峰厛缁存姢璇ョ墿鏂欏悗鍐嶅鍏�");
+                continue;
+            }
+            if (component == null) {
+                respVO.getFailureList().put(rowKey, "瀛愰」鐗╂枡缂栫爜[" + componentCode
+                        + "]鍦ㄧ敓浜х墿鏂欑鐞嗕腑涓嶅瓨鍦紝璇峰厛缁存姢璇ョ墿鏂欏悗鍐嶅鍏�");
+                continue;
+            }
+            if (parent.getId().equals(component.getId())) {
+                respVO.getFailureList().put(rowKey, "鐖堕」涓庡瓙椤逛笉鑳芥槸鍚屼竴鐗╂枡[" + parentCode + "]");
+                continue;
+            }
+            String pairKey = pairKey(parent.getId(), component.getId());
+            // 2.3 宸插瓨鍦ㄧ殑鐖跺瓙缁勫悎锛氶粯璁よ烦杩囷紝鍕鹃�夆�滄敮鎸佹洿鏂扳�濇椂鍙洿鏂扮敤閲�/澶囨敞锛堢姸鎬佷繚鎸佸師鍊硷級
+            MesMdProductBomDO existing = existingPairMap.get(pairKey);
+            if (existing != null) {
+                if (!Boolean.TRUE.equals(updateSupport)) {
+                    respVO.getFailureList().put(rowKey, "璇ョ埗瀛愮粍鍚堝凡瀛樺湪 BOM锛屾湰娆℃湭鏇存柊锛�"
+                            + "濡傞渶瑕嗙洊鐢ㄩ噺璇峰嬀閫夆�滄敮鎸佹洿鏂扳�濆悗閲嶆柊瀵煎叆");
+                    continue;
+                }
+                MesMdProductBomDO updateObj = new MesMdProductBomDO().setId(existing.getId())
+                        .setQuantity(importVO.getQuantity())
+                        .setRemark(StrUtil.blankToDefault(importVO.getRemark(), existing.getRemark()));
+                productBomMapper.updateById(updateObj);
+                respVO.getUpdateList().add(parentCode + "鈫�" + componentCode);
+                continue;
+            }
+            // 2.4 鏂囦欢鍐呴噸澶�
+            if (filePairs.contains(pairKey)) {
+                respVO.getFailureList().put(rowKey, "鍚屼竴鐖跺瓙缁勫悎鍦ㄦ枃浠朵腑閲嶅鍑虹幇锛岃鍚堝苟涓轰竴琛屽悗閲嶆柊瀵煎叆");
+                continue;
+            }
+            // 2.5 闂幆妫�娴嬶紙鍩轰簬鐜版湁 BOM + 鏈枃浠跺凡鎺ュ彈鐨勮锛�
+            if (reaches(graph, component.getId(), parent.getId())) {
+                respVO.getFailureList().put(rowKey, "鏂板鍚� BOM 灏嗗舰鎴愬惊鐜紩鐢紙" + parentCode
+                        + "鈫�" + componentCode + "锛夛紝璇疯皟鏁寸埗瀛愬叧绯诲悗閲嶆柊瀵煎叆");
+                continue;
+            }
+            // 2.6 鏂板
+            MesMdProductBomSaveReqVO saveReqVO = new MesMdProductBomSaveReqVO();
+            saveReqVO.setItemId(parent.getId());
+            saveReqVO.setBomItemId(component.getId());
+            saveReqVO.setQuantity(importVO.getQuantity());
+            saveReqVO.setStatus(0); // 瀵煎叆鐨� BOM 榛樿鍚敤
+            saveReqVO.setRemark(StrUtil.blankToDefault(importVO.getRemark(), null));
+            createProductBom(saveReqVO);
+            filePairs.add(pairKey);
+            graph.computeIfAbsent(parent.getId(), key -> new HashSet<>()).add(component.getId());
+            respVO.getCreateList().add(parentCode + "鈫�" + componentCode);
+        }
+        return respVO;
+    }
+
+    private MesMdItemDO lookupItem(Map<String, Optional<MesMdItemDO>> cache, String code) {
+        return cache.computeIfAbsent(code,
+                key -> Optional.ofNullable(itemService.getItemByCode(key))).orElse(null);
+    }
+
+    private static String pairKey(Long itemId, Long bomItemId) {
+        return itemId + ":" + bomItemId;
+    }
+
+    /** 娌跨幇鏈夛紙鍚湰娆″凡鎺ュ彈鐨勶級鐖跺瓙杈逛粠 start 鍑哄彂锛屽垽鏂兘鍚﹀埌杈� target */
+    private static boolean reaches(Map<Long, Set<Long>> graph, Long start, Long target) {
+        Deque<Long> stack = new ArrayDeque<>();
+        Set<Long> visited = new HashSet<>();
+        stack.push(start);
+        while (!stack.isEmpty()) {
+            Long node = stack.pop();
+            if (!visited.add(node)) {
+                continue;
+            }
+            if (node.equals(target)) {
+                return true;
+            }
+            for (Long next : graph.getOrDefault(node, Set.of())) {
+                stack.push(next);
+            }
+        }
+        return false;
+    }
+
 }

--
Gitblit v1.9.3