fix(kyzn): 物料新增 500 —— 修复 MES 物料分类编码重复导致 selectOne 抛 TooManyResultsException

- MesMdItemTypeMapper.selectByCode 改为列表查询,命中多行时取 id 最小的一条,避免 selectOne 抛异常
- MDM 同步物料到 MES 时,MES 侧找不到分类给出业务提示(1_050_057),不再 NPE 变 500
- 新增 mom-kyzn 数据修复脚本:缆线 IT_002 -> 002,辅材让出 IT_002,消除重复编码
已添加1个文件
已修改3个文件
46 ■■■■■ 文件已修改
sql/mysql/fix_kyzn_mes_md_item_type_code_dup_20261102.sql 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/enums/ErrorCodeConstants.java 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemServiceImpl.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/md/item/MesMdItemTypeMapper.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/fix_kyzn_mes_md_item_type_code_dup_20261102.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
-- åº·æ„æ™ºèƒ½ mom-kyzn:物料分类编码重复导致「物料管理-物料新增」500 ä¿®å¤è„šæœ¬
-- çŽ°è±¡ï¼šPOST /admin-api/mdm/item/create è¿”回 {"code":500,"msg":"系统异常"}
--       org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 2
--       ä½äºŽ MesMdItemTypeMapper.selectByCode -> MesMdItemTypeServiceImpl.getItemTypeByCode -> MesMdItemApiImpl.buildSaveReqVO
-- æ ¹å› ï¼šmes_md_item_type ä¸­ã€Œç¼†çº¿ã€(parent=3, IT_002) ä¸Žé¡¶å±‚「辅材」(IT_002) ç¼–码重复;
--       MDM ç‰©æ–™åŒæ­¥åˆ° MES æ—¶æŒ‰ code æŸ¥ MES åˆ†ç±»ï¼ŒselectOne å‘½ä¸­ 2 è¡Œç›´æŽ¥æŠ›å¼‚常 -> 500。
--       åŒæ—¶ MDM è®¡é‡å•位编码为 002/003(ç±³/ä»¶)、分类编码(如 004) ä¸Ž MES åˆ†ç±»ç¼–码(IT_00x) ä¸é€šç”¨ï¼Œ
--       æ•…这里把「缆线」的编码改为与 MDM å•位编码一致的 002,使同步链路可用。
-- æ‰§è¡Œåº“:101.245.92.159:9000/mom-kyzn(容器 mysql),已备份:/data/backup_mes_md_item_type_20261102.sql
-- å¹‚等:按 id å®šä½ï¼Œå¯é‡å¤æ‰§è¡Œ
USE `mom-kyzn`;
-- 1. æ¸…理本次排查过程中插入的临时行(若存在)
UPDATE mes_md_item_type SET deleted = 1 WHERE id = 17 AND code = '002';
-- 2. é‡å¤ç¼–码修正:13/2 è®©å‡º IT_002,缆线(14) æ”¹ä¸º 002(与 MDM å•位「米」编码一致)
UPDATE mes_md_item_type SET code = 'IT_010' WHERE id = 13 AND code = 'IT_002';
UPDATE mes_md_item_type SET code = 'IT_002-1' WHERE id = 2 AND code = 'IT_002';
UPDATE mes_md_item_type SET code = '002' WHERE id = 14 AND code = 'IT_002';
-- 3. æ ¡éªŒï¼šä¸åº”再有重复编码
SELECT code, COUNT(*) AS cnt FROM mes_md_item_type WHERE deleted = 0 GROUP BY code HAVING cnt > 1;
SELECT id, code, name, parent_id FROM mes_md_item_type WHERE id IN (2, 13, 14) ORDER BY id;
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/enums/ErrorCodeConstants.java
@@ -26,6 +26,7 @@
    ErrorCode MDM_ITEM_CATEGORY_PARENT_NOT_EXISTS = new ErrorCode(1_050_054, "父分类不存在");
    ErrorCode MDM_ITEM_CATEGORY_EXISTS_CHILDREN = new ErrorCode(1_050_055, "存在子分类,无法删除");
    ErrorCode MDM_ITEM_CATEGORY_EXISTS_ITEM = new ErrorCode(1_050_056, "分类下存在物料,无法删除");
    ErrorCode MDM_ITEM_CATEGORY_NOT_SYNC_MES = new ErrorCode(1_050_057, "物料分类(id={})在 MES ä¸­ä¸å­˜åœ¨ï¼Œæ— æ³•同步,请先在 MES ç‰©æ–™åˆ†ç±»ä¸­åˆ›å»ºç¼–码一致且未重复的分类");
    // ========== è®¡é‡å•位相关 1-050-101 ~ 1-050-150 ==========
    ErrorCode MDM_UNIT_MEASURE_NOT_EXISTS = new ErrorCode(1_050_101, "计量单位不存在");
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemServiceImpl.java
@@ -152,6 +152,11 @@
        String itemTypeCode = null;
        if (reqVO.getCategoryId() != null) {
            CommonResult<MesMdItemTypeRespDTO> itemType = mesMdItemTypeApi.getItemType(reqVO.getCategoryId());
            // åˆ†ç±»åœ¨ MES ä¾§ä¸å­˜åœ¨æ—¶ï¼ˆå¦‚历史数据中 MDM åˆ†ç±»ä¸Ž MES åˆ†ç±»ä¸åŒæ­¥ï¼‰ï¼Œ
            // ç»™å‡ºæ˜Žç¡®çš„业务提示,而不是抛 NullPointerException å˜æˆ 500
            if (itemType == null || itemType.getData() == null) {
                throw exception(MDM_ITEM_CATEGORY_NOT_SYNC_MES, reqVO.getCategoryId());
            }
            itemTypeCode = itemType.getData().getCode();
        }
        // ç»„装同步 DTO
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/md/item/MesMdItemTypeMapper.java
@@ -6,6 +6,7 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemTypeDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Comparator;
import java.util.List;
/**
@@ -31,8 +32,21 @@
        return selectOne(MesMdItemTypeDO::getParentId, parentId, MesMdItemTypeDO::getCode, code);
    }
    default List<MesMdItemTypeDO> selectListByCode(String code) {
        return selectList(MesMdItemTypeDO::getCode, code);
    }
    default MesMdItemTypeDO selectByCode(String code) {
        return selectOne(MesMdItemTypeDO::getCode, code);
        // ç¼–码在历史数据中并不是唯一约束(如父分类下的「缆线」与顶层「辅材」都曾用过 IT_002),
        // ç›´æŽ¥ç”¨ selectOne ä¼šåœ¨å‘½ä¸­å¤šè¡Œæ—¶æŠ› TooManyResultsException;这里改为按列表查询。
        List<MesMdItemTypeDO> list = selectListByCode(code);
        if (list.isEmpty()) {
            return null;
        }
        // å‘½ä¸­å¤šè¡Œæ—¶å– id æœ€å°çš„一条(最早创建的分类),保证同步链路可用
        return list.stream()
                .min(Comparator.comparing(MesMdItemTypeDO::getId))
                .orElse(null);
    }
    default Long selectCountByParentId(Long parentId) {