package cn.iocoder.yudao.module.crm.service.cancellation; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.crm.controller.admin.cancellation.vo.CrmCancellationAuditReqVO; import cn.iocoder.yudao.module.crm.controller.admin.cancellation.vo.CrmCancellationPageReqVO; import cn.iocoder.yudao.module.crm.controller.admin.cancellation.vo.CrmCancellationRespVO; import cn.iocoder.yudao.module.crm.controller.admin.cancellation.vo.CrmCancellationSaveReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.cancellation.CrmCancellationDO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; import cn.iocoder.yudao.module.crm.dal.mysql.cancellation.CrmCancellationMapper; import cn.iocoder.yudao.module.crm.dal.mysql.customer.CrmCustomerMapper; 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.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants; import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.flowable.engine.repository.ProcessDefinition; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; 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.crm.util.CrmAuditStatusUtils.convertBpmResultToAuditStatus; @Service @Validated @Slf4j public class CrmCancellationServiceImpl implements CrmCancellationService { private static final Integer AUDIT_DRAFT = 0; /** * BPM 销户审批分类编码 */ private static final String CANCELLATION_APPROVE_CATEGORY_CODE = "cancellation_approve"; @Resource private CrmCancellationMapper cancellationMapper; @Resource private CrmCustomerMapper customerMapper; @Resource private BpmProcessInstanceApi bpmProcessInstanceApi; @Resource private BpmCategoryService bpmCategoryService; @Resource private BpmProcessDefinitionService bpmProcessDefinitionService; @Override @Transactional(rollbackFor = Exception.class) public Long createCancellation(CrmCancellationSaveReqVO createReqVO) { if (StrUtil.isBlank(createReqVO.getCancellationNo())) { createReqVO.setCancellationNo(generateCancellationNo()); } else if (cancellationMapper.selectByCancellationNo(createReqVO.getCancellationNo()) != null) { throw exception(ErrorCodeConstants.CRM_CANCELLATION_NO_DUPLICATE); } CrmCustomerDO customer = customerMapper.selectById(createReqVO.getCustomerId()); if (customer == null) { throw exception(ErrorCodeConstants.CUSTOMER_NOT_EXISTS); } if (StrUtil.isBlank(createReqVO.getCustomerName())) { createReqVO.setCustomerName(customer.getName()); } if (createReqVO.getApplyTime() == null) { createReqVO.setApplyTime(LocalDateTime.now()); } if (createReqVO.getAuditStatus() == null) { createReqVO.setAuditStatus(AUDIT_DRAFT); } CrmCancellationDO entity = BeanUtils.toBean(createReqVO, CrmCancellationDO.class); cancellationMapper.insert(entity); return entity.getId(); } @Override public void updateCancellation(CrmCancellationSaveReqVO updateReqVO) { CrmCancellationDO exists = cancellationMapper.selectById(updateReqVO.getId()); if (exists == null) { throw exception(ErrorCodeConstants.CRM_CANCELLATION_NOT_EXISTS); } CrmCancellationDO entity = BeanUtils.toBean(updateReqVO, CrmCancellationDO.class); cancellationMapper.updateById(entity); } @Override public void deleteCancellation(Long id) { CrmCancellationDO exists = cancellationMapper.selectById(id); if (exists == null) { throw exception(ErrorCodeConstants.CRM_CANCELLATION_NOT_EXISTS); } cancellationMapper.deleteById(id); } @Override public CrmCancellationRespVO getCancellation(Long id) { CrmCancellationDO entity = cancellationMapper.selectById(id); return entity == null ? null : BeanUtils.toBean(entity, CrmCancellationRespVO.class); } @Override public PageResult getCancellationPage(CrmCancellationPageReqVO pageReqVO) { return BeanUtils.toBean(cancellationMapper.selectPage(pageReqVO), CrmCancellationRespVO.class); } @Override public List getCancellationList(CrmCancellationPageReqVO reqVO) { return BeanUtils.toBean(cancellationMapper.selectList(reqVO), CrmCancellationRespVO.class); } @Override public void auditCancellation(CrmCancellationAuditReqVO auditReqVO) { CrmCancellationDO exists = cancellationMapper.selectById(auditReqVO.getId()); if (exists == null) { throw exception(ErrorCodeConstants.CRM_CANCELLATION_NOT_EXISTS); } CrmCancellationDO update = new CrmCancellationDO(); update.setId(exists.getId()); update.setAuditStatus(auditReqVO.getAuditStatus()); if (StrUtil.isNotBlank(auditReqVO.getRemark())) { update.setRemark(auditReqVO.getRemark()); } cancellationMapper.updateById(update); } @Override public void submitCancellation(Long id, String processDefinitionKey, Long userId) { // 1. 校验销户申请是否在草稿状态 CrmCancellationDO cancellation = cancellationMapper.selectById(id); if (cancellation == null) { throw exception(ErrorCodeConstants.CRM_CANCELLATION_NOT_EXISTS); } if (ObjUtil.notEqual(cancellation.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus())) { throw exception(ErrorCodeConstants.CRM_CANCELLATION_SUBMIT_FAIL_NOT_DRAFT); } // 2. 校验销户审批分类和流程定义是否存在,且传入的流程定义处于激活状态 validateCancellationApproveCategoryAndProcessDefinition(); ProcessDefinition processDefinition = bpmProcessDefinitionService.getActiveProcessDefinition(processDefinitionKey); if (processDefinition == null) { throw exception(ErrorCodeConstants.CRM_CANCELLATION_BPM_PROCESS_DEFINITION_NOT_EXISTS); } // 3. 创建销户审批流程实例 String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO().setProcessDefinitionKey(processDefinitionKey) .setBusinessKey(String.valueOf(id))); // 4. 更新销户申请流程编号与审批状态 cancellationMapper.updateById(new CrmCancellationDO().setId(id).setProcessInstanceId(processInstanceId) .setAuditStatus(CrmAuditStatusEnum.PROCESS.getStatus())); } @Override public void updateCancellationAuditStatus(Long id, Integer bpmResult) { // 1.1 校验销户申请是否存在 CrmCancellationDO cancellation = cancellationMapper.selectById(id); if (cancellation == null) { throw exception(ErrorCodeConstants.CRM_CANCELLATION_NOT_EXISTS); } // 1.2 只有审批中,可以更新审批结果 if (ObjUtil.notEqual(cancellation.getAuditStatus(), CrmAuditStatusEnum.PROCESS.getStatus())) { log.error("[updateCancellationAuditStatus][cancellation({}) 不处于审批中,无法更新审批结果({})]", cancellation.getId(), bpmResult); throw exception(ErrorCodeConstants.CRM_CANCELLATION_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS); } // 2. 更新销户申请审批结果 Integer auditStatus = convertBpmResultToAuditStatus(bpmResult); cancellationMapper.updateById(new CrmCancellationDO().setId(id).setAuditStatus(auditStatus)); } @Override public List> getCancellationApproveProcessDefinitionList() { // 1. 校验销户审批分类和流程定义是否存在 validateCancellationApproveCategoryAndProcessDefinition(); // 2. 获取分类下的流程定义信息 List definitionInfoList = bpmProcessDefinitionService .getProcessDefinitionInfoListByCategory(CANCELLATION_APPROVE_CATEGORY_CODE); // 3. 获取流程定义详情 Set processDefinitionIds = convertSet(definitionInfoList, BpmProcessDefinitionInfoDO::getProcessDefinitionId); List processDefinitions = bpmProcessDefinitionService.getProcessDefinitionList(processDefinitionIds); // 4. 过滤:只保留激活状态,且相同 key 只保留最新版本 Map latestVersionMap = new HashMap<>(); for (ProcessDefinition pd : processDefinitions) { if (pd.isSuspended()) { continue; } ProcessDefinition existing = latestVersionMap.get(pd.getKey()); if (existing == null || pd.getVersion() > existing.getVersion()) { latestVersionMap.put(pd.getKey(), pd); } } // 5. 返回流程定义列表 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()); item.put("version", pd.getVersion()); result.add(item); } return result; } /** * 校验 BPM 销户审批分类和流程定义是否存在 */ private void validateCancellationApproveCategoryAndProcessDefinition() { // 1. 校验分类是否存在 Map categoryMap = bpmCategoryService.getCategoryMap(Collections.singletonList(CANCELLATION_APPROVE_CATEGORY_CODE)); if (!categoryMap.containsKey(CANCELLATION_APPROVE_CATEGORY_CODE)) { throw exception(ErrorCodeConstants.CRM_CANCELLATION_BPM_PROCESS_DEFINITION_NOT_EXISTS); } // 2. 校验分类下是否有可用的流程定义 List definitionInfoList = bpmProcessDefinitionService .getProcessDefinitionInfoListByCategory(CANCELLATION_APPROVE_CATEGORY_CODE); if (CollUtil.isEmpty(definitionInfoList)) { throw exception(ErrorCodeConstants.CRM_CANCELLATION_BPM_PROCESS_DEFINITION_NOT_EXISTS); } // 3. 校验是否有激活状态的流程定义 Set processDefinitionIds = convertSet(definitionInfoList, BpmProcessDefinitionInfoDO::getProcessDefinitionId); List processDefinitions = bpmProcessDefinitionService.getProcessDefinitionList(processDefinitionIds); boolean hasActiveDefinition = processDefinitions.stream().anyMatch(pd -> !pd.isSuspended()); if (!hasActiveDefinition) { throw exception(ErrorCodeConstants.CRM_CANCELLATION_BPM_PROCESS_DEFINITION_NOT_EXISTS); } } private String generateCancellationNo() { return "CL" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + RandomUtil.randomNumbers(4); } }