Commit 9a2eb2d02cbe8bae30af5e77c819eede896fa457
Merge remote-tracking branch 'refs/remotes/yudao/develop' into develop
Showing
7 changed files
with
119 additions
and
125 deletions
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
| 1 | -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.enums.task.BpmDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. <a href="https://blog.csdn.net/bobozai86/article/details/105210414" /> * * HistoricProcessInstance & ProcessInstance 的关系: * 1. <a href=" https://my.oschina.net/843294669/blog/71902" /> * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List<ProcessInstance> getProcessInstances(Set<String> ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List<HistoricProcessInstance> processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map<String, Object> variables, String businessKey, Map<String, List<Long>> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map<String, List<Long>> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List<UserTask> userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List<Long> assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 管理员取消,不用校验是否为自己的 AdminUserRespDTO user = adminUserApi.getUser(userId); // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void updateProcessInstanceWhenCancel(FlowableCancelledEvent event) { // 1. 判断是否为 Reject 不通过。如果是,则不进行更新. // 因为,updateProcessInstanceReject 方法(审批不通过),已经进行更新了 if (BpmDeleteReasonEnum.isRejectReason((String) event.getCause())) { return; } // 2. 更新流程实例 status runtimeService.setVariable(event.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); // 3. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId()); // 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.CANCEL.getStatus())); } @Override public void updateProcessInstanceWhenApprove(ProcessInstance instance) { // 1. 更新流程实例 status runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.APPROVE.getStatus()); // 2. 发送流程被【通过】的消息 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); // 3. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId()); processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.APPROVE.getStatus())); } @Override @Transactional(rollbackFor = Exception.class) public void updateProcessInstanceReject(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); // 2. 删除流程实例,以实现驳回任务时,取消整个审批流程 ProcessInstance processInstance = getProcessInstance(id); deleteProcessInstance(id, StrUtil.format(BpmDeleteReasonEnum.REJECT_TASK.format(reason))); // 3. 发送流程被【不通过】的消息 messageService.sendMessageWhenProcessInstanceReject(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(processInstance, reason)); // 4. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.REJECT.getStatus())); } private void deleteProcessInstance(String id, String reason) { runtimeService.deleteProcessInstance(id, reason); } } | ||
| 2 | \ No newline at end of file | 1 | \ No newline at end of file |
| 2 | +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.enums.task.BpmDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. <a href="https://blog.csdn.net/bobozai86/article/details/105210414" /> * * HistoricProcessInstance & ProcessInstance 的关系: * 1. <a href=" https://my.oschina.net/843294669/blog/71902" /> * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List<ProcessInstance> getProcessInstances(Set<String> ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List<HistoricProcessInstance> processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map<String, Object> variables, String businessKey, Map<String, List<Long>> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map<String, List<Long>> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List<UserTask> userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List<Long> assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 管理员取消,不用校验是否为自己的 AdminUserRespDTO user = adminUserApi.getUser(userId); // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void updateProcessInstanceWhenCancel(FlowableCancelledEvent event) { // 1. 判断是否为 Reject 不通过。如果是,则不进行更新. // 因为,updateProcessInstanceReject 方法(审批不通过),已经进行更新了 if (BpmDeleteReasonEnum.isRejectReason((String) event.getCause())) { return; } // 2. 更新流程实例 status runtimeService.setVariable(event.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); // 3. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId()); // 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.CANCEL.getStatus())); } @Override public void updateProcessInstanceWhenApprove(ProcessInstance instance) { // 1. 更新流程实例 status runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.APPROVE.getStatus()); // 2. 发送流程被【通过】的消息 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); // 3. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId()); processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.APPROVE.getStatus())); } @Override @Transactional(rollbackFor = Exception.class) public void updateProcessInstanceReject(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); // 2. 删除流程实例,以实现驳回任务时,取消整个审批流程 ProcessInstance processInstance = getProcessInstance(id); deleteProcessInstance(id, StrUtil.format(BpmDeleteReasonEnum.REJECT_TASK.format(reason))); // 3. 发送流程被【不通过】的消息 messageService.sendMessageWhenProcessInstanceReject(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(processInstance, reason)); // 4. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.REJECT.getStatus())); } private void deleteProcessInstance(String id, String reason) { runtimeService.deleteProcessInstance(id, reason); } } | ||
| 3 | \ No newline at end of file | 3 | \ No newline at end of file |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsPerformanceServiceImpl.java
| 1 | package cn.iocoder.yudao.module.crm.service.statistics; | 1 | package cn.iocoder.yudao.module.crm.service.statistics; |
| 2 | 2 | ||
| 3 | import cn.hutool.core.collection.CollUtil; | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | +import cn.hutool.core.date.LocalDateTimeUtil; | ||
| 4 | import cn.hutool.core.util.ObjUtil; | 5 | import cn.hutool.core.util.ObjUtil; |
| 5 | import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceReqVO; | 6 | import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceReqVO; |
| 6 | import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO; | 7 | import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO; |
| @@ -13,8 +14,9 @@ import org.springframework.stereotype.Service; | @@ -13,8 +14,9 @@ import org.springframework.stereotype.Service; | ||
| 13 | import org.springframework.validation.annotation.Validated; | 14 | import org.springframework.validation.annotation.Validated; |
| 14 | 15 | ||
| 15 | import jakarta.annotation.Resource; | 16 | import jakarta.annotation.Resource; |
| 16 | -import java.util.Collections; | ||
| 17 | -import java.util.List; | 17 | + |
| 18 | +import java.math.BigDecimal; | ||
| 19 | +import java.util.*; | ||
| 18 | import java.util.function.Function; | 20 | import java.util.function.Function; |
| 19 | 21 | ||
| 20 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; | 22 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; |
| @@ -38,7 +40,7 @@ public class CrmStatisticsPerformanceServiceImpl implements CrmStatisticsPerform | @@ -38,7 +40,7 @@ public class CrmStatisticsPerformanceServiceImpl implements CrmStatisticsPerform | ||
| 38 | 40 | ||
| 39 | @Override | 41 | @Override |
| 40 | public List<CrmStatisticsPerformanceRespVO> getContractCountPerformance(CrmStatisticsPerformanceReqVO performanceReqVO) { | 42 | public List<CrmStatisticsPerformanceRespVO> getContractCountPerformance(CrmStatisticsPerformanceReqVO performanceReqVO) { |
| 41 | - // TODO @scholar:我们可以换个思路实现,减少数据库的计算量; | 43 | + // TODO @scholar:可以把下面这个注释,你理解后,重新整理下,写到 getPerformance 里; |
| 42 | // 比如说,2024 年的合同数据,是不是 2022-12 到 2024-12-31,每个月的统计呢? | 44 | // 比如说,2024 年的合同数据,是不是 2022-12 到 2024-12-31,每个月的统计呢? |
| 43 | // 理解之后,我们可以数据 group by 年-月,20222-12 到 2024-12-31 的,然后内存在聚合出 CrmStatisticsPerformanceRespVO 这样 | 45 | // 理解之后,我们可以数据 group by 年-月,20222-12 到 2024-12-31 的,然后内存在聚合出 CrmStatisticsPerformanceRespVO 这样 |
| 44 | // 这样,我们就可以减少数据库的计算量,提升性能;同时 SQL 也会很简单,开发者理解起来也简单哈; | 46 | // 这样,我们就可以减少数据库的计算量,提升性能;同时 SQL 也会很简单,开发者理解起来也简单哈; |
| @@ -55,28 +57,99 @@ public class CrmStatisticsPerformanceServiceImpl implements CrmStatisticsPerform | @@ -55,28 +57,99 @@ public class CrmStatisticsPerformanceServiceImpl implements CrmStatisticsPerform | ||
| 55 | return getPerformance(performanceReqVO, performanceMapper::selectReceivablePricePerformance); | 57 | return getPerformance(performanceReqVO, performanceMapper::selectReceivablePricePerformance); |
| 56 | } | 58 | } |
| 57 | 59 | ||
| 60 | + // TODO @scholar:代码注释,应该有 3 个变量哈; | ||
| 58 | /** | 61 | /** |
| 59 | * 获得员工业绩数据 | 62 | * 获得员工业绩数据 |
| 60 | * | 63 | * |
| 61 | * @param performanceReqVO 参数 | 64 | * @param performanceReqVO 参数 |
| 62 | - * @param performanceFunction 排行榜方法 | ||
| 63 | - * @return 排行版数据 | 65 | + * @param performanceFunction 员工业绩统计方法 |
| 66 | + * @return 员工业绩数据 | ||
| 64 | */ | 67 | */ |
| 68 | + // TODO @scholar:下面一行的变量,超过一行了,阅读不美观;可以考虑每一行一个变量; | ||
| 65 | private List<CrmStatisticsPerformanceRespVO> getPerformance(CrmStatisticsPerformanceReqVO performanceReqVO, Function<CrmStatisticsPerformanceReqVO, | 69 | private List<CrmStatisticsPerformanceRespVO> getPerformance(CrmStatisticsPerformanceReqVO performanceReqVO, Function<CrmStatisticsPerformanceReqVO, |
| 66 | List<CrmStatisticsPerformanceRespVO>> performanceFunction) { | 70 | List<CrmStatisticsPerformanceRespVO>> performanceFunction) { |
| 67 | 71 | ||
| 72 | + // TODO @scholar:没使用到的变量,建议删除; | ||
| 73 | + List<CrmStatisticsPerformanceRespVO> performanceRespVOList; | ||
| 74 | + | ||
| 68 | // 1. 获得用户编号数组 | 75 | // 1. 获得用户编号数组 |
| 69 | final List<Long> userIds = getUserIds(performanceReqVO); | 76 | final List<Long> userIds = getUserIds(performanceReqVO); |
| 70 | if (CollUtil.isEmpty(userIds)) { | 77 | if (CollUtil.isEmpty(userIds)) { |
| 71 | return Collections.emptyList(); | 78 | return Collections.emptyList(); |
| 72 | } | 79 | } |
| 73 | performanceReqVO.setUserIds(userIds); | 80 | performanceReqVO.setUserIds(userIds); |
| 74 | - // 2. 获得排行数据 | 81 | + // TODO @scholar:1. 和 2. 之间,可以考虑换一行;保证每一块逻辑的间隔; |
| 82 | + // 2. 获得业绩数据 | ||
| 83 | + // TODO @scholar:复数变量,建议使用 s 或者 list 结果;这里用 performanceList 好列; | ||
| 75 | List<CrmStatisticsPerformanceRespVO> performance = performanceFunction.apply(performanceReqVO); | 84 | List<CrmStatisticsPerformanceRespVO> performance = performanceFunction.apply(performanceReqVO); |
| 76 | - if (CollUtil.isEmpty(performance)) { | ||
| 77 | - return Collections.emptyList(); | 85 | + |
| 86 | + // 获取查询的年份 | ||
| 87 | + // TODO @scholar:逻辑可以简化一下; | ||
| 88 | + // TODO 1)把 performance 转换成 map;key 是 time,value 是 count | ||
| 89 | + // TODO 2)当前年,遍历 1-12 月份,去 map 拿到 count;接着月份 -1,去 map 拿 count;再年份 -1,拿 count | ||
| 90 | + String currentYear = LocalDateTimeUtil.format(performanceReqVO.getTimes()[0],"yyyy"); | ||
| 91 | + | ||
| 92 | + // 构造查询当年和前一年,每年12个月的年月组合 | ||
| 93 | + List<String> allMonths = new ArrayList<>(); | ||
| 94 | + for (int year = Integer.parseInt(currentYear)-1; year <= Integer.parseInt(currentYear); year++) { | ||
| 95 | + for (int month = 1; month <= 12; month++) { | ||
| 96 | + allMonths.add(String.format("%d%02d", year, month)); | ||
| 97 | + } | ||
| 98 | + } | ||
| 99 | + | ||
| 100 | + List<CrmStatisticsPerformanceRespVO> computedList = new ArrayList<>(); | ||
| 101 | + List<CrmStatisticsPerformanceRespVO> respVOList = new ArrayList<>(); | ||
| 102 | + | ||
| 103 | + // 生成computedList基础数据 | ||
| 104 | + // 构造完整的2*12个月的数据,如果某月数据缺失,需要补上0,一年12个月不能有缺失 | ||
| 105 | + for (String month : allMonths) { | ||
| 106 | + CrmStatisticsPerformanceRespVO foundData = performance.stream() | ||
| 107 | + .filter(data -> data.getTime().equals(month)) | ||
| 108 | + .findFirst() | ||
| 109 | + .orElse(null); | ||
| 110 | + | ||
| 111 | + if (foundData != null) { | ||
| 112 | + computedList.add(foundData); | ||
| 113 | + } else { | ||
| 114 | + CrmStatisticsPerformanceRespVO missingData = new CrmStatisticsPerformanceRespVO(); | ||
| 115 | + missingData.setTime(month); | ||
| 116 | + missingData.setCurrentMonthCount(BigDecimal.ZERO); | ||
| 117 | + missingData.setLastMonthCount(BigDecimal.ZERO); | ||
| 118 | + missingData.setLastYearCount(BigDecimal.ZERO); | ||
| 119 | + computedList.add(missingData); | ||
| 120 | + } | ||
| 121 | + } | ||
| 122 | + //根据查询年份和前一年的数据,计算查询年份的同比环比数据 | ||
| 123 | + for (CrmStatisticsPerformanceRespVO currentData : computedList) { | ||
| 124 | + String currentMonth = currentData.getTime(); | ||
| 125 | + | ||
| 126 | + // 根据当年和前一年的月销售数据,计算currentYear的完整数据 | ||
| 127 | + if (currentMonth.startsWith(currentYear)) { | ||
| 128 | + // 计算 LastMonthCount | ||
| 129 | + int currentIndex = computedList.indexOf(currentData); | ||
| 130 | + if (currentIndex > 0) { | ||
| 131 | + CrmStatisticsPerformanceRespVO lastMonthData = computedList.get(currentIndex - 1); | ||
| 132 | + currentData.setLastMonthCount(lastMonthData.getCurrentMonthCount()); | ||
| 133 | + } else { | ||
| 134 | + currentData.setLastMonthCount(BigDecimal.ZERO); // 第一个月的 LastMonthCount 设为0 | ||
| 135 | + } | ||
| 136 | + | ||
| 137 | + // 计算 LastYearCount | ||
| 138 | + String lastYearMonth = String.valueOf(Integer.parseInt(currentMonth) - 100); | ||
| 139 | + CrmStatisticsPerformanceRespVO lastYearData = computedList.stream() | ||
| 140 | + .filter(data -> data.getTime().equals(lastYearMonth)) | ||
| 141 | + .findFirst() | ||
| 142 | + .orElse(null); | ||
| 143 | + | ||
| 144 | + if (lastYearData != null) { | ||
| 145 | + currentData.setLastYearCount(lastYearData.getCurrentMonthCount()); | ||
| 146 | + } else { | ||
| 147 | + currentData.setLastYearCount(BigDecimal.ZERO); // 如果去年同月数据不存在,设为0 | ||
| 148 | + } | ||
| 149 | + respVOList.add(currentData);//给前端只需要返回查询当年的数据,不需要前一年数据 | ||
| 150 | + } | ||
| 78 | } | 151 | } |
| 79 | - return performance; | 152 | + return respVOList; |
| 80 | } | 153 | } |
| 81 | 154 | ||
| 82 | /** | 155 | /** |
yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/statistics/CrmStatisticsPerformanceMapper.xml
| @@ -5,75 +5,28 @@ | @@ -5,75 +5,28 @@ | ||
| 5 | <select id="selectContractCountPerformance" | 5 | <select id="selectContractCountPerformance" |
| 6 | resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO"> | 6 | resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO"> |
| 7 | SELECT | 7 | SELECT |
| 8 | - t.time as time, | ||
| 9 | - COALESCE(t.currentMonthCount,0) as currentMonthCount, | ||
| 10 | - COALESCE(y.lastMonthCount,0) as lastMonthCount, | ||
| 11 | - COALESCE(z.lastYearCount,0) as lastYearCount | ||
| 12 | - FROM | ||
| 13 | - (SELECT | ||
| 14 | - COUNT(1) AS currentMonthCount, | ||
| 15 | - DATE_FORMAT(order_date, '%Y-%m') AS time | ||
| 16 | - FROM crm_contract | 8 | + DATE_FORMAT(order_date, '%Y%m') AS time, |
| 9 | + COUNT(1) AS currentMonthCount | ||
| 10 | + FROM crm_contract | ||
| 17 | WHERE deleted = 0 | 11 | WHERE deleted = 0 |
| 12 | + <!-- TODO @scholar:20 改成静态类引入 --> | ||
| 18 | AND audit_status = 20 | 13 | AND audit_status = 20 |
| 19 | AND owner_user_id in | 14 | AND owner_user_id in |
| 20 | - <foreach collection="userIds" item="userId" open="(" close=")" separator=","> | ||
| 21 | - #{userId} | ||
| 22 | - </foreach> | ||
| 23 | - AND DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y') | ||
| 24 | - GROUP BY time)t | ||
| 25 | - LEFT JOIN | ||
| 26 | - (SELECT | ||
| 27 | - COUNT(1) AS lastMonthCount, | ||
| 28 | - DATE_FORMAT(DATE_ADD(order_date,INTERVAL 1 MONTH), '%Y-%m') AS time | ||
| 29 | - FROM crm_contract | ||
| 30 | - WHERE deleted = 0 | ||
| 31 | - AND audit_status = 20 | ||
| 32 | - AND owner_user_id in | ||
| 33 | - <foreach collection="userIds" item="userId" open="(" close=")" separator=","> | ||
| 34 | - #{userId} | ||
| 35 | - </foreach> | ||
| 36 | - AND (DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y') | ||
| 37 | - or DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')-1) | ||
| 38 | - GROUP BY time)y ON t.time = y.time | ||
| 39 | - LEFT JOIN | ||
| 40 | - (SELECT | ||
| 41 | - COUNT(1) AS lastYearCount, | ||
| 42 | - DATE_FORMAT(DATE_ADD(order_date,INTERVAL 1 YEAR), '%Y-%m') AS time | ||
| 43 | - FROM crm_contract | ||
| 44 | - WHERE deleted = 0 | ||
| 45 | - AND audit_status = 20 | ||
| 46 | - AND owner_user_id in | ||
| 47 | - <foreach collection="userIds" item="userId" open="(" close=")" separator=","> | ||
| 48 | - #{userId} | ||
| 49 | - </foreach> | ||
| 50 | - AND DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')-1 | ||
| 51 | - GROUP BY time)z ON t.time = z.time | 15 | + <foreach collection="userIds" item="userId" open="(" close=")" separator=","> |
| 16 | + #{userId} | ||
| 17 | + </foreach> | ||
| 18 | + <!-- TODO @scholar:CrmStatisticsPerformanceReqVO 传递 year,然后 java 代码里,转换出 times;这样,order_time 使用范围查询,避免使用函数 --> | ||
| 19 | + AND (DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime}, '%Y') | ||
| 20 | + or DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime}, '%Y') - 1) | ||
| 21 | + GROUP BY time | ||
| 52 | </select> | 22 | </select> |
| 23 | + | ||
| 24 | + <!-- TODO @scholar:参考上面,调整下这个 SQL 的排版、和代码建议哈 --> | ||
| 53 | <select id="selectContractPricePerformance" | 25 | <select id="selectContractPricePerformance" |
| 54 | resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO"> | 26 | resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO"> |
| 55 | SELECT | 27 | SELECT |
| 56 | - t.time as time, | ||
| 57 | - COALESCE(t.currentMonthCount,0) as currentMonthCount, | ||
| 58 | - COALESCE(y.lastMonthCount,0) as lastMonthCount, | ||
| 59 | - COALESCE(z.lastYearCount,0) as lastYearCount | ||
| 60 | - FROM | ||
| 61 | - (SELECT | ||
| 62 | - IFNULL(SUM(total_price), 0) AS currentMonthCount, | ||
| 63 | - DATE_FORMAT(order_date, '%Y-%m') AS time | ||
| 64 | - FROM crm_contract | ||
| 65 | - WHERE deleted = 0 | ||
| 66 | - AND audit_status = 20 | ||
| 67 | - AND owner_user_id in | ||
| 68 | - <foreach collection="userIds" item="userId" open="(" close=")" separator=","> | ||
| 69 | - #{userId} | ||
| 70 | - </foreach> | ||
| 71 | - AND DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y') | ||
| 72 | - GROUP BY time)t | ||
| 73 | - LEFT JOIN | ||
| 74 | - (SELECT | ||
| 75 | - IFNULL(SUM(total_price), 0) AS lastMonthCount, | ||
| 76 | - DATE_FORMAT(DATE_ADD(order_date,INTERVAL 1 MONTH), '%Y-%m') AS time | 28 | + DATE_FORMAT(order_date, '%Y%m') AS time, |
| 29 | + IFNULL(SUM(total_price), 0) AS currentMonthCount | ||
| 77 | FROM crm_contract | 30 | FROM crm_contract |
| 78 | WHERE deleted = 0 | 31 | WHERE deleted = 0 |
| 79 | AND audit_status = 20 | 32 | AND audit_status = 20 |
| @@ -83,46 +36,15 @@ | @@ -83,46 +36,15 @@ | ||
| 83 | </foreach> | 36 | </foreach> |
| 84 | AND (DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y') | 37 | AND (DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y') |
| 85 | or DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')-1) | 38 | or DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')-1) |
| 86 | - GROUP BY time)y ON t.time = y.time | ||
| 87 | - LEFT JOIN | ||
| 88 | - (SELECT | ||
| 89 | - IFNULL(SUM(total_price), 0) AS lastYearCount, | ||
| 90 | - DATE_FORMAT(DATE_ADD(order_date,INTERVAL 1 YEAR), '%Y-%m') AS time | ||
| 91 | - FROM crm_contract | ||
| 92 | - WHERE deleted = 0 | ||
| 93 | - AND audit_status = 20 | ||
| 94 | - AND owner_user_id in | ||
| 95 | - <foreach collection="userIds" item="userId" open="(" close=")" separator=","> | ||
| 96 | - #{userId} | ||
| 97 | - </foreach> | ||
| 98 | - AND DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')-1 | ||
| 99 | - GROUP BY time)z ON t.time = z.time | 39 | + GROUP BY time |
| 100 | </select> | 40 | </select> |
| 101 | 41 | ||
| 42 | + <!-- TODO @scholar:参考上面,调整下这个 SQL 的排版、和代码建议哈 --> | ||
| 102 | <select id="selectReceivablePricePerformance" | 43 | <select id="selectReceivablePricePerformance" |
| 103 | resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO"> | 44 | resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO"> |
| 104 | SELECT | 45 | SELECT |
| 105 | - t.time as time, | ||
| 106 | - COALESCE(t.currentMonthCount,0) as currentMonthCount, | ||
| 107 | - COALESCE(y.lastMonthCount,0) as lastMonthCount, | ||
| 108 | - COALESCE(z.lastYearCount,0) as lastYearCount | ||
| 109 | - FROM | ||
| 110 | - (SELECT | ||
| 111 | - IFNULL(SUM(price), 0) AS currentMonthCount, | ||
| 112 | - DATE_FORMAT(return_time, '%Y-%m') AS time | ||
| 113 | - FROM crm_receivable | ||
| 114 | - WHERE deleted = 0 | ||
| 115 | - AND audit_status = 20 | ||
| 116 | - AND owner_user_id in | ||
| 117 | - <foreach collection="userIds" item="userId" open="(" close=")" separator=","> | ||
| 118 | - #{userId} | ||
| 119 | - </foreach> | ||
| 120 | - AND DATE_FORMAT(return_time, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y') | ||
| 121 | - GROUP BY time)t | ||
| 122 | - LEFT JOIN | ||
| 123 | - (SELECT | ||
| 124 | - IFNULL(SUM(price), 0) AS lastMonthCount, | ||
| 125 | - DATE_FORMAT(DATE_ADD(return_time,INTERVAL 1 MONTH), '%Y-%m') AS time | 46 | + DATE_FORMAT(return_time, '%Y%m') AS time, |
| 47 | + IFNULL(SUM(price), 0) AS currentMonthCount | ||
| 126 | FROM crm_receivable | 48 | FROM crm_receivable |
| 127 | WHERE deleted = 0 | 49 | WHERE deleted = 0 |
| 128 | AND audit_status = 20 | 50 | AND audit_status = 20 |
| @@ -132,21 +54,7 @@ | @@ -132,21 +54,7 @@ | ||
| 132 | </foreach> | 54 | </foreach> |
| 133 | AND (DATE_FORMAT(return_time, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y') | 55 | AND (DATE_FORMAT(return_time, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y') |
| 134 | or DATE_FORMAT(return_time, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')-1) | 56 | or DATE_FORMAT(return_time, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')-1) |
| 135 | - GROUP BY time)y ON t.time = y.time | ||
| 136 | - LEFT JOIN | ||
| 137 | - (SELECT | ||
| 138 | - IFNULL(SUM(price), 0) AS lastYearCount, | ||
| 139 | - DATE_FORMAT(DATE_ADD(return_time,INTERVAL 1 YEAR), '%Y-%m') AS time | ||
| 140 | - FROM crm_receivable | ||
| 141 | - WHERE deleted = 0 | ||
| 142 | - AND audit_status = 20 | ||
| 143 | - AND owner_user_id in | ||
| 144 | - <foreach collection="userIds" item="userId" open="(" close=")" separator=","> | ||
| 145 | - #{userId} | ||
| 146 | - </foreach> | ||
| 147 | - AND DATE_FORMAT(return_time, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')-1 | ||
| 148 | - GROUP BY time)z ON t.time = z.time | 57 | + GROUP BY time |
| 149 | </select> | 58 | </select> |
| 150 | 59 | ||
| 151 | - | ||
| 152 | </mapper> | 60 | </mapper> |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/DeliveryExpressTemplateServiceImpl.java
| @@ -88,7 +88,7 @@ public class DeliveryExpressTemplateServiceImpl implements DeliveryExpressTempla | @@ -88,7 +88,7 @@ public class DeliveryExpressTemplateServiceImpl implements DeliveryExpressTempla | ||
| 88 | List<DeliveryExpressTemplateFreeDO> oldList = expressTemplateFreeMapper.selectListByTemplateId(templateId); | 88 | List<DeliveryExpressTemplateFreeDO> oldList = expressTemplateFreeMapper.selectListByTemplateId(templateId); |
| 89 | List<DeliveryExpressTemplateFreeDO> newList = INSTANCE.convertTemplateFreeList(templateId, frees); | 89 | List<DeliveryExpressTemplateFreeDO> newList = INSTANCE.convertTemplateFreeList(templateId, frees); |
| 90 | List<List<DeliveryExpressTemplateFreeDO>> diffList = CollectionUtils.diffList(oldList, newList, | 90 | List<List<DeliveryExpressTemplateFreeDO>> diffList = CollectionUtils.diffList(oldList, newList, |
| 91 | - (oldVal, newVal) -> ObjectUtil.equal(oldVal.getId(), newVal.getTemplateId())); | 91 | + (oldVal, newVal) -> ObjectUtil.equal(oldVal.getId(), newVal.getId())); |
| 92 | 92 | ||
| 93 | // 第二步,批量添加、修改、删除 | 93 | // 第二步,批量添加、修改、删除 |
| 94 | if (CollUtil.isNotEmpty(diffList.get(0))) { | 94 | if (CollUtil.isNotEmpty(diffList.get(0))) { |
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java
| @@ -36,13 +36,15 @@ public class PayWalletServiceImpl implements PayWalletService { | @@ -36,13 +36,15 @@ public class PayWalletServiceImpl implements PayWalletService { | ||
| 36 | 36 | ||
| 37 | @Resource | 37 | @Resource |
| 38 | private PayWalletMapper walletMapper; | 38 | private PayWalletMapper walletMapper; |
| 39 | + | ||
| 39 | @Resource | 40 | @Resource |
| 41 | + @Lazy // 延迟加载,避免循环依赖 | ||
| 40 | private PayWalletTransactionService walletTransactionService; | 42 | private PayWalletTransactionService walletTransactionService; |
| 41 | @Resource | 43 | @Resource |
| 42 | - @Lazy | 44 | + @Lazy // 延迟加载,避免循环依赖 |
| 43 | private PayOrderService orderService; | 45 | private PayOrderService orderService; |
| 44 | @Resource | 46 | @Resource |
| 45 | - @Lazy | 47 | + @Lazy // 延迟加载,避免循环依赖 |
| 46 | private PayRefundService refundService; | 48 | private PayRefundService refundService; |
| 47 | 49 | ||
| 48 | @Override | 50 | @Override |
yudao-server/src/main/resources/application-dev.yaml
| @@ -198,6 +198,16 @@ justauth: | @@ -198,6 +198,16 @@ justauth: | ||
| 198 | client-secret: 1wTb7hYxnpT2TUbIeHGXGo7T0odav1ic10mLdyyATOw | 198 | client-secret: 1wTb7hYxnpT2TUbIeHGXGo7T0odav1ic10mLdyyATOw |
| 199 | agent-id: 1000004 | 199 | agent-id: 1000004 |
| 200 | ignore-check-redirect-uri: true | 200 | ignore-check-redirect-uri: true |
| 201 | + # noinspection SpringBootApplicationYaml | ||
| 202 | + WECHAT_MINI_APP: # 微信小程序 | ||
| 203 | + client-id: ${wx.miniapp.appid} | ||
| 204 | + client-secret: ${wx.miniapp.secret} | ||
| 205 | + ignore-check-redirect-uri: true | ||
| 206 | + ignore-check-state: true # 微信小程序,不会使用到 state,所以不进行校验 | ||
| 207 | + WECHAT_MP: # 微信公众号 | ||
| 208 | + client-id: ${wx.mp.app-id} | ||
| 209 | + client-secret: ${wx.mp.secret} | ||
| 210 | + ignore-check-redirect-uri: true | ||
| 201 | cache: | 211 | cache: |
| 202 | type: REDIS | 212 | type: REDIS |
| 203 | prefix: 'social_auth_state:' # 缓存前缀,目前只对 Redis 缓存生效,默认 JUSTAUTH::STATE:: | 213 | prefix: 'social_auth_state:' # 缓存前缀,目前只对 Redis 缓存生效,默认 JUSTAUTH::STATE:: |
yudao-server/src/main/resources/application-local.yaml
| @@ -254,6 +254,7 @@ justauth: | @@ -254,6 +254,7 @@ justauth: | ||
| 254 | client-secret: 1wTb7hYxnpT2TUbIeHGXGo7T0odav1ic10mLdyyATOw | 254 | client-secret: 1wTb7hYxnpT2TUbIeHGXGo7T0odav1ic10mLdyyATOw |
| 255 | agent-id: 1000004 | 255 | agent-id: 1000004 |
| 256 | ignore-check-redirect-uri: true | 256 | ignore-check-redirect-uri: true |
| 257 | + # noinspection SpringBootApplicationYaml | ||
| 257 | WECHAT_MINI_APP: # 微信小程序 | 258 | WECHAT_MINI_APP: # 微信小程序 |
| 258 | client-id: ${wx.miniapp.appid} | 259 | client-id: ${wx.miniapp.appid} |
| 259 | client-secret: ${wx.miniapp.secret} | 260 | client-secret: ${wx.miniapp.secret} |