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 | 141 +++++++++++++++++++++++++++++++++++++++--------
1 files changed, 117 insertions(+), 24 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 a777693..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,11 +13,15 @@
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.PurchaseOrderApi;
@@ -25,6 +29,7 @@
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;
@@ -68,6 +77,8 @@
private MesProRouteProductService routeProductService;
@Resource
private MesProRouteProductBomService routeProductBomService;
+ @Resource
+ private MesProRouteProcessService routeProcessService;
@Resource
private MesWmMaterialStockMapper materialStockMapper;
@Resource
@@ -115,6 +126,18 @@
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)
@@ -150,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());
}
@@ -227,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);
@@ -257,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())) {
@@ -272,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
@@ -287,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;
}
@@ -308,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