gaoluyang
2026-10-26 48ee7c963f29f5614a8f86c08608620cc6310793
feat: 完善产品编码与技术文件管理
已添加8个文件
已修改17个文件
368 ■■■■■ 文件已修改
yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/enums/MdmItemTypeEnum.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/MdmItemSyncApiImpl.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemCreateReqDTO.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemRespDTO.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemSyncReqDTO.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemUpdateReqDTO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/MdmItemController.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemExcelVO.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemImportExcelVO.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemRespVO.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemSaveReqVO.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemTechnicalFileCreateReqVO.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemTechnicalFileRespVO.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/item/MdmItemCodeSequenceDO.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/item/MdmItemDO.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/item/MdmItemTechnicalFileDO.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/item/MdmItemCodeSequenceMapper.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/item/MdmItemMapper.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/item/MdmItemTechnicalFileMapper.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/enums/ErrorCodeConstants.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/enums/MdmItemTypeEnum.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemServiceImpl.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemTechnicalFileService.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemTechnicalFileServiceImpl.java 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/storage/StorageRecordTypeEnum.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/enums/MdmItemTypeEnum.java
@@ -12,9 +12,11 @@
@AllArgsConstructor
public enum MdmItemTypeEnum {
    MATERIAL(1, "物料"),
    PRODUCT(2, "产品"),
    SEMI_FINISHED(3, "半成品");
    RAW_MATERIAL(1, "原材料"),
    SEMI_FINISHED(2, "半成品"),
    FINISHED_PRODUCT(3, "成品"),
    AUXILIARY(4, "辅材"),
    OTHER(5, "其他");
    /**
     * ç±»åž‹ç¼–码
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/MdmItemSyncApiImpl.java
@@ -41,6 +41,10 @@
        if (existingItem != null) {
            // æ›´æ–°
            item.setId(existingItem.getId());
            // MES åŒæ­¥è¯·æ±‚暂不维护技术参数,避免同步时把 MDM å·²ç»´æŠ¤çš„参数清空。
            if (syncReqDTO.getTechnicalParameters() == null) {
                item.setTechnicalParameters(existingItem.getTechnicalParameters());
            }
            itemMapper.updateById(item);
            itemId = existingItem.getId();
        } else {
@@ -99,4 +103,4 @@
        }
    }
}
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemCreateReqDTO.java
@@ -21,6 +21,9 @@
    @Schema(description = "规格型号", example = "M8*20")
    private String specification;
    @Schema(description = "技术参数")
    private String technicalParameters;
    @Schema(description = "物料分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Long categoryId;
@@ -60,4 +63,4 @@
    @Schema(description = "备注", example = "备注信息")
    private String remark;
}
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemRespDTO.java
@@ -24,6 +24,9 @@
    @Schema(description = "规格型号", example = "M8*20")
    private String specification;
    @Schema(description = "技术参数")
    private String technicalParameters;
    @Schema(description = "物料分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Long categoryId;
@@ -72,4 +75,4 @@
    @Schema(description = "备注", example = "备注信息")
    private String remark;
}
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemSyncReqDTO.java
@@ -23,6 +23,9 @@
    @Schema(description = "规格型号", example = "M6*20")
    private String specification;
    @Schema(description = "技术参数")
    private String technicalParameters;
    @Schema(description = "物料类型:1原料, 2半成品, 3成品, 4辅料", example = "1")
    private Long itemType;
@@ -106,4 +109,4 @@
    @Schema(description = "质量状态必填", example = "false")
    private Boolean qualityStatusFlag;
}
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemUpdateReqDTO.java
@@ -24,6 +24,9 @@
    @Schema(description = "规格型号", example = "M8*20")
    private String specification;
    @Schema(description = "技术参数")
    private String technicalParameters;
    @Schema(description = "物料分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Long categoryId;
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/MdmItemController.java
@@ -14,6 +14,8 @@
import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemSaveReqVO;
import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemStatusUpdateReqVO;
import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemSyncUpdateReqVO;
import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemTechnicalFileCreateReqVO;
import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemTechnicalFileRespVO;
import cn.iocoder.yudao.module.mdm.dal.dataobject.category.MdmItemCategoryDO;
import cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemBatchConfigDO;
import cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemDO;
@@ -23,6 +25,7 @@
import cn.iocoder.yudao.module.mdm.service.category.MdmItemCategoryService;
import cn.iocoder.yudao.module.mdm.service.item.MdmItemBatchConfigService;
import cn.iocoder.yudao.module.mdm.service.item.MdmItemService;
import cn.iocoder.yudao.module.mdm.service.item.MdmItemTechnicalFileService;
import cn.iocoder.yudao.module.mdm.service.unit.MdmUnitMeasureService;
import cn.iocoder.yudao.module.mes.api.item.MesMdItemApi;
import cn.iocoder.yudao.module.mes.api.wm.warehouse.MesWmWarehouseApi;
@@ -70,6 +73,9 @@
    @Resource
    private MdmItemBatchConfigService itemBatchConfigService;
    @Resource
    private MdmItemTechnicalFileService technicalFileService;
    @PostMapping("/create")
    @Operation(summary = "创建物料")
    @PreAuthorize("@ss.hasPermission('mdm:item:create')")
@@ -99,6 +105,20 @@
    public CommonResult<Boolean> updateItemSync(@Valid @RequestBody MdmItemSyncUpdateReqVO reqVO) {
        itemService.updateItemSync(reqVO.getId(), reqVO.getSync());
        return success(true);
    }
    @PostMapping("/technical-file/create")
    @Operation(summary = "上传产品技术文件版本")
    @PreAuthorize("@ss.hasPermission('mdm:item:update')")
    public CommonResult<Long> createTechnicalFile(@Valid @RequestBody MdmItemTechnicalFileCreateReqVO reqVO) {
        return success(technicalFileService.createTechnicalFile(reqVO));
    }
    @GetMapping("/technical-file/list")
    @Operation(summary = "获得产品技术文件历史")
    @PreAuthorize("@ss.hasPermission('mdm:item:query')")
    public CommonResult<List<MdmItemTechnicalFileRespVO>> getTechnicalFileList(@RequestParam("itemId") Long itemId) {
        return success(technicalFileService.getTechnicalFileList(itemId));
    }
    @DeleteMapping("/delete")
@@ -349,4 +369,4 @@
        return success(itemService.importItemList(list, updateSupport));
    }
}
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemExcelVO.java
@@ -14,7 +14,7 @@
@Data
public class MdmItemExcelVO {
    @ExcelProperty("物料编码")
    @ExcelProperty("产品编号")
    private String code;
    @ExcelProperty("物料名称")
@@ -26,7 +26,10 @@
    @ExcelProperty("规格型号")
    private String specification;
    @ExcelProperty("物料类型")
    @ExcelProperty("技术参数")
    private String technicalParameters;
    @ExcelProperty("产品类型")
    private String itemTypeName;
    @ExcelProperty("物料分类")
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemImportExcelVO.java
@@ -17,7 +17,7 @@
@NoArgsConstructor
public class MdmItemImportExcelVO {
    @ExcelProperty("物料编码")
    @ExcelProperty("产品编号(修改时用于匹配,新增时自动生成)")
    private String code;
    @ExcelProperty("物料名称")
@@ -29,6 +29,12 @@
    @ExcelProperty("规格型号")
    private String specification;
    @ExcelProperty("技术参数")
    private String technicalParameters;
    @ExcelProperty("产品类型(1原材料 2半成品 3成品 4辅材 5其他)")
    private Integer itemType;
    @ExcelProperty("物料分类编码")
    private String categoryCode;
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemRespVO.java
@@ -25,6 +25,9 @@
    @Schema(description = "规格型号", example = "M8*20")
    private String specification;
    @Schema(description = "技术参数")
    private String technicalParameters;
    @Schema(description = "物料分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Long categoryId;
@@ -138,4 +141,4 @@
    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
    private LocalDateTime createTime;
}
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemSaveReqVO.java
@@ -12,7 +12,7 @@
    @Schema(description = "物料编号", example = "1")
    private Long id;
    @Schema(description = "物料编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "ITEM001")
    @Schema(description = "物料编码(由系统自动生成,修改时忽略前端传入值)", example = "100100001")
    private String code;
    @Schema(description = "物料名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "螺丝")
@@ -23,6 +23,9 @@
    @Schema(description = "规格型号", example = "M8*20")
    private String specification;
    @Schema(description = "技术参数")
    private String technicalParameters;
    @Schema(description = "物料分类编号(关联 MES ç‰©æ–™åˆ†ç±» mes_md_item_type)", example = "1")
    private Long categoryId;
@@ -48,7 +51,7 @@
    @Schema(description = "默认仓库编码", example = "WH001")
    private String warehouseCode;
    @Schema(description = "物料类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @Schema(description = "产品类型:1原材料 2半成品 3成品 4辅材 5其他", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Integer itemType;
    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@@ -115,4 +118,4 @@
    private Long mdmItemId;
}
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemTechnicalFileCreateReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
package cn.iocoder.yudao.module.mdm.controller.admin.item.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Data
public class MdmItemTechnicalFileCreateReqVO {
    @NotNull
    @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED)
    private Long itemId;
    @NotNull
    @Schema(description = "已上传的 storage blob ID", requiredMode = Schema.RequiredMode.REQUIRED)
    private Long blobId;
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemTechnicalFileRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package cn.iocoder.yudao.module.mdm.controller.admin.item.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class MdmItemTechnicalFileRespVO {
    private Long id;
    private Long itemId;
    private Long blobId;
    private Integer versionNo;
    private String fileName;
    private String previewURL;
    private String downloadURL;
    private LocalDateTime createTime;
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/item/MdmItemCodeSequenceDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package cn.iocoder.yudao.module.mdm.dal.dataobject.item;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/** ç‰©æ–™ç¼–码分段流水。每个四位前缀独立递增。 */
@Data
@TableName("mdm_item_code_sequence")
public class MdmItemCodeSequenceDO {
    @TableId
    private String prefix;
    private Integer currentValue;
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/item/MdmItemDO.java
@@ -55,6 +55,9 @@
     */
    private String specification;
    /** æŠ€æœ¯å‚æ•° */
    private String technicalParameters;
    /**
     * ç‰©æ–™åˆ†ç±»ç¼–号
     *
@@ -213,4 +216,4 @@
     */
    private String remark;
}
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/item/MdmItemTechnicalFileDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.mdm.dal.dataobject.item;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("mdm_item_technical_file")
@KeySequence("mdm_item_technical_file_seq")
public class MdmItemTechnicalFileDO extends BaseDO {
    @TableId
    private Long id;
    private Long itemId;
    private Long storageBlobId;
    private Integer versionNo;
    private String fileName;
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/item/MdmItemCodeSequenceMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.mdm.dal.mysql.item;
import cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemCodeSequenceDO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
@Mapper
public interface MdmItemCodeSequenceMapper extends BaseMapper<MdmItemCodeSequenceDO> {
    @Insert("INSERT IGNORE INTO mdm_item_code_sequence(prefix, current_value) VALUES (#{prefix}, 0)")
    void insertIfAbsent(String prefix);
    @Select("SELECT current_value FROM mdm_item_code_sequence WHERE prefix = #{prefix} FOR UPDATE")
    Integer selectCurrentValueForUpdate(String prefix);
    @Update("UPDATE mdm_item_code_sequence SET current_value = current_value + 1 WHERE prefix = #{prefix}")
    int increment(String prefix);
    @Update("UPDATE mdm_item_code_sequence SET current_value = #{value} WHERE prefix = #{prefix}")
    int updateCurrentValue(@org.apache.ibatis.annotations.Param("prefix") String prefix,
                           @org.apache.ibatis.annotations.Param("value") Integer value);
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/item/MdmItemMapper.java
@@ -20,6 +20,9 @@
@Mapper
public interface MdmItemMapper extends BaseMapperX<MdmItemDO> {
    @org.apache.ibatis.annotations.Select("SELECT COALESCE(MAX(CAST(SUBSTRING(code, 5) AS UNSIGNED)), 0) FROM mdm_item WHERE deleted = 0 AND code LIKE CONCAT(#{prefix}, '%')")
    Integer selectMaxSequenceByPrefix(@Param("prefix") String prefix);
    default PageResult<MdmItemDO> selectPage(MdmItemPageReqVO reqVO, Collection<Long> categoryIds) {
        return selectPage(reqVO, new LambdaQueryWrapperX<MdmItemDO>()
                .likeIfPresent(MdmItemDO::getCode, reqVO.getCode())
@@ -92,4 +95,4 @@
     */
    List<Map<String, Object>> selectItemReferenceCount(@Param("itemId") Long itemId);
}
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/item/MdmItemTechnicalFileMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,26 @@
package cn.iocoder.yudao.module.mdm.dal.mysql.item;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemTechnicalFileDO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface MdmItemTechnicalFileMapper extends BaseMapperX<MdmItemTechnicalFileDO> {
    default List<MdmItemTechnicalFileDO> selectListByItemId(Long itemId) {
        return selectList(new LambdaQueryWrapper<MdmItemTechnicalFileDO>()
                .eq(MdmItemTechnicalFileDO::getItemId, itemId)
                .orderByDesc(MdmItemTechnicalFileDO::getVersionNo));
    }
    default Integer selectMaxVersionNo(Long itemId) {
        MdmItemTechnicalFileDO row = selectOne(new LambdaQueryWrapper<MdmItemTechnicalFileDO>()
                .eq(MdmItemTechnicalFileDO::getItemId, itemId)
                .orderByDesc(MdmItemTechnicalFileDO::getVersionNo)
                .last("LIMIT 1"));
        return row == null || row.getVersionNo() == null ? 0 : row.getVersionNo();
    }
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/enums/ErrorCodeConstants.java
@@ -18,6 +18,8 @@
    ErrorCode MDM_ITEM_SKU_CODE_DUPLICATE = new ErrorCode(1_050_006, "SKU编码已存在");
    ErrorCode MDM_ITEM_EXISTS_REFERENCE = new ErrorCode(1_050_007, "物料已被业务引用,无法删除");
    ErrorCode MDM_ITEM_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_050_008, "导入的物料数据不能为空");
    ErrorCode MDM_ITEM_TYPE_INVALID = new ErrorCode(1_050_009, "产品类型无效");
    ErrorCode MDM_ITEM_CODE_SEQUENCE_EXHAUSTED = new ErrorCode(1_050_010, "产品编号流水号已超过五位");
    // ========== ç‰©æ–™åˆ†ç±»ç›¸å…³ 1-050-051 ~ 1-050-100 ==========
    ErrorCode MDM_ITEM_CATEGORY_NOT_EXISTS = new ErrorCode(1_050_051, "物料分类不存在");
@@ -46,4 +48,4 @@
    ErrorCode MDM_WAREHOUSE_NAME_DUPLICATE = new ErrorCode(1_050_203, "仓库名称已存在");
    ErrorCode MDM_WAREHOUSE_IS_DISABLE = new ErrorCode(1_050_204, "仓库已禁用");
}
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/enums/MdmItemTypeEnum.java
@@ -12,9 +12,11 @@
@AllArgsConstructor
public enum MdmItemTypeEnum {
    MATERIAL(1, "物料"),
    PRODUCT(2, "产品"),
    SEMI_FINISHED(3, "半成品");
    RAW_MATERIAL(1, "原材料"),
    SEMI_FINISHED(2, "半成品"),
    FINISHED_PRODUCT(3, "成品"),
    AUXILIARY(4, "辅材"),
    OTHER(5, "其他");
    /**
     * ç±»åž‹ç¼–码
@@ -38,4 +40,4 @@
        return null;
    }
}
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemServiceImpl.java
@@ -19,6 +19,7 @@
import cn.iocoder.yudao.module.mdm.dal.dataobject.unit.MdmUnitMeasureDO;
import cn.iocoder.yudao.module.mdm.dal.mysql.item.MdmItemBatchConfigMapper;
import cn.iocoder.yudao.module.mdm.dal.mysql.item.MdmItemMapper;
import cn.iocoder.yudao.module.mdm.dal.mysql.item.MdmItemCodeSequenceMapper;
import cn.iocoder.yudao.module.mdm.dal.mysql.unit.MdmUnitMeasureMapper;
import cn.iocoder.yudao.module.mdm.service.brand.MdmBrandService;
import cn.iocoder.yudao.module.mdm.service.category.MdmItemCategoryService;
@@ -60,6 +61,9 @@
    private MdmItemMapper itemMapper;
    @Resource
    private MdmItemCodeSequenceMapper itemCodeSequenceMapper;
    @Resource
    private MdmItemCategoryService itemCategoryService;
    @Resource
    private MdmUnitMeasureService unitMeasureService;
@@ -85,6 +89,7 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createItem(MdmItemSaveReqVO createReqVO) {
        createReqVO.setCode(generateItemCode(createReqVO.getItemType()));
        // æ ¡éªŒæ•°æ®
        validateItemSaveData(createReqVO);
@@ -107,7 +112,10 @@
    @Transactional(rollbackFor = Exception.class)
    public void updateItem(MdmItemSaveReqVO updateReqVO) {
        // æ ¡éªŒå­˜åœ¨
        validateItemExists(updateReqVO.getId());
        MdmItemDO existingItem = validateItemExists(updateReqVO.getId());
        // ç¼–号由系统生成,修改产品时保持原编号不变。
        updateReqVO.setCode(existingItem.getCode());
        updateReqVO.setItemType(existingItem.getItemType());
        // æ ¡éªŒæ•°æ®
        validateItemSaveData(updateReqVO);
@@ -127,8 +135,41 @@
    }
    private void validateItemSaveData(MdmItemSaveReqVO reqVO) {
        if (reqVO.getItemType() == null || reqVO.getItemType() < 1 || reqVO.getItemType() > 5) {
            throw exception(MDM_ITEM_TYPE_INVALID);
        }
        // æ ¡éªŒç‰©æ–™ç¼–码的唯一性
        validateItemCodeUnique(reqVO.getId(), reqVO.getCode());
    }
    /** ç”Ÿæˆå››ä½ç±»åž‹å‰ç¼€ + äº”位流水号,共九位。流水行在事务内加锁,避免并发重复。 */
    private String generateItemCode(Integer itemType) {
        String prefix = switch (itemType == null ? 0 : itemType) {
            case 1 -> "1001";
            case 2 -> "1002";
            case 3 -> "1003";
            case 4 -> "1004";
            case 5 -> "1005";
            default -> throw exception(MDM_ITEM_TYPE_INVALID);
        };
        itemCodeSequenceMapper.insertIfAbsent(prefix);
        Integer current = itemCodeSequenceMapper.selectCurrentValueForUpdate(prefix);
        if (current == null) {
            current = itemMapper.selectMaxSequenceByPrefix(prefix);
            itemCodeSequenceMapper.insertIfAbsent(prefix);
            itemCodeSequenceMapper.updateCurrentValue(prefix, current);
        }
        int maxExisting = itemMapper.selectMaxSequenceByPrefix(prefix);
        if (current < maxExisting) {
            current = maxExisting;
            itemCodeSequenceMapper.updateCurrentValue(prefix, current);
        }
        if (current >= 99_999) {
            throw exception(MDM_ITEM_CODE_SEQUENCE_EXHAUSTED);
        }
        itemCodeSequenceMapper.increment(prefix);
        int next = current + 1;
        return prefix + String.format("%05d", next);
    }
    /**
@@ -468,8 +509,8 @@
        TransactionTemplate importTxTemplate = new TransactionTemplate(transactionManager);
        for (MdmItemImportExcelVO importVO : importList) {
            // 2.1 åŸºç¡€æ ¡éªŒ
            if (StrUtil.isBlank(importVO.getCode()) || StrUtil.isBlank(importVO.getName())) {
                respVO.getFailureList().put(buildFailKey(importVO), "物料编码和物料名称不能为空");
            if (StrUtil.isBlank(importVO.getName())) {
                respVO.getFailureList().put(buildFailKey(importVO), "物料名称不能为空");
                continue;
            }
            // 2.2 è§£æžåˆ†ç±»
@@ -511,10 +552,12 @@
            // 2.6 ç»„装 SaveReqVO(复用 create/update é€»è¾‘)
            MdmItemSaveReqVO saveReqVO = new MdmItemSaveReqVO();
            // æ–°å¢žæ—¶å¿½ç•¥ Excel ä¸­çš„物料编码,由 MDM æŒ‰äº§å“ç±»åž‹è‡ªåŠ¨ç”Ÿæˆã€‚
            saveReqVO.setCode(importVO.getCode());
            saveReqVO.setName(importVO.getName());
            saveReqVO.setBarCode(importVO.getBarCode());
            saveReqVO.setSpecification(importVO.getSpecification());
            saveReqVO.setTechnicalParameters(importVO.getTechnicalParameters());
            saveReqVO.setCategoryId(categoryId);
            saveReqVO.setUnitMeasureId(unitMeasureId);
            saveReqVO.setUnitMeasureId2(unitMeasureId2);
@@ -532,10 +575,10 @@
            saveReqVO.setStatus(importVO.getStatus() != null ? importVO.getStatus() : cn.iocoder.yudao.framework.common.enums.CommonStatusEnum.ENABLE.getStatus());
            saveReqVO.setSyncMes(importVO.getSyncMes() != null ? importVO.getSyncMes() : false);
            saveReqVO.setRemark(importVO.getRemark());
            saveReqVO.setItemType(1); // é»˜è®¤ç‰©æ–™ç±»åž‹ï¼šç‰©æ–™
            saveReqVO.setItemType(importVO.getItemType() != null ? importVO.getItemType() : 1); // æœªæä¾›ç±»åž‹æ—¶é»˜è®¤ä¸ºåŽŸææ–™
            // 2.7 å·²å­˜åœ¨ï¼ˆæŒ‰ç¼–码判断)
            MdmItemDO existByCode = itemMapper.selectByCode(importVO.getCode());
            MdmItemDO existByCode = StrUtil.isBlank(importVO.getCode()) ? null : itemMapper.selectByCode(importVO.getCode());
            if (existByCode != null && !Boolean.TRUE.equals(isUpdateSupport)) {
                respVO.getFailureList().put(buildFailKey(importVO), "物料编码已存在");
                continue;
@@ -587,4 +630,4 @@
                StrUtil.blankToDefault(importVO.getName(), "(名称和编码为空的行)"));
    }
}
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemTechnicalFileService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,11 @@
package cn.iocoder.yudao.module.mdm.service.item;
import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemTechnicalFileCreateReqVO;
import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemTechnicalFileRespVO;
import java.util.List;
public interface MdmItemTechnicalFileService {
    Long createTechnicalFile(MdmItemTechnicalFileCreateReqVO reqVO);
    List<MdmItemTechnicalFileRespVO> getTechnicalFileList(Long itemId);
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemTechnicalFileServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,69 @@
package cn.iocoder.yudao.module.mdm.service.item;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemTechnicalFileCreateReqVO;
import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemTechnicalFileRespVO;
import cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemTechnicalFileDO;
import cn.iocoder.yudao.module.mdm.dal.mysql.item.MdmItemTechnicalFileMapper;
import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collections;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mdm.enums.ErrorCodeConstants.MDM_ITEM_NOT_EXISTS;
@Service
public class MdmItemTechnicalFileServiceImpl implements MdmItemTechnicalFileService {
    private static final String RECORD_TYPE = "mdm_item_technical_file";
    private final MdmItemTechnicalFileMapper mapper;
    private final MdmItemService itemService;
    private final StorageAttachmentApi storageAttachmentApi;
    public MdmItemTechnicalFileServiceImpl(MdmItemTechnicalFileMapper mapper,
                                           MdmItemService itemService,
                                           StorageAttachmentApi storageAttachmentApi) {
        this.mapper = mapper;
        this.itemService = itemService;
        this.storageAttachmentApi = storageAttachmentApi;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createTechnicalFile(MdmItemTechnicalFileCreateReqVO reqVO) {
        if (itemService.getItem(reqVO.getItemId()) == null) {
            throw exception(MDM_ITEM_NOT_EXISTS);
        }
        int version = mapper.selectMaxVersionNo(reqVO.getItemId()) + 1;
        // è®°å½• ID å¿…须与附件关系记录 ID ä¸€è‡´ï¼Œå› æ­¤å…ˆæ’入版本记录,再绑定附件。
        MdmItemTechnicalFileDO file = new MdmItemTechnicalFileDO();
        file.setItemId(reqVO.getItemId());
        file.setStorageBlobId(reqVO.getBlobId());
        file.setVersionNo(version);
        mapper.insert(file);
        storageAttachmentApi.bindAttachments("file", RECORD_TYPE, file.getId(),
                Collections.singletonList(reqVO.getBlobId()));
        return file.getId();
    }
    @Override
    public List<MdmItemTechnicalFileRespVO> getTechnicalFileList(Long itemId) {
        return mapper.selectListByItemId(itemId).stream().map(file -> {
            MdmItemTechnicalFileRespVO resp = BeanUtils.toBean(file, MdmItemTechnicalFileRespVO.class);
            resp.setBlobId(file.getStorageBlobId());
            List<StorageBlobRespDTO> blobs = storageAttachmentApi.listAttachments(RECORD_TYPE, file.getId());
            if (blobs != null && !blobs.isEmpty()) {
                StorageBlobRespDTO blob = blobs.get(0);
                resp.setFileName(blob.getName());
                resp.setPreviewURL(blob.getPreviewURL());
                resp.setDownloadURL(blob.getDownloadURL());
            }
            return resp;
        }).toList();
    }
}
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/storage/StorageRecordTypeEnum.java
@@ -65,6 +65,8 @@
    // ===== MES =====
    MES_PD_DOCUMENT("mes_pd_document", "MES è®¾è®¡èµ„æ–™"),
    MDM_ITEM_TECHNICAL_FILE("mdm_item_technical_file", "MDM ç‰©æ–™æŠ€æœ¯æ–‡ä»¶"),
    // ===== SRM =====
    SRM_SUPPLIER_CERTIFICATE("srm_supplier_certificate", "SRM ä¾›åº”商资质证书"),
    SRM_TENDER_PROJECT("srm_tender_project", "SRM æ‹›æ ‡é¡¹ç›®"),