package cn.iocoder.yudao.module.aftersales.service.compensation; 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.aftersales.controller.admin.compensation.vo.AfterSaleCompensationAuditReqVO; import cn.iocoder.yudao.module.aftersales.controller.admin.compensation.vo.AfterSaleCompensationPageReqVO; import cn.iocoder.yudao.module.aftersales.controller.admin.compensation.vo.AfterSaleCompensationSaveReqVO; import cn.iocoder.yudao.module.aftersales.dal.dataobject.compensation.AfterSaleCompensationDO; import cn.iocoder.yudao.module.aftersales.dal.mysql.compensation.AfterSaleCompensationMapper; import cn.iocoder.yudao.module.aftersales.enums.AfterSaleCompensationAuditStatusEnum; import cn.iocoder.yudao.module.aftersales.enums.ErrorCodeConstants; 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 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.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; import static cn.iocoder.yudao.module.aftersales.util.AfterSaleCompensationStatusUtils.convertBpmResultToAuditStatus; /** * 售后赔付/服务补偿单 Service 实现类 */ @Service @Validated @Slf4j public class AfterSaleCompensationServiceImpl implements AfterSaleCompensationService { private static final Integer AUDIT_DRAFT = 0; private static final Integer AUDIT_PASS = 20; /** * BPM 赔付审批分类编码 */ private static final String COMPENSATION_APPROVE_CATEGORY_CODE = "compensation_approve"; @Resource private AfterSaleCompensationMapper compensationMapper; @Resource private BpmProcessInstanceApi bpmProcessInstanceApi; @Resource private BpmCategoryService bpmCategoryService; @Resource private BpmProcessDefinitionService bpmProcessDefinitionService; @Override @Transactional(rollbackFor = Exception.class) public Long createCompensation(AfterSaleCompensationSaveReqVO createReqVO) { String compensationNo = generateCompensationNo(); if (compensationMapper.selectByCompensationNo(compensationNo) != null) { throw exception(ErrorCodeConstants.COMPENSATION_NO_EXISTS); } AfterSaleCompensationDO entity = BeanUtils.toBean(createReqVO, AfterSaleCompensationDO.class, req -> req .setCompensationNo(compensationNo) .setAuditStatus(AUDIT_DRAFT)); compensationMapper.insert(entity); return entity.getId(); } @Override public void updateCompensation(AfterSaleCompensationSaveReqVO updateReqVO) { AfterSaleCompensationDO exists = compensationMapper.selectById(updateReqVO.getId()); if (exists == null) { throw exception(ErrorCodeConstants.COMPENSATION_NOT_EXISTS); } if (ObjUtil.notEqual(exists.getAuditStatus(), AUDIT_DRAFT)) { throw exception(ErrorCodeConstants.COMPENSATION_DELETE_FAIL_NOT_DRAFT); } AfterSaleCompensationDO entity = BeanUtils.toBean(updateReqVO, AfterSaleCompensationDO.class, req -> req .setCompensationNo(exists.getCompensationNo()) .setAuditStatus(exists.getAuditStatus())); compensationMapper.updateById(entity); } @Override @Transactional(rollbackFor = Exception.class) public void deleteCompensation(Long id) { AfterSaleCompensationDO exists = compensationMapper.selectById(id); if (exists == null) { throw exception(ErrorCodeConstants.COMPENSATION_NOT_EXISTS); } if (ObjUtil.notEqual(exists.getAuditStatus(), AUDIT_DRAFT)) { throw exception(ErrorCodeConstants.COMPENSATION_DELETE_FAIL_NOT_DRAFT); } compensationMapper.deleteById(id); } @Override public AfterSaleCompensationDO getCompensation(Long id) { return compensationMapper.selectById(id); } @Override public PageResult getCompensationPage(AfterSaleCompensationPageReqVO pageReqVO) { return compensationMapper.selectPage(pageReqVO); } @Override public void submitCompensation(Long id, String processDefinitionKey, Long userId) { // 1. 校验赔付单是否在草稿状态 AfterSaleCompensationDO compensation = compensationMapper.selectById(id); if (compensation == null) { throw exception(ErrorCodeConstants.COMPENSATION_NOT_EXISTS); } if (ObjUtil.notEqual(compensation.getAuditStatus(), AfterSaleCompensationAuditStatusEnum.DRAFT.getStatus())) { throw exception(ErrorCodeConstants.COMPENSATION_SUBMIT_FAIL_NOT_DRAFT); } // 2. 校验赔付审批分类和流程定义是否存在,且传入的流程定义处于激活状态 validateCompensationApproveCategoryAndProcessDefinition(); ProcessDefinition processDefinition = bpmProcessDefinitionService.getActiveProcessDefinition(processDefinitionKey); if (processDefinition == null) { throw exception(ErrorCodeConstants.COMPENSATION_BPM_PROCESS_DEFINITION_NOT_EXISTS); } // 3. 创建赔付审批流程实例 String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO().setProcessDefinitionKey(processDefinitionKey) .setBusinessKey(String.valueOf(id))); // 4. 更新赔付单流程编号与审批状态 compensationMapper.updateById(new AfterSaleCompensationDO().setId(id).setProcessInstanceId(processInstanceId) .setAuditStatus(AfterSaleCompensationAuditStatusEnum.PROCESS.getStatus())); } @Override public void auditCompensation(AfterSaleCompensationAuditReqVO auditReqVO) { AfterSaleCompensationDO exists = compensationMapper.selectById(auditReqVO.getId()); if (exists == null) { throw exception(ErrorCodeConstants.COMPENSATION_NOT_EXISTS); } AfterSaleCompensationDO update = new AfterSaleCompensationDO(); update.setId(exists.getId()); update.setAuditStatus(auditReqVO.getAuditStatus()); update.setAuditUserId(getLoginUserId()); update.setAuditTime(LocalDateTime.now()); if (StrUtil.isNotBlank(auditReqVO.getRemark())) { update.setAuditRemark(auditReqVO.getRemark()); } compensationMapper.updateById(update); } @Override public void updateCompensationAuditStatus(Long id, Integer bpmResult) { // 1.1 校验赔付单是否存在 AfterSaleCompensationDO compensation = compensationMapper.selectById(id); if (compensation == null) { throw exception(ErrorCodeConstants.COMPENSATION_NOT_EXISTS); } // 1.2 只有审批中,可以更新审批结果 if (ObjUtil.notEqual(compensation.getAuditStatus(), AfterSaleCompensationAuditStatusEnum.PROCESS.getStatus())) { log.error("[updateCompensationAuditStatus][compensation({}) 不处于审批中,无法更新审批结果({})]", compensation.getId(), bpmResult); throw exception(ErrorCodeConstants.COMPENSATION_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS); } // 2. 更新赔付单审批结果 Integer auditStatus = convertBpmResultToAuditStatus(bpmResult); compensationMapper.updateById(new AfterSaleCompensationDO().setId(id).setAuditStatus(auditStatus)); } @Override public List> getCompensationApproveProcessDefinitionList() { // 1. 校验赔付审批分类和流程定义是否存在 validateCompensationApproveCategoryAndProcessDefinition(); // 2. 获取分类下的流程定义信息 List definitionInfoList = bpmProcessDefinitionService .getProcessDefinitionInfoListByCategory(COMPENSATION_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 validateCompensationApproveCategoryAndProcessDefinition() { // 1. 校验分类是否存在 Map categoryMap = bpmCategoryService.getCategoryMap(Collections.singletonList(COMPENSATION_APPROVE_CATEGORY_CODE)); if (!categoryMap.containsKey(COMPENSATION_APPROVE_CATEGORY_CODE)) { throw exception(ErrorCodeConstants.COMPENSATION_BPM_PROCESS_DEFINITION_NOT_EXISTS); } // 2. 校验分类下是否有可用的流程定义 List definitionInfoList = bpmProcessDefinitionService .getProcessDefinitionInfoListByCategory(COMPENSATION_APPROVE_CATEGORY_CODE); if (CollUtil.isEmpty(definitionInfoList)) { throw exception(ErrorCodeConstants.COMPENSATION_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.COMPENSATION_BPM_PROCESS_DEFINITION_NOT_EXISTS); } } private String generateCompensationNo() { return "PS" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + RandomUtil.randomNumbers(4); } }