package cn.iocoder.yudao.module.mes.service.pd.knowledge; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.mes.dal.dataobject.pd.knowledge.KnowledgeDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pd.knowledge.KnowledgeStandardDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pd.knowledge.KnowledgeResourceDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pd.knowledge.KnowledgeReferenceDO; import cn.iocoder.yudao.module.mes.dal.mysql.pd.knowledge.KnowledgeMapper; import cn.iocoder.yudao.module.mes.dal.mysql.pd.knowledge.KnowledgeStandardMapper; import cn.iocoder.yudao.module.mes.dal.mysql.pd.knowledge.KnowledgeResourceMapper; import cn.iocoder.yudao.module.mes.dal.mysql.pd.knowledge.KnowledgeReferenceMapper; import cn.iocoder.yudao.module.mes.service.dv.machinery.MesDvMachineryService; import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService; import cn.iocoder.yudao.module.mes.service.pro.process.MesProProcessService; import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; import cn.iocoder.yudao.module.mes.service.md.item.MesMdProductBomService; import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteService; import cn.iocoder.yudao.module.mes.service.pd.processparamtemplate.MesPdProcessParamTemplateService; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import jakarta.annotation.Resource; import lombok.Data; import org.springframework.stereotype.Service; import org.springframework.dao.DuplicateKeyException; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.util.List; import java.util.Objects; import java.util.Set; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException; @Service public class KnowledgeWriteService { @Resource private KnowledgeMapper knowledgeMapper; @Resource private KnowledgeStandardMapper standardMapper; @Resource private KnowledgeResourceMapper resourceMapper; @Resource private KnowledgeReferenceMapper referenceMapper; @Resource private MesDvMachineryService machineryService; @Resource private MesMdWorkstationService workstationService; @Resource private MesProProcessService processService; @Resource private MesMdItemService itemService; @Resource private MesMdProductBomService bomService; @Resource private MesProRouteService routeService; @Resource private MesPdProcessParamTemplateService templateService; @Data public static class SaveRequest { private Long id; private String code; private String name; private Long productItemId; private Long bomId; private BigDecimal voltageMin; private BigDecimal voltageMax; private String market; private Long routeId; private Long templateId; private String description; private List standards; private List resources; } @Data public static class ResourceInput { private Long processId; private String resourceType; private Long resourceId; private String capabilityDescription; } @Transactional(rollbackFor = Exception.class) public Long create(SaveRequest input) { validate(input); String code = input.getCode().trim(); if (!knowledgeMapper.selectList(new LambdaQueryWrapperX() .eq(KnowledgeDO::getCode, code).eq(KnowledgeDO::getVersion, 1)).isEmpty()) { throw invalidParamException("知识编码已存在"); } var record = new KnowledgeDO(); record.setCode(code); record.setVersion(1); record.setStatus(0); copyFields(record, input); try { knowledgeMapper.insert(record); } catch (DuplicateKeyException e) { throw invalidParamException("知识编码已存在"); } saveStandards(record.getId(), input.getStandards()); saveResources(record.getId(), input.getResources()); return record.getId(); } @Transactional(rollbackFor = Exception.class) public void update(SaveRequest input) { KnowledgeDO existing = input == null || input.getId() == null ? null : knowledgeMapper.selectById(input.getId()); if (existing == null || !Integer.valueOf(0).equals(existing.getStatus())) { throw invalidParamException("知识不存在或不是可编辑的草稿"); } validate(input); if (!existing.getCode().equals(input.getCode().trim())) throw invalidParamException("知识编码不可修改"); var update = new LambdaUpdateWrapper() .eq(KnowledgeDO::getId, existing.getId()).eq(KnowledgeDO::getStatus, 0) .set(KnowledgeDO::getName, input.getName().trim()) .set(KnowledgeDO::getProductItemId, input.getProductItemId()).set(KnowledgeDO::getBomId, input.getBomId()) .set(KnowledgeDO::getVoltageMin, input.getVoltageMin()).set(KnowledgeDO::getVoltageMax, input.getVoltageMax()) .set(KnowledgeDO::getMarket, input.getMarket()).set(KnowledgeDO::getRouteId, input.getRouteId()) .set(KnowledgeDO::getTemplateId, input.getTemplateId()).set(KnowledgeDO::getDescription, input.getDescription()); if (knowledgeMapper.update(null, update) != 1) throw invalidParamException("知识状态已变化,请刷新后重试"); if (input.getStandards() != null) { standardMapper.delete(new LambdaQueryWrapperX().eq(KnowledgeStandardDO::getKnowledgeId, existing.getId())); saveStandards(existing.getId(), input.getStandards()); } if (input.getResources() != null) { resourceMapper.delete(new LambdaQueryWrapperX().eq(KnowledgeResourceDO::getKnowledgeId, existing.getId())); saveResources(existing.getId(), input.getResources()); } } @Transactional(rollbackFor = Exception.class) public void publish(Long id) { KnowledgeDO record = id == null ? null : knowledgeMapper.selectById(id); if (record == null || !Integer.valueOf(0).equals(record.getStatus())) throw invalidParamException("知识不存在或不是草稿"); if (record.getRouteId() == null) throw invalidParamException("发布前请选择工艺路线"); routeService.validateRouteExists(record.getRouteId()); if (record.getTemplateId() != null) templateService.validateProcessParamTemplateExists(record.getTemplateId()); var update = new LambdaUpdateWrapper().eq(KnowledgeDO::getId, id) .eq(KnowledgeDO::getStatus, 0).eq(KnowledgeDO::getRouteId, record.getRouteId()); if (record.getTemplateId() == null) update.isNull(KnowledgeDO::getTemplateId); else update.eq(KnowledgeDO::getTemplateId, record.getTemplateId()); if (knowledgeMapper.update(null, update.set(KnowledgeDO::getStatus, 1)) != 1) throw invalidParamException("知识状态已变化,请刷新后重试"); // 同一编码只保留当前发布版本,旧版仍可作为引用快照追溯。 knowledgeMapper.update(null, new LambdaUpdateWrapper() .eq(KnowledgeDO::getCode, record.getCode()).eq(KnowledgeDO::getStatus, 1) .lt(KnowledgeDO::getVersion, record.getVersion()).set(KnowledgeDO::getStatus, 2)); } @Transactional(rollbackFor = Exception.class) public Long revise(Long id) { KnowledgeDO source = id == null ? null : knowledgeMapper.selectById(id); if (source == null || !Integer.valueOf(1).equals(source.getStatus())) throw invalidParamException("只能从已发布的知识创建新版本"); List versions = knowledgeMapper.selectList(new LambdaQueryWrapperX() .eq(KnowledgeDO::getCode, source.getCode()).orderByDesc(KnowledgeDO::getVersion)); if (versions.stream().anyMatch(version -> Integer.valueOf(0).equals(version.getStatus()))) throw invalidParamException("该知识已有未发布的草稿版本"); int nextVersion = versions.stream().map(KnowledgeDO::getVersion).max(Integer::compareTo).orElse(0) + 1; var draft = new KnowledgeDO(); draft.setCode(source.getCode()); draft.setVersion(nextVersion); draft.setStatus(0); draft.setName(source.getName()); draft.setProductItemId(source.getProductItemId()); draft.setBomId(source.getBomId()); draft.setVoltageMin(source.getVoltageMin()); draft.setVoltageMax(source.getVoltageMax()); draft.setMarket(source.getMarket()); draft.setRouteId(source.getRouteId()); draft.setTemplateId(source.getTemplateId()); draft.setDescription(source.getDescription()); try { knowledgeMapper.insert(draft); } catch (DuplicateKeyException e) { throw invalidParamException("版本已变化,请刷新后重试"); } List standards = standardMapper.selectList(new LambdaQueryWrapperX() .eq(KnowledgeStandardDO::getKnowledgeId, source.getId())).stream().map(KnowledgeStandardDO::getStandardCode).toList(); saveStandards(draft.getId(), standards); List resources = resourceMapper.selectList(new LambdaQueryWrapperX() .eq(KnowledgeResourceDO::getKnowledgeId, source.getId())); for (KnowledgeResourceDO resource : resources) { var copy = new KnowledgeResourceDO(); copy.setKnowledgeId(draft.getId()); copy.setProcessId(resource.getProcessId()); copy.setResourceType(resource.getResourceType()); copy.setResourceId(resource.getResourceId()); copy.setCapabilityDescription(resource.getCapabilityDescription()); resourceMapper.insert(copy); } return draft.getId(); } /** 记录参考关系,不修改目标路线或复制路线内容。 */ @Transactional(rollbackFor = Exception.class) public void referenceRoute(Long knowledgeId, Long targetRouteId) { if (knowledgeId == null || targetRouteId == null) throw invalidParamException("请选择知识及目标工艺路线"); KnowledgeDO source = knowledgeMapper.selectOne(new LambdaQueryWrapperX() .eq(KnowledgeDO::getId, knowledgeId).last("FOR UPDATE")); if (source == null || !Integer.valueOf(1).equals(source.getStatus())) throw invalidParamException("只能参考当前已发布的知识"); routeService.validateRouteExists(targetRouteId); var reference = new KnowledgeReferenceDO(); reference.setKnowledgeId(source.getId()); reference.setKnowledgeVersion(source.getVersion()); reference.setTargetType("ROUTE"); reference.setTargetId(targetRouteId); try { referenceMapper.insert(reference); } catch (DuplicateKeyException e) { throw invalidParamException("该路线已参考此知识版本"); } } private void validate(SaveRequest input) { if (input == null || input.getCode() == null || input.getCode().isBlank() || input.getCode().length() > 64 || input.getName() == null || input.getName().isBlank() || input.getName().length() > 200) throw invalidParamException("请填写有效的知识编码和名称"); if (input.getMarket() == null || !Set.of("ALL", "DOMESTIC", "EXPORT").contains(input.getMarket())) throw invalidParamException("内外销类型不正确"); if ((input.getVoltageMin() != null && input.getVoltageMin().signum() < 0) || (input.getVoltageMax() != null && input.getVoltageMax().signum() < 0) || (input.getVoltageMin() != null && input.getVoltageMax() != null && input.getVoltageMin().compareTo(input.getVoltageMax()) > 0)) throw invalidParamException("电压范围不正确"); if (input.getProductItemId() != null) itemService.validateItemExists(input.getProductItemId()); if (input.getBomId() != null) { if (input.getProductItemId() == null) throw invalidParamException("选择 BOM 明细前请先选择适用物料"); boolean found = bomService.getProductBomListByItemId(input.getProductItemId()).stream() .anyMatch(bom -> Objects.equals(bom.getItemId(), input.getProductItemId()) && Objects.equals(bom.getBomItemId(), input.getBomId()) && Objects.equals(bom.getStatus(), 0)); if (!found) throw invalidParamException("所选 BOM 子物料不存在、未启用或不属于该物料"); } if (input.getRouteId() != null) routeService.validateRouteExists(input.getRouteId()); if (input.getTemplateId() != null) templateService.validateProcessParamTemplateExists(input.getTemplateId()); if (input.getStandards() != null && input.getStandards().stream().anyMatch(s -> s == null || s.isBlank() || s.trim().length() > 128)) throw invalidParamException("标准编码不能为空或超过 128 字符"); if (input.getResources() != null) for (ResourceInput resource : input.getResources()) { if (resource == null || resource.getResourceType() == null || !Set.of("MACHINE", "WORKSTATION", "OTHER").contains(resource.getResourceType())) throw invalidParamException("资源类型不正确"); if (resource.getCapabilityDescription() == null || resource.getCapabilityDescription().isBlank() || resource.getCapabilityDescription().length() > 500) throw invalidParamException("请填写不超过 500 字的资源能力说明"); if (resource.getProcessId() != null) processService.validateProcessExists(resource.getProcessId()); if ("MACHINE".equals(resource.getResourceType())) { if (resource.getResourceId() == null) throw invalidParamException("请选择设备"); machineryService.validateMachineryExists(resource.getResourceId()); } else if ("WORKSTATION".equals(resource.getResourceType())) { if (resource.getResourceId() == null) throw invalidParamException("请选择工作站"); workstationService.validateWorkstationExists(resource.getResourceId()); } else if (resource.getResourceId() != null) throw invalidParamException("其他资源不能指定设备或工作站编号"); } } private void copyFields(KnowledgeDO record, SaveRequest input) { record.setName(input.getName().trim()); record.setProductItemId(input.getProductItemId()); record.setBomId(input.getBomId()); record.setVoltageMin(input.getVoltageMin()); record.setVoltageMax(input.getVoltageMax()); record.setMarket(input.getMarket()); record.setRouteId(input.getRouteId()); record.setTemplateId(input.getTemplateId()); record.setDescription(input.getDescription()); } private void saveStandards(Long id, List standards) { if (standards == null) return; standards.stream().map(String::trim).distinct().forEach(code -> { var standard = new KnowledgeStandardDO(); standard.setKnowledgeId(id); standard.setStandardCode(code); standardMapper.insert(standard); }); } private void saveResources(Long id, List resources) { if (resources == null) return; for (ResourceInput resource : resources) { var item = new KnowledgeResourceDO(); item.setKnowledgeId(id); item.setProcessId(resource.getProcessId()); item.setResourceType(resource.getResourceType()); item.setResourceId(resource.getResourceId()); item.setCapabilityDescription(resource.getCapabilityDescription().trim()); resourceMapper.insert(item); } } }