package cn.iocoder.yudao.module.mes.service.pd.project; import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.mes.controller.admin.pd.project.vo.MesPdProjectPageReqVO; import cn.iocoder.yudao.module.mes.controller.admin.pd.project.vo.MesPdProjectSaveReqVO; import cn.iocoder.yudao.module.mes.dal.dataobject.pd.document.MesPdDocumentDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pd.documentaudit.MesPdDocumentAuditDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pd.archive.MesPdArchiveDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pd.project.MesPdProjectDO; import cn.iocoder.yudao.module.mes.dal.mysql.pd.project.MesPdProjectMapper; import cn.iocoder.yudao.module.mes.dal.mysql.pd.documentaudit.MesPdDocumentAuditMapper; import cn.iocoder.yudao.module.mes.dal.mysql.pd.archive.MesPdArchiveMapper; import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodeRuleCodeEnum; import cn.iocoder.yudao.module.mes.enums.pd.MesPdProjectStatusEnum; import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService; import cn.iocoder.yudao.module.mes.service.pd.document.MesPdDocumentService; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.flowable.engine.repository.ProcessDefinition; 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.time.LocalDateTime; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; /** * MES 设计项目 Service 实现类 * * @author 超级管理员 */ @Service @Validated @Slf4j public class MesPdProjectServiceImpl implements MesPdProjectService { /** * BPM 产品项目审批分类编码 */ private static final String BPM_PROCESS_DEFINITION_CATEGORY_CODE = "mes_pd_project_approve"; @Resource private MesPdProjectMapper projectMapper; @Resource @Lazy private MesPdDocumentService documentService; @Resource private MesPdDocumentAuditMapper documentAuditMapper; @Resource private MesPdArchiveMapper archiveMapper; @Resource @Lazy private MesMdAutoCodeRecordService autoCodeRecordService; @Resource private BpmProcessInstanceApi processInstanceApi; @Resource private BpmCategoryService bpmCategoryService; @Resource private BpmProcessDefinitionService bpmProcessDefinitionService; @Override @Transactional(rollbackFor = Exception.class) public Long createProject(MesPdProjectSaveReqVO createReqVO) { // 校验任务编码唯一 validateTaskCodeUnique(null, createReqVO.getTaskCode()); // 插入 MesPdProjectDO project = MesPdProjectDO.builder() .taskCode(createReqVO.getTaskCode()) .taskName(createReqVO.getTaskName()) .productCode(createReqVO.getProductCode()) .productName(createReqVO.getProductName()) .version(createReqVO.getVersion()) .status(MesPdProjectStatusEnum.INITIATE.getStatus()) .chiefDesigner(createReqVO.getChiefDesigner()) .reviewer(createReqVO.getReviewer()) .planFinishTime(createReqVO.getPlanFinishTime()) .remark(createReqVO.getRemark()) .build(); projectMapper.insert(project); return project.getId(); } @Override @Transactional(rollbackFor = Exception.class) public void updateProject(MesPdProjectSaveReqVO updateReqVO) { // 校验存在 MesPdProjectDO project = validateProjectExists(updateReqVO.getId()); // 校验状态为待发布 validateProjectInitiate(project); // 校验任务编码唯一 validateTaskCodeUnique(updateReqVO.getId(), updateReqVO.getTaskCode()); // 更新 MesPdProjectDO updateObj = MesPdProjectDO.builder() .id(updateReqVO.getId()) .taskCode(updateReqVO.getTaskCode()) .taskName(updateReqVO.getTaskName()) .productCode(updateReqVO.getProductCode()) .productName(updateReqVO.getProductName()) .version(updateReqVO.getVersion()) .chiefDesigner(updateReqVO.getChiefDesigner()) .reviewer(updateReqVO.getReviewer()) .planFinishTime(updateReqVO.getPlanFinishTime()) .remark(updateReqVO.getRemark()) .build(); projectMapper.updateById(updateObj); } @Override @Transactional(rollbackFor = Exception.class) public void deleteProject(Long id) { // 校验存在 MesPdProjectDO project = validateProjectExists(id); // 校验状态为待发布 validateProjectInitiate(project); // 删除资料子表 documentService.deleteByProjectId(id); // 删除 projectMapper.deleteById(id); } @Override public MesPdProjectDO validateProjectExists(Long id) { MesPdProjectDO project = projectMapper.selectById(id); if (project == null) { throw exception(PD_PROJECT_NOT_EXISTS); } return project; } @Override public MesPdProjectDO getProject(Long id) { return projectMapper.selectById(id); } @Override public PageResult getProjectPage(MesPdProjectPageReqVO pageReqVO) { return projectMapper.selectPage(pageReqVO); } @Override public void publishProject(Long id) { // 校验存在 MesPdProjectDO project = validateProjectExists(id); // 校验状态为待发布 if (!MesPdProjectStatusEnum.INITIATE.getStatus().equals(project.getStatus())) { throw exception(PD_PROJECT_NOT_INITIATE_FOR_PUBLISH); } // 更新状态 projectMapper.updateById(MesPdProjectDO.builder() .id(id) .status(MesPdProjectStatusEnum.PUBLISH.getStatus()) .build()); } @Override @Transactional(rollbackFor = Exception.class) public void uploadProject(Long id, Long userId) { // 校验存在 MesPdProjectDO project = validateProjectExists(id); // 校验状态为待上传 if (!MesPdProjectStatusEnum.PUBLISH.getStatus().equals(project.getStatus())) { throw exception(PD_PROJECT_NOT_PUBLISH_FOR_UPLOAD); } // 更新项目状态为待审核 projectMapper.updateById(MesPdProjectDO.builder() .id(id) .status(MesPdProjectStatusEnum.UPLOAD.getStatus()) .build()); // 自动为该项目下所有资料创建审核记录 List documents = documentService.getDocumentListByProjectId(id); for (MesPdDocumentDO document : documents) { MesPdDocumentAuditDO audit = MesPdDocumentAuditDO.builder() .auditNo(generateAuditNo()) .documentId(document.getId()) .documentName(document.getDocumentName()) .documentVersion(document.getDocumentVersion()) .reviewer(project.getReviewer()) .auditStatus(0) // 待审核 .build(); documentAuditMapper.insert(audit); } } @Override @Transactional(rollbackFor = Exception.class) public void submitProjectApproval(Long id, String processDefinitionKey) { // 1. 校验存在 + 待审核状态 MesPdProjectDO project = validateProjectExists(id); if (!MesPdProjectStatusEnum.UPLOAD.getStatus().equals(project.getStatus())) { throw exception(PD_PROJECT_NOT_UPLOAD_FOR_APPROVAL); } // 2. 校验该项目下所有资料审核记录均已审核(不存在待审核的记录) List documents = documentService.getDocumentListByProjectId(id); List documentIds = documents.stream().map(MesPdDocumentDO::getId).toList(); if (documentAuditMapper.existsPendingAuditByDocumentIds(documentIds)) { throw exception(PD_PROJECT_AUDIT_NOT_FINISHED); } // 3. 创建 BPM 流程实例 String processInstanceId = processInstanceApi.createProcessInstance(getLoginUserId(), new BpmProcessInstanceCreateReqDTO() .setProcessDefinitionKey(processDefinitionKey) .setBusinessKey(String.valueOf(id))); // 4. 更新项目状态为审批中 projectMapper.updateById(MesPdProjectDO.builder() .id(id) .status(MesPdProjectStatusEnum.APPROVING.getStatus()) .processInstanceId(processInstanceId) .processDefinitionKey(processDefinitionKey) .build()); } @Override public List> getProjectApproveProcessDefinitionList() { // 1. 校验分类是否存在 List categories = bpmCategoryService.getCategoryListByCode( Collections.singletonList(BPM_PROCESS_DEFINITION_CATEGORY_CODE)); if (CollUtil.isEmpty(categories)) { throw exception(PD_PROJECT_BPM_CATEGORY_NOT_EXISTS); } // 2. 获取分类下的流程定义信息 List definitionInfoList = bpmProcessDefinitionService .getProcessDefinitionInfoListByCategory(BPM_PROCESS_DEFINITION_CATEGORY_CODE); if (CollUtil.isEmpty(definitionInfoList)) { return Collections.emptyList(); } // 3. 过滤激活状态,保留最新版本 Map latestVersionMap = new HashMap<>(); for (BpmProcessDefinitionInfoDO info : definitionInfoList) { ProcessDefinition pd = bpmProcessDefinitionService.getProcessDefinition(info.getProcessDefinitionId()); if (pd == null || pd.isSuspended()) { continue; } ProcessDefinition existing = latestVersionMap.get(pd.getKey()); if (existing == null || pd.getVersion() > existing.getVersion()) { latestVersionMap.put(pd.getKey(), pd); } } // 4. 返回流程定义列表 List> result = new ArrayList<>(); for (ProcessDefinition pd : latestVersionMap.values()) { Map item = new HashMap<>(); item.put("id", pd.getId()); item.put("key", pd.getKey()); item.put("name", pd.getName()); result.add(item); } return result; } @Override @Transactional(rollbackFor = Exception.class) public void updateProjectAuditStatus(Long id, Integer bpmResult) { // 1. 校验存在 MesPdProjectDO project = validateProjectExists(id); if (!MesPdProjectStatusEnum.APPROVING.getStatus().equals(project.getStatus())) { log.warn("[updateProjectAuditStatus] 设计项目({}) 不处于审批中状态,当前状态:{}", id, project.getStatus()); return; } // 2. 根据审批结果更新状态 Integer newStatus = convertBpmResultToProjectStatus(bpmResult); if (newStatus != null) { projectMapper.updateById(MesPdProjectDO.builder() .id(id) .status(newStatus) .build()); } } /** * 转换 BPM 审批结果为设计项目状态 */ private Integer convertBpmResultToProjectStatus(Integer bpmResult) { if (BpmTaskStatusEnum.APPROVE.getStatus().equals(bpmResult)) { return MesPdProjectStatusEnum.AUDIT.getStatus(); } else if (BpmTaskStatusEnum.REJECT.getStatus().equals(bpmResult)) { return MesPdProjectStatusEnum.UPLOAD.getStatus(); } else if (BpmTaskStatusEnum.CANCEL.getStatus().equals(bpmResult)) { return MesPdProjectStatusEnum.UPLOAD.getStatus(); } return null; } @Override @Transactional(rollbackFor = Exception.class) public void archiveProject(Long id, Long userId) { // 校验存在 MesPdProjectDO project = validateProjectExists(id); // 校验状态为待归档 if (!MesPdProjectStatusEnum.AUDIT.getStatus().equals(project.getStatus())) { throw exception(PD_PROJECT_NOT_AUDIT_FOR_ARCHIVE); } // 更新项目状态为已归档 projectMapper.updateById(MesPdProjectDO.builder() .id(id) .status(MesPdProjectStatusEnum.ARCHIVE.getStatus()) .build()); // 自动创建归档台账记录 MesPdArchiveDO archive = MesPdArchiveDO.builder() .archiveCode(generateArchiveCode()) .projectId(id) .archiver(userId) .archiveStatus(1) // 已归档 .archiveTime(LocalDateTime.now()) .build(); archiveMapper.insert(archive); } @Override public Map getProjectMap(Collection ids) { List list = projectMapper.selectBatchIds(ids); return convertMap(list, MesPdProjectDO::getId); } @Override public List getProjectByProductCode(String productCode) { return projectMapper.selectList(new QueryWrapper().lambda().eq(MesPdProjectDO::getProductCode,productCode).eq(MesPdProjectDO::getStatus, MesPdProjectStatusEnum.ARCHIVE.getStatus())); } private void validateProjectInitiate(MesPdProjectDO project) { if (!MesPdProjectStatusEnum.INITIATE.getStatus().equals(project.getStatus())) { throw exception(PD_PROJECT_NOT_INITIATE); } } private void validateTaskCodeUnique(Long id, String taskCode) { MesPdProjectDO existing = projectMapper.selectByTaskCode(taskCode); if (existing == null) { return; } // 如果 id 为空,说明是新增,编码重复 if (id == null) { throw exception(PD_PROJECT_CODE_DUPLICATE); } // 如果 id 不为空,说明是修改,编码重复且不是同一条记录 if (!existing.getId().equals(id)) { throw exception(PD_PROJECT_CODE_DUPLICATE); } } /** * 生成审核单号(使用编码规则) */ private String generateAuditNo() { return autoCodeRecordService.generateAutoCode(MesMdAutoCodeRuleCodeEnum.PD_DOCUMENT_AUDIT_CODE.getCode()); } /** * 生成归档编号(使用编码规则) */ private String generateArchiveCode() { return autoCodeRecordService.generateAutoCode(MesMdAutoCodeRuleCodeEnum.PD_ARCHIVE_CODE.getCode()); } }