云
6 小时以前 3edf8c4fc07f9784e2d04ee7a98692bb457749eb
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,16 @@
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
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;
@@ -35,6 +43,8 @@
    @Resource
    private MesProRouteProcessMapper routeProcessMapper;
    @Resource
    private MesProRouteProcessOutputMapper routeProcessOutputMapper;
    @Resource
    @Lazy
@@ -63,13 +73,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());
@@ -81,7 +88,7 @@
    public void updateRouteProcess(MesProRouteProcessSaveReqVO updateReqVO) {
        // 1.0 已启用的工艺路线,不允许操作
        routeService.validateRouteNotEnable(updateReqVO.getRouteId());
        // 1.1 校验存在;部分更新(如仅调整产出产品)时未传的必填字段用现有值回填
        // 1.1 校验存在;兼容只调整产出配置的部分更新请求
        MesProRouteProcessDO existing = validateRouteProcessExists(updateReqVO.getId());
        if (updateReqVO.getProcessId() == null) {
            updateReqVO.setProcessId(existing.getProcessId());
@@ -98,13 +105,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());
@@ -118,8 +123,9 @@
        // 1.2 已启用的工艺路线,不允许操作
        routeService.validateRouteNotEnable(routeProcess.getRouteId());
        // 2. 删除
        // 2. 删除(含产出归属配置)
        routeProcessMapper.deleteById(id);
        routeProcessOutputMapper.deleteByRouteProcessId(id);
        // 3. 重建工序链
        rebuildProcessChain(routeProcess.getRouteId());
@@ -218,7 +224,165 @@
            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))
                    .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))
                .map(MesProRouteProcessOutputDO::getItemId)
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());
    }
    @Override
    public List<MesProRouteProcessOutputDO> getRouteProcessOutputListByRouteProcessIds(Collection<Long> routeProcessIds) {
        if (CollUtil.isEmpty(routeProcessIds)) {
            return Collections.emptyList();
        }
        return routeProcessOutputMapper.selectListByRouteProcessIds(routeProcessIds);
    }
    /**
     * 计算工序产出物料的聚合串(逗号分隔,去重)
     *
     * 优先使用产出归属配置(outputs),未传时兼容 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::getSort,
                            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())) {
            for (MesProRouteProcessSaveReqVO.OutputItem output : reqVO.getOutputs()) {
                if (output == null || output.getItemId() == null) {
                    continue;
                }
                index++;
                if (!keys.add(output.getProductId() + "_" + output.getItemId())) {
                    continue;
                }
                outputs.add(buildOutput(routeProcess, output.getProductId(), output.getItemId(),
                        output.getSort() != null ? output.getSort() : index, output.getRemark()));
            }
        } else {
            // 兼容只传 outputItemIds 的调用方:产出是路线产品时归属到自身,否则视为通用
            for (Long itemId : reqVO.getOutputItemIds()) {
                if (itemId == null) {
                    continue;
                }
                index++;
                Long productId = routeProductIds.contains(itemId) ? itemId : null;
                if (!keys.add(productId + "_" + itemId)) {
                    continue;
                }
                outputs.add(buildOutput(routeProcess, productId, itemId, index, null));
            }
        }
        if (CollUtil.isNotEmpty(outputs)) {
            routeProcessOutputMapper.insertBatch(outputs);
        }
    }
    private MesProRouteProcessOutputDO buildOutput(MesProRouteProcessDO routeProcess, Long productId,
                                                   Long itemId, Integer sort, String remark) {
        return MesProRouteProcessOutputDO.builder()
                .routeProcessId(routeProcess.getId())
                .routeId(routeProcess.getRouteId())
                .processId(routeProcess.getProcessId())
                .productId(productId)
                .itemId(itemId)
                .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
@@ -243,6 +407,7 @@
    @Override
    public void deleteRouteProcessByRouteId(Long routeId) {
        routeProcessMapper.deleteByRouteId(routeId);
        routeProcessOutputMapper.deleteByRouteId(routeId);
    }
}