package cn.iocoder.yudao.module.mes.service.pro.maintenanceplan; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.erp.api.purchase.PurchasePlanApi; import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchasePlanCreateReqDTO; import cn.iocoder.yudao.module.mes.controller.admin.pro.maintenanceplan.vo.MesProMaintenancePlanImportExcelVO; import cn.iocoder.yudao.module.mes.controller.admin.pro.maintenanceplan.vo.MesProMaintenancePlanImportRespVO; import cn.iocoder.yudao.module.mes.controller.admin.pro.maintenanceplan.vo.MesProMaintenancePlanPageReqVO; import cn.iocoder.yudao.module.mes.controller.admin.pro.maintenanceplan.vo.MesProMaintenancePlanSaveReqVO; import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.MesProWorkOrderSaveReqVO; import cn.iocoder.yudao.module.mes.dal.dataobject.cal.team.MesCalTeamDO; import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.maintenanceplan.MesProMaintenancePlanDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.maintenanceplan.MesProMaintenancePlanItemDO; import cn.iocoder.yudao.module.mes.dal.mysql.pro.maintenanceplan.MesProMaintenancePlanItemMapper; import cn.iocoder.yudao.module.mes.dal.mysql.pro.maintenanceplan.MesProMaintenancePlanMapper; import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodeRuleCodeEnum; import cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderSourceTypeEnum; import cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderTypeEnum; import cn.iocoder.yudao.module.mes.service.cal.team.MesCalTeamService; import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService; import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; /** * MES 电力作业计划 Service 实现类 * * @author xiaoyi */ @Service @Validated @Slf4j public class MesProMaintenancePlanServiceImpl implements MesProMaintenancePlanService { /** * 状态:草稿 */ private static final Integer STATUS_DRAFT = 0; /** * 状态:已下发 */ private static final Integer STATUS_ISSUED = 10; /** * 状态:已完成 */ private static final Integer STATUS_FINISHED = 20; /** * 状态:已取消 */ private static final Integer STATUS_CANCELLED = 30; @Resource private MesProMaintenancePlanMapper planMapper; @Resource private MesProMaintenancePlanItemMapper planItemMapper; @Resource private MesCalTeamService teamService; @Resource private MesMdItemService itemService; @Resource private PurchasePlanApi purchasePlanApi; @Resource private MesProWorkOrderService workOrderService; @Resource private MesMdAutoCodeRecordService autoCodeRecordService; @Override @Transactional(rollbackFor = Exception.class) public Long createPlan(MesProMaintenancePlanSaveReqVO createReqVO) { // 1. 生成计划编码 String code = generatePlanCode(); if (planMapper.selectByCode(code) != null) { throw exception(MD_ITEM_CODE_DUPLICATE); } // 2. 校验班组并回填 fillTeamInfo(createReqVO); // 3. 插入计划 MesProMaintenancePlanDO plan = BeanUtils.toBean(createReqVO, MesProMaintenancePlanDO.class, req -> req .setCode(code) .setStatus(STATUS_DRAFT)); planMapper.insert(plan); // 4. 插入明细 insertPlanItems(plan.getId(), createReqVO.getItems()); return plan.getId(); } @Override @Transactional(rollbackFor = Exception.class) public void updatePlan(MesProMaintenancePlanSaveReqVO updateReqVO) { // 1. 校验存在 + 草稿状态 MesProMaintenancePlanDO oldPlan = validatePlanCanModify(updateReqVO.getId()); fillTeamInfo(updateReqVO); // 2. 更新主表 MesProMaintenancePlanDO updateObj = BeanUtils.toBean(updateReqVO, MesProMaintenancePlanDO.class, req -> req .setCode(oldPlan.getCode()) .setStatus(oldPlan.getStatus())); planMapper.updateById(updateObj); // 3. 更新明细:先删后插 planItemMapper.deleteByPlanId(updateReqVO.getId()); insertPlanItems(updateReqVO.getId(), updateReqVO.getItems()); } @Override @Transactional(rollbackFor = Exception.class) public void deletePlan(Collection ids) { if (CollUtil.isEmpty(ids)) { return; } for (Long id : ids) { validatePlanCanModify(id); planItemMapper.deleteByPlanId(id); planMapper.deleteById(id); } } @Override public MesProMaintenancePlanDO getPlan(Long id) { return planMapper.selectById(id); } @Override public List getPlanItemListByPlanId(Long planId) { return planItemMapper.selectListByPlanId(planId); } @Override public List getPlanItemListByPlanIds(Collection planIds) { return planItemMapper.selectListByPlanIds(planIds); } @Override public PageResult getPlanPage(MesProMaintenancePlanPageReqVO pageReqVO) { return planMapper.selectPage(pageReqVO); } @Override @Transactional(rollbackFor = Exception.class) public void issuePlan(Long id) { MesProMaintenancePlanDO plan = validatePlanExists(id); if (!Objects.equals(plan.getStatus(), STATUS_DRAFT)) { throw exception(PRO_WORK_ORDER_NOT_PREPARE); } planMapper.updateById(new MesProMaintenancePlanDO().setId(id).setStatus(STATUS_ISSUED)); } @Override @Transactional(rollbackFor = Exception.class) public void finishPlan(Long id) { MesProMaintenancePlanDO plan = validatePlanExists(id); if (!Objects.equals(plan.getStatus(), STATUS_ISSUED)) { throw exception(PRO_WORK_ORDER_NOT_CONFIRMED); } planMapper.updateById(new MesProMaintenancePlanDO().setId(id).setStatus(STATUS_FINISHED)); } @Override @Transactional(rollbackFor = Exception.class) public Long generatePurchasePlan(Long id) { // 1. 校验计划存在且已下发 MesProMaintenancePlanDO plan = validatePlanExists(id); if (!Objects.equals(plan.getStatus(), STATUS_ISSUED)) { throw exception(PRO_WORK_ORDER_NOT_CONFIRMED); } // 2. 获取明细 List planItems = planItemMapper.selectListByPlanId(id); if (CollUtil.isEmpty(planItems)) { throw exception(PRO_WORK_ORDER_NOT_EXISTS); } // 3. 组装采购计划 DTO(MES 物料 -> MDM 物料) Map itemMap = itemService.getItemMap( planItems.stream().map(MesProMaintenancePlanItemDO::getItemId).collect(Collectors.toSet())); List items = planItems.stream().map(item -> { ErpPurchasePlanCreateReqDTO.Item dtoItem = new ErpPurchasePlanCreateReqDTO.Item(); MesMdItemDO mesItem = itemMap.get(item.getItemId()); dtoItem.setProductId(mesItem != null ? mesItem.getMdmItemId() : item.getItemId()); dtoItem.setProductUnitId(mesItem != null ? mesItem.getUnitMeasureId() : null); dtoItem.setCount(item.getQuantity()); dtoItem.setRemark(item.getRemark()); return dtoItem; }).collect(Collectors.toList()); // 4. 调用 ERP 生成采购计划 ErpPurchasePlanCreateReqDTO createReqDTO = new ErpPurchasePlanCreateReqDTO(); createReqDTO.setName("作业计划采购-" + plan.getName()); createReqDTO.setDemandSource(2); // 运维/生产计划 createReqDTO.setPlanDate(plan.getPlanDate()); createReqDTO.setRemark("来源计划: " + plan.getCode()); createReqDTO.setItems(items); return purchasePlanApi.createPurchasePlan(createReqDTO); } @Override @Transactional(rollbackFor = Exception.class) public Long generateWorkOrder(Long id) { // 1. 校验计划存在且已下发 MesProMaintenancePlanDO plan = validatePlanExists(id); if (!Objects.equals(plan.getStatus(), STATUS_ISSUED)) { throw exception(PRO_WORK_ORDER_NOT_CONFIRMED); } // 2. 已生成过工单则直接返回 if (plan.getWorkOrderId() != null) { return plan.getWorkOrderId(); } // 3. 组装工单 MesProWorkOrderSaveReqVO workOrderReq = new MesProWorkOrderSaveReqVO(); workOrderReq.setCode(generateWorkOrderCode()); workOrderReq.setName(plan.getName()); workOrderReq.setType(convertPlanTypeToWorkOrderType(plan.getPlanType())); workOrderReq.setOrderSourceType(MesProWorkOrderSourceTypeEnum.MAINTENANCE_PLAN.getType()); workOrderReq.setOrderSourceCode(plan.getCode()); workOrderReq.setQuantity(BigDecimal.ONE); workOrderReq.setRequestDate(plan.getPlanDate() != null ? plan.getPlanDate() : java.time.LocalDateTime.now()); workOrderReq.setRemark("来自电力作业计划: " + plan.getCode()); Long workOrderId = workOrderService.createWorkOrder(workOrderReq); // 4. 回写计划 MesProMaintenancePlanDO updateObj = new MesProMaintenancePlanDO(); updateObj.setId(id); updateObj.setWorkOrderId(workOrderId); planMapper.updateById(updateObj); return workOrderId; } @Override @Transactional(rollbackFor = Exception.class) public MesProMaintenancePlanImportRespVO importPlanList(List importList, boolean updateSupport) { if (CollUtil.isEmpty(importList)) { throw exception(MES_PRO_MAINTENANCE_PLAN_IMPORT_LIST_IS_EMPTY); } MesProMaintenancePlanImportRespVO respVO = MesProMaintenancePlanImportRespVO.builder() .createNames(new ArrayList<>()).updateNames(new ArrayList<>()) .failureNames(new LinkedHashMap<>()).build(); // 1. 按计划名称分组(同一计划名称的多行合并为一个计划,每行一条备件/物资明细) int rowIndex = 1; Map> groupMap = new LinkedHashMap<>(); for (MesProMaintenancePlanImportExcelVO item : importList) { if (StrUtil.isBlank(item.getName())) { respVO.getFailureNames().put("第 " + rowIndex + " 行", "计划名称不能为空"); rowIndex++; continue; } groupMap.computeIfAbsent(item.getName(), k -> new ArrayList<>()).add(item); rowIndex++; } // 2. 逐组处理 groupMap.forEach((name, rows) -> { MesProMaintenancePlanImportExcelVO main = rows.get(0); List items = new ArrayList<>(); for (MesProMaintenancePlanImportExcelVO row : rows) { if (StrUtil.isBlank(row.getItemCode())) { respVO.getFailureNames().put(name, "物料/备件编码不能为空"); return; } MesMdItemDO item = itemService.getItemByCode(row.getItemCode().trim()); if (item == null) { respVO.getFailureNames().put(name, "物料/备件编码[" + row.getItemCode() + "]不存在"); return; } MesProMaintenancePlanSaveReqVO.Item it = new MesProMaintenancePlanSaveReqVO.Item(); it.setItemId(item.getId()); it.setQuantity(row.getItemQuantity()); it.setRemark(row.getItemRemark()); items.add(it); } MesProMaintenancePlanSaveReqVO reqVO = BeanUtils.toBean(main, MesProMaintenancePlanSaveReqVO.class); reqVO.setItems(items); MesProMaintenancePlanDO existPlan = planMapper.selectByName(name); try { if (existPlan == null) { reqVO.setId(null); createPlan(reqVO); respVO.getCreateNames().add(name); } else if (updateSupport) { reqVO.setId(existPlan.getId()); updatePlan(reqVO); respVO.getUpdateNames().add(name); } else { respVO.getFailureNames().put(name, "计划名称已存在"); } } catch (ServiceException e) { respVO.getFailureNames().put(name, e.getMessage()); } }); return respVO; } /** * 计划类型 -> 工单类型映射 */ private Integer convertPlanTypeToWorkOrderType(Integer planType) { return switch (planType) { case 1 -> MesProWorkOrderTypeEnum.POWER_OPERATION.getType(); // 电网运行 -> 电力运维 case 2 -> MesProWorkOrderTypeEnum.POWER_GENERATION.getType(); // 发电 case 3 -> MesProWorkOrderTypeEnum.POWER_OPERATION.getType(); // 变电运维 -> 电力运维 case 4 -> MesProWorkOrderTypeEnum.POWER_MAINTENANCE.getType(); // 线路检修 -> 检修 default -> MesProWorkOrderTypeEnum.POWER_OPERATION.getType(); }; } private void insertPlanItems(Long planId, List items) { if (CollUtil.isEmpty(items)) { return; } planItemMapper.insertBatch(items.stream().map(item -> BeanUtils.toBean(item, MesProMaintenancePlanItemDO.class).setPlanId(planId)) .collect(Collectors.toList())); } private void fillTeamInfo(MesProMaintenancePlanSaveReqVO reqVO) { if (reqVO.getTeamId() == null) { return; } MesCalTeamDO team = teamService.validateTeamExists(reqVO.getTeamId()); if (team != null) { reqVO.setTeamName(team.getName()); } } private String generatePlanCode() { try { return autoCodeRecordService.generateAutoCode( MesMdAutoCodeRuleCodeEnum.PRO_MAINTENANCE_PLAN_CODE.getCode()); } catch (Exception e) { log.warn("[generatePlanCode] 编码规则 {} 不存在,使用时间戳生成", MesMdAutoCodeRuleCodeEnum.PRO_MAINTENANCE_PLAN_CODE.getCode(), e); return "MP" + DateUtil.format(java.time.LocalDateTime.now(), "yyyyMMddHHmmssSSS"); } } /** * 生成工单编码 * * 优先使用编码规则;若环境中未配置 PRO_WORK_ORDER_CODE 规则,则降级为时间戳编码,避免生成工单被阻断 */ private String generateWorkOrderCode() { try { return autoCodeRecordService.generateAutoCode( MesMdAutoCodeRuleCodeEnum.PRO_WORK_ORDER_CODE.getCode()); } catch (Exception e) { log.warn("[generateWorkOrderCode] 编码规则 {} 不存在,使用时间戳生成", MesMdAutoCodeRuleCodeEnum.PRO_WORK_ORDER_CODE.getCode(), e); return "WO" + DateUtil.format(java.time.LocalDateTime.now(), "yyyyMMddHHmmssSSS"); } } private MesProMaintenancePlanDO validatePlanExists(Long id) { MesProMaintenancePlanDO plan = planMapper.selectById(id); if (plan == null) { throw exception(PRO_WORK_ORDER_NOT_EXISTS); } return plan; } private MesProMaintenancePlanDO validatePlanCanModify(Long id) { MesProMaintenancePlanDO plan = validatePlanExists(id); if (!Objects.equals(plan.getStatus(), STATUS_DRAFT)) { throw exception(PRO_WORK_ORDER_NOT_PREPARE); } return plan; } }