云
2026-10-26 63ba72f5d65a3a4dcd56d9228582042448af61d7
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemTypeServiceImpl.java
@@ -2,17 +2,26 @@
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.mdm.api.category.MdmItemCategoryApi;
import cn.iocoder.yudao.module.mdm.api.category.dto.MdmItemCategoryRespDTO;
import cn.iocoder.yudao.module.mdm.api.category.dto.MdmItemCategorySaveReqDTO;
import cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.type.MesMdItemTypeImportExcelVO;
import cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.type.MesMdItemTypeImportRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.type.MesMdItemTypeListReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.type.MesMdItemTypeSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemTypeDO;
import cn.iocoder.yudao.module.mes.dal.mysql.md.item.MesMdItemTypeMapper;
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.*;
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.*;
@@ -20,70 +29,91 @@
/**
 * MES 物料产品分类 Service 实现类
 *
 * @author 芋道源码
 * 物料分类已统一由 MDM 管理,数据存储在 mdm_item_category 表。
 * 本服务保留 MES 的分类接口,底层全部通过 {@link MdmItemCategoryApi} 读写 MDM 分类。
 *
 * @author 超级管理员
 */
@Service
@Validated
public class MesMdItemTypeServiceImpl implements MesMdItemTypeService {
    @Resource
    private MesMdItemTypeMapper itemTypeMapper;
    /** MDM 物料类型:物料 */
    private static final Integer MDM_ITEM_TYPE_MATERIAL = 1;
    /** MDM 物料类型:产品 */
    private static final Integer MDM_ITEM_TYPE_PRODUCT = 2;
    /** MES 物料/产品标识:产品 */
    private static final String ITEM_OR_PRODUCT_PRODUCT = "PRODUCT";
    @Resource
    @Lazy // 延迟加载,避免循环依赖
    private MdmItemCategoryApi mdmItemCategoryApi;
    @Resource
    @Lazy
    private MesMdItemService itemService;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createItemType(MesMdItemTypeSaveReqVO createReqVO) {
        // 校验数据
        validateItemTypeSaveData(createReqVO);
        // 插入
        MesMdItemTypeDO itemType = BeanUtils.toBean(createReqVO, MesMdItemTypeDO.class);
        itemTypeMapper.insert(itemType);
        return itemType.getId();
        // 校验父分类
        validateParentItemType(createReqVO.getId(), createReqVO.getParentId());
        // 写入 MDM 分类表
        CommonResult<Long> result = mdmItemCategoryApi.createItemCategory(buildSaveDTO(createReqVO));
        return result.getData();
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateItemType(MesMdItemTypeSaveReqVO updateReqVO) {
        // 校验存在
        validateItemTypeExists(updateReqVO.getId());
        // 校验数据
        validateItemTypeSaveData(updateReqVO);
        // 更新
        MesMdItemTypeDO updateObj = BeanUtils.toBean(updateReqVO, MesMdItemTypeDO.class);
        itemTypeMapper.updateById(updateObj);
        // 校验父分类
        validateParentItemType(updateReqVO.getId(), updateReqVO.getParentId());
        // 更新 MDM 分类表
        MdmItemCategorySaveReqDTO updateDTO = buildSaveDTO(updateReqVO);
        updateDTO.setId(updateReqVO.getId());
        mdmItemCategoryApi.updateItemCategory(updateDTO);
    }
    private void validateItemTypeSaveData(MesMdItemTypeSaveReqVO reqVO) {
        // 校验父分类编号的有效性
        validateParentItemType(reqVO.getId(), reqVO.getParentId());
        // 校验分类名称的唯一性
        validateItemTypeNameUnique(reqVO.getId(), reqVO.getParentId(), reqVO.getName());
        // 校验分类编码的唯一性
        validateItemTypeCodeUnique(reqVO.getId(), reqVO.getParentId(), reqVO.getCode());
    private MdmItemCategorySaveReqDTO buildSaveDTO(MesMdItemTypeSaveReqVO reqVO) {
        MdmItemCategorySaveReqDTO dto = new MdmItemCategorySaveReqDTO();
        dto.setParentId(ObjUtil.defaultIfNull(reqVO.getParentId(), MesMdItemTypeDO.PARENT_ID_ROOT));
        dto.setCode(reqVO.getCode());
        dto.setName(reqVO.getName());
        dto.setItemOrProduct(reqVO.getItemOrProduct());
        dto.setItemType(convertMdmItemType(reqVO.getItemOrProduct()));
        dto.setSort(ObjUtil.defaultIfNull(reqVO.getSort(), 0));
        dto.setStatus(ObjUtil.defaultIfNull(reqVO.getStatus(), CommonStatusEnum.ENABLE.getStatus()));
        dto.setRemark(reqVO.getRemark());
        return dto;
    }
    /**
     * MES 物料/产品标识 -> MDM 物料类型
     */
    private Integer convertMdmItemType(String itemOrProduct) {
        if (StrUtil.isBlank(itemOrProduct)) {
            return null;
        }
        return ITEM_OR_PRODUCT_PRODUCT.equalsIgnoreCase(itemOrProduct)
                ? MDM_ITEM_TYPE_PRODUCT : MDM_ITEM_TYPE_MATERIAL;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteItemType(Long id) {
        // 1.1 校验存在
        validateItemTypeExists(id);
        // 1.2 校验是否有子分类
        if (itemTypeMapper.selectCountByParentId(id) > 0) {
            throw exception(MD_ITEM_TYPE_EXITS_CHILDREN);
        }
        // 1.3 校验是否有物料
        // 1.2 校验是否有物料
        if (itemService.getItemCountByItemTypeId(id) > 0) {
            throw exception(MD_ITEM_TYPE_EXITS_ITEM);
        }
        // 2. 删除
        itemTypeMapper.deleteById(id);
        // 2. 删除(子分类、MDM 物料引用由 MDM 侧校验)
        mdmItemCategoryApi.deleteItemCategory(id);
    }
    private void validateItemTypeExists(Long id) {
        if (itemTypeMapper.selectById(id) == null) {
        if (getItemType(id) == null) {
            throw exception(MD_ITEM_TYPE_NOT_EXISTS);
        }
    }
@@ -97,64 +127,51 @@
            throw exception(MD_ITEM_TYPE_PARENT_ERROR);
        }
        // 2. 父分类不存在
        MesMdItemTypeDO parentItemType = itemTypeMapper.selectById(parentId);
        MesMdItemTypeDO parentItemType = getItemType(parentId);
        if (parentItemType == null) {
            throw exception(MD_ITEM_TYPE_PARENT_NOT_EXITS);
        }
        // 3. 递归校验父分类,如果父分类是自己的子分类,则报错,避免形成环路
        if (id == null) { // id 为空,说明新增,不需要考虑环路
        if (id == null) {
            return;
        }
        for (int i = 0; i < Short.MAX_VALUE; i++) {
            // 3.1 校验环路
            parentId = parentItemType.getParentId();
            if (Objects.equals(id, parentId)) {
                throw exception(MD_ITEM_TYPE_PARENT_IS_CHILD);
            }
            // 3.2 继续递归下一级父分类
            if (parentId == null || MesMdItemTypeDO.PARENT_ID_ROOT.equals(parentId)) {
                break;
            }
            parentItemType = itemTypeMapper.selectById(parentId);
            parentItemType = getItemType(parentId);
            if (parentItemType == null) {
                break;
            }
        }
    }
    private void validateItemTypeNameUnique(Long id, Long parentId, String name) {
        MesMdItemTypeDO itemType = itemTypeMapper.selectByParentIdAndName(parentId, name);
        if (itemType == null) {
            return;
        }
        if (ObjUtil.notEqual(itemType.getId(), id)) {
            throw exception(MD_ITEM_TYPE_NAME_DUPLICATE);
        }
    }
    private void validateItemTypeCodeUnique(Long id, Long parentId, String code) {
        MesMdItemTypeDO itemType = itemTypeMapper.selectByParentIdAndCode(parentId, code);
        if (itemType == null) {
            return;
        }
        if (ObjUtil.notEqual(itemType.getId(), id)) {
            throw exception(MD_ITEM_TYPE_CODE_DUPLICATE);
        }
    }
    @Override
    public MesMdItemTypeDO getItemType(Long id) {
        return itemTypeMapper.selectById(id);
        CommonResult<MdmItemCategoryRespDTO> result = mdmItemCategoryApi.getItemCategory(id);
        return result.isSuccess() ? convertToMesDO(result.getData()) : null;
    }
    @Override
    public MesMdItemTypeDO getItemTypeByCode(String code) {
        return itemTypeMapper.selectByCode(code);
        CommonResult<MdmItemCategoryRespDTO> result = mdmItemCategoryApi.getItemCategoryByCode(code);
        return result.isSuccess() ? convertToMesDO(result.getData()) : null;
    }
    @Override
    public List<MesMdItemTypeDO> getItemTypeList(MesMdItemTypeListReqVO listReqVO) {
        return itemTypeMapper.selectList(listReqVO);
        List<MesMdItemTypeDO> list = getAllItemTypeList();
        return list.stream()
                .filter(itemType -> StrUtil.isBlank(listReqVO.getName())
                        || StrUtil.contains(itemType.getName(), listReqVO.getName()))
                .filter(itemType -> listReqVO.getStatus() == null
                        || Objects.equals(listReqVO.getStatus(), itemType.getStatus()))
                .sorted(Comparator.comparing(itemType -> ObjUtil.defaultIfNull(itemType.getSort(), 0)))
                .toList();
    }
    @Override
@@ -162,21 +179,30 @@
        if (CollUtil.isEmpty(ids)) {
            return Collections.emptyList();
        }
        return itemTypeMapper.selectByIds(ids);
        CommonResult<List<MdmItemCategoryRespDTO>> result = mdmItemCategoryApi.getItemCategoryList(ids);
        return convertToMesDOList(result.isSuccess() ? result.getData() : null);
    }
    private List<MesMdItemTypeDO> getAllItemTypeList() {
        CommonResult<List<MdmItemCategoryRespDTO>> result = mdmItemCategoryApi.getItemCategoryAllList();
        return convertToMesDOList(result.isSuccess() ? result.getData() : null);
    }
    private List<MesMdItemTypeDO> convertToMesDOList(List<MdmItemCategoryRespDTO> list) {
        if (CollUtil.isEmpty(list)) {
            return new ArrayList<>();
        }
        return new ArrayList<>(list.stream().map(this::convertToMesDO).toList());
    }
    @Override
    public List<MesMdItemTypeDO> getItemTypeChildrenList(Long parentId) {
        List<MesMdItemTypeDO> allList = itemTypeMapper.selectList(); // 分类总量小,全量查
        // 递归收集所有子分类
        List<MesMdItemTypeDO> allList = getAllItemTypeList();
        List<MesMdItemTypeDO> result = new ArrayList<>();
        collectItemTypeChildren(result, parentId, allList);
        return result;
    }
    /**
     * 递归收集所有子分类
     */
    private void collectItemTypeChildren(List<MesMdItemTypeDO> result, Long parentId, List<MesMdItemTypeDO> allList) {
        for (MesMdItemTypeDO itemType : allList) {
            if (Objects.equals(itemType.getParentId(), parentId)) {
@@ -186,4 +212,112 @@
        }
    }
    /**
     * 转换 MDM 分类 DTO 为 MES DO
     */
    private MesMdItemTypeDO convertToMesDO(MdmItemCategoryRespDTO dto) {
        if (dto == null) {
            return null;
        }
        MesMdItemTypeDO mesDO = new MesMdItemTypeDO();
        mesDO.setId(dto.getId());
        mesDO.setCode(dto.getCode());
        mesDO.setName(dto.getName());
        mesDO.setParentId(dto.getParentId());
        mesDO.setItemOrProduct(dto.getItemOrProduct());
        mesDO.setSort(dto.getSort());
        mesDO.setStatus(dto.getStatus());
        mesDO.setRemark(dto.getRemark());
        mesDO.setCreateTime(dto.getCreateTime());
        return mesDO;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public MesMdItemTypeImportRespVO importItemTypeList(List<MesMdItemTypeImportExcelVO> importItemTypeList,
                                                        boolean updateSupport) {
        // 1. 校验非空
        if (CollUtil.isEmpty(importItemTypeList)) {
            throw exception(MD_ITEM_TYPE_IMPORT_LIST_IS_EMPTY);
        }
        // 2. 逐行处理
        MesMdItemTypeImportRespVO respVO = MesMdItemTypeImportRespVO.builder()
                .createCodes(new ArrayList<>()).updateCodes(new ArrayList<>())
                .failureCodes(new LinkedHashMap<>()).build();
        List<MesMdItemTypeDO> allList = getAllItemTypeList();
        AtomicInteger index = new AtomicInteger(1);
        importItemTypeList.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;
            }
            if (StrUtil.isBlank(importItem.getItemOrProduct())) {
                respVO.getFailureCodes().put(key, "物料/产品标识不能为空");
                return;
            }
            // 2.1 解析父分类(为空则默认为根分类)
            Long parentId = MesMdItemTypeDO.PARENT_ID_ROOT;
            if (StrUtil.isNotBlank(importItem.getParentCode())) {
                MesMdItemTypeDO parentItemType = allList.stream()
                        .filter(itemType -> Objects.equals(importItem.getParentCode(), itemType.getCode()))
                        .findFirst().orElse(null);
                if (parentItemType == null) {
                    respVO.getFailureCodes().put(key, "父分类编码[" + importItem.getParentCode() + "]不存在");
                    return;
                }
                parentId = parentItemType.getId();
            }
            // 2.2 判断是否存在(同一父分类下编码唯一)
            Long finalParentId = parentId;
            MesMdItemTypeDO existItemType = allList.stream()
                    .filter(itemType -> Objects.equals(finalParentId, itemType.getParentId())
                            && Objects.equals(importItem.getCode(), itemType.getCode()))
                    .findFirst().orElse(null);
            try {
                MdmItemCategorySaveReqDTO dto = new MdmItemCategorySaveReqDTO();
                dto.setParentId(parentId);
                dto.setCode(importItem.getCode());
                dto.setName(importItem.getName());
                dto.setItemOrProduct(importItem.getItemOrProduct());
                dto.setItemType(convertMdmItemType(importItem.getItemOrProduct()));
                dto.setSort(ObjUtil.defaultIfNull(importItem.getSort(), 0));
                dto.setStatus(ObjUtil.defaultIfNull(importItem.getStatus(), CommonStatusEnum.ENABLE.getStatus()));
                dto.setRemark(importItem.getRemark());
                if (existItemType == null) {
                    // 2.2.1 新增
                    dto.setId(null);
                    Long id = mdmItemCategoryApi.createItemCategory(dto).getData();
                    MesMdItemTypeDO created = new MesMdItemTypeDO();
                    created.setId(id);
                    created.setParentId(parentId);
                    created.setCode(importItem.getCode());
                    created.setName(importItem.getName());
                    created.setItemOrProduct(importItem.getItemOrProduct());
                    created.setSort(dto.getSort());
                    created.setStatus(dto.getStatus());
                    created.setRemark(importItem.getRemark());
                    allList.add(created);
                    respVO.getCreateCodes().add(importItem.getCode());
                } else if (updateSupport) {
                    // 2.2.2 更新
                    dto.setId(existItemType.getId());
                    mdmItemCategoryApi.updateItemCategory(dto);
                    respVO.getUpdateCodes().add(importItem.getCode());
                } else {
                    respVO.getFailureCodes().put(key, "分类编码已存在");
                }
            } catch (ServiceException e) {
                respVO.getFailureCodes().put(key, e.getMessage());
            }
        });
        return respVO;
    }
}