package cn.iocoder.yudao.module.mdm.service.item; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemPageReqVO; import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemSaveReqVO; import cn.iocoder.yudao.module.mdm.dal.dataobject.category.MdmItemCategoryDO; import cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemDO; import cn.iocoder.yudao.module.mdm.dal.dataobject.unit.MdmUnitMeasureDO; import cn.iocoder.yudao.module.mdm.dal.mysql.item.MdmItemMapper; import cn.iocoder.yudao.module.mdm.service.brand.MdmBrandService; import cn.iocoder.yudao.module.mdm.service.category.MdmItemCategoryService; 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.item.MesMdItemTypeApi; import cn.iocoder.yudao.module.mes.api.item.dto.MesMdItemSyncReqDTO; import cn.iocoder.yudao.module.mes.api.item.dto.MesMdItemTypeRespDTO; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; import static cn.iocoder.yudao.module.mdm.enums.ErrorCodeConstants.*; /** * MDM 物料主数据 Service 实现类 * * @author 芋道源码 */ @Service @Validated public class MdmItemServiceImpl implements MdmItemService { @Resource private MdmItemMapper itemMapper; @Resource private MdmItemCategoryService itemCategoryService; @Resource private MdmUnitMeasureService unitMeasureService; @Resource private MdmBrandService brandService; @Resource private MesMdItemApi mesMdItemApi; @Resource private MesMdItemTypeApi mesMdItemTypeApi; @Override @Transactional(rollbackFor = Exception.class) public Long createItem(MdmItemSaveReqVO createReqVO) { // 校验数据 validateItemSaveData(createReqVO); // 插入 MDM 物料 MdmItemDO item = BeanUtils.toBean(createReqVO, MdmItemDO.class); itemMapper.insert(item); // 同步到 MES(勾选 syncMes 时) if (Boolean.TRUE.equals(createReqVO.getSyncMes())) { syncItemToMes(createReqVO); } return item.getId(); } @Override @Transactional(rollbackFor = Exception.class) public void updateItem(MdmItemSaveReqVO updateReqVO) { // 校验存在 validateItemExists(updateReqVO.getId()); // 校验数据 validateItemSaveData(updateReqVO); // 更新 MDM 物料 MdmItemDO updateObj = BeanUtils.toBean(updateReqVO, MdmItemDO.class); itemMapper.updateById(updateObj); // 同步到 MES(勾选 syncMes 时) if (Boolean.TRUE.equals(updateReqVO.getSyncMes())) { syncItemToMes(updateReqVO); } } private void validateItemSaveData(MdmItemSaveReqVO reqVO) { // 校验物料编码的唯一性 validateItemCodeUnique(reqVO.getId(), reqVO.getCode()); } /** * 同步物料到 MES(单位/分类按 code 映射) * * MDM 的 unitMeasureId/categoryId 与 MES 的 unitMeasureId/itemTypeId 分属两套主数据, * 这里先在 MDM 侧解析出 code,再交由 MesMdItemApi 在 MES 侧解析为对应 id。 * 同步失败将抛出异常,触发外层 @Transactional 整体回滚(MDM 操作一并回滚)。 */ private void syncItemToMes(MdmItemSaveReqVO reqVO) { // 解析计量单位 id -> code String unitCode = null; if (reqVO.getUnitMeasureId() != null) { MdmUnitMeasureDO unitMeasure = unitMeasureService.getUnitMeasure(reqVO.getUnitMeasureId()); if (unitMeasure == null) { throw exception(MDM_UNIT_MEASURE_NOT_EXISTS); } unitCode = unitMeasure.getCode(); } // 解析分类 id -> code(使用 MES 物料分类 API) String itemTypeCode = null; if (reqVO.getCategoryId() != null) { CommonResult result = mesMdItemTypeApi.getItemType(reqVO.getCategoryId()); if (result != null && result.isSuccess() && result.getData() != null) { itemTypeCode = result.getData().getCode(); } } else if (reqVO.getItemType() != null) { // 根据 itemType 查找对应分类编码 // itemType: 1原料, 2半成品, 3成品, 4辅料 String itemTypeName = convertItemTypeToName(reqVO.getItemType()); CommonResult> listResult = mesMdItemTypeApi.getItemTypeList(null); if (listResult != null && listResult.isSuccess() && listResult.getData() != null) { itemTypeCode = listResult.getData().stream() .filter(c -> c.getName() != null && c.getName().equals(itemTypeName)) .findFirst() .map(MesMdItemTypeRespDTO::getCode) .orElse(null); } } // 组装同步 DTO MesMdItemSyncReqDTO syncReqDTO = new MesMdItemSyncReqDTO(); syncReqDTO.setCode(reqVO.getCode()); syncReqDTO.setName(reqVO.getName()); syncReqDTO.setSpecification(reqVO.getSpecification()); syncReqDTO.setUnitCode(unitCode); syncReqDTO.setItemTypeCode(itemTypeCode); syncReqDTO.setSafeStockFlag(Boolean.TRUE); // 保留 MDM 的 minStock/maxStock,避免被 MES 清零 syncReqDTO.setMinStock(reqVO.getMinStock()); syncReqDTO.setMaxStock(reqVO.getMaxStock()); syncReqDTO.setHighValue(reqVO.getIsHighValue()); syncReqDTO.setBatchFlag(reqVO.getIsBatchManaged()); syncReqDTO.setRemark(reqVO.getRemark()); // create 走 createItem,update 走 updateItem(MES 侧按 code upsert) if (reqVO.getId() == null) { mesMdItemApi.createItem(syncReqDTO); } else { mesMdItemApi.updateItem(syncReqDTO); } } @Override public void updateItemStatus(Long id, Integer status) { // 校验存在 validateItemExists(id); // 更新状态 itemMapper.updateById(new MdmItemDO().setId(id).setStatus(status)); } @Override @Transactional(rollbackFor = Exception.class) public void deleteItem(Long id) { // 校验存在 MdmItemDO item = validateItemExists(id); // 解决软删除唯一键冲突:如果已存在相同 code 且已软删除的记录,先物理删除它 MdmItemDO existingDeleted = itemMapper.selectByCodeAndDeleted(item.getCode(), true); if (existingDeleted != null && !existingDeleted.getId().equals(id)) { itemMapper.physicalDeleteById(existingDeleted.getId()); } // 软删除当前记录 itemMapper.deleteById(id); } @Override public MdmItemDO validateItemExists(Long id) { MdmItemDO item = itemMapper.selectById(id); if (item == null) { throw exception(MDM_ITEM_NOT_EXISTS); } return item; } @Override public MdmItemDO validateItemExistsAndEnable(Long id) { MdmItemDO item = validateItemExists(id); if (ObjUtil.notEqual(cn.iocoder.yudao.framework.common.enums.CommonStatusEnum.ENABLE.getStatus(), item.getStatus())) { throw exception(MDM_ITEM_IS_DISABLE); } return item; } private void validateItemCodeUnique(Long id, String code) { MdmItemDO item = itemMapper.selectByCode(code); if (item == null) { return; } if (ObjUtil.notEqual(item.getId(), id)) { throw exception(MDM_ITEM_CODE_DUPLICATE); } } private void validateCategoryExists(Long categoryId) { if (categoryId == null) { return; } CommonResult result = mesMdItemTypeApi.getItemType(categoryId); if (result == null || !result.isSuccess() || result.getData() == null) { throw exception(MDM_ITEM_CATEGORY_NOT_EXISTS); } } private void validateUnitMeasureExists(Long unitMeasureId) { if (unitMeasureService.getUnitMeasure(unitMeasureId) == null) { throw exception(MDM_UNIT_MEASURE_NOT_EXISTS); } } private void validateBrandExists(Long brandId) { if (brandService.getBrand(brandId) == null) { throw exception(MDM_BRAND_NOT_EXISTS); } } /** * 将 itemType 转换为中文名称 * * @param itemType 物料类型:1原料, 2半成品, 3成品, 4辅料 * @return 中文名称 */ private String convertItemTypeToName(Integer itemType) { if (itemType == null) { return null; } switch (itemType) { case 1: return "原料"; case 2: return "半成品"; case 3: return "成品"; case 4: return "辅料"; default: return null; } } @Override public MdmItemDO getItem(Long id) { return itemMapper.selectById(id); } @Override public PageResult getItemPage(MdmItemPageReqVO pageReqVO) { // 查找时,如果查找某个分类编号,则包含它的子分类 Set categoryIds = null; if (pageReqVO.getCategoryId() != null) { categoryIds = new HashSet<>(); categoryIds.add(pageReqVO.getCategoryId()); List children = itemCategoryService.getItemCategoryChildrenList(pageReqVO.getCategoryId()); categoryIds.addAll(convertSet(children, cn.iocoder.yudao.module.mdm.dal.dataobject.category.MdmItemCategoryDO::getId)); } // 分页查询 return itemMapper.selectPage(pageReqVO, categoryIds); } @Override public List getItemList(Collection ids) { if (CollUtil.isEmpty(ids)) { return Collections.emptyList(); } return itemMapper.selectBatchIds(ids); } @Override public Long getItemCountByCategoryId(Long categoryId) { return itemMapper.selectCountByCategoryId(categoryId); } @Override public Long getItemCountByUnitMeasureId(Long unitMeasureId) { return itemMapper.selectCountByUnitMeasureId(unitMeasureId); } @Override public Long getItemCountByBrandId(Long brandId) { return itemMapper.selectCountByBrandId(brandId); } @Override public MdmItemDO getItemByCode(String code) { return itemMapper.selectByCode(code); } }