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/service/pro/route/MesProRouteProcessServiceImpl.java | 275 ++++++++++++++++++++++++++++++++++++++++++++++++++++---
1 files changed, 260 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 816d104..d9520f2 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,19 @@
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
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;
@@ -27,14 +38,25 @@
/**
* MES 宸ヨ壓璺嚎宸ュ簭 Service 瀹炵幇绫�
*
- * @author 鑺嬮亾婧愮爜
+ * @author 瓒呯骇绠$悊鍛�
*/
@Service
@Validated
public class MesProRouteProcessServiceImpl implements MesProRouteProcessService {
+ /**
+ * 浜у嚭鎺掑簭锛氫富浜у嚭浼樺厛锛屽叾娆℃寜 sort锛屾渶鍚庢寜 id
+ */
+ private static final Comparator<MesProRouteProcessOutputDO> OUTPUT_ORDER = Comparator
+ .comparing((MesProRouteProcessOutputDO o) -> Boolean.TRUE.equals(o.getMainFlag()),
+ Comparator.reverseOrder())
+ .thenComparing(MesProRouteProcessOutputDO::getSort, Comparator.nullsLast(Comparator.naturalOrder()))
+ .thenComparing(MesProRouteProcessOutputDO::getId, Comparator.nullsLast(Comparator.naturalOrder()));
+
@Resource
private MesProRouteProcessMapper routeProcessMapper;
+ @Resource
+ private MesProRouteProcessOutputMapper routeProcessOutputMapper;
@Resource
@Lazy
@@ -63,13 +85,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());
@@ -92,13 +111,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());
@@ -112,8 +129,9 @@
// 1.2 宸插惎鐢ㄧ殑宸ヨ壓璺嚎锛屼笉鍏佽鎿嶄綔
routeService.validateRouteNotEnable(routeProcess.getRouteId());
- // 2. 鍒犻櫎
+ // 2. 鍒犻櫎锛堝惈浜у嚭褰掑睘閰嶇疆锛�
routeProcessMapper.deleteById(id);
+ routeProcessOutputMapper.deleteByRouteProcessId(id);
// 3. 閲嶅缓宸ュ簭閾�
rebuildProcessChain(routeProcess.getRouteId());
@@ -212,7 +230,233 @@
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))
+ .sorted(OUTPUT_ORDER)
+ .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))
+ .sorted(OUTPUT_ORDER)
+ .map(MesProRouteProcessOutputDO::getItemId)
+ .filter(Objects::nonNull)
+ .distinct()
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public List<MesProRouteProcessOutputDO> getProcessOutputs(Long routeProcessId, Long productId) {
+ if (routeProcessId == null) {
+ return Collections.emptyList();
+ }
+ List<MesProRouteProcessOutputDO> outputs = routeProcessOutputMapper.selectListByRouteProcessId(routeProcessId);
+ if (CollUtil.isEmpty(outputs)) {
+ return Collections.emptyList();
+ }
+ // 涓讳骇鍑轰紭鍏堟帓搴忥紝骞舵寜 itemId 鍘婚噸
+ Map<Long, MesProRouteProcessOutputDO> distinct = new LinkedHashMap<>();
+ outputs.stream()
+ .filter(output -> output.getProductId() == null || output.getProductId().equals(productId))
+ .filter(output -> output.getItemId() != null)
+ .sorted(OUTPUT_ORDER)
+ .forEach(output -> distinct.putIfAbsent(output.getItemId(), output));
+ return new ArrayList<>(distinct.values());
+ }
+
+ @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 o) -> Boolean.TRUE.equals(o.getMainFlag()),
+ Comparator.reverseOrder())
+ .thenComparing(MesProRouteProcessSaveReqVO.OutputItem::getSort,
+ Comparator.nullsLast(Comparator.naturalOrder()))
+ .thenComparing(MesProRouteProcessSaveReqVO.OutputItem::getItemId,
+ 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())) {
+ List<MesProRouteProcessSaveReqVO.OutputItem> items = reqVO.getOutputs().stream()
+ .filter(Objects::nonNull)
+ .filter(output -> output.getItemId() != null)
+ .collect(Collectors.toList());
+ // 鏍¢獙锛氬悓涓�褰掑睘浜у搧鍒嗙粍鍐呬富浜у嚭鍞竴銆侀厤姣斿悎娉�
+ validateOutputs(items);
+ for (MesProRouteProcessSaveReqVO.OutputItem output : items) {
+ index++;
+ if (!keys.add(output.getProductId() + "_" + output.getItemId())) {
+ continue;
+ }
+ boolean main = Boolean.TRUE.equals(output.getMainFlag());
+ BigDecimal quantity = main ? BigDecimal.ONE : output.getQuantity();
+ outputs.add(buildOutput(routeProcess, output.getProductId(), output.getItemId(),
+ output.getSort() != null ? output.getSort() : index, quantity, main, output.getRemark()));
+ }
+ } else {
+ // 鍏煎鍙紶 outputItemIds 鐨勮皟鐢ㄦ柟锛氫骇鍑烘槸璺嚎浜у搧鏃跺綊灞炲埌鑷韩锛屽惁鍒欒涓洪�氱敤锛�
+ // 姣忎釜褰掑睘浜у搧鍒嗙粍鐨勭涓�鏉¢粯璁や綔涓轰富浜у嚭
+ Set<Long> mainAssignedProducts = new HashSet<>();
+ for (Long itemId : reqVO.getOutputItemIds()) {
+ if (itemId == null) {
+ continue;
+ }
+ Long productId = routeProductIds.contains(itemId) ? itemId : null;
+ if (!keys.add(productId + "_" + itemId)) {
+ continue;
+ }
+ index++;
+ boolean main = mainAssignedProducts.add(productId);
+ outputs.add(buildOutput(routeProcess, productId, itemId, index, BigDecimal.ONE, main, null));
+ }
+ }
+ if (CollUtil.isNotEmpty(outputs)) {
+ routeProcessOutputMapper.insertBatch(outputs);
+ }
+ }
+
+ /**
+ * 鏍¢獙宸ュ簭浜у嚭閰嶇疆锛氬悓涓�褰掑睘浜у搧鍒嗙粍鍐呮渶澶氫竴涓富浜у嚭銆佸涓骇鍑烘椂蹇呴』鎸囧畾涓讳骇鍑猴紝闈炰富浜у嚭閰嶆瘮蹇呴』澶т簬 0
+ */
+ private void validateOutputs(List<MesProRouteProcessSaveReqVO.OutputItem> items) {
+ if (CollUtil.isEmpty(items)) {
+ return;
+ }
+ Map<Long, List<MesProRouteProcessSaveReqVO.OutputItem>> groupByProduct = new HashMap<>();
+ for (MesProRouteProcessSaveReqVO.OutputItem item : items) {
+ groupByProduct.computeIfAbsent(item.getProductId(), key -> new ArrayList<>()).add(item);
+ }
+ for (List<MesProRouteProcessSaveReqVO.OutputItem> group : groupByProduct.values()) {
+ long mainCount = group.stream().filter(item -> Boolean.TRUE.equals(item.getMainFlag())).count();
+ if (mainCount > 1) {
+ throw exception(PRO_ROUTE_PROCESS_OUTPUT_MAIN_DUPLICATE);
+ }
+ if (mainCount == 0 && group.size() > 1) {
+ throw exception(PRO_ROUTE_PROCESS_OUTPUT_MAIN_REQUIRED);
+ }
+ for (MesProRouteProcessSaveReqVO.OutputItem item : group) {
+ if (Boolean.TRUE.equals(item.getMainFlag())) {
+ continue;
+ }
+ // 鏈~閰嶆瘮榛樿鎸� 1 澶勭悊锛屽~浜嗗垯蹇呴』澶т簬 0
+ if (item.getQuantity() != null && item.getQuantity().compareTo(BigDecimal.ZERO) <= 0) {
+ throw exception(PRO_ROUTE_PROCESS_OUTPUT_RATIO_INVALID);
+ }
+ }
+ }
+ }
+
+ private MesProRouteProcessOutputDO buildOutput(MesProRouteProcessDO routeProcess, Long productId,
+ Long itemId, Integer sort, BigDecimal quantity,
+ Boolean mainFlag, String remark) {
+ return MesProRouteProcessOutputDO.builder()
+ .routeProcessId(routeProcess.getId())
+ .routeId(routeProcess.getRouteId())
+ .processId(routeProcess.getProcessId())
+ .productId(productId)
+ .itemId(itemId)
+ .quantity(mainFlag != null && mainFlag ? BigDecimal.ONE : quantity)
+ .mainFlag(Boolean.TRUE.equals(mainFlag))
+ .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
@@ -237,6 +481,7 @@
@Override
public void deleteRouteProcessByRouteId(Long routeId) {
routeProcessMapper.deleteByRouteId(routeId);
+ routeProcessOutputMapper.deleteByRouteId(routeId);
}
}
--
Gitblit v1.9.3