business/src/main/java/com/ycl/controller/ProjectProcessController.java
@@ -34,7 +34,7 @@ @PostMapping("/page") @ApiOperation(value = "分页", notes = "分页") @PreAuthorize("@ss.hasPermi('projectProcess:page')") // @PreAuthorize("@ss.hasPermi('projectProcess:page')") public Result page(ProjectProcessQuery query) { return projectProcessService.page(query); } @@ -42,16 +42,23 @@ @PostMapping("/set") @ApiOperation(value = "项目设置流程", notes = "项目设置流程") @PreAuthorize("@ss.hasPermi('projectProcess:set')") // @PreAuthorize("@ss.hasPermi('projectProcess:set')") public Result projectSetProcess(@RequestBody @Validated ProjectProcessForm form) { return projectProcessService.projectSetProcess(form); } @GetMapping("/{id}") @ApiOperation(value = "详情", notes = "详情") @PreAuthorize("@ss.hasPermi('projectProcess:detail')") public Result detail(@PathVariable("id") Integer id) { return projectProcessService.detail(id); @GetMapping("/detail/{projectId}/{processId}") @ApiOperation(value = "获取项目流程详情信息", notes = "获取项目流程详情信息") // @PreAuthorize("@ss.hasPermi('projectProcess:detail')") public Result detail(@PathVariable("projectId") Long projectId, @PathVariable("processId") String processId) { return projectProcessService.detail(projectId, processId); } @PostMapping("/start/{projectId}/{processId}") @ApiOperation(value = "启动流程", notes = "启动流程") // @PreAuthorize("@ss.hasPermi('projectProcess:start')") public Result startProcess(@PathVariable("projectId") String projectId, @PathVariable("processId") String processId) { return projectProcessService.startProcess(projectId, processId); } } business/src/main/java/com/ycl/domain/entity/ProjectProcess.java
@@ -24,8 +24,11 @@ private Long projectId; @TableField("flowable_process_id") /** 流程ID */ /** 流程定义ID */ private String flowableProcessId; @TableField("process_instance_id") /** 流程实例id */ private String processInstanceId; } business/src/main/java/com/ycl/domain/vo/ProjectProcessDetailVO.java
New file @@ -0,0 +1,86 @@ package com.ycl.domain.vo; import lombok.Data; import java.util.List; /** * @author:xp * @date:2024/11/27 16:25 */ @Data public class ProjectProcessDetailVO { /** * 项目id */ private Long projectId; /** * 项目名称 */ private String projectName; /** * 项目代码 */ private String projectCode; /** * 项目标签 */ private List<String> tagList; /** * 代办列表 */ private List<Object> taskList; private TaskStatistics statistics; @Data public static class TaskStatistics { /** * 总共任务数 */ private Long totalTaskNum = 0L; /** * 待办任务数 */ private Long todoTaskNum = 0L; /** * 当前任务 */ private Object currentTask; /** * 剩余任务数 */ private Long remainingTaskNum = 0L; /** * 按时完成任务数 */ private Long timelyFinishedTaskNum = 0L; /** * 超时完成任务数 */ private Long overtimeFinishedTaskNum = 0L; /** * 临期任务数 */ private Long willOvertimeTaskNum = 0L; /** * 督办任务数 */ private Long urgeTaskNum = 0L; } } business/src/main/java/com/ycl/domain/vo/ProjectProcessVO.java
@@ -27,4 +27,7 @@ @ApiModelProperty("流程名称") private String flowableProcessName; @ApiModelProperty("流程实例id") private String processInstanceId; } business/src/main/java/com/ycl/service/ProjectProcessService.java
@@ -23,11 +23,12 @@ Result page(ProjectProcessQuery query); /** * 根据id查找 * @param id * 根据项目id查找 * @param projectId * @param processId * @return */ Result detail(Integer id); Result detail(Long projectId, String processId); /** * 项目设置流程 @@ -36,4 +37,13 @@ * @return */ Result projectSetProcess(ProjectProcessForm form); /** * 启动流程 * * @param processId 流程定义id * @param projectId 项目id,作为业务id存入activity表 * @return */ Result startProcess(String projectId, String processId); } business/src/main/java/com/ycl/service/impl/ProcessConfigInfoServiceImpl.java
@@ -91,23 +91,23 @@ public Result list(ProcessConfigInfoQuery query) { List<FlowProcDefWithConfigDto> dataList = flowDeployMapper.selectDeployListWithConfig(query); // 根据 processDefId 分组,并取每组中 版本号 最大的那一个 Map<String, FlowProcDefWithConfigDto> groupedByProcessDefId = dataList.stream() .collect(Collectors.toMap( FlowProcDefWithConfigDto::getProcessDefId, // keyMapper: 提取 processDefId 作为键 Function.identity(), // valueMapper: 直接使用对象作为值 BinaryOperator.maxBy(Comparator.comparingInt(FlowProcDefWithConfigDto::getProcessDefVersion)) // mergeFunction: 比较 processDefVersion,取最大的 )); List<FlowProcDefWithConfigDto> resultList = new ArrayList<>(groupedByProcessDefId.values()); // // 根据 processDefId 分组,并取每组中 版本号 最大的那一个 // Map<String, FlowProcDefWithConfigDto> groupedByProcessDefId = dataList.stream() // .collect(Collectors.toMap( // FlowProcDefWithConfigDto::getProcessDefId, // keyMapper: 提取 processDefId 作为键 // Function.identity(), // valueMapper: 直接使用对象作为值 // BinaryOperator.maxBy(Comparator.comparingInt(FlowProcDefWithConfigDto::getProcessDefVersion)) // mergeFunction: 比较 processDefVersion,取最大的 // )); // List<FlowProcDefWithConfigDto> resultList = new ArrayList<>(groupedByProcessDefId.values()); // 加载挂表单 for (FlowProcDefWithConfigDto procDef : resultList) { for (FlowProcDefWithConfigDto procDef : dataList) { SysForm sysForm = sysDeployFormService.selectSysDeployFormByDeployId(procDef.getDeploymentId()); if (Objects.nonNull(sysForm)) { procDef.setFormName(sysForm.getFormName()); procDef.setFormId(sysForm.getFormId()); } } return Result.ok().data(resultList); return Result.ok().data(dataList); } } business/src/main/java/com/ycl/service/impl/ProjectProcessServiceImpl.java
@@ -1,9 +1,21 @@ package com.ycl.service.impl; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper; import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper; import com.ycl.common.constant.ProcessConstants; import com.ycl.common.core.domain.AjaxResult; import com.ycl.common.core.domain.entity.SysRole; import com.ycl.common.core.domain.entity.SysUser; import com.ycl.common.enums.FlowComment; import com.ycl.common.utils.SecurityUtils; import com.ycl.domain.dto.FlowTaskDto; import com.ycl.domain.entity.ProjectInfo; import com.ycl.domain.entity.ProjectProcess; import com.ycl.domain.vo.ProjectProcessDetailVO; import com.ycl.mapper.ProjectInfoMapper; import com.ycl.mapper.ProjectProcessMapper; import com.ycl.service.ProjectProcessService; import com.ycl.common.base.Result; @@ -11,14 +23,30 @@ import com.ycl.domain.form.ProjectProcessForm; import com.ycl.domain.vo.ProjectProcessVO; import com.ycl.domain.query.ProjectProcessQuery; import com.ycl.system.service.ISysRoleService; import com.ycl.system.service.ISysUserService; import org.apache.commons.lang3.StringUtils; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.FlowElement; import org.flowable.bpmn.model.Process; import org.flowable.bpmn.model.StartEvent; import org.flowable.engine.*; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.identitylink.api.IdentityLink; import org.flowable.task.api.Task; import org.flowable.task.api.TaskQuery; import org.flowable.task.api.history.HistoricTaskInstance; import org.flowable.task.api.history.HistoricTaskInstanceQuery; import org.springframework.stereotype.Service; import lombok.RequiredArgsConstructor; import com.ycl.framework.utils.PageUtil; import org.springframework.beans.BeanUtils; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; import java.util.List; import java.util.Objects; import java.util.*; import java.util.stream.Collectors; /** @@ -32,6 +60,14 @@ public class ProjectProcessServiceImpl extends ServiceImpl<ProjectProcessMapper, ProjectProcess> implements ProjectProcessService { private final ProjectProcessMapper projectProcessMapper; private final RuntimeService runtimeService; private final TaskService taskService; private final IdentityService identityService; private final RepositoryService repositoryService; private final ProjectInfoMapper projectInfoMapper; private final HistoryService historyService; private final ISysUserService sysUserService; private final ISysRoleService sysRoleService; /** * 分页查询 @@ -46,15 +82,48 @@ } /** * 根据id查找 * @param id * 获取流程详情 * @param projectId * @return */ @Override public Result detail(Integer id) { ProjectProcessVO vo = baseMapper.getById(id); Assert.notNull(vo, "记录不存在"); return Result.ok().data(vo); public Result detail(Long projectId, String processId) { // 项目信息 ProjectInfo projectInfo = new LambdaQueryChainWrapper<>(projectInfoMapper) .select(ProjectInfo::getId, ProjectInfo::getProjectName, ProjectInfo::getProjectCode) .eq(ProjectInfo::getId, projectId) .one(); if (Objects.isNull(projectInfo)) { return Result.error("该项目不存在"); } ProjectProcess projectProcess = new LambdaQueryChainWrapper<>(baseMapper) .eq(ProjectProcess::getProjectId, projectId) .eq(ProjectProcess::getFlowableProcessId, processId) .one(); if (Objects.isNull(projectProcess)) { return Result.error("该项目未设置流程"); } ProjectProcessDetailVO detail = new ProjectProcessDetailVO(); detail.setProjectId(projectId); detail.setProjectName(projectInfo.getProjectName()); detail.setProjectCode(projectInfo.getProjectCode()); ProjectProcessDetailVO.TaskStatistics taskStatistics = new ProjectProcessDetailVO.TaskStatistics(); // 状态统计 taskStatistics.setTotalTaskNum(this.getTotalTaskNum(processId)); taskStatistics.setTodoTaskNum(this.getTodoTaskNum(projectProcess.getProcessInstanceId())); // taskStatistics.setCurrentTask(this.getCurrentNodeTaskList(projectProcess.getProcessInstanceId())); taskStatistics.setRemainingTaskNum(this.getNotFinishedTaskNum(projectProcess.getProcessInstanceId())); detail.setStatistics(taskStatistics); Result result = Result.ok(); // 代办任务 this.getTodoTaskList(projectProcess.getProcessInstanceId(),"", 1, 5, result); return result.data(detail); } @@ -76,4 +145,172 @@ } return Result.ok("流程变更成功"); } @Override @Transactional(rollbackFor = Exception.class) public Result startProcess(String projectId, String processId) { ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(processId) .latestVersion().singleResult(); if (Objects.nonNull(processDefinition) && processDefinition.isSuspended()) { return Result.error("该流程已被挂起,请先激活流程"); } Map<String, Object> variables = new HashMap<>(2); // 设置流程发起人Id到流程中 SysUser sysUser = SecurityUtils.getLoginUser().getUser(); identityService.setAuthenticatedUserId(sysUser.getUserId().toString()); variables.put(ProcessConstants.PROCESS_INITIATOR, sysUser.getUserId()); ProcessInstance processInstance = runtimeService.startProcessInstanceById(processId, projectId, variables); // // 流程发起时 跳过发起人节点 // // 给第一步申请人节点设置任务执行人和意见 // Task task = taskService.createTaskQuery().processInstanceId(processInstance.getProcessInstanceId()).singleResult(); // if (Objects.nonNull(task)) { // taskService.addComment(task.getId(), processInstance.getProcessInstanceId(), FlowComment.NORMAL.getType(), sysUser.getNickName() + "发起流程申请"); // taskService.complete(task.getId(), variables); // } // 项目流程关联流程实例id new LambdaUpdateChainWrapper<>(baseMapper) .eq(ProjectProcess::getProjectId, projectId) .eq(ProjectProcess::getFlowableProcessId, processId) .set(ProjectProcess::getProcessInstanceId, processInstance.getProcessInstanceId()) .update(); return Result.ok("流程启动成功"); } private void getTodoTaskList(String processInstanceId, String name, Integer pageNum, Integer pageSize, Result result) { TaskQuery taskQuery = taskService.createTaskQuery() .active() .processInstanceId(processInstanceId) .includeProcessVariables() .orderByTaskCreateTime().desc(); // TODO 传入名称查询不到数据? if (StringUtils.isNotBlank(name)) { taskQuery.processDefinitionNameLike(name); } result.total(taskQuery.count()); List<Task> taskList = taskQuery.listPage(pageSize * (pageNum - 1), pageSize); List<FlowTaskDto> flowList = new ArrayList<>(); for (Task task : taskList) { FlowTaskDto flowTask = new FlowTaskDto(); // 当前流程信息 flowTask.setTaskId(task.getId()); flowTask.setTaskDefKey(task.getTaskDefinitionKey()); flowTask.setCreateTime(task.getCreateTime()); flowTask.setProcDefId(task.getProcessDefinitionId()); flowTask.setExecutionId(task.getExecutionId()); flowTask.setTaskName(task.getName()); // 流程定义信息 ProcessDefinition pd = repositoryService.createProcessDefinitionQuery() .processDefinitionId(task.getProcessDefinitionId()) .singleResult(); flowTask.setDeployId(pd.getDeploymentId()); flowTask.setProcDefName(pd.getName()); flowTask.setProcDefVersion(pd.getVersion()); flowTask.setProcInsId(task.getProcessInstanceId()); // 流程发起人信息 HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery() .processInstanceId(task.getProcessInstanceId()) .singleResult(); SysUser startUser = sysUserService.selectUserById(Long.parseLong(historicProcessInstance.getStartUserId())); flowTask.setStartUserId(startUser.getUserId().toString()); flowTask.setStartUserName(startUser.getNickName()); flowTask.setStartDeptName(Objects.nonNull(startUser.getDept()) ? startUser.getDept().getDeptName() : ""); // 流程处理人信息 List<IdentityLink> identityLinksForTask = taskService.getIdentityLinksForTask(task.getId()); for (IdentityLink identityLink : identityLinksForTask) { // 绑定的是用户,查出用户姓名、部门 if (StringUtils.isNotBlank(identityLink.getUserId())) { SysUser sysUser = sysUserService.selectUserById(Long.parseLong(identityLink.getUserId())); if (Objects.nonNull(sysUser)) { flowTask.setAssigneeId(sysUser.getUserId()); if (Objects.nonNull(sysUser.getDept())) { flowTask.setAssigneeDeptName(sysUser.getDept().getDeptName()); } flowTask.setAssigneeName(sysUser.getNickName()); } // 绑定的是角色,查出角色名称 } else if (StringUtils.isNotBlank(identityLink.getGroupId())) { SysRole role = sysRoleService.selectRoleById(Long.parseLong(identityLink.getGroupId())); if (Objects.nonNull(role)) { flowTask.setAssigneeId(Long.parseLong(identityLink.getGroupId())); flowTask.setAssigneeDeptName("由拥有角色:【" + role.getRoleName() + "】的人处理"); flowTask.setAssigneeName("暂未处理"); } } } flowList.add(flowTask); } result.put("taskList", flowList); } /** * 获取流程节点数(总任务数,不包含开始、结束等特殊的,只统计UserTask类型的) * * @param processDefinitionId 流程定义id * @return */ private Long getTotalTaskNum(String processDefinitionId) { // 获取流程定义 ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() .processDefinitionId(processDefinitionId) .singleResult(); if (processDefinition == null) { throw new IllegalArgumentException("流程定义ID无效: " + processDefinitionId); } // 获取BPMN模型 BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId); if (bpmnModel == null) { throw new IllegalStateException("无法获取BPMN模型: " + processDefinitionId); } // 获取流程对象 Process process = bpmnModel.getProcessById(processDefinition.getKey()); if (process == null) { throw new IllegalStateException("无法获取流程对象: " + processDefinition.getKey()); } // 计算任务节点数量 Long taskNodeCount = 0L; List<FlowElement> flowElements = process.getFlowElements().stream().toList(); for (FlowElement flowElement : flowElements) { if (flowElement instanceof org.flowable.bpmn.model.UserTask) { taskNodeCount++; } } return taskNodeCount; } /** * 获取流程剩余未完成的任务数 * * @param processInstanceId * @return */ private Long getNotFinishedTaskNum(String processInstanceId) { return historyService.createHistoricTaskInstanceQuery().processInstanceId(processInstanceId).processUnfinished().count(); } /** * 获取待办任务数 * * @param processInstanceId * @return */ private Long getTodoTaskNum(String processInstanceId) { return taskService.createTaskQuery().active().processInstanceId(processInstanceId).count(); } /** * 获取当前环节的所有任务数 * * @param processInstanceId * @return */ private List<Task> getCurrentNodeTaskList(String processInstanceId) { return taskService.createTaskQuery().processDefinitionId(processInstanceId).list(); } } business/src/main/resources/mapper/ProjectProcessMapper.xml
@@ -18,7 +18,6 @@ <result column="area_code" property="areaCode" /> <!-- <result column="management_centralization" property="managementCentralization" />--> <result column="project_approval_type" property="projectApprovalType" /> <result column="investment_catalogue" property="investmentCatalogue" /> <result column="importance_type" property="importanceType" /> <result column="year" property="year" /> <result column="year_invest_amount" property="yearInvestAmount" /> flowable/src/main/java/com/ycl/common/enums/FlowComment.java
@@ -11,7 +11,8 @@ /** * 说明 */ NORMAL("1", "正常意见"), SUBMIT("submit", "/提交表单/提交审核,总之代表上报的操作"), NORMAL("1", "正常意见,审核通过的意思"), REBACK("2", "退回意见"), REJECT("3", "驳回意见"), DELEGATE("4", "委派意见"), flowable/src/main/java/com/ycl/controller/FlowTaskController.java
@@ -22,6 +22,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Map; /** * <p>工作流任务管理<p> @@ -84,7 +85,7 @@ } @ApiOperation(value = "流程初始化表单", response = FlowTaskDto.class) @ApiOperation(value = "获取流程关联的表单", response = FlowTaskDto.class) @GetMapping(value = "/flowFormData") public AjaxResult flowFormData(String deployId) { return flowTaskService.flowFormData(deployId); @@ -96,7 +97,16 @@ return flowTaskService.processVariables(taskId); } @ApiOperation(value = "审批任务") @ApiOperation(value = "完成提交表单任务/普通提交") @Log(title = "完成提交表单任务/普通提交", businessType = BusinessType.INSERT) @PostMapping("/complete/form/{taskId}") public AjaxResult completeSubmitForm(@ApiParam(value = "流程定义id") @PathVariable(value = "taskId") String taskId, @ApiParam(value = "变量集合,json对象") @RequestBody Map<String, Object> variables) { return flowTaskService.completeSubmitForm(taskId, variables); } @ApiOperation(value = "完成审批任务") @Log(title = "审批任务", businessType = BusinessType.UPDATE) @PostMapping(value = "/complete") public AjaxResult complete(@RequestBody FlowTaskVo flowTaskVo) { flowable/src/main/java/com/ycl/service/IFlowTaskService.java
@@ -5,6 +5,7 @@ import com.ycl.domain.vo.FlowTaskVo; import java.io.InputStream; import java.util.Map; /** * @author Tony @@ -213,4 +214,13 @@ * @return */ AjaxResult flowTaskInfo(String procInsId, String elementId); /** * 完成提交表单任务 * * @param taskId 任务id * @param variables 表单数据 * @return */ AjaxResult completeSubmitForm(String taskId, Map<String, Object> variables); } flowable/src/main/java/com/ycl/service/impl/FlowTaskServiceImpl.java
@@ -82,7 +82,7 @@ private final ISysFormService sysFormService; /** * 完成任务 * 完成审核任务 * * @param taskVo 请求实体参数 */ @@ -103,6 +103,25 @@ taskService.complete(taskVo.getTaskId(), taskVo.getVariables()); } return AjaxResult.success(); } /** * 完成表单提交任务/普通任务 * * @param taskId 任务id * @param variables 表单数据 * @return */ @Override public AjaxResult completeSubmitForm(String taskId, Map<String, Object> variables) { Task task = taskService.createTaskQuery().taskId(taskId).singleResult(); if (Objects.isNull(task)) { return AjaxResult.error("任务不存在"); } // variables.put(taskId + "formJson", variables.get("formJson")); taskService.addComment(taskId, task.getProcessInstanceId(), FlowComment.SUBMIT.getType(), "完成提交"); taskService.complete(taskId, variables); return AjaxResult.success("提交成功"); } /** @@ -1114,7 +1133,13 @@ } else { parameters = taskService.getVariables(taskId); } JSONObject oldVariables = JSONObject.parseObject(JSON.toJSONString(parameters.get("formJson"))); if (Objects.isNull(oldVariables)) { // 如果是空的,直接使用主表单 String deploymentId = runtimeService.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult().getDeploymentId(); return this.flowFormData(deploymentId); } List<JSONObject> oldFields = JSON.parseObject(JSON.toJSONString(oldVariables.get("widgetList")), new TypeReference<List<JSONObject>>() { }); // 设置已填写的表单为禁用状态 @@ -1215,6 +1240,7 @@ return AjaxResult.success(flowTask); } /** * 将Object类型的数据转化成Map<String,Object> * start/src/main/resources/application.yml
@@ -41,6 +41,8 @@ level: com.ruoyi: debug org.springframework: warn org.flowable.engine.impl.persistence.entity.*: debug org.flowable.task.service.impl.persistence.entity.*: debug # 用户配置 user: @@ -78,7 +80,7 @@ # 令牌密钥 secret: gfabcdefghijklmnopqrstuvwxyz12 # 令牌有效期(默认30分钟) expireTime: 1200 expireTime: 10000 # PageHelper分页插件 pagehelper: @@ -107,13 +109,13 @@ # 工作流 Flowable 配置,flowable完整的配置,参见:@see https://www.flowable.com/open-source/docs/bpmn/ch05a-Spring-Boot/#flowable-application-properties flowable: database-schema-update: true # 自动更新flowable表结构,第一次连接数据库时可以设置为true # 关闭各个模块生成表,目前只使用工作流基础表 # # 关闭各个模块生成表,目前只使用工作流基础表 idm: # idm是flowable的身份管理模块:即用户、认证、权限等 enabled: false cmmn: # cmmn是flowable的案例管理模块,体现在flowable-ui中的:流程建模器、UI,@see https://www.flowable.com/open-source/docs/cmmn/ch06-cmmn enabled: true dmn: # dmn是flowable的决策模型,体现在flowable-ui中的决策表 @see https://www.flowable.com/open-source/docs/dmn/ch06-DMN-Introduction enabled: false app: # app的功能是为flowable在spring中高效运行而提供了很多bean,与flowable本身的内容无关 enabled: true check-process-definitions: true # cmmn: # cmmn是flowable的案例管理模块,体现在flowable-ui中的:流程建模器、UI,@see https://www.flowable.com/open-source/docs/cmmn/ch06-cmmn # enabled: true # dmn: # dmn是flowable的决策模型,体现在flowable-ui中的决策表 @see https://www.flowable.com/open-source/docs/dmn/ch06-DMN-Introduction # enabled: false # app: # app的功能是为flowable在spring中高效运行而提供了很多bean,与flowable本身的内容无关 # enabled: true # check-process-definitions: true