6 小时以前 9bad721754fe8bbe2e5f459d0706e0fefac569f3
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/indicator/MesQcIndicatorServiceImpl.java
@@ -3,6 +3,7 @@
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
@@ -10,6 +11,7 @@
import cn.iocoder.yudao.module.mes.controller.admin.qc.indicator.vo.MesQcIndicatorSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicator.MesQcIndicatorDO;
import cn.iocoder.yudao.module.mes.dal.mysql.qc.indicator.MesQcIndicatorMapper;
import cn.iocoder.yudao.module.mes.enums.qc.MesQcIndicatorItemTypeEnum;
import cn.iocoder.yudao.module.mes.enums.qc.MesQcResultValueTypeEnum;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
@@ -19,8 +21,10 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
@@ -43,6 +47,7 @@
        // 插入
        MesQcIndicatorDO indicator = BeanUtils.toBean(createReqVO, MesQcIndicatorDO.class);
        normalizeHierarchy(indicator);
        indicatorMapper.insert(indicator);
        return indicator.getId();
    }
@@ -56,6 +61,7 @@
        // 更新
        MesQcIndicatorDO updateObj = BeanUtils.toBean(updateReqVO, MesQcIndicatorDO.class);
        normalizeHierarchy(updateObj);
        indicatorMapper.updateById(updateObj);
    }
@@ -63,6 +69,11 @@
    public void deleteIndicator(Long id) {
        // 校验存在
        validateIndicatorExists(id);
        // 级联删除子项:指标只支持两层,挂在它下面的都是它的明细项,父删了子无处可挂
        List<MesQcIndicatorDO> children = indicatorMapper.selectListByParentId(id);
        if (CollUtil.isNotEmpty(children)) {
            indicatorMapper.deleteByIds(convertList(children, MesQcIndicatorDO::getId));
        }
        // 删除
        indicatorMapper.deleteById(id);
    }
@@ -72,8 +83,100 @@
        validateIndicatorCodeUnique(id, saveReqVO.getCode());
        // 校验名称唯一
        validateIndicatorNameUnique(id, saveReqVO.getName());
        // 校验层级归属(只允许两层)
        validateHierarchy(id, saveReqVO);
        // 校验结果值属性
        validateResultSpecification(saveReqVO.getResultType(), saveReqVO.getResultSpecification());
        validateResultSpecification(saveReqVO.getItemType(), saveReqVO.getResultType(), saveReqVO.getResultSpecification());
    }
    /**
     * 校验层级归属。只允许两层:分组项必须顶级,录入项可挂到分组项下。
     * <p>
     * 「父必须是分组项」这条本身就掐死了第三层,所以不再单独写一条「父必须顶级」的校验。
     * 按业务优先级只抛第一处不满足,避免一次抛多条让用户抓不住重点。
     */
    private void validateHierarchy(Long id, MesQcIndicatorSaveReqVO reqVO) {
        Long parentId = ObjectUtils.defaultIfNull(reqVO.getParentId(), MesQcIndicatorDO.PARENT_ID_ROOT);
        Integer itemType = ObjectUtils.defaultIfNull(reqVO.getItemType(), MesQcIndicatorItemTypeEnum.ENTRY.getType());
        // 1. 分组项只能顶级
        if (MesQcIndicatorItemTypeEnum.isGroup(itemType) && !MesQcIndicatorDO.PARENT_ID_ROOT.equals(parentId)) {
            throw new ServiceException(QC_INDICATOR_GROUP_CANNOT_NEST.getCode(),
                    String.format("分组项[%s]必须作为顶级指标,不能挂到其它指标下(当前选择了父指标[%s])。"
                                    + "如需调整层级,请把「条目类型」改为录入项后再选择所属分组。",
                            defaultStr(reqVO.getName()), nameOf(parentId)));
        }
        // 2. 父指标必须存在且是分组项
        if (!MesQcIndicatorDO.PARENT_ID_ROOT.equals(parentId)) {
            if (Objects.equals(id, parentId)) {
                throw exception(QC_INDICATOR_PARENT_SELF);
            }
            MesQcIndicatorDO parent = indicatorMapper.selectById(parentId);
            if (parent == null) {
                throw new ServiceException(QC_INDICATOR_PARENT_NOT_EXISTS.getCode(),
                        String.format("所属分组不存在(指标编号 %d 查无此指标,可能已被删除)。请重新选择所属分组,或清空该项作为顶级指标。",
                                parentId));
            }
            if (!MesQcIndicatorItemTypeEnum.isGroup(parent.getItemType())) {
                throw new ServiceException(QC_INDICATOR_PARENT_NOT_GROUP.getCode(),
                        String.format("所选父指标[%s]不是分组项(当前条目类型为[%s]),不能作为所属分组。"
                                        + "请改选一个分组项,或先在指标列表把[%s]的条目类型改为分组项。",
                                defaultStr(parent.getName()), itemTypeNameOf(parent.getItemType()), defaultStr(parent.getName())));
            }
        }
        // 3. 录入项必须有结果值类型(分组项没有结果值,现网分组行 result_type 就是 NULL)
        if (!MesQcIndicatorItemTypeEnum.isGroup(itemType) && reqVO.getResultType() == null) {
            throw exception(QC_INDICATOR_RESULT_TYPE_REQUIRED);
        }
        // 4. 自己已经有子项时,不能再被别人收编、也不能反过来变成子项
        if (id == null) {
            return;
        }
        Long childCount = indicatorMapper.selectCountByParentId(id);
        if (childCount <= 0) {
            return;
        }
        if (!MesQcIndicatorDO.PARENT_ID_ROOT.equals(parentId)) {
            throw new ServiceException(QC_INDICATOR_HAS_CHILDREN.getCode(),
                    String.format("指标[%s]下已挂有 %d 个录入项,不能再挂到其它分组下(指标只支持两层)。"
                                    + "请先删除或移走这 %d 个子项,再修改它的所属分组。",
                            defaultStr(reqVO.getName()), childCount, childCount));
        }
        if (!MesQcIndicatorItemTypeEnum.isGroup(itemType)) {
            throw new ServiceException(QC_INDICATOR_HAS_CHILDREN.getCode(),
                    String.format("指标[%s]下已挂有 %d 个录入项,不能再把它的条目类型改为录入项,否则这些子项会失去所属分组。"
                                    + "请先删除或移走这 %d 个子项,再修改条目类型。",
                            defaultStr(reqVO.getName()), childCount, childCount));
        }
    }
    /**
     * 落库前补齐层级字段:分组项清空结果值(现网分组行没有结果值),排序号留空则排到同级末尾
     */
    private void normalizeHierarchy(MesQcIndicatorDO indicator) {
        indicator.setParentId(ObjectUtils.defaultIfNull(indicator.getParentId(), MesQcIndicatorDO.PARENT_ID_ROOT));
        indicator.setItemType(ObjectUtils.defaultIfNull(indicator.getItemType(), MesQcIndicatorItemTypeEnum.ENTRY.getType()));
        if (MesQcIndicatorItemTypeEnum.isGroup(indicator.getItemType())) {
            indicator.setResultType(null);
            indicator.setResultSpecification(null);
        }
        if (indicator.getSortOrder() == null) {
            Integer max = indicatorMapper.selectMaxSortOrder(indicator.getParentId());
            indicator.setSortOrder((max == null ? 0 : max) + (MesQcIndicatorDO.PARENT_ID_ROOT.equals(indicator.getParentId()) ? 10 : 1));
        }
    }
    private String nameOf(Long id) {
        MesQcIndicatorDO indicator = id == null ? null : indicatorMapper.selectById(id);
        return indicator == null ? String.valueOf(id) : defaultStr(indicator.getName());
    }
    private static String itemTypeNameOf(Integer itemType) {
        return MesQcIndicatorItemTypeEnum.isGroup(itemType)
                ? MesQcIndicatorItemTypeEnum.GROUP.getName() : MesQcIndicatorItemTypeEnum.ENTRY.getName();
    }
    private static String defaultStr(String value) {
        return StrUtil.blankToDefault(value, "");
    }
    private void validateIndicatorExists(Long id) {
@@ -102,7 +205,11 @@
        }
    }
    private void validateResultSpecification(Integer resultType, String resultSpecification) {
    private void validateResultSpecification(Integer itemType, Integer resultType, String resultSpecification) {
        // 分组项没有结果值,落库前会被清空,这里不校验
        if (MesQcIndicatorItemTypeEnum.isGroup(ObjectUtils.defaultIfNull(itemType, MesQcIndicatorItemTypeEnum.ENTRY.getType()))) {
            return;
        }
        if (ObjectUtils.equalsAny(resultType, MesQcResultValueTypeEnum.FILE.getType(),
                MesQcResultValueTypeEnum.DICT.getType())) {
            if (StrUtil.isBlank(resultSpecification)) {
@@ -127,6 +234,11 @@
    }
    @Override
    public List<MesQcIndicatorDO> getIndicatorGroupList() {
        return indicatorMapper.selectListByItemType(MesQcIndicatorItemTypeEnum.GROUP.getType());
    }
    @Override
    public List<MesQcIndicatorDO> getIndicatorList(Collection<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return Collections.emptyList();