From ac2e2b5af3726ee64dc37c3ebf26989a0346f22a Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期六, 07 十一月 2026 19:16:05 +0800
Subject: [PATCH] feat(feedback): 优化报工审批流程并增加报工人校验

---
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderBomController.java |  148 +++++++++++++++++++++++++++++++++++++++---------
 1 files changed, 119 insertions(+), 29 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 2f2795d..f798cc2 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
@@ -13,18 +13,23 @@
 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.unitmeasure.MesMdUnitMeasureDO;
+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.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.pro.task.MesProTaskDO;
+import cn.iocoder.yudao.module.mes.service.pro.task.MesProTaskService;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO;
 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.PurchaseOrderApi;
 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.unitmeasure.MesMdUnitMeasureService;
+import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProcessService;
 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;
@@ -40,6 +45,7 @@
 import org.springframework.web.bind.annotation.*;
 
 import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.util.*;
 import java.util.Comparator;
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -51,6 +57,9 @@
 @RequestMapping("/mes/pro/work-order-bom")
 @Validated
 public class MesProWorkOrderBomController {
+
+    @Resource
+    private MesProTaskService taskService;
 
     @Resource
     private MesProWorkOrderBomService workOrderBomService;
@@ -69,9 +78,11 @@
     @Resource
     private MesProRouteProductBomService routeProductBomService;
     @Resource
+    private MesProRouteProcessService routeProcessService;
+    @Resource
     private MesWmMaterialStockMapper materialStockMapper;
     @Resource
-    private ErpPurchaseOrderApi purchaseOrderApi;
+    private PurchaseOrderApi purchaseOrderApi;
 
     @PostMapping("/create")
     @Operation(summary = "鍒涘缓宸ュ崟 BOM")
@@ -100,7 +111,6 @@
     @GetMapping("/get")
     @Operation(summary = "鑾峰緱宸ュ崟 BOM")
     @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
-    @PreAuthorize("@ss.hasPermission('mes:pro-work-order:query')")
     public CommonResult<MesProWorkOrderBomRespVO> getWorkOrderBom(@RequestParam("id") Long id) {
         MesProWorkOrderBomDO workOrderBom = workOrderBomService.getWorkOrderBom(id);
         if (workOrderBom == null) {
@@ -111,16 +121,26 @@
 
     @GetMapping("/page")
     @Operation(summary = "鑾峰緱宸ュ崟 BOM 鍒嗛〉")
-    @PreAuthorize("@ss.hasPermission('mes:pro-work-order:query')")
     public CommonResult<PageResult<MesProWorkOrderBomRespVO>> getWorkOrderBomPage(@Valid MesProWorkOrderBomPageReqVO pageReqVO) {
         PageResult<MesProWorkOrderBomDO> pageResult = workOrderBomService.getWorkOrderBomPage(pageReqVO);
         return success(new PageResult<>(buildWorkOrderBomRespVOList(pageResult.getList()), pageResult.getTotal()));
     }
 
+    @GetMapping("/list-by-task-id")
+    @Operation(summary = "鑾峰緱鐢熶骇浠诲姟宸ュ簭鐨勬姇鍏ョ墿鏂�")
+    public CommonResult<List<MesProWorkOrderBomRespVO>> getWorkOrderBomListByTaskId(@RequestParam("taskId") Long taskId) {
+        MesProTaskDO task = taskService.validateTaskExists(taskId);
+        if (task.getWorkOrderId() == null || task.getProcessId() == null) {
+            return success(Collections.emptyList());
+        }
+        List<MesProWorkOrderBomDO> list = workOrderBomService.getWorkOrderBomListByWorkOrderId(task.getWorkOrderId())
+                .stream().filter(bom -> Objects.equals(bom.getProcessId(), task.getProcessId())).toList();
+        return success(buildWorkOrderBomRespVOList(list));
+    }
+
     @GetMapping("/item-list-by-work-order-id")
     @Operation(summary = "鑾峰緱宸ュ崟鐗╂枡闇�姹傚垪琛�")
     @Parameter(name = "workOrderId", description = "宸ュ崟缂栧彿", required = true)
-    @PreAuthorize("@ss.hasPermission('mes:pro-work-order:query')")
     public CommonResult<List<MesProWorkOrderItemRespVO>> getWorkOrderBomItemListByWorkOrderId(
             @RequestParam("workOrderId") Long workOrderId) {
         // 1. 鑾峰彇宸ュ崟
@@ -153,7 +173,7 @@
         }
 
         // 5. 鎸夊伐搴忎骇鍑洪摼閫愬眰灞曞紑 BOM锛屽緱鍒板彾瀛愮墿鏂欙紙姣忓崟浣嶆垚鍝佺殑鐢ㄩ噺锛�
-        Map<Long, BigDecimal> leafItems = buildWorkOrderItems(processList, bomList, routeId);
+        Map<Long, BigDecimal> leafItems = buildWorkOrderItems(processList, bomList, routeId, workOrder.getProductId());
         if (CollUtil.isEmpty(leafItems)) {
             return success(Collections.emptyList());
         }
@@ -230,29 +250,24 @@
      *
      * @param processList 宸ュ崟宸ュ簭鍒楄〃锛堟寜 sort 鍗囧簭锛�
      * @param bomList     宸ュ崟 BOM 琛岋紙鎵�鏈夊伐搴忕殑鎶曟枡鏄庣粏锛�
-     * @param routeId     宸ヨ壓璺嚎缂栧彿锛堝彲涓� null锛宯ull 鏃惰烦杩囪矾鐢� BOM 鏌ヨ锛�
+     * @param routeId     宸ヨ壓璺嚎缂栧彿锛堝彲涓� null锛宯ull 鏃惰烦杩囪矾鐢� BOM 鏌ヨ涓庨厤姣斿洖鏌ワ級
+     * @param productId   浜у搧缂栧彿锛堢敤浜庡洖鏌ヨ浜у搧鐨勪骇鍑洪厤姣旓級
      * @return 鍙跺瓙鐗╂枡 Map锛歩temId 鈫� 闇�姹傛暟閲�
      */
     private Map<Long, BigDecimal> buildWorkOrderItems(List<MesProWorkOrderProcessDO> processList,
                                                        List<MesProWorkOrderBomDO> bomList,
-                                                       Long routeId) {
-        // 1. 鏋勫缓浜у嚭鍝� 鈫� 宸ュ簭鎶曟枡閰嶆柟锛坧er-unit锛�
-        //    outputItemId 鈫� [{itemId, quantity}, ...]
+                                                       Long routeId, Long productId) {
+        // 1. 宸ュ簭鎶曟枡閰嶆柟锛歱rocessId 鈫� 鎶曟枡鏄庣粏
         Map<Long, List<MesProWorkOrderBomDO>> processBomMap = new HashMap<>();
-        for (MesProWorkOrderProcessDO process : processList) {
-            if (process.getOutputItemId() == null) {
-                continue;
+        for (MesProWorkOrderBomDO bom : bomList) {
+            if (bom.getProcessId() != null) {
+                processBomMap.computeIfAbsent(bom.getProcessId(), key -> new ArrayList<>()).add(bom);
             }
-            List<MesProWorkOrderBomDO> items = new ArrayList<>();
-            for (MesProWorkOrderBomDO bom : bomList) {
-                if (bom.getProcessId().equals(process.getProcessId())) {
-                    items.add(bom);
-                }
-            }
-            processBomMap.put(Long.valueOf(process.getOutputItemId().split(",")[0].trim()), items);
         }
+        // 2. 浜у嚭鐗╂枡 鈫� 鎵�灞炲伐搴� + 姣忚疆浜у嚭閲忥紙涓讳骇鍑烘寜 1锛岃仈浜у嚭鎸夎矾绾块厤姣旓級
+        Map<Long, OutputProducer> outputProducerMap = buildOutputProducers(processList, routeId, productId);
 
-        // 2. 鍙栨渶鍚庝竴閬撳伐搴忥紙sort 鏈�澶э級浣滀负灞曞紑璧风偣锛岄�愬眰鍚戝墠閫掓帹
+        // 3. 鍙栨渶鍚庝竴閬撳伐搴忥紙sort 鏈�澶э級浣滀负灞曞紑璧风偣锛岄�愬眰鍚戝墠閫掓帹
         MesProWorkOrderProcessDO lastProcess = processList.stream()
                 .max(Comparator.comparing(MesProWorkOrderProcessDO::getSort))
                 .orElse(null);
@@ -260,7 +275,7 @@
             return Collections.emptyMap();
         }
 
-        // 3. 浠ユ渶鍚庝竴閬撳伐搴忕殑鎶曟枡涓鸿捣鐐�
+        // 3.1 浠ユ渶鍚庝竴閬撳伐搴忕殑鎶曟枡涓鸿捣鐐�
         Map<Long, BigDecimal> currentLayer = new LinkedHashMap<>();
         for (MesProWorkOrderBomDO bom : bomList) {
             if (bom.getProcessId().equals(lastProcess.getProcessId())) {
@@ -275,8 +290,10 @@
         Map<Long, BigDecimal> leafItems = new LinkedHashMap<>();
         for (int i = 0; i < Byte.MAX_VALUE && CollUtil.isNotEmpty(currentLayer); i++) {
             Map<Long, BigDecimal> nextLayer = new LinkedHashMap<>();
+            // 4.1 鍚屼竴宸ュ簭鐨勫涓仈浜у嚭鍏堝綊骞讹紝鍐嶆寜杞鍙睍寮�涓�娆★紝閬垮厤鎶曟枡閲嶅璁$畻
+            Map<Long, List<Map.Entry<Long, BigDecimal>>> processOutputs = new LinkedHashMap<>();
 
-            // 4.1 鎵归噺鏌ヨ璺敱浜у搧 BOM锛堢敤浜庨潪宸ュ簭浜у嚭鐨勭墿鏂欏睍寮�锛�
+            // 4.2 鎵归噺鏌ヨ璺敱浜у搧 BOM锛堢敤浜庨潪宸ュ簭浜у嚭鐨勭墿鏂欏睍寮�锛�
             Map<Long, List<MesProRouteProductBomDO>> routeSubBomMap;
             if (routeId != null) {
                 List<MesProRouteProductBomDO> routeSubBomList = routeProductBomService
@@ -290,12 +307,10 @@
                 Long itemId = entry.getKey();
                 BigDecimal qty = entry.getValue();
 
-                // 鎯呭喌涓�锛氱墿鏂欐槸鏌愬伐搴忕殑浜у嚭鍝� 鈫� 鐢ㄥ伐搴忛厤鏂规浛浠�
-                List<MesProWorkOrderBomDO> processBom = processBomMap.get(itemId);
-                if (CollUtil.isNotEmpty(processBom)) {
-                    for (MesProWorkOrderBomDO child : processBom) {
-                        nextLayer.merge(child.getItemId(), qty.multiply(child.getQuantity()), BigDecimal::add);
-                    }
+                // 鎯呭喌涓�锛氱墿鏂欐槸鏌愬伐搴忕殑浜у嚭鍝� 鈫� 璁板叆璇ュ伐搴忥紝寰呮暣灞傚綊骞跺悗灞曞紑
+                OutputProducer producer = outputProducerMap.get(itemId);
+                if (producer != null && CollUtil.isNotEmpty(processBomMap.get(producer.processId()))) {
+                    processOutputs.computeIfAbsent(producer.processId(), key -> new ArrayList<>()).add(entry);
                     continue;
                 }
 
@@ -311,11 +326,86 @@
                 // 鎯呭喌涓夛細鍙跺瓙鐗╂枡
                 leafItems.merge(itemId, qty, BigDecimal::add);
             }
+
+            // 鎯呭喌涓�锛堢画锛夛細杞 = max(鍚勪骇鍑洪渶姹傞噺 / 璇ヤ骇鍑烘瘡杞骇鍑洪噺)锛岀敤杞灞曞紑涓�娆¤宸ュ簭鎶曟枡
+            for (Map.Entry<Long, List<Map.Entry<Long, BigDecimal>>> entry : processOutputs.entrySet()) {
+                BigDecimal runs = BigDecimal.ZERO;
+                for (Map.Entry<Long, BigDecimal> output : entry.getValue()) {
+                    OutputProducer producer = outputProducerMap.get(output.getKey());
+                    BigDecimal perUnit = producer != null && producer.perUnit().signum() > 0
+                            ? producer.perUnit() : BigDecimal.ONE;
+                    BigDecimal outputRuns = output.getValue().divide(perUnit, 10, RoundingMode.HALF_UP);
+                    if (outputRuns.compareTo(runs) > 0) {
+                        runs = outputRuns;
+                    }
+                }
+                for (MesProWorkOrderBomDO child : processBomMap.get(entry.getKey())) {
+                    nextLayer.merge(child.getItemId(), runs.multiply(child.getQuantity()), BigDecimal::add);
+                }
+            }
             currentLayer = nextLayer;
         }
         return leafItems;
     }
 
+    /**
+     * 鏋勫缓浜у嚭鐗╂枡 鈫� 鎵�灞炲伐搴� + 姣忚疆浜у嚭閲忋��
+     * 宸ュ簭浜у嚭蹇収鐨勭涓�浣嶄负涓讳骇鍑猴紙姣忚疆浜у嚭 1锛夛紝鍏朵綑浜у嚭浠庤矾绾块厤缃洖鏌ラ厤姣旓紝缂哄け鎸� 1銆�
+     */
+    private Map<Long, OutputProducer> buildOutputProducers(List<MesProWorkOrderProcessDO> processList,
+                                                           Long routeId, Long productId) {
+        Map<Long, OutputProducer> outputProducerMap = new HashMap<>();
+        if (CollUtil.isEmpty(processList)) {
+            return outputProducerMap;
+        }
+        Map<Long, Long> routeProcessIds = new HashMap<>();
+        if (routeId != null) {
+            for (MesProRouteProcessDO routeProcess : routeProcessService.getRouteProcessListByRouteId(routeId)) {
+                if (routeProcess.getProcessId() != null && routeProcess.getId() != null) {
+                    routeProcessIds.putIfAbsent(routeProcess.getProcessId(), routeProcess.getId());
+                }
+            }
+        }
+        for (MesProWorkOrderProcessDO process : processList) {
+            List<Long> outputIds = parseItemIds(process.getOutputItemId());
+            if (CollUtil.isEmpty(outputIds)) {
+                continue;
+            }
+            Map<Long, BigDecimal> ratioMap = new HashMap<>();
+            Long routeProcessId = routeProcessIds.get(process.getProcessId());
+            if (routeProcessId != null) {
+                for (MesProRouteProcessOutputDO config : routeProcessService.getProcessOutputs(routeProcessId, productId)) {
+                    if (config.getItemId() != null && config.getQuantity() != null) {
+                        ratioMap.putIfAbsent(config.getItemId(), config.getQuantity());
+                    }
+                }
+            }
+            for (int i = 0; i < outputIds.size(); i++) {
+                Long itemId = outputIds.get(i);
+                BigDecimal perUnit = i == 0 ? BigDecimal.ONE : ratioMap.getOrDefault(itemId, BigDecimal.ONE);
+                outputProducerMap.putIfAbsent(itemId, new OutputProducer(process.getProcessId(), perUnit));
+            }
+        }
+        return outputProducerMap;
+    }
+
+    private List<Long> parseItemIds(String csv) {
+        if (csv == null || csv.isBlank()) {
+            return Collections.emptyList();
+        }
+        List<Long> itemIds = new ArrayList<>();
+        for (String part : csv.split(",")) {
+            String value = part.trim();
+            if (!value.isEmpty()) {
+                itemIds.add(Long.valueOf(value));
+            }
+        }
+        return itemIds;
+    }
+
+    private record OutputProducer(Long processId, BigDecimal perUnit) {
+    }
+
     // ==================== 鎷兼帴鐗╂枡闇�姹� VO锛堝惈搴撳瓨鏍¢獙锛� ====================
 
     private List<MesProWorkOrderItemRespVO> buildItemRespVOList(Map<Long, BigDecimal> requirements,

--
Gitblit v1.9.3