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.framework.mybatis.core.mapper.BaseMapperX; 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.MdmItemBatchConfigMapper; 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 com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import jakarta.annotation.Resource; import org.springframework.beans.factory.annotation.Autowired; 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.stream.Collectors; 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 @Lazy private MesMdItemApi mesMdItemApi; @Resource private MdmItemBatchConfigMapper itemBatchConfigMapper; @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); // 保存批次配置(如果有) saveBatchConfig(item.getId(), createReqVO); // 同步到 MES(勾选 syncMes 时) createReqVO.setId(item.getId()); 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); // 保存批次配置(如果有) saveBatchConfig(updateReqVO.getId(), updateReqVO); // 同步到 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(使用 MDM 本地分类服务) String itemTypeCode = null; if (reqVO.getCategoryId() != null) { CommonResult itemType = mesMdItemTypeApi.getItemType(reqVO.getCategoryId()); itemTypeCode = itemType.getData().getCode(); } // 组装同步 DTO MesMdItemSyncReqDTO syncReqDTO = new MesMdItemSyncReqDTO(); syncReqDTO.setCode(reqVO.getCode()); syncReqDTO.setMdmItemId(reqVO.getId()); 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()); // 批次配置字段 syncReqDTO.setProduceDateFlag(reqVO.getProduceDateFlag()); syncReqDTO.setExpireDateFlag(reqVO.getExpireDateFlag()); syncReqDTO.setReceiptDateFlag(reqVO.getReceiptDateFlag()); syncReqDTO.setVendorFlag(reqVO.getVendorFlag()); syncReqDTO.setPurchaseOrderCodeFlag(reqVO.getPurchaseOrderCodeFlag()); syncReqDTO.setLotNumberFlag(reqVO.getLotNumberFlag()); syncReqDTO.setQualityStatusFlag(reqVO.getQualityStatusFlag()); // 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 @Transactional(rollbackFor = Exception.class) public void deleteItemList(Collection ids) { if (CollUtil.isEmpty(ids)) { return; } // 逐个删除(复用单条删除逻辑,保持校验) ids.forEach(this::deleteItem); } @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; } // 使用 MDM 本地分类服务校验 MdmItemCategoryDO category = itemCategoryService.getItemCategory(categoryId); if (category == 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); } /** * 保存批次配置(到 MDM 本地表) */ private void saveBatchConfig(Long itemId, MdmItemSaveReqVO reqVO) { // 检查是否有批次配置数据 if (reqVO.getProduceDateFlag() == null && reqVO.getExpireDateFlag() == null && reqVO.getReceiptDateFlag() == null && reqVO.getVendorFlag() == null && reqVO.getPurchaseOrderCodeFlag() == null && reqVO.getLotNumberFlag() == null && reqVO.getQualityStatusFlag() == null) { return; } // 查询已有配置 cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemBatchConfigDO existing = itemBatchConfigMapper.selectOne( cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemBatchConfigDO::getItemId, itemId); cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemBatchConfigDO config = new cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemBatchConfigDO(); config.setItemId(itemId); config.setProduceDateFlag(Boolean.TRUE.equals(reqVO.getProduceDateFlag())); config.setExpireDateFlag(Boolean.TRUE.equals(reqVO.getExpireDateFlag())); config.setReceiptDateFlag(Boolean.TRUE.equals(reqVO.getReceiptDateFlag())); config.setVendorFlag(Boolean.TRUE.equals(reqVO.getVendorFlag())); config.setPurchaseOrderCodeFlag(Boolean.TRUE.equals(reqVO.getPurchaseOrderCodeFlag())); config.setLotNumberFlag(Boolean.TRUE.equals(reqVO.getLotNumberFlag())); config.setQualityStatusFlag(Boolean.TRUE.equals(reqVO.getQualityStatusFlag())); // 清空不需要的字段 config.setClientFlag(false); config.setSalesOrderCodeFlag(false); config.setWorkOrderFlag(false); config.setTaskFlag(false); config.setWorkstationFlag(false); config.setToolFlag(false); config.setMoldFlag(false); if (existing != null) { config.setId(existing.getId()); itemBatchConfigMapper.updateById(config); } else { itemBatchConfigMapper.insert(config); } } }