diff --git a/src/main/java/com/qs/serve/modules/bpm/conf/FlowServiceFactory.java b/src/main/java/com/qs/serve/modules/bpm/conf/FlowServiceFactory.java index ae06a15..6078bc2 100644 --- a/src/main/java/com/qs/serve/modules/bpm/conf/FlowServiceFactory.java +++ b/src/main/java/com/qs/serve/modules/bpm/conf/FlowServiceFactory.java @@ -1,5 +1,6 @@ package com.qs.serve.modules.bpm.conf; +import com.qs.serve.modules.bpm.service.BpmFormService; import lombok.Getter; import org.flowable.engine.*; import org.springframework.beans.factory.annotation.Qualifier; @@ -27,8 +28,8 @@ public class FlowServiceFactory { @Resource protected TaskService taskService; - //@Resource - //protected FormService formService; + @Resource + protected BpmFormService formService; @Resource protected HistoryService historyService; diff --git a/src/main/java/com/qs/serve/modules/bpm/controller/BpmDeployFormController.java b/src/main/java/com/qs/serve/modules/bpm/controller/BpmDeployFormController.java new file mode 100644 index 0000000..1f40587 --- /dev/null +++ b/src/main/java/com/qs/serve/modules/bpm/controller/BpmDeployFormController.java @@ -0,0 +1,103 @@ +package com.qs.serve.modules.bpm.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.qs.serve.common.model.annotation.SysLog; +import com.qs.serve.common.model.dto.PageVo; +import com.qs.serve.common.model.dto.R; +import com.qs.serve.common.model.enums.BizType; +import com.qs.serve.common.model.enums.SystemModule; +import com.qs.serve.common.util.PageUtil; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import com.qs.serve.modules.bpm.entity.BpmDeployForm; +import com.qs.serve.modules.bpm.service.BpmDeployFormService; + +import javax.validation.Valid; +import java.util.List; + +/** + * 工作流 部署表单关联 后台接口 + * @author YenHex + * @since 2022-08-23 + */ +@Slf4j +@AllArgsConstructor +@RestController +@RequestMapping("bpm/deployForm") +public class BpmDeployFormController { + + private BpmDeployFormService bpmDeployFormService; + + /** + * 翻页查询 + * @param param + * @return + */ + @GetMapping("/page") + @PreAuthorize("hasRole('bpm:deployForm:query')") + public R> getPage(BpmDeployForm param){ + PageUtil.startPage(); + LambdaQueryWrapper deployFormWrapper = new LambdaQueryWrapper<>(param); + List list = bpmDeployFormService.list(deployFormWrapper); + return R.byPageHelperList(list); + } + + /** + * 根据ID查询 + * @param id + * @return + */ + @GetMapping("/getById/{id}") + @SysLog(module = SystemModule.BPM, title = "部署表单关联", biz = BizType.QUERY) + @PreAuthorize("hasRole('bpm:deployForm:query')") + public R getById(@PathVariable("id") String id){ + BpmDeployForm bpmDeployForm = bpmDeployFormService.getById(id); + return R.ok(bpmDeployForm); + } + + + + /** + * 根据ID更新 + * @param param + * @return + */ + @PostMapping("/updateById") + @SysLog(module = SystemModule.BPM, title = "部署表单关联", biz = BizType.UPDATE) + @PreAuthorize("hasRole('bpm:deployForm:update')") + public R updateById(@RequestBody @Valid BpmDeployForm param){ + boolean result = bpmDeployFormService.updateById(param); + return R.isTrue(result); + } + + /** + * 新增部署表单关联 + * @param param + * @return + */ + @PostMapping("/save") + @SysLog(module = SystemModule.BPM, title = "部署表单关联", biz = BizType.INSERT) + @PreAuthorize("hasRole('bpm:deployForm:insert')") + public R save(@RequestBody @Valid BpmDeployForm param){ + boolean result = bpmDeployFormService.save(param); + return R.isTrue(result); + } + + /** + * 删除部署表单关联 + * @param id + * @return + */ + @DeleteMapping("/deleteById/{id}") + @SysLog(module = SystemModule.BPM, title = "部署表单关联", biz = BizType.DELETE) + @PreAuthorize("hasRole('bpm:deployForm:delete')") + public R deleteById(@PathVariable("id") String id){ + boolean result = bpmDeployFormService.removeById(id); + return R.isTrue(result); + } + +} + diff --git a/src/main/java/com/qs/serve/modules/bpm/controller/BpmFlowDefinitionController.java b/src/main/java/com/qs/serve/modules/bpm/controller/BpmFlowDefinitionController.java deleted file mode 100644 index 20a22c1..0000000 --- a/src/main/java/com/qs/serve/modules/bpm/controller/BpmFlowDefinitionController.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.qs.serve.modules.bpm.controller; - -import lombok.AllArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * 流程定义 - * @author YenHex - * @since 2022/8/18 - */ -@Slf4j -@AllArgsConstructor -@RestController -@RequestMapping("bpm/definition") -public class BpmFlowDefinitionController { - - //流程定义列表 - //保存流程 - //获取流程 - //删除流程 - //启动流程实例 - //激活或挂起流程定义 - // - -} diff --git a/src/main/java/com/qs/serve/modules/bpm/controller/FlowDefinitionController.java b/src/main/java/com/qs/serve/modules/bpm/controller/FlowDefinitionController.java new file mode 100644 index 0000000..e55f1c7 --- /dev/null +++ b/src/main/java/com/qs/serve/modules/bpm/controller/FlowDefinitionController.java @@ -0,0 +1,94 @@ +package com.qs.serve.modules.bpm.controller; + +import com.qs.serve.common.model.dto.PageVo; +import com.qs.serve.common.model.dto.R; +import com.qs.serve.common.util.PageUtil; +import com.qs.serve.modules.bpm.entity.dto.DeployBpmnBo; +import com.qs.serve.modules.bpm.entity.dto.FlowProcDefDto; +import com.qs.serve.modules.bpm.service.IFlowDefinitionService; +import com.qs.serve.modules.sys.service.SysRoleService; +import com.qs.serve.modules.sys.service.SysUserService; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +/** + * 流程定义 + * @author YenHex + * @since 2022/8/18 + */ +@Slf4j +@AllArgsConstructor +@RestController +@RequestMapping("bpm/definition") +public class FlowDefinitionController { + + private IFlowDefinitionService flowDefinitionService; + + private SysUserService userService; + + private SysRoleService sysRoleService; + + /** + * 流程定义列表 + * @param name + * @return + */ + @GetMapping(value = "/list") + public R> list(@RequestParam(required = false) String name) { + PageUtil.startPage(); + List list = flowDefinitionService.list(name); + return R.byPageHelperList(list); + } + + /** + * 部署流程 + * @param deployBpmnBo + * @return + */ + @PostMapping("/deploy") + public R deploy(@RequestBody DeployBpmnBo deployBpmnBo){ + return R.ok(); + } + + /** + * 根据流程定义id启动流程实例 + * @param procDefId 流程定义id + * @param variables 变量集合,json对象 + * @return + */ + @PostMapping("/start/{procDefId}") + public R start(@PathVariable(value = "procDefId") String procDefId, @RequestBody Map variables) { + flowDefinitionService.startProcessInstanceById(procDefId, variables); + return R.ok(); + } + + /** + * 激活或挂起流程定义 + * @param state 1:激活,2:挂起 + * @param deployId 流程部署ID + * @return + */ + @PutMapping(value = "/updateState") + public R updateState(@RequestParam Integer state,@RequestParam String deployId) { + flowDefinitionService.updateState(state, deployId); + return R.ok(); + } + + /** + * 删除流程 + * @param deployIds + * @return + */ + @DeleteMapping(value = "/delete") + public R delete(String[] deployIds) { + for (String deployId : deployIds) { + flowDefinitionService.delete(deployId); + } + return R.ok(); + } + +} diff --git a/src/main/java/com/qs/serve/modules/bpm/controller/FlowInstanceController.java b/src/main/java/com/qs/serve/modules/bpm/controller/FlowInstanceController.java new file mode 100644 index 0000000..ac8ebfc --- /dev/null +++ b/src/main/java/com/qs/serve/modules/bpm/controller/FlowInstanceController.java @@ -0,0 +1,72 @@ +package com.qs.serve.modules.bpm.controller; + +import com.qs.serve.common.model.dto.R; +import com.qs.serve.modules.bpm.entity.vo.FlowTaskVo; +import com.qs.serve.modules.bpm.service.IFlowInstanceService; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +/** + * 流程实例管理 + * @author YenHex + * @since 2022/8/23 + */ +@Slf4j +@AllArgsConstructor +@RestController +@RequestMapping("bpm/instance") +public class FlowInstanceController { + + private IFlowInstanceService flowInstanceService; + + /** + * 根据流程定义id启动流程实例 + * @param procDefId 流程定义id + * @param variables 变量集合,json对象 + * @return + */ + @PostMapping("/startBy/{procDefId}") + public R startById(@PathVariable(value = "procDefId") String procDefId, @RequestBody Map variables) { + flowInstanceService.startProcessInstanceById(procDefId, variables); + return R.ok(); + } + + /** + * 激活或挂起流程实例 + * @param state 1:激活,2:挂起 + * @param instanceId 流程实例ID + * @return + */ + @PostMapping(value = "/updateState") + public R updateState( @RequestParam Integer state, @RequestParam String instanceId) { + flowInstanceService.updateState(state,instanceId); + return R.ok(); + } + + /** + * 结束流程实例 + * @param flowTaskVo + * @return + */ + @PostMapping(value = "/stopProcessInstance") + public R stopProcessInstance(@RequestBody FlowTaskVo flowTaskVo) { + flowInstanceService.stopProcessInstance(flowTaskVo); + return R.ok(); + } + + /** + * 删除流程实例 + * @param instanceId 流程实例ID + * @param deleteReason 删除原因 + * @return + */ + @DeleteMapping(value = "/delete") + public R delete(@RequestParam String instanceId, @RequestParam(required = false) String deleteReason) { + flowInstanceService.delete(instanceId,deleteReason); + return R.ok(); + } + +} diff --git a/src/main/java/com/qs/serve/modules/bpm/entity/BpmDeployForm.java b/src/main/java/com/qs/serve/modules/bpm/entity/BpmDeployForm.java new file mode 100644 index 0000000..55ab500 --- /dev/null +++ b/src/main/java/com/qs/serve/modules/bpm/entity/BpmDeployForm.java @@ -0,0 +1,74 @@ +package com.qs.serve.modules.bpm.entity; + +import java.time.LocalDateTime; +import java.io.Serializable; + +import com.baomidou.mybatisplus.annotation.*; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import org.hibernate.validator.constraints.Length; +import org.springframework.format.annotation.DateTimeFormat; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.NotBlank; + +/** + * 部署表单关联 实体类 + * @author YenHex + * @since 2022-08-23 + */ +@Data +@TableName("bpm_deploy_form") +public class BpmDeployForm implements Serializable { + + private static final long serialVersionUID = 1L; + + /** id */ + @TableId(type = IdType.AUTO) + private Long id; + + /** 表单主键 */ + private Long formId; + + /** 流程定义主键 */ + @Length(max = 32,message = "流程定义主键长度不能超过32字") + private String deployId; + + /** 备注 */ + @Length(max = 255,message = "备注长度不能超过255字") + private String remark; + + /** 创建时间 */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** 创建人 */ + @TableField(fill = FieldFill.INSERT) + private String createBy; + + /** 更新时间 */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") + @TableField(fill = FieldFill.UPDATE) + private LocalDateTime updateTime; + + /** 更新人 */ + @TableField(fill = FieldFill.UPDATE) + private String updateBy; + + /** 租户id */ + @JsonIgnore + @JsonProperty + private String tenantId; + + /** 删除标识 */ + @JsonIgnore + @JsonProperty + private Boolean delFlag; + +} + diff --git a/src/main/java/com/qs/serve/modules/bpm/entity/dto/BpmnDto.java b/src/main/java/com/qs/serve/modules/bpm/entity/dto/BpmnDto.java deleted file mode 100644 index af8aa55..0000000 --- a/src/main/java/com/qs/serve/modules/bpm/entity/dto/BpmnDto.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.qs.serve.modules.bpm.entity.dto; - -import lombok.Data; - -/** - * @author YenHex - * @since 2022/8/15 - */ -@Data -public class BpmnDto { - - private static class StartEvent{ - private String id; - private String name; - } - private static class EndEvent{ - private String id; - private String name; - } - private static class ServiceTask{ - private String id; - private String name; - private String activitiClass; - } - -} diff --git a/src/main/java/com/qs/serve/modules/bpm/entity/dto/DeployBpmnBo.java b/src/main/java/com/qs/serve/modules/bpm/entity/dto/DeployBpmnBo.java new file mode 100644 index 0000000..f94f85c --- /dev/null +++ b/src/main/java/com/qs/serve/modules/bpm/entity/dto/DeployBpmnBo.java @@ -0,0 +1,23 @@ +package com.qs.serve.modules.bpm.entity.dto; + +import lombok.Data; + +import java.util.List; + +/** + * @author YenHex + * @since 2022/8/15 + */ +@Data +public class DeployBpmnBo { + + /** 流程定义名称 */ + private String definitionName; + + /** 流程定义名称 */ + private String groupName; + + /** 前端视图对象 */ + private DeployViewerDto viewer; + +} diff --git a/src/main/java/com/qs/serve/modules/bpm/entity/dto/DeployViewerDto.java b/src/main/java/com/qs/serve/modules/bpm/entity/dto/DeployViewerDto.java new file mode 100644 index 0000000..a6344e0 --- /dev/null +++ b/src/main/java/com/qs/serve/modules/bpm/entity/dto/DeployViewerDto.java @@ -0,0 +1,31 @@ +package com.qs.serve.modules.bpm.entity.dto; + +import lombok.Data; +import java.util.List; +/** + * @author YenHex + * @since 2022/8/15 + */ +@Data +public class DeployViewerDto { + + private String nodeId; + + private String nodeLabel; + + /** + * approval -- userTask + * branch -- ex-gateway x2 + * condition -- flow + * stat + * end + */ + private String type; + + private List conditions; + + private List conditionNodes; + + private List childNode; + +} diff --git a/src/main/java/com/qs/serve/modules/bpm/entity/dto/FlowNextDto.java b/src/main/java/com/qs/serve/modules/bpm/entity/dto/FlowNextDto.java index 3ca5a40..1f1a16b 100644 --- a/src/main/java/com/qs/serve/modules/bpm/entity/dto/FlowNextDto.java +++ b/src/main/java/com/qs/serve/modules/bpm/entity/dto/FlowNextDto.java @@ -2,6 +2,7 @@ package com.qs.serve.modules.bpm.entity.dto; import com.qs.serve.modules.sys.entity.SysRole; import com.qs.serve.modules.sys.entity.SysUser; +import lombok.Data; import java.io.Serializable; import java.util.List; @@ -11,6 +12,7 @@ import java.util.List; * @author YenHex * @since 2022/8/18 */ +@Data public class FlowNextDto implements Serializable { private String type; diff --git a/src/main/java/com/qs/serve/modules/bpm/entity/dto/FlowTaskDto.java b/src/main/java/com/qs/serve/modules/bpm/entity/dto/FlowTaskDto.java index 1855698..e12ab5d 100644 --- a/src/main/java/com/qs/serve/modules/bpm/entity/dto/FlowTaskDto.java +++ b/src/main/java/com/qs/serve/modules/bpm/entity/dto/FlowTaskDto.java @@ -36,7 +36,7 @@ public class FlowTaskDto { /** * 任务执行人Id */ - private Long assigneeId; + private String assigneeId; /** * 部门名称 diff --git a/src/main/java/com/qs/serve/modules/bpm/mapper/BpmDeployFormMapper.java b/src/main/java/com/qs/serve/modules/bpm/mapper/BpmDeployFormMapper.java new file mode 100644 index 0000000..4664529 --- /dev/null +++ b/src/main/java/com/qs/serve/modules/bpm/mapper/BpmDeployFormMapper.java @@ -0,0 +1,14 @@ +package com.qs.serve.modules.bpm.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.qs.serve.modules.bpm.entity.BpmDeployForm; + +/** + * 部署表单关联 Mapper + * @author YenHex + * @date 2022-08-23 + */ +public interface BpmDeployFormMapper extends BaseMapper { + +} + diff --git a/src/main/java/com/qs/serve/modules/bpm/service/BpmDeployFormService.java b/src/main/java/com/qs/serve/modules/bpm/service/BpmDeployFormService.java new file mode 100644 index 0000000..b9d4b67 --- /dev/null +++ b/src/main/java/com/qs/serve/modules/bpm/service/BpmDeployFormService.java @@ -0,0 +1,17 @@ +package com.qs.serve.modules.bpm.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.qs.serve.modules.bpm.entity.BpmDeployForm; +import com.qs.serve.modules.bpm.entity.BpmForm; + +/** + * 部署表单关联 服务接口 + * @author YenHex + * @date 2022-08-23 + */ +public interface BpmDeployFormService extends IService { + + BpmForm selectSysDeployFormByDeployId(String deployId); + +} + diff --git a/src/main/java/com/qs/serve/modules/bpm/service/impl/BpmDeployFormServiceImpl.java b/src/main/java/com/qs/serve/modules/bpm/service/impl/BpmDeployFormServiceImpl.java new file mode 100644 index 0000000..aece7cc --- /dev/null +++ b/src/main/java/com/qs/serve/modules/bpm/service/impl/BpmDeployFormServiceImpl.java @@ -0,0 +1,37 @@ +package com.qs.serve.modules.bpm.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.qs.serve.modules.bpm.entity.BpmForm; +import com.qs.serve.modules.bpm.mapper.BpmFormMapper; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import com.qs.serve.modules.bpm.entity.BpmDeployForm; +import com.qs.serve.modules.bpm.service.BpmDeployFormService; +import com.qs.serve.modules.bpm.mapper.BpmDeployFormMapper; + +/** + * 部署表单关联 服务实现类 + * @author YenHex + * @since 2022-08-23 + */ +@Slf4j +@Service +@AllArgsConstructor +public class BpmDeployFormServiceImpl extends ServiceImpl implements BpmDeployFormService { + + private final BpmFormMapper bpmFormMapper; + + @Override + public BpmForm selectSysDeployFormByDeployId(String deployId) { + LambdaQueryWrapper lqw = new LambdaQueryWrapper<>(); + lqw.eq(BpmDeployForm::getDeployId,deployId); + BpmDeployForm deployForm = getOne(lqw); + if(deployForm!=null){ + return bpmFormMapper.selectById(deployForm.getFormId()); + } + return null; + } +} + diff --git a/src/main/java/com/qs/serve/modules/bpm/service/impl/IFlowTaskServiceImpl.java b/src/main/java/com/qs/serve/modules/bpm/service/impl/IFlowTaskServiceImpl.java index 5c5942c..b9cabd0 100644 --- a/src/main/java/com/qs/serve/modules/bpm/service/impl/IFlowTaskServiceImpl.java +++ b/src/main/java/com/qs/serve/modules/bpm/service/impl/IFlowTaskServiceImpl.java @@ -1,21 +1,31 @@ package com.qs.serve.modules.bpm.service.impl; +import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; import com.qs.serve.common.model.dto.R; import com.qs.serve.common.util.Assert; import com.qs.serve.common.util.AuthContextUtils; import com.qs.serve.common.util.CollectionUtil; +import com.qs.serve.modules.bpm.common.consts.ProcessConstants; import com.qs.serve.modules.bpm.common.enums.FlowComment; +import com.qs.serve.modules.bpm.common.util.FindNextNodeUtil; import com.qs.serve.modules.bpm.common.util.FlowableUtils; import com.qs.serve.modules.bpm.conf.FlowServiceFactory; +import com.qs.serve.modules.bpm.entity.BpmForm; +import com.qs.serve.modules.bpm.entity.dto.FlowCommentDto; +import com.qs.serve.modules.bpm.entity.dto.FlowNextDto; import com.qs.serve.modules.bpm.entity.dto.FlowTaskDto; +import com.qs.serve.modules.bpm.entity.dto.FlowViewerDto; import com.qs.serve.modules.bpm.entity.vo.FlowTaskVo; +import com.qs.serve.modules.bpm.service.BpmDeployFormService; import com.qs.serve.modules.bpm.service.IFlowTaskService; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.google.common.collect.Lists; import com.qs.serve.modules.sys.entity.SysDept; +import com.qs.serve.modules.sys.entity.SysRole; import com.qs.serve.modules.sys.entity.SysUser; import com.qs.serve.modules.sys.service.SysDeptService; +import com.qs.serve.modules.sys.service.SysRoleService; import com.qs.serve.modules.sys.service.SysUserService; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -67,6 +77,8 @@ public class IFlowTaskServiceImpl extends FlowServiceFactory implements IFlowTas private final SysUserService sysUserService; private final SysDeptService sysDeptService; + private final SysRoleService sysRoleService; + private final BpmDeployFormService bpmDeployFormService; /** * 完成任务 @@ -99,7 +111,118 @@ public class IFlowTaskServiceImpl extends FlowServiceFactory implements IFlowTas @Override public void taskReject(FlowTaskVo flowTaskVo) { if (taskService.createTaskQuery().taskId(flowTaskVo.getTaskId()).singleResult().isSuspended()) { - Assert.throwEx("任务处于挂起状态"); + Assert.throwEx("任务处于挂起状态!"); + } + // 当前任务 task + Task task = taskService.createTaskQuery().taskId(flowTaskVo.getTaskId()).singleResult(); + // 获取流程定义信息 + ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(task.getProcessDefinitionId()).singleResult(); + // 获取所有节点信息 + Process process = repositoryService.getBpmnModel(processDefinition.getId()).getProcesses().get(0); + // 获取全部节点列表,包含子节点 + Collection allElements = FlowableUtils.getAllElements(process.getFlowElements(), null); + // 获取当前任务节点元素 + FlowElement source = null; + if (allElements != null) { + for (FlowElement flowElement : allElements) { + // 类型为用户节点 + if (flowElement.getId().equals(task.getTaskDefinitionKey())) { + // 获取节点信息 + source = flowElement; + } + } + } + + // 目的获取所有跳转到的节点 targetIds + // 获取当前节点的所有父级用户任务节点 + // 深度优先算法思想:延边迭代深入 + List parentUserTaskList = FlowableUtils.iteratorFindParentUserTasks(source, null, null); + if (parentUserTaskList == null || parentUserTaskList.size() == 0) { + Assert.throwEx("当前节点为初始任务节点,不能驳回"); + } + // 获取活动 ID 即节点 Key + List parentUserTaskKeyList = new ArrayList<>(); + parentUserTaskList.forEach(item -> parentUserTaskKeyList.add(item.getId())); + // 获取全部历史节点活动实例,即已经走过的节点历史,数据采用开始时间升序 + List historicTaskInstanceList = historyService.createHistoricTaskInstanceQuery().processInstanceId(task.getProcessInstanceId()).orderByHistoricTaskInstanceStartTime().asc().list(); + // 数据清洗,将回滚导致的脏数据清洗掉 + List lastHistoricTaskInstanceList = FlowableUtils.historicTaskInstanceClean(allElements, historicTaskInstanceList); + // 此时历史任务实例为倒序,获取最后走的节点 + List targetIds = new ArrayList<>(); + // 循环结束标识,遇到当前目标节点的次数 + int number = 0; + StringBuilder parentHistoricTaskKey = new StringBuilder(); + for (String historicTaskInstanceKey : lastHistoricTaskInstanceList) { + // 当会签时候会出现特殊的,连续都是同一个节点历史数据的情况,这种时候跳过 + if (parentHistoricTaskKey.toString().equals(historicTaskInstanceKey)) { + continue; + } + parentHistoricTaskKey = new StringBuilder(historicTaskInstanceKey); + if (historicTaskInstanceKey.equals(task.getTaskDefinitionKey())) { + number++; + } + // 在数据清洗后,历史节点就是唯一一条从起始到当前节点的历史记录,理论上每个点只会出现一次 + // 在流程中如果出现循环,那么每次循环中间的点也只会出现一次,再出现就是下次循环 + // number == 1,第一次遇到当前节点 + // number == 2,第二次遇到,代表最后一次的循环范围 + if (number == 2) { + break; + } + // 如果当前历史节点,属于父级的节点,说明最后一次经过了这个点,需要退回这个点 + if (parentUserTaskKeyList.contains(historicTaskInstanceKey)) { + targetIds.add(historicTaskInstanceKey); + } + } + + + // 目的获取所有需要被跳转的节点 currentIds + // 取其中一个父级任务,因为后续要么存在公共网关,要么就是串行公共线路 + UserTask oneUserTask = parentUserTaskList.get(0); + // 获取所有正常进行的任务节点 Key,这些任务不能直接使用,需要找出其中需要撤回的任务 + List runTaskList = taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).list(); + List runTaskKeyList = new ArrayList<>(); + runTaskList.forEach(item -> runTaskKeyList.add(item.getTaskDefinitionKey())); + // 需驳回任务列表 + List currentIds = new ArrayList<>(); + // 通过父级网关的出口连线,结合 runTaskList 比对,获取需要撤回的任务 + List currentUserTaskList = FlowableUtils.iteratorFindChildUserTasks(oneUserTask, runTaskKeyList, null, null); + currentUserTaskList.forEach(item -> currentIds.add(item.getId())); + + + // 规定:并行网关之前节点必须需存在唯一用户任务节点,如果出现多个任务节点,则并行网关节点默认为结束节点,原因为不考虑多对多情况 + if (targetIds.size() > 1 && currentIds.size() > 1) { + Assert.throwEx("任务出现多对多情况,无法撤回"); + } + + // 循环获取那些需要被撤回的节点的ID,用来设置驳回原因 + List currentTaskIds = new ArrayList<>(); + currentIds.forEach(currentId -> runTaskList.forEach(runTask -> { + if (currentId.equals(runTask.getTaskDefinitionKey())) { + currentTaskIds.add(runTask.getId()); + } + })); + // 设置驳回意见 + currentTaskIds.forEach(item -> taskService.addComment(item, task.getProcessInstanceId(), FlowComment.REJECT.getType(), flowTaskVo.getComment())); + + try { + // 如果父级任务多于 1 个,说明当前节点不是并行节点,原因为不考虑多对多情况 + if (targetIds.size() > 1) { + // 1 对 多任务跳转,currentIds 当前节点(1),targetIds 跳转到的节点(多) + runtimeService.createChangeActivityStateBuilder() + .processInstanceId(task.getProcessInstanceId()). + moveSingleActivityIdToActivityIds(currentIds.get(0), targetIds).changeState(); + } + // 如果父级任务只有一个,因此当前任务可能为网关中的任务 + if (targetIds.size() == 1) { + // 1 对 1 或 多 对 1 情况,currentIds 当前要跳转的节点列表(1或多),targetIds.get(0) 跳转到的节点(1) + runtimeService.createChangeActivityStateBuilder() + .processInstanceId(task.getProcessInstanceId()) + .moveActivityIdsToSingleActivityId(currentIds, targetIds.get(0)).changeState(); + } + } catch (FlowableObjectNotFoundException e) { + Assert.throwEx("未找到流程实例,流程可能已发生变化"); + } catch (FlowableException e) { + Assert.throwEx("无法取消或开始活动"); } } @@ -109,7 +232,72 @@ public class IFlowTaskServiceImpl extends FlowServiceFactory implements IFlowTas */ @Override public void taskReturn(FlowTaskVo flowTaskVo) { + if (taskService.createTaskQuery().taskId(flowTaskVo.getTaskId()).singleResult().isSuspended()) { + Assert.throwEx("任务处于挂起状态"); + } + // 当前任务 task + Task task = taskService.createTaskQuery().taskId(flowTaskVo.getTaskId()).singleResult(); + // 获取流程定义信息 + ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(task.getProcessDefinitionId()).singleResult(); + // 获取所有节点信息 + Process process = repositoryService.getBpmnModel(processDefinition.getId()).getProcesses().get(0); + // 获取全部节点列表,包含子节点 + Collection allElements = FlowableUtils.getAllElements(process.getFlowElements(), null); + // 获取当前任务节点元素 + FlowElement source = null; + // 获取跳转的节点元素 + FlowElement target = null; + if (allElements != null) { + for (FlowElement flowElement : allElements) { + // 当前任务节点元素 + if (flowElement.getId().equals(task.getTaskDefinitionKey())) { + source = flowElement; + } + // 跳转的节点元素 + if (flowElement.getId().equals(flowTaskVo.getTargetKey())) { + target = flowElement; + } + } + } + // 从当前节点向前扫描 + // 如果存在路线上不存在目标节点,说明目标节点是在网关上或非同一路线上,不可跳转 + // 否则目标节点相对于当前节点,属于串行 + Boolean isSequential = FlowableUtils.iteratorCheckSequentialReferTarget(source, flowTaskVo.getTargetKey(), null, null); + if (!isSequential) { + Assert.throwEx("当前节点相对于目标节点,不属于串行关系,无法回退"); + } + + // 获取所有正常进行的任务节点 Key,这些任务不能直接使用,需要找出其中需要撤回的任务 + List runTaskList = taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).list(); + List runTaskKeyList = new ArrayList<>(); + runTaskList.forEach(item -> runTaskKeyList.add(item.getTaskDefinitionKey())); + // 需退回任务列表 + List currentIds = new ArrayList<>(); + // 通过父级网关的出口连线,结合 runTaskList 比对,获取需要撤回的任务 + List currentUserTaskList = FlowableUtils.iteratorFindChildUserTasks(target, runTaskKeyList, null, null); + currentUserTaskList.forEach(item -> currentIds.add(item.getId())); + + // 循环获取那些需要被撤回的节点的ID,用来设置驳回原因 + List currentTaskIds = new ArrayList<>(); + currentIds.forEach(currentId -> runTaskList.forEach(runTask -> { + if (currentId.equals(runTask.getTaskDefinitionKey())) { + currentTaskIds.add(runTask.getId()); + } + })); + // 设置回退意见 + currentTaskIds.forEach(currentTaskId -> taskService.addComment(currentTaskId, task.getProcessInstanceId(), FlowComment.REBACK.getType(), flowTaskVo.getComment())); + + try { + // 1 对 1 或 多 对 1 情况,currentIds 当前要跳转的节点列表(1或多),targetKey 跳转到的节点(1) + runtimeService.createChangeActivityStateBuilder() + .processInstanceId(task.getProcessInstanceId()) + .moveActivityIdsToSingleActivityId(currentIds, flowTaskVo.getTargetKey()).changeState(); + } catch (FlowableObjectNotFoundException e) { + Assert.throwEx("未找到流程实例,流程可能已发生变化"); + } catch (FlowableException e) { + Assert.throwEx("无法取消或开始活动"); + } } /** @@ -388,34 +576,290 @@ public class IFlowTaskServiceImpl extends FlowServiceFactory implements IFlowTas return R.ok(page); } + /** + * 已办任务列表 + * @param pageNum 当前页码 + * @param pageSize 每页条数 + * @return + */ @Override public R finishedList(Integer pageNum, Integer pageSize) { - return null; + Page page = new Page<>(); + String userId = AuthContextUtils.getSysUserId(); + HistoricTaskInstanceQuery taskInstanceQuery = historyService.createHistoricTaskInstanceQuery() + .includeProcessVariables() + .finished() + .taskAssignee(userId) + .orderByHistoricTaskInstanceEndTime() + .desc(); + List historicTaskInstanceList = taskInstanceQuery.listPage(pageSize * (pageNum - 1), pageSize); + List hisTaskList = Lists.newArrayList(); + for (HistoricTaskInstance histTask : historicTaskInstanceList) { + FlowTaskDto flowTask = new FlowTaskDto(); + // 当前流程信息 + flowTask.setTaskId(histTask.getId()); + // 审批人员信息 + flowTask.setCreateTime(histTask.getCreateTime()); + flowTask.setFinishTime(histTask.getEndTime()); + flowTask.setDuration(getDate(histTask.getDurationInMillis())); + flowTask.setProcDefId(histTask.getProcessDefinitionId()); + flowTask.setTaskDefKey(histTask.getTaskDefinitionKey()); + flowTask.setTaskName(histTask.getName()); + + // 流程定义信息 + ProcessDefinition pd = repositoryService.createProcessDefinitionQuery() + .processDefinitionId(histTask.getProcessDefinitionId()) + .singleResult(); + flowTask.setDeployId(pd.getDeploymentId()); + flowTask.setProcDefName(pd.getName()); + flowTask.setProcDefVersion(pd.getVersion()); + flowTask.setProcInsId(histTask.getProcessInstanceId()); + flowTask.setHisProcInsId(histTask.getProcessInstanceId()); + + // 流程发起人信息 + HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery() + .processInstanceId(histTask.getProcessInstanceId()) + .singleResult(); + SysUser startUser = sysUserService.getById(historicProcessInstance.getStartUserId()); + flowTask.setStartUserId(startUser.getId()); + flowTask.setStartUserName(startUser.getName()); + if(startUser.getDeptId()!=null){ + SysDept sysDept = sysDeptService.getById(startUser.getDeptId()); + flowTask.setStartDeptName(sysDept!=null?sysDept.getName():null); + } + hisTaskList.add(flowTask); + } + page.setTotal(taskInstanceQuery.count()); + page.setRecords(hisTaskList); +// Map result = new HashMap<>(); +// result.put("result",page); +// result.put("finished",true); + return R.ok(page); } + /** + * 流程历史流转记录 + * @param procInsId 流程实例Id + * @param deployId + * @return + */ @Override public R flowRecord(String procInsId, String deployId) { - return null; + Map map = new HashMap(); + if (StringUtils.isNotBlank(procInsId)) { + List list = historyService + .createHistoricActivityInstanceQuery() + .processInstanceId(procInsId) + .orderByHistoricActivityInstanceStartTime() + .desc().list(); + List hisFlowList = new ArrayList<>(); + for (HistoricActivityInstance histIns : list) { + if (StringUtils.isNotBlank(histIns.getTaskId())) { + FlowTaskDto flowTask = new FlowTaskDto(); + flowTask.setTaskId(histIns.getTaskId()); + flowTask.setTaskName(histIns.getActivityName()); + flowTask.setCreateTime(histIns.getStartTime()); + flowTask.setFinishTime(histIns.getEndTime()); + if (StringUtils.isNotBlank(histIns.getAssignee())) { + SysUser sysUser = sysUserService.getById(histIns.getAssignee()); + flowTask.setAssigneeId(sysUser.getId()); + flowTask.setAssigneeName(sysUser.getName()); + if(sysUser.getDeptId()!=null){ + SysDept sysDept = sysDeptService.getById(sysUser.getDeptId()); + flowTask.setDeptName(sysDept==null?null:sysDept.getName()); + } + } + // 展示审批人员 + List linksForTask = historyService.getHistoricIdentityLinksForTask(histIns.getTaskId()); + StringBuilder stringBuilder = new StringBuilder(); + for (HistoricIdentityLink identityLink : linksForTask) { + // 获选人,候选组/角色(多个) + if ("candidate".equals(identityLink.getType())) { + if (StringUtils.isNotBlank(identityLink.getUserId())) { + SysUser sysUser = sysUserService.getById(identityLink.getUserId()); + stringBuilder.append(sysUser.getName()).append(","); + } + if (StringUtils.isNotBlank(identityLink.getGroupId())) { + SysRole sysRole = sysRoleService.getById(identityLink.getGroupId()); + stringBuilder.append(sysRole.getName()).append(","); + } + } + } + if (StringUtils.isNotBlank(stringBuilder)) { + flowTask.setCandidate(stringBuilder.substring(0, stringBuilder.length() - 1)); + } + + flowTask.setDuration(histIns.getDurationInMillis() == null || histIns.getDurationInMillis() == 0 ? null : getDate(histIns.getDurationInMillis())); + // 获取意见评论内容 + List commentList = taskService.getProcessInstanceComments(histIns.getProcessInstanceId()); + commentList.forEach(comment -> { + if (histIns.getTaskId().equals(comment.getTaskId())) { + flowTask.setComment(FlowCommentDto.builder().type(comment.getType()).comment(comment.getFullMessage()).build()); + } + }); + hisFlowList.add(flowTask); + } + } + map.put("flowList", hisFlowList); +// // 查询当前任务是否完成 +// List taskList = taskService.createTaskQuery().processInstanceId(procInsId).list(); +// if (CollectionUtils.isNotEmpty(taskList)) { +// map.put("finished", true); +// } else { +// map.put("finished", false); +// } + } + // 第一次申请获取初始化表单 + if (StringUtils.isNotBlank(deployId)) { + BpmForm sysForm = bpmDeployFormService.selectSysDeployFormByDeployId(deployId); + if (Objects.isNull(sysForm)) { + return R.error("请先配置流程表单"); + } + map.put("formData", JSONObject.parseObject(sysForm.getFormContext())); + } + return R.ok(map); } + /** + * 根据任务ID查询挂载的表单信息 + * @param taskId 任务Id + * @return + */ @Override public Task getTaskForm(String taskId) { - return null; + Task task = taskService.createTaskQuery().taskId(taskId).singleResult(); + return task; } + /** + * 获取流程执行过程 + * @param procInsId + * @param executionId + * @return + */ @Override public R getFlowViewer(String procInsId, String executionId) { - return null; + List flowViewerList = new ArrayList<>(); + FlowViewerDto flowViewerDto; + // 获取任务开始节点(临时处理方式) + List startNodeList = historyService.createHistoricActivityInstanceQuery() + .processInstanceId(procInsId) + .orderByHistoricActivityInstanceStartTime() + .asc().listPage(0,3); + for (HistoricActivityInstance startInstance : startNodeList) { + if (!"sequenceFlow".equals(startInstance.getActivityType())) { + flowViewerDto = new FlowViewerDto(); + if (!"sequenceFlow".equals(startInstance.getActivityType())) { + flowViewerDto.setKey(startInstance.getActivityId()); + // 根据流程节点处理时间校验改节点是否已完成 + flowViewerDto.setCompleted(!Objects.isNull(startInstance.getEndTime())); + flowViewerList.add(flowViewerDto); + } + } + } + // 历史节点 + List hisActIns = historyService.createHistoricActivityInstanceQuery() + .executionId(executionId) + .orderByHistoricActivityInstanceStartTime() + .asc().list(); + for (HistoricActivityInstance activityInstance : hisActIns) { + if (!"sequenceFlow".equals(activityInstance.getActivityType())) { + flowViewerDto = new FlowViewerDto(); + flowViewerDto.setKey(activityInstance.getActivityId()); + // 根据流程节点处理时间校验改节点是否已完成 + flowViewerDto.setCompleted(!Objects.isNull(activityInstance.getEndTime())); + flowViewerList.add(flowViewerDto); + } + } + return R.ok(flowViewerList); } + /** + * 获取流程变量 + * @param taskId + * @return + */ @Override public R processVariables(String taskId) { - return null; + // 流程变量 + HistoricTaskInstance historicTaskInstance = historyService.createHistoricTaskInstanceQuery().includeProcessVariables().finished().taskId(taskId).singleResult(); + if (Objects.nonNull(historicTaskInstance)) { + return R.ok(historicTaskInstance.getProcessVariables()); + } else { + Map variables = taskService.getVariables(taskId); + return R.ok(variables); + } } + /** + * 获取下一节点 + * @param flowTaskVo 任务 + * @return + */ @Override public R getNextFlowNode(FlowTaskVo flowTaskVo) { - return null; + List allUserList = sysUserService.list(); + // Step 1. 获取当前节点并找到下一步节点 + Task task = taskService.createTaskQuery().taskId(flowTaskVo.getTaskId()).singleResult(); + FlowNextDto flowNextDto = new FlowNextDto(); + if (Objects.nonNull(task)) { + // Step 2. 获取当前流程所有流程变量(网关节点时需要校验表达式) + Map variables = taskService.getVariables(task.getId()); + List nextUserTask = FindNextNodeUtil.getNextUserTasks(repositoryService, task, variables); + if (CollectionUtils.isNotEmpty(nextUserTask)) { + for (UserTask userTask : nextUserTask) { + MultiInstanceLoopCharacteristics multiInstance = userTask.getLoopCharacteristics(); + // 会签节点 + if (Objects.nonNull(multiInstance)) { + List list = allUserList; + flowNextDto.setVars(ProcessConstants.PROCESS_MULTI_INSTANCE_USER); + flowNextDto.setType(ProcessConstants.PROCESS_MULTI_INSTANCE); + flowNextDto.setUserList(list); + } else { + + // 读取自定义节点属性 判断是否是否需要动态指定任务接收人员、组 + String dataType = userTask.getAttributeValue(ProcessConstants.NAMASPASE, ProcessConstants.PROCESS_CUSTOM_DATA_TYPE); + String userType = userTask.getAttributeValue(ProcessConstants.NAMASPASE, ProcessConstants.PROCESS_CUSTOM_USER_TYPE); + + // 处理加载动态指定下一节点接收人员信息 + if (ProcessConstants.DATA_TYPE.equals(dataType)) { + // 指定单个人员 + if (ProcessConstants.USER_TYPE_ASSIGNEE.equals(userType)) { + List list = allUserList; + flowNextDto.setVars(ProcessConstants.PROCESS_APPROVAL); + flowNextDto.setType(ProcessConstants.USER_TYPE_ASSIGNEE); + flowNextDto.setUserList(list); + } + // 候选人员(多个) + if (ProcessConstants.USER_TYPE_USERS.equals(userType)) { + List list = allUserList; + flowNextDto.setVars(ProcessConstants.PROCESS_APPROVAL); + flowNextDto.setType(ProcessConstants.USER_TYPE_USERS); + flowNextDto.setUserList(list); + } + // 候选组 + if (ProcessConstants.USER_TYPE_ROUPS.equals(userType)) { + List sysRoles = sysRoleService.list(); + flowNextDto.setVars(ProcessConstants.PROCESS_APPROVAL); + flowNextDto.setType(ProcessConstants.USER_TYPE_ROUPS); + flowNextDto.setRoleList(sysRoles); + } + } else { + flowNextDto.setType(ProcessConstants.FIXED); + } + } + } + } else { + return R.ok("流程已完结", null); + } + } + return R.ok(flowNextDto); + } + + + private static Predicate distinctByKey(Function keyExtractor) { + Set seen = ConcurrentHashMap.newKeySet(); + return t -> seen.add(keyExtractor.apply(t)); } /**