From b3fe06c151dc8455649bf0a46e481ceb83e3ea2a Mon Sep 17 00:00:00 2001
From: 吴羡 <16976746+Xian-TT3@user.noreply.gitee.com>
Date: 星期六, 07 十一月 2026 13:37:54 +0800
Subject: [PATCH] feat: 完成好水川 MOM 需求1/2/4/5/8(装箱、BOM成本、销售合同、劳动合同、留样)

---
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderBomController.java |  144 ++++++++++++++++++++++++++++++++++++-----------
 1 files changed, 109 insertions(+), 35 deletions(-)

diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderBomController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderBomController.java
index a0ce794..f2f0d4e 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderBomController.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderBomController.java
@@ -12,19 +12,25 @@
 import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.bom.MesProWorkOrderBomSaveReqVO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemTypeDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdProductBomDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO;
 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.workorder.MesProWorkOrderBomDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderProcessDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemconsume.MesWmItemConsumeLineDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.wm.itemconsume.MesWmItemConsumeLineMapper;
 import cn.iocoder.yudao.module.mes.dal.mysql.wm.materialstock.MesWmMaterialStockMapper;
 import cn.iocoder.yudao.module.erp.api.purchase.ErpPurchaseOrderApi;
 import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderItemRespDTO;
 import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
 import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemTypeService;
+import cn.iocoder.yudao.module.mes.service.md.item.MesMdProductBomService;
 import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService;
+import cn.iocoder.yudao.module.mes.service.pro.cost.MesProMaterialPriceService;
+import cn.iocoder.yudao.module.mes.service.pro.cost.dto.MesProMaterialPriceRespDTO;
 import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProductBomService;
 import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProductService;
 import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderBomService;
@@ -70,6 +76,12 @@
     private MesProRouteProductBomService routeProductBomService;
     @Resource
     private MesWmMaterialStockMapper materialStockMapper;
+    @Resource
+    private MesWmItemConsumeLineMapper consumeLineMapper;
+    @Resource
+    private MesMdProductBomService productBomService;
+    @Resource
+    private MesProMaterialPriceService materialPriceService;
     @Resource
     private ErpPurchaseOrderApi purchaseOrderApi;
 
@@ -126,43 +138,18 @@
             return success(Collections.emptyList());
         }
 
-        // 2. 鑾峰彇宸ュ崟宸ュ簭鍒楄〃锛堝惈鍚勫伐搴忕殑鎶曟枡鏄庣粏涓庝骇鍑哄搧锛�
+        // 2. 鑾峰彇宸ュ崟宸ュ簭鍒楄〃涓庡伐鍗� BOM
         List<MesProWorkOrderProcessDO> processList = workOrderProcessService
                 .getWorkOrderProcessListByWorkOrderId(workOrderId);
-        if (CollUtil.isEmpty(processList)) {
-
-            return success(Collections.emptyList());
-        }
-
-        // 3. 鑾峰彇宸ュ崟 BOM 琛岋紙鍚勫伐搴忕殑鎶曟枡璁板綍锛屾寜 processId 鍒嗙粍浣跨敤锛�
         List<MesProWorkOrderBomDO> bomList = workOrderBomService.getWorkOrderBomListByWorkOrderId(workOrderId);
-        if (CollUtil.isEmpty(bomList)) {
+
+        // 3. 璁$畻鏈鐢熶骇鐨勬�荤墿鏂欓渶姹�
+        Map<Long, BigDecimal> totalRequirements = buildTotalRequirements(workOrder, processList, bomList);
+        if (CollUtil.isEmpty(totalRequirements)) {
             return success(Collections.emptyList());
         }
 
-        // 4. 閫氳繃浜у搧ID鍙嶆煡宸ヨ壓璺嚎锛堝伐鍗� routeId 鍙兘涓虹┖锛�
-        Long routeId = null;
-        if (workOrder.getProductId() != null) {
-            MesProRouteProductDO routeProduct = routeProductService.getRouteProductByItemId(workOrder.getProductId());
-            if (routeProduct != null) {
-                routeId = routeProduct.getRouteId();
-            }
-        }
-
-        // 5. 鎸夊伐搴忎骇鍑洪摼閫愬眰灞曞紑 BOM锛屽緱鍒板彾瀛愮墿鏂欙紙姣忓崟浣嶆垚鍝佺殑鐢ㄩ噺锛�
-        Map<Long, BigDecimal> leafItems = buildWorkOrderItems(processList, bomList, routeId);
-        if (CollUtil.isEmpty(leafItems)) {
-            return success(Collections.emptyList());
-        }
-
-        // 6. 涔樹互宸ュ崟鐢熶骇鏁伴噺锛屽緱鍒版�婚渶姹�
-        BigDecimal orderQty = workOrder.getQuantity() != null ? workOrder.getQuantity() : BigDecimal.ONE;
-        Map<Long, BigDecimal> totalRequirements = new LinkedHashMap<>();
-        for (Map.Entry<Long, BigDecimal> entry : leafItems.entrySet()) {
-            totalRequirements.put(entry.getKey(), entry.getValue().multiply(orderQty));
-        }
-
-        // 7. 鏌ヨ鏉ユ簮涓烘湰宸ュ崟鐨勯噰璐鍗曟槑缁嗭紝鎸変骇鍝佹眹鎬诲湪閫旈噰璐暟
+        // 4. 鏌ヨ鏉ユ簮涓烘湰宸ュ崟鐨勯噰璐鍗曟槑缁嗭紝鎸変骇鍝佹眹鎬诲湪閫旈噰璐暟
         //    sourceType=1 琛ㄧず鏉ユ簮绫诲瀷涓�"鐢熶骇宸ュ崟"
         Map<Long, BigDecimal> purchaseOrderQtyMap = new HashMap<>();
         List<ErpPurchaseOrderItemRespDTO> poItems = purchaseOrderApi
@@ -179,8 +166,83 @@
             }
         }
 
-        // 8. 鏌ヨ搴撳瓨锛屾嫾鎺� VO
-        return success(buildItemRespVOList(totalRequirements, purchaseOrderQtyMap));
+        // 5. 鏌ヨ鏈伐鍗曠疮璁℃秷鑰楋紝鎷兼帴 VO
+        Map<Long, BigDecimal> actualQuantityMap = buildActualQuantityMap(workOrderId);
+        return success(buildItemRespVOList(totalRequirements, purchaseOrderQtyMap, actualQuantityMap));
+    }
+
+    /**
+     * 璁$畻宸ュ崟鎬荤墿鏂欓渶姹傘��
+     *
+     * 浼樺厛绾э細
+     * 1. 宸ュ簭浜у嚭閾惧睍寮�锛堟湁宸ュ簭銆佹湁宸ュ崟 BOM 鏃讹級
+     * 2. 浜у搧 BOM 脳 宸ュ崟鏁伴噺锛堟湭閰嶇疆宸ュ簭/宸ュ崟 BOM 鏃剁殑鍏滃簳锛屼繚璇佽兘鎸夊崟閲忕畻鏂欙級
+     * 3. 宸ュ崟 BOM 鍏滃簳锛堟棤宸ュ簭琛屽凡鎸夊伐鍗曟暟閲忔斁澶э紱鏈夊伐搴忚涓哄崟浣嶇敤閲忥紝闇�涔樺伐鍗曟暟閲忥級
+     */
+    private Map<Long, BigDecimal> buildTotalRequirements(MesProWorkOrderDO workOrder,
+                                                         List<MesProWorkOrderProcessDO> processList,
+                                                         List<MesProWorkOrderBomDO> bomList) {
+        BigDecimal orderQty = workOrder.getQuantity() != null ? workOrder.getQuantity() : BigDecimal.ONE;
+        // 1. 宸ュ簭浜у嚭閾惧睍寮�
+        if (CollUtil.isNotEmpty(processList) && CollUtil.isNotEmpty(bomList)) {
+            Long routeId = null;
+            if (workOrder.getProductId() != null) {
+                MesProRouteProductDO routeProduct = routeProductService
+                        .getRouteProductByItemId(workOrder.getProductId());
+                if (routeProduct != null) {
+                    routeId = routeProduct.getRouteId();
+                }
+            }
+            Map<Long, BigDecimal> leafItems = buildWorkOrderItems(processList, bomList, routeId);
+            if (CollUtil.isNotEmpty(leafItems)) {
+                return multiplyQuantity(leafItems, orderQty);
+            }
+        }
+        // 2. 浜у搧 BOM 脳 宸ュ崟鏁伴噺
+        List<MesMdProductBomDO> productBomList = productBomService
+                .getProductBomListByItemId(workOrder.getProductId());
+        if (CollUtil.isNotEmpty(productBomList)) {
+            Map<Long, BigDecimal> requirements = new LinkedHashMap<>();
+            for (MesMdProductBomDO bom : productBomList) {
+                if (bom.getBomItemId() != null && bom.getQuantity() != null) {
+                    requirements.merge(bom.getBomItemId(),
+                            bom.getQuantity().multiply(orderQty), BigDecimal::add);
+                }
+            }
+            return requirements;
+        }
+        // 3. 宸ュ崟 BOM 鍏滃簳
+        if (CollUtil.isNotEmpty(bomList)) {
+            Map<Long, BigDecimal> requirements = new LinkedHashMap<>();
+            for (MesProWorkOrderBomDO bom : bomList) {
+                if (bom.getItemId() == null || bom.getQuantity() == null) {
+                    continue;
+                }
+                BigDecimal quantity = bom.getProcessId() == null
+                        ? bom.getQuantity() : bom.getQuantity().multiply(orderQty);
+                requirements.merge(bom.getItemId(), quantity, BigDecimal::add);
+            }
+            return requirements;
+        }
+        return Collections.emptyMap();
+    }
+
+    private static Map<Long, BigDecimal> multiplyQuantity(Map<Long, BigDecimal> source, BigDecimal factor) {
+        Map<Long, BigDecimal> result = new LinkedHashMap<>();
+        for (Map.Entry<Long, BigDecimal> entry : source.entrySet()) {
+            result.put(entry.getKey(), entry.getValue().multiply(factor));
+        }
+        return result;
+    }
+
+    private Map<Long, BigDecimal> buildActualQuantityMap(Long workOrderId) {
+        Map<Long, BigDecimal> result = new HashMap<>();
+        for (MesWmItemConsumeLineDO line : consumeLineMapper.selectListByWorkOrderId(workOrderId)) {
+            if (line.getItemId() != null && line.getQuantity() != null) {
+                result.merge(line.getItemId(), line.getQuantity(), BigDecimal::add);
+            }
+        }
+        return result;
     }
 
     // ==================== 鎷兼帴 VO ====================
@@ -318,13 +380,16 @@
     // ==================== 鎷兼帴鐗╂枡闇�姹� VO锛堝惈搴撳瓨鏍¢獙锛� ====================
 
     private List<MesProWorkOrderItemRespVO> buildItemRespVOList(Map<Long, BigDecimal> requirements,
-                                                                  Map<Long, BigDecimal> purchaseOrderQtyMap) {
-        // 1. 鎵归噺鑾峰彇鐗╂枡鍩虹淇℃伅
+                                                                  Map<Long, BigDecimal> purchaseOrderQtyMap,
+                                                                  Map<Long, BigDecimal> actualQuantityMap) {
+        // 1. 鎵归噺鑾峰彇鐗╂枡鍩虹淇℃伅 + 鐗╂枡浠锋牸
         Map<Long, MesMdItemDO> itemMap = itemService.getItemMap(requirements.keySet());
         Map<Long, MesMdUnitMeasureDO> unitMeasureMap = unitMeasureService.getUnitMeasureMap(
                 convertSet(itemMap.values(), MesMdItemDO::getUnitMeasureId));
         Map<Long, MesMdItemTypeDO> itemTypeMap = itemTypeService.getItemTypeMap(
                 convertSet(itemMap.values(), MesMdItemDO::getItemTypeId));
+        Map<Long, MesProMaterialPriceRespDTO> priceMap = materialPriceService
+                .getMaterialPriceMap(requirements.keySet());
 
         // 2. 鎵归噺鏌ヨ搴撳瓨锛屾寜 itemId 姹囨�诲彲鐢ㄩ噺锛堝湪搴� - 鍐荤粨 - 鍗犵敤锛�
         Map<Long, BigDecimal> stockMap = new HashMap<>();
@@ -352,9 +417,18 @@
             // 鍏呰冻鍒ゆ柇 = 鍙敤搴撳瓨 + 鍦ㄩ�旈噰璐暟 >= 闇�姹傛暟閲�
             BigDecimal totalSupply = stockQty.add(purchaseQty);
 
+            MesProMaterialPriceRespDTO price = priceMap.get(item.getId());
+            BigDecimal unitPrice = price != null && price.getUnitPrice() != null
+                    ? price.getUnitPrice() : BigDecimal.ZERO;
+            BigDecimal amount = requiredQty.multiply(unitPrice);
+
             MesProWorkOrderItemRespVO vo = new MesProWorkOrderItemRespVO()
                     .setItemId(item.getId()).setQuantity(requiredQty)
                     .setItemCode(item.getCode()).setItemName(item.getName()).setItemSpecification(item.getSpecification())
+                    .setActualQuantity(actualQuantityMap.getOrDefault(item.getId(), BigDecimal.ZERO))
+                    .setUnitPrice(unitPrice)
+                    .setAmount(amount)
+                    .setPriceMissing(price == null || Boolean.TRUE.equals(price.getPriceMissing()))
                     .setStockQuantity(stockQty)
                     .setPurchaseOrderQuantity(purchaseQty)
                     .setStockSufficient(totalSupply.compareTo(requiredQty) >= 0);

--
Gitblit v1.9.3