From 3edf8c4fc07f9784e2d04ee7a98692bb457749eb Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期一, 26 十月 2026 16:28:10 +0800
Subject: [PATCH] feat(mes): 新增工艺路线完整导入功能并优化相关验证
---
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/route/MesProRouteServiceImpl.java | 179 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 177 insertions(+), 2 deletions(-)
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/route/MesProRouteServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/route/MesProRouteServiceImpl.java
index 7db3bd2..a50b366 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/route/MesProRouteServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/route/MesProRouteServiceImpl.java
@@ -3,27 +3,46 @@
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.route.vo.MesProRouteImportExcelVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.route.vo.MesProRouteImportRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.route.vo.MesProRoutePageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.route.vo.MesProRouteSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteDO;
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.MesProRouteProductDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProductBomDO;
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.mysql.pro.route.MesProRouteMapper;
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.wm.warehouse.MesWmWarehouseAreaService;
+import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
+import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
+import cn.iocoder.yudao.module.mdm.enums.MdmItemTypeEnum;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Set;
+import java.util.Comparator;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import java.util.concurrent.atomic.AtomicInteger;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
@@ -31,7 +50,7 @@
/**
* MES 宸ヨ壓璺嚎 Service 瀹炵幇绫�
*
- * @author 鑺嬮亾婧愮爜
+ * @author 瓒呯骇绠$悊鍛�
*/
@Service
@Validated
@@ -52,6 +71,12 @@
@Resource
@Lazy
private MesMdItemService itemService;
+ @Resource
+ private MesMdItemTypeService itemTypeService;
+ @Resource
+ private MesWmWarehouseAreaService warehouseAreaService;
+ @Resource
+ private MdmItemApi mdmItemApi;
@Override
public Long createRoute(MesProRouteSaveReqVO createReqVO) {
@@ -141,13 +166,115 @@
// 3. 鎵�鏈変骇鍝佸繀椤婚厤缃簡 BOM 娑堣��
List<MesProRouteProductDO> productList = routeProductService.getRouteProductListByRouteId(routeId);
for (MesProRouteProductDO product : productList) {
+ MesMdItemDO item = itemService.validateItemExists(product.getItemId());
+ if (product.getWarehouseId() == null || product.getLocationId() == null || product.getAreaId() == null) {
+ throw exception(PRO_ROUTE_PRODUCT_WAREHOUSE_REQUIRED, item.getName());
+ }
+ warehouseAreaService.validateWarehouseAreaExists(product.getWarehouseId(),
+ product.getLocationId(), product.getAreaId());
List<MesProRouteProductBomDO> bomList = routeProductBomService
.getRouteProductBomList(routeId, null, product.getItemId());
if (CollUtil.isEmpty(bomList)) {
- MesMdItemDO item = itemService.validateItemExists(product.getItemId());
throw exception(PRO_ROUTE_ENABLE_PRODUCT_NO_BOM, item.getName());
}
}
+ validateProcessOutputs(processList, productList);
+ }
+
+ private void validateProcessOutputs(List<MesProRouteProcessDO> processList,
+ List<MesProRouteProductDO> productList) {
+ if (CollUtil.isEmpty(productList)) {
+ throw exception(PRO_ROUTE_LAST_OUTPUT_INVALID, processList.get(processList.size() - 1).getProcessId());
+ }
+ Set<Long> boundItemIds = productList.stream().map(MesProRouteProductDO::getItemId).collect(Collectors.toSet());
+ Set<Long> finishedItemIds = productList.stream()
+ .map(MesProRouteProductDO::getItemId)
+ .filter(itemId -> isItemUnderCategory(itemId, "FG"))
+ .collect(Collectors.toSet());
+ MesProRouteProcessDO lastProcess = processList.stream()
+ .max(Comparator.comparing(MesProRouteProcessDO::getSort)).orElseThrow();
+ for (MesProRouteProcessDO process : processList) {
+ Set<Long> outputIds = parseOutputItemIds(process.getOutputItemId());
+ boolean last = process.getId().equals(lastProcess.getId());
+ if (CollUtil.isEmpty(outputIds)) {
+ // 涓棿宸ュ簭涓嶄竴瀹氬舰鎴愬彲鍏ュ簱鐨勭嫭绔嬪崐鎴愬搧锛屽厑璁镐笉閰嶇疆浜у嚭锛涙湯閬撳伐搴忓繀椤讳骇鍑烘垚鍝併��
+ if (last) {
+ throw exception(PRO_ROUTE_LAST_OUTPUT_INVALID, process.getProcessId());
+ }
+ continue;
+ }
+ for (Long outputId : outputIds) {
+ if (!boundItemIds.contains(outputId)) {
+ MesMdItemDO item = itemService.validateItemExists(outputId);
+ throw exception(PRO_ROUTE_PROCESS_OUTPUT_NOT_BOUND, item.getName());
+ }
+ }
+ if (last) {
+ if (finishedItemIds.isEmpty() || !outputIds.equals(finishedItemIds)) {
+ throw exception(PRO_ROUTE_LAST_OUTPUT_INVALID, process.getProcessId());
+ }
+ } else if (outputIds.stream().anyMatch(itemId -> !isItemUnderCategory(itemId, "SF"))) {
+ throw exception(PRO_ROUTE_MIDDLE_OUTPUT_INVALID, process.getProcessId());
+ }
+ }
+ // 4. 鏈亾宸ュ簭鎴愬搧浜у嚭蹇呴』鎸変骇鍝佸綊灞烇紝閬垮厤鈥滈�氱敤鈥濅骇鍑轰覆鍒板叾瀹冧骇鍝佺殑宸ュ崟
+ List<MesProRouteProcessOutputDO> lastOutputs = routeProcessService
+ .getRouteProcessOutputListByRouteProcessIds(Collections.singletonList(lastProcess.getId()));
+ if (CollUtil.isNotEmpty(lastOutputs)) {
+ boolean attributionInvalid = lastOutputs.stream().anyMatch(output ->
+ output.getProductId() == null || !output.getProductId().equals(output.getItemId()));
+ Set<Long> attributedProducts = lastOutputs.stream()
+ .map(MesProRouteProcessOutputDO::getProductId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ if (attributionInvalid || !attributedProducts.containsAll(finishedItemIds)) {
+ throw exception(PRO_ROUTE_LAST_OUTPUT_ATTRIBUTION_REQUIRED, lastProcess.getProcessId());
+ }
+ }
+ }
+
+ private Set<Long> parseOutputItemIds(String value) {
+ if (StrUtil.isBlank(value)) {
+ return Collections.emptySet();
+ }
+ return Arrays.stream(value.split(",")).map(String::trim).filter(StrUtil::isNotBlank)
+ .map(Long::valueOf).collect(Collectors.toSet());
+ }
+
+ private boolean isItemUnderCategory(Long itemId, String rootCode) {
+ MesMdItemDO item = itemService.validateItemExists(itemId);
+ MesMdItemTypeDO type = itemTypeService.getItemType(item.getItemTypeId());
+ while (type != null) {
+ if ("SF".equalsIgnoreCase(type.getCode())
+ || (type.getName() != null && type.getName().contains("鍗婃垚鍝�"))) {
+ return "SF".equals(rootCode);
+ }
+ if ("FG".equalsIgnoreCase(type.getCode()) || isFinishedCategoryName(type.getName())) {
+ return "FG".equals(rootCode);
+ }
+ if (type.getParentId() == null || MesMdItemTypeDO.PARENT_ID_ROOT.equals(type.getParentId())) {
+ break;
+ }
+ type = itemTypeService.getItemType(type.getParentId());
+ }
+ // MES 鍒嗙被鏈娇鐢� FG/SF 缂栫爜鏃讹紝鎸� MDM 涓绘暟鎹殑鐗╂枡绫诲瀷鍒ゅ畾銆�
+ CommonResult<MdmItemRespDTO> mdmResult = mdmItemApi.getItemByCode(item.getCode());
+ if (mdmResult == null || !mdmResult.isSuccess() || mdmResult.getData() == null) {
+ return false;
+ }
+ MdmItemRespDTO mdmItem = mdmResult.getData();
+ Integer mdmType = mdmItem.getItemType();
+ // 鍘嗗彶鍚屾鍙兘鎶婂垎绫� ID 瀛樺湪 itemType锛屼笉鑳芥妸鍒嗙被 2/3 璇垽涓烘垚鍝�/鍗婃垚鍝併��
+ if (Objects.equals(mdmItem.getCategoryId(), mdmType == null ? null : mdmType.longValue())) {
+ return false;
+ }
+ return "FG".equals(rootCode) ? MdmItemTypeEnum.PRODUCT.getType().equals(mdmType)
+ : "SF".equals(rootCode) && MdmItemTypeEnum.SEMI_FINISHED.getType().equals(mdmType);
+ }
+
+ private boolean isFinishedCategoryName(String name) {
+ return name != null && !name.contains("鍗婃垚鍝�") && !name.contains("闈炴垚鍝�")
+ && (name.contains("浜ф垚鍝�") || name.contains("鎴愬搧"));
}
@Override
@@ -181,4 +308,52 @@
return routeMapper.selectByIds(ids);
}
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public MesProRouteImportRespVO importRouteList(List<MesProRouteImportExcelVO> importList,
+ boolean updateSupport) {
+ // 1. 鏍¢獙闈炵┖
+ if (CollUtil.isEmpty(importList)) {
+ throw exception(PRO_ROUTE_IMPORT_LIST_IS_EMPTY);
+ }
+
+ // 2. 閫愯澶勭悊
+ MesProRouteImportRespVO respVO = MesProRouteImportRespVO.builder()
+ .createCodes(new ArrayList<>()).updateCodes(new ArrayList<>())
+ .failureCodes(new LinkedHashMap<>()).build();
+ AtomicInteger index = new AtomicInteger(1);
+ importList.forEach(importItem -> {
+ int currentIndex = index.getAndIncrement();
+ String key = StrUtil.blankToDefault(importItem.getCode(), "绗� " + currentIndex + " 琛�");
+ if (StrUtil.isBlank(importItem.getCode())) {
+ respVO.getFailureCodes().put(key, "宸ヨ壓璺嚎缂栫爜涓嶈兘涓虹┖");
+ return;
+ }
+ if (StrUtil.isBlank(importItem.getName())) {
+ respVO.getFailureCodes().put(key, "宸ヨ壓璺嚎鍚嶇О涓嶈兘涓虹┖");
+ return;
+ }
+ // 2.1 鍒ゆ柇锛氬垱寤� or 鏇存柊
+ MesProRouteDO existRoute = routeMapper.selectByCode(importItem.getCode());
+ if (existRoute == null) {
+ // 2.1.1 鏂板锛堜笌鎵嬪伐鍒涘缓涓�鑷达細榛樿绂佺敤锛岄�氳繃鍚敤鎺ュ彛鏍¢獙鍚庡惎鐢級
+ MesProRouteDO route = BeanUtils.toBean(importItem, MesProRouteDO.class);
+ route.setId(null);
+ route.setStatus(CommonStatusEnum.DISABLE.getStatus());
+ routeMapper.insert(route);
+ respVO.getCreateCodes().add(importItem.getCode());
+ } else if (updateSupport) {
+ // 2.1.2 鏇存柊锛堢姸鎬佺敱鍚敤/绂佺敤鎺ュ彛鍗曠嫭缁存姢锛屽鍏ヤ笉淇敼鐘舵�侊級
+ MesProRouteDO updateObj = BeanUtils.toBean(importItem, MesProRouteDO.class);
+ updateObj.setId(existRoute.getId());
+ updateObj.setCode(existRoute.getCode());
+ routeMapper.updateById(updateObj);
+ respVO.getUpdateCodes().add(importItem.getCode());
+ } else {
+ respVO.getFailureCodes().put(key, "宸ヨ壓璺嚎缂栫爜宸插瓨鍦�");
+ }
+ });
+ return respVO;
+ }
+
}
--
Gitblit v1.9.3