云
2026-10-25 d54fbc9d4f8981fa0f7f613b33a28a8cd4f764ea
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/route/MesProRouteServiceImpl.java
@@ -16,8 +16,11 @@
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 jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@@ -29,6 +32,10 @@
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.stream.Collectors;
import java.util.concurrent.atomic.AtomicInteger;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -58,6 +65,10 @@
    @Resource
    @Lazy
    private MesMdItemService itemService;
    @Resource
    private MesMdItemTypeService itemTypeService;
    @Resource
    private MesWmWarehouseAreaService warehouseAreaService;
    @Override
    public Long createRoute(MesProRouteSaveReqVO createReqVO) {
@@ -147,13 +158,75 @@
        // 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());
            if (CollUtil.isEmpty(outputIds)) {
                throw exception(PRO_ROUTE_PROCESS_OUTPUT_REQUIRED, process.getProcessId());
            }
            for (Long outputId : outputIds) {
                if (!boundItemIds.contains(outputId)) {
                    MesMdItemDO item = itemService.validateItemExists(outputId);
                    throw exception(PRO_ROUTE_PROCESS_OUTPUT_NOT_BOUND, item.getName());
                }
            }
            if (process.getId().equals(lastProcess.getId())) {
                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());
            }
        }
    }
    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 (rootCode.equals(type.getCode())) {
                return true;
            }
            if (type.getParentId() == null || MesMdItemTypeDO.PARENT_ID_ROOT.equals(type.getParentId())) {
                return false;
            }
            type = itemTypeService.getItemType(type.getParentId());
        }
        return false;
    }
    @Override