package cn.iocoder.yudao.module.crm.service.refund; 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.refund.vo.CrmRefundAuditReqVO; import cn.iocoder.yudao.module.crm.controller.admin.refund.vo.CrmRefundPageReqVO; import cn.iocoder.yudao.module.crm.controller.admin.refund.vo.CrmRefundRespVO; import cn.iocoder.yudao.module.crm.controller.admin.refund.vo.CrmRefundSaveReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; import cn.iocoder.yudao.module.crm.dal.dataobject.refund.CrmRefundDO; import cn.iocoder.yudao.module.crm.dal.mysql.customer.CrmCustomerMapper; import cn.iocoder.yudao.module.crm.dal.mysql.refund.CrmRefundMapper; 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 CrmRefundServiceImpl implements CrmRefundService { private static final Integer AUDIT_DRAFT = 0; private static final Integer AUDIT_PASS = 20; /** * BPM 退费审批分类编码 */ private static final String REFUND_APPROVE_CATEGORY_CODE = "refund_approve"; @Resource private CrmRefundMapper refundMapper; @Resource private CrmCustomerMapper customerMapper; @Resource private BpmProcessInstanceApi bpmProcessInstanceApi; @Resource private BpmCategoryService bpmCategoryService; @Resource private BpmProcessDefinitionService bpmProcessDefinitionService; @Override @Transactional(rollbackFor = Exception.class) public Long createRefund(CrmRefundSaveReqVO createReqVO) { if (StrUtil.isBlank(createReqVO.getRefundNo())) { createReqVO.setRefundNo(generateRefundNo()); } else if (refundMapper.selectByRefundNo(createReqVO.getRefundNo()) != null) { throw exception(ErrorCodeConstants.CRM_REFUND_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); } CrmRefundDO entity = BeanUtils.toBean(createReqVO, CrmRefundDO.class); refundMapper.insert(entity); return entity.getId(); } @Override public void updateRefund(CrmRefundSaveReqVO updateReqVO) { CrmRefundDO exists = refundMapper.selectById(updateReqVO.getId()); if (exists == null) { throw exception(ErrorCodeConstants.CRM_REFUND_NOT_EXISTS); } CrmRefundDO entity = BeanUtils.toBean(updateReqVO, CrmRefundDO.class); refundMapper.updateById(entity); } @Override public void deleteRefund(Long id) { CrmRefundDO exists = refundMapper.selectById(id); if (exists == null) { throw exception(ErrorCodeConstants.CRM_REFUND_NOT_EXISTS); } refundMapper.deleteById(id); } @Override public CrmRefundRespVO getRefund(Long id) { CrmRefundDO entity = refundMapper.selectById(id); return entity == null ? null : BeanUtils.toBean(entity, CrmRefundRespVO.class); } @Override public PageResult getRefundPage(CrmRefundPageReqVO pageReqVO) { return BeanUtils.toBean(refundMapper.selectPage(pageReqVO), CrmRefundRespVO.class); } @Override public List getRefundList(CrmRefundPageReqVO reqVO) { return BeanUtils.toBean(refundMapper.selectList(reqVO), CrmRefundRespVO.class); } @Override public void auditRefund(CrmRefundAuditReqVO auditReqVO) { CrmRefundDO exists = refundMapper.selectById(auditReqVO.getId()); if (exists == null) { throw exception(ErrorCodeConstants.CRM_REFUND_NOT_EXISTS); } CrmRefundDO update = new CrmRefundDO(); update.setId(exists.getId()); update.setAuditStatus(auditReqVO.getAuditStatus()); if (StrUtil.isNotBlank(auditReqVO.getRemark())) { update.setRemark(auditReqVO.getRemark()); } refundMapper.updateById(update); } @Override public void submitRefund(Long id, String processDefinitionKey, Long userId) { // 1. 校验退费单是否在草稿状态 CrmRefundDO refund = refundMapper.selectById(id); if (refund == null) { throw exception(ErrorCodeConstants.CRM_REFUND_NOT_EXISTS); } if (ObjUtil.notEqual(refund.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus())) { throw exception(ErrorCodeConstants.CRM_REFUND_SUBMIT_FAIL_NOT_DRAFT); } // 2. 校验退费审批分类和流程定义是否存在,且传入的流程定义处于激活状态 validateRefundApproveCategoryAndProcessDefinition(); ProcessDefinition processDefinition = bpmProcessDefinitionService.getActiveProcessDefinition(processDefinitionKey); if (processDefinition == null) { throw exception(ErrorCodeConstants.CRM_REFUND_BPM_PROCESS_DEFINITION_NOT_EXISTS); } // 3. 创建退费审批流程实例 String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO().setProcessDefinitionKey(processDefinitionKey) .setBusinessKey(String.valueOf(id))); // 4. 更新退费单流程编号与审批状态 refundMapper.updateById(new CrmRefundDO().setId(id).setProcessInstanceId(processInstanceId) .setAuditStatus(CrmAuditStatusEnum.PROCESS.getStatus())); } @Override public void updateRefundAuditStatus(Long id, Integer bpmResult) { // 1.1 校验退费单是否存在 CrmRefundDO refund = refundMapper.selectById(id); if (refund == null) { throw exception(ErrorCodeConstants.CRM_REFUND_NOT_EXISTS); } // 1.2 只有审批中,可以更新审批结果 if (ObjUtil.notEqual(refund.getAuditStatus(), CrmAuditStatusEnum.PROCESS.getStatus())) { log.error("[updateRefundAuditStatus][refund({}) 不处于审批中,无法更新审批结果({})]", refund.getId(), bpmResult); throw exception(ErrorCodeConstants.CRM_REFUND_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS); } // 2. 更新退费单审批结果 Integer auditStatus = convertBpmResultToAuditStatus(bpmResult); refundMapper.updateById(new CrmRefundDO().setId(id).setAuditStatus(auditStatus)); } @Override public List> getRefundApproveProcessDefinitionList() { // 1. 校验退费审批分类和流程定义是否存在 validateRefundApproveCategoryAndProcessDefinition(); // 2. 获取分类下的流程定义信息 List definitionInfoList = bpmProcessDefinitionService .getProcessDefinitionInfoListByCategory(REFUND_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 validateRefundApproveCategoryAndProcessDefinition() { // 1. 校验分类是否存在 Map categoryMap = bpmCategoryService.getCategoryMap(Collections.singletonList(REFUND_APPROVE_CATEGORY_CODE)); if (!categoryMap.containsKey(REFUND_APPROVE_CATEGORY_CODE)) { throw exception(ErrorCodeConstants.CRM_REFUND_BPM_PROCESS_DEFINITION_NOT_EXISTS); } // 2. 校验分类下是否有可用的流程定义 List definitionInfoList = bpmProcessDefinitionService .getProcessDefinitionInfoListByCategory(REFUND_APPROVE_CATEGORY_CODE); if (CollUtil.isEmpty(definitionInfoList)) { throw exception(ErrorCodeConstants.CRM_REFUND_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_REFUND_BPM_PROCESS_DEFINITION_NOT_EXISTS); } } private String generateRefundNo() { return "RF" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + RandomUtil.randomNumbers(4); } }