diff --git a/pqs-common/common-web/src/main/java/com/njcn/web/filter/XssRequestWrapper.java b/pqs-common/common-web/src/main/java/com/njcn/web/filter/XssRequestWrapper.java index 85eb8d4b6..ca92150f5 100644 --- a/pqs-common/common-web/src/main/java/com/njcn/web/filter/XssRequestWrapper.java +++ b/pqs-common/common-web/src/main/java/com/njcn/web/filter/XssRequestWrapper.java @@ -34,7 +34,7 @@ import java.util.stream.Stream; @Slf4j public class XssRequestWrapper extends HttpServletRequestWrapper { - private final static String[] WHITE_PARAMETER_NAME = {"password", "mxContent", "docContent", "bgImage","fileContent"}; + private final static String[] WHITE_PARAMETER_NAME = {"password", "mxContent", "docContent", "bgImage","fileContent","flowableXml"}; public XssRequestWrapper(HttpServletRequest request) { diff --git a/pqs-process/process-api/src/main/java/com/njcn/process/enums/AuditProcessEnum.java b/pqs-process/process-api/src/main/java/com/njcn/process/enums/AuditProcessEnum.java new file mode 100644 index 000000000..4717e0356 --- /dev/null +++ b/pqs-process/process-api/src/main/java/com/njcn/process/enums/AuditProcessEnum.java @@ -0,0 +1,38 @@ +package com.njcn.process.enums; + +import lombok.Getter; + +/** + * pqs + * + * @author cdf + * @date 2024/4/2 + */ +@Getter +public enum AuditProcessEnum { + + + New(0,"新建"), + + WaitAudit(1,"待审核"), + AuditPass(2,"审核通过"), + AuditRefuse(3,"审核未通过"), + Release(4,"已发布"), + Finish(5,"已完成") + + + ; + + + + + private final Integer status; + + private final String statusDes; + + + AuditProcessEnum(Integer status,String statusDes){ + this.status = status; + this.statusDes = statusDes; + } +} diff --git a/pqs-process/process-api/src/main/java/com/njcn/process/enums/FlowComment.java b/pqs-process/process-api/src/main/java/com/njcn/process/enums/FlowComment.java new file mode 100644 index 000000000..8aed274a8 --- /dev/null +++ b/pqs-process/process-api/src/main/java/com/njcn/process/enums/FlowComment.java @@ -0,0 +1,43 @@ +package com.njcn.process.enums; + +/** + * 流程意见类型 + * + * @author Tony + * @date 2021/4/19 + */ +public enum FlowComment { + + /** + * 说明 + */ + NORMAL("1", "正常意见"), + REBACK("2", "退回意见"), + REJECT("3", "驳回意见"), + DELEGATE("4", "委派意见"), + ASSIGN("5", "转办意见"), + STOP("6", "终止流程"); + + /** + * 类型 + */ + private final String type; + + /** + * 说明 + */ + private final String remark; + + FlowComment(String type, String remark) { + this.type = type; + this.remark = remark; + } + + public String getType() { + return type; + } + + public String getRemark() { + return remark; + } +} diff --git a/pqs-process/process-api/src/main/java/com/njcn/process/pojo/dto/flowable/FlowCommentDto.java b/pqs-process/process-api/src/main/java/com/njcn/process/pojo/dto/flowable/FlowCommentDto.java new file mode 100644 index 000000000..3fe179d12 --- /dev/null +++ b/pqs-process/process-api/src/main/java/com/njcn/process/pojo/dto/flowable/FlowCommentDto.java @@ -0,0 +1,25 @@ +package com.njcn.process.pojo.dto.flowable; + +import lombok.Builder; +import lombok.Data; + +import java.io.Serializable; + +/** + * @author Tony + * @date 2021/3/28 15:50 + */ +@Data +@Builder +public class FlowCommentDto implements Serializable { + + /** + * 意见类别 0 正常意见 1 退回意见 2 驳回意见 + */ + private String type; + + /** + * 意见内容 + */ + private String comment; +} diff --git a/pqs-process/process-api/src/main/java/com/njcn/process/pojo/dto/flowable/FlowProcDefDto.java b/pqs-process/process-api/src/main/java/com/njcn/process/pojo/dto/flowable/FlowProcDefDto.java new file mode 100644 index 000000000..016ec3507 --- /dev/null +++ b/pqs-process/process-api/src/main/java/com/njcn/process/pojo/dto/flowable/FlowProcDefDto.java @@ -0,0 +1,56 @@ +package com.njcn.process.pojo.dto.flowable; +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.Date; + +/** + *

流程定义

+ * + * @author Tony + * @date 2021-04-03 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@ApiModel("流程定义") +public class FlowProcDefDto implements Serializable { + + @ApiModelProperty("流程id") + private String id; + + @ApiModelProperty("流程名称") + private String name; + + @ApiModelProperty("流程key") + private String flowKey; + + @ApiModelProperty("流程分类") + private String category; + + @ApiModelProperty("配置表单名称") + private String formName; + + @ApiModelProperty("配置表单id") + private Long formId; + + @ApiModelProperty("版本") + private int version; + + @ApiModelProperty("部署ID") + private String deploymentId; + + @ApiModelProperty("流程定义状态: 1:激活 , 2:中止") + private int suspensionState; + + @ApiModelProperty("部署时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date deploymentTime; + + +} diff --git a/pqs-process/process-api/src/main/java/com/njcn/process/pojo/dto/flowable/FlowSaveXmlVo.java b/pqs-process/process-api/src/main/java/com/njcn/process/pojo/dto/flowable/FlowSaveXmlVo.java new file mode 100644 index 000000000..58f13839a --- /dev/null +++ b/pqs-process/process-api/src/main/java/com/njcn/process/pojo/dto/flowable/FlowSaveXmlVo.java @@ -0,0 +1,28 @@ +package com.njcn.process.pojo.dto.flowable; + +import lombok.Data; + +import java.io.Serializable; + +/** + * @author Tony + * @date 2021/3/28 19:48 + */ +@Data +public class FlowSaveXmlVo implements Serializable { + + /** + * 流程名称 + */ + private String name; + + /** + * 流程分类 + */ + private String category; + + /** + * xml 文件 + */ + private String flowableXml; +} diff --git a/pqs-process/process-api/src/main/java/com/njcn/process/pojo/dto/flowable/FlowTaskDto.java b/pqs-process/process-api/src/main/java/com/njcn/process/pojo/dto/flowable/FlowTaskDto.java new file mode 100644 index 000000000..3bc6e7437 --- /dev/null +++ b/pqs-process/process-api/src/main/java/com/njcn/process/pojo/dto/flowable/FlowTaskDto.java @@ -0,0 +1,103 @@ +package com.njcn.process.pojo.dto.flowable; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; +import java.util.Date; + +/** + *

工作流任务

+ * + * @author Tony + * @date 2021-04-03 + */ +@Getter +@Setter +@ApiModel("工作流任务相关-返回参数") +public class FlowTaskDto implements Serializable { + + @ApiModelProperty("任务编号") + private String taskId; + + @ApiModelProperty("任务执行编号") + private String executionId; + + @ApiModelProperty("任务名称") + private String taskName; + + @ApiModelProperty("任务Key") + private String taskDefKey; + + @ApiModelProperty("任务执行人Id") + private String assigneeId; + + @ApiModelProperty("部门名称") + private String deptName; + + @ApiModelProperty("流程发起人部门名称") + private String startDeptName; + + @ApiModelProperty("任务执行人名称") + private String assigneeName; + + @ApiModelProperty("任务执行人部门") + private String assigneeDeptName;; + + @ApiModelProperty("流程发起人Id") + private String startUserId; + + @ApiModelProperty("流程发起人名称") + private String startUserName; + + @ApiModelProperty("流程类型") + private String category; + + @ApiModelProperty("流程变量信息") + private Object procVars; + + @ApiModelProperty("局部变量信息") + private Object taskLocalVars; + + @ApiModelProperty("流程部署编号") + private String deployId; + + @ApiModelProperty("流程ID") + private String procDefId; + + @ApiModelProperty("流程key") + private String procDefKey; + + @ApiModelProperty("流程定义名称") + private String procDefName; + + @ApiModelProperty("流程定义内置使用版本") + private int procDefVersion; + + @ApiModelProperty("流程实例ID") + private String procInsId; + + @ApiModelProperty("历史流程实例ID") + private String hisProcInsId; + + @ApiModelProperty("任务耗时") + private String duration; + + @ApiModelProperty("任务意见") + private FlowCommentDto comment; + + @ApiModelProperty("候选执行人") + private String candidate; + + @ApiModelProperty("任务创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + @ApiModelProperty("任务完成时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date finishTime; + +} diff --git a/pqs-process/process-api/src/main/java/com/njcn/process/pojo/param/RGeneralSurveyPlanAuditUserParam.java b/pqs-process/process-api/src/main/java/com/njcn/process/pojo/param/RGeneralSurveyPlanAuditUserParam.java new file mode 100644 index 000000000..e311213bf --- /dev/null +++ b/pqs-process/process-api/src/main/java/com/njcn/process/pojo/param/RGeneralSurveyPlanAuditUserParam.java @@ -0,0 +1,26 @@ +package com.njcn.process.pojo.param; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; +import java.util.List; + +/** + * pqs + * + * @author cdf + * @date 2024/3/29 + */ +@Data +public class RGeneralSurveyPlanAuditUserParam { + + @ApiModelProperty(name = "planIds",value = "提交的计划编号id集合") + @NotEmpty(message = "计划编号不可为空") + private List planIds; + + @ApiModelProperty(name = "auditUser",value = "审核人id") + @NotBlank(message = "请选择审核人") + private String auditUser; +} diff --git a/pqs-process/process-api/src/main/java/com/njcn/process/pojo/po/FlowFormAss.java b/pqs-process/process-api/src/main/java/com/njcn/process/pojo/po/FlowFormAss.java new file mode 100644 index 000000000..155cf4933 --- /dev/null +++ b/pqs-process/process-api/src/main/java/com/njcn/process/pojo/po/FlowFormAss.java @@ -0,0 +1,23 @@ +package com.njcn.process.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * pqs + * + * @author cdf + * @date 2024/4/2 + */ +@Data +@TableName(value = "flow_form_ass") +public class FlowFormAss { + + @TableId + private String id; + + private String definitionId; + + private String formId; +} diff --git a/pqs-process/process-api/src/main/java/com/njcn/process/pojo/vo/RGeneralSurveyPlanVO.java b/pqs-process/process-api/src/main/java/com/njcn/process/pojo/vo/RGeneralSurveyPlanVO.java index 6052d7753..e4d6ea7de 100644 --- a/pqs-process/process-api/src/main/java/com/njcn/process/pojo/vo/RGeneralSurveyPlanVO.java +++ b/pqs-process/process-api/src/main/java/com/njcn/process/pojo/vo/RGeneralSurveyPlanVO.java @@ -10,7 +10,7 @@ import java.util.Date; import java.util.List; /** - * Description: + * Description: 普测只针对电站层级 * 接口文档访问地址:http://serverIP:port/swagger-ui.html * Date: 2022/11/11 15:20【需求编号】 * @@ -66,12 +66,23 @@ public class RGeneralSurveyPlanVO { @ApiModelProperty(value="审核人") private String checkPerson; + + + @ApiModelProperty(value="审核人中文名称") + private String checkPersonName; /** * 审核意见 */ @ApiModelProperty(value="审核意见") private String checkComment; + + @ApiModelProperty(value="创建人") + private String createPerson; + + @ApiModelProperty(value="创建人名称") + private String createPersonName; + /** * 上传时间 */ diff --git a/pqs-process/process-boot/src/main/java/com/njcn/process/controller/RGeneralSurveyPlanController.java b/pqs-process/process-boot/src/main/java/com/njcn/process/controller/RGeneralSurveyPlanController.java index 55b8416e8..7580c9e52 100644 --- a/pqs-process/process-boot/src/main/java/com/njcn/process/controller/RGeneralSurveyPlanController.java +++ b/pqs-process/process-boot/src/main/java/com/njcn/process/controller/RGeneralSurveyPlanController.java @@ -133,7 +133,7 @@ public class RGeneralSurveyPlanController extends BaseController { @ApiImplicitParam(name = "rGeneralSurveyPlanQueryParm", value = "普测计划查询参数", required = true) public HttpResult> queryPlan(@Validated @RequestBody RGeneralSurveyPlanQueryParm rGeneralSurveyPlanQueryParm) { String methodDescribe = getMethodDescribe("queryPlan"); - IPage rGeneralSurveyPlanVOS = rGeneralSurveyPlanPOService.query(rGeneralSurveyPlanQueryParm, Stream.of("0", "1", "2", "3").collect(Collectors.toList()), "1"); + IPage rGeneralSurveyPlanVOS = rGeneralSurveyPlanPOService.query(rGeneralSurveyPlanQueryParm, Stream.of("0", "1", "2", "3","4","5").collect(Collectors.toList()), "1"); return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, rGeneralSurveyPlanVOS, methodDescribe); } @@ -266,4 +266,19 @@ public class RGeneralSurveyPlanController extends BaseController { } + /** + * 提交审核 + * @author cdf + * @date 2024/3/29 + */ + + @ApiOperation("提交审核") + @PostMapping(value = "submitAuditUser") + @ApiImplicitParam(name = "rGeneralSurveyPlanAuditUserParam", value = "实体参数", required = true) + public HttpResult submitAuditUser(@RequestBody @Validated RGeneralSurveyPlanAuditUserParam rGeneralSurveyPlanAuditUserParam) { + String methodDescribe = getMethodDescribe("submitAuditUser"); + rGeneralSurveyPlanPOService.submitAuditUser(rGeneralSurveyPlanAuditUserParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe); + } + } diff --git a/pqs-process/process-boot/src/main/java/com/njcn/process/controller/flowable/FlowDefinitionController.java b/pqs-process/process-boot/src/main/java/com/njcn/process/controller/flowable/FlowDefinitionController.java index acbd4b434..9b06be938 100644 --- a/pqs-process/process-boot/src/main/java/com/njcn/process/controller/flowable/FlowDefinitionController.java +++ b/pqs-process/process-boot/src/main/java/com/njcn/process/controller/flowable/FlowDefinitionController.java @@ -1,8 +1,11 @@ package com.njcn.process.controller.flowable; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.njcn.common.pojo.enums.response.CommonResponseEnum; import com.njcn.common.pojo.response.HttpResult; import com.njcn.common.utils.HttpResultUtil; +import com.njcn.process.pojo.dto.flowable.FlowProcDefDto; +import com.njcn.process.pojo.dto.flowable.FlowSaveXmlVo; import com.njcn.process.service.flowable.IFlowDefinitionService; import com.njcn.process.service.flowable.IFlowTaskService; import com.njcn.web.controller.BaseController; @@ -11,6 +14,7 @@ import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.IOUtils; import org.flowable.common.engine.api.FlowableObjectNotFoundException; import org.flowable.engine.HistoryService; import org.flowable.engine.RepositoryService; @@ -23,12 +27,20 @@ import org.flowable.engine.runtime.ProcessInstance; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; import java.util.Map; import java.util.Objects; +import static cn.hutool.core.util.CharsetUtil.UTF_8; + /** * pqs * 工作流 + * * @author cdf * @date 2023/4/10 */ @@ -44,27 +56,17 @@ public class FlowDefinitionController extends BaseController { private final RepositoryService repositoryService; - private final RuntimeService runtimeService; - - private final TaskService taskService; - - private final HistoryService historyService; - - - private final IFlowTaskService flowTaskService; - - @GetMapping("deployment") @ApiOperation(value = "工作流_部署流程") public void createDeployment() { Deployment deployment = repositoryService.createDeployment() - .addClasspathResource("gaojing.bpmn20.xml") - .name("技术监督告警流程").category("gaojing") + .addClasspathResource("puce.bpmn20.xml") + .name("谐波普测管理").category("xbpc") .deploy(); ProcessDefinition definition = repositoryService.createProcessDefinitionQuery().deploymentId(deployment.getId()).singleResult(); - repositoryService.setProcessDefinitionCategory(definition.getId(), "gaojing"); + repositoryService.setProcessDefinitionCategory(definition.getId(), "xbpc"); System.out.println(deployment.getId()); } @@ -73,10 +75,10 @@ public class FlowDefinitionController extends BaseController { @PostMapping("/start") public HttpResult start(@ApiParam(value = "流程定义id") @RequestParam(value = "procDefId") String procDefId, @ApiParam(value = "监督单id") @RequestParam(value = "thsIndex") String thsIndex, - @ApiParam(value = "变量集合,json对象") @RequestBody Map variables) { + @ApiParam(value = "变量集合,json对象") @RequestBody Map variables) { String methodDescribe = getMethodDescribe("start"); - String res = flowDefinitionService.startProcessInstanceById(procDefId,thsIndex, variables); - if(Objects.nonNull(res)){ + String res = flowDefinitionService.startProcessInstanceById(procDefId, thsIndex, variables); + if (Objects.nonNull(res)) { return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, res, methodDescribe); } return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, null, methodDescribe); @@ -84,12 +86,79 @@ public class FlowDefinitionController extends BaseController { @ApiOperation(value = "工作流_定义删除") - @DeleteMapping(value = "delete/{deployIds}") - public HttpResult delete(@PathVariable String[] deployIds) { + @PostMapping(value = "delete") + public HttpResult delete(@RequestBody List deployIds) { String methodDescribe = getMethodDescribe("delete"); for (String deployId : deployIds) { flowDefinitionService.delete(deployId); } return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe); } + + @GetMapping(value = "/list") + @ApiOperation(value = "流程定义列表") + public HttpResult> list(@ApiParam(value = "当前页码", required = true) @RequestParam("pageNum") Integer pageNum, + @ApiParam(value = "每页条数", required = true) @RequestParam("pageSize") Integer pageSize, + @ApiParam(value = "流程名称") @RequestParam(value = "name", required = false) String name) { + String methodDescribe = getMethodDescribe("list"); + Page pageResult = flowDefinitionService.list(name, pageNum, pageSize); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, pageResult, methodDescribe); + + } + + + @ApiOperation(value = "保存流程设计器内的xml文件") + @PostMapping("/save") + public HttpResult save(@RequestBody FlowSaveXmlVo vo) { + String methodDescribe = getMethodDescribe("save"); + + InputStream in = null; + try { + in = new ByteArrayInputStream(vo.getFlowableXml().getBytes(StandardCharsets.UTF_8)); + flowDefinitionService.importFile(vo.getName(), vo.getCategory(), in); + } catch (Exception e) { + log.error("导入失败:", e); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, e.getMessage(), methodDescribe); + } finally { + try { + if (in != null) { + in.close(); + } + } catch (IOException e) { + log.error("关闭输入流出错", e); + } + } + + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, "导入成功", methodDescribe); + } + + + @ApiOperation(value = "读取xml文件") + @GetMapping("/readXml") + public HttpResult readXml(@ApiParam(value = "流程定义id") @RequestParam(value = "deployId") String deployId) { + String methodDescribe = getMethodDescribe("readXml"); + try { + String result = flowDefinitionService.readXml(deployId); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } catch (Exception e) { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, "加载xml文件异常", methodDescribe); + } + } + + + /** + * 挂载表单 + * + * @author cdf + * @date 2024/4/2 + */ + @ApiOperation(value = "挂载表单") + @GetMapping("/assFormWithDeploy") + public HttpResult assFormWithDeploy(@ApiParam(value = "流程定义id") @RequestParam(value = "deployId") String deployId, @ApiParam(value = "表单功能id") @RequestParam(value = "formId") String formId) { + String methodDescribe = getMethodDescribe("assFormWithDeploy"); + flowDefinitionService.assFormWithDeploy(deployId, formId); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe); + } + + } diff --git a/pqs-process/process-boot/src/main/java/com/njcn/process/controller/flowable/FlowInstanceController.java b/pqs-process/process-boot/src/main/java/com/njcn/process/controller/flowable/FlowInstanceController.java new file mode 100644 index 000000000..ded6bf707 --- /dev/null +++ b/pqs-process/process-boot/src/main/java/com/njcn/process/controller/flowable/FlowInstanceController.java @@ -0,0 +1,80 @@ +package com.njcn.process.controller.flowable; + + + +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.common.utils.HttpResultUtil; +import com.njcn.process.pojo.vo.flowable.FlowTaskVo; +import com.njcn.process.service.flowable.IFlowInstanceService; +import com.njcn.web.controller.BaseController; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +/** + *

工作流流程实例管理

+ * + * @author Tony + * @date 2021-04-03 + */ +@Slf4j +@Api(tags = "工作流流程实例管理") +@RestController +@RequestMapping("/flowable/instance") +public class FlowInstanceController extends BaseController { + + @Autowired + private IFlowInstanceService flowInstanceService; + + @ApiOperation(value = "根据流程定义id启动流程实例") + @PostMapping("/startBy/{procDefId}") + public HttpResult startById(@ApiParam(value = "流程定义id") @PathVariable(value = "procDefId") String procDefId, + @ApiParam(value = "变量集合,json对象") @RequestBody Map variables) { + String methodDescribe = getMethodDescribe("startById"); + String result = flowInstanceService.startProcessInstanceById(procDefId, variables); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + + + } + + + @ApiOperation(value = "激活或挂起流程实例") + @PostMapping(value = "/updateState") + public HttpResult updateState(@ApiParam(value = "1:激活,2:挂起", required = true) @RequestParam Integer state, + @ApiParam(value = "流程实例ID", required = true) @RequestParam String instanceId) { + String methodDescribe = getMethodDescribe("updateState"); + + flowInstanceService.updateState(state,instanceId); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe); + + } + + @ApiOperation("结束流程实例") + @PostMapping(value = "/stopProcessInstance") + public HttpResult stopProcessInstance(@RequestBody FlowTaskVo flowTaskVo) { + + String methodDescribe = getMethodDescribe("stopProcessInstance"); + + flowInstanceService.stopProcessInstance(flowTaskVo); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe); + + } + + @ApiOperation(value = "删除流程实例") + @GetMapping(value = "/delete") + public HttpResult delete(@ApiParam(value = "流程实例ID", required = true) @RequestParam String instanceIds) { + String methodDescribe = getMethodDescribe("delete"); + + + flowInstanceService.delete(instanceIds,"测试删除666"); + + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe); + + } +} diff --git a/pqs-process/process-boot/src/main/java/com/njcn/process/controller/flowable/FlowTaskController.java b/pqs-process/process-boot/src/main/java/com/njcn/process/controller/flowable/FlowTaskController.java index a2380fce8..824fb47dc 100644 --- a/pqs-process/process-boot/src/main/java/com/njcn/process/controller/flowable/FlowTaskController.java +++ b/pqs-process/process-boot/src/main/java/com/njcn/process/controller/flowable/FlowTaskController.java @@ -1,9 +1,11 @@ package com.njcn.process.controller.flowable; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.njcn.common.pojo.enums.response.CommonResponseEnum; import com.njcn.common.pojo.response.HttpResult; import com.njcn.common.utils.HttpResultUtil; +import com.njcn.process.pojo.dto.flowable.FlowTaskDto; import com.njcn.process.pojo.vo.flowable.FlowQueryVo; import com.njcn.process.pojo.vo.flowable.FlowTaskVo; import com.njcn.process.service.flowable.IFlowTaskService; @@ -70,5 +72,65 @@ public class FlowTaskController extends BaseController { } + @ApiOperation(value = "我发起的流程", response = HttpResult.class) + @GetMapping(value = "/myProcess") + public HttpResult> myProcess(FlowQueryVo queryVo) { + String methodDescribe = getMethodDescribe("myProcess"); + + Page page = flowTaskService.myProcess(queryVo); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, page, methodDescribe); + } + + @ApiOperation(value = "获取已办任务", response = FlowTaskDto.class) + @GetMapping(value = "/finishedList") + public HttpResult> finishedList(FlowQueryVo queryVo) { + String methodDescribe = getMethodDescribe("finishedList"); + Page page = flowTaskService.finishedList(queryVo); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, page, methodDescribe); + } + + + @ApiOperation(value = "获取待办列表", response = FlowTaskDto.class) + @GetMapping(value = "/todoList") + public HttpResult> todoList(FlowQueryVo queryVo) { + String methodDescribe = getMethodDescribe("todoList"); + Page page = flowTaskService.todoList(queryVo); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, page, methodDescribe); + } + + + @ApiOperation(value = "获取流程变量", response = FlowTaskDto.class) + @GetMapping(value = "/processVariables/{taskId}") + public HttpResult> processVariables(@ApiParam(value = "流程任务Id") @PathVariable(value = "taskId") String taskId) { + String methodDescribe = getMethodDescribe("processVariables"); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, flowTaskService.processVariables(taskId), methodDescribe); + + } + + @ApiOperation(value = "流程历史流转记录", response = FlowTaskDto.class) + @GetMapping(value = "/flowRecord") + public HttpResult> flowRecord(String procInsId, String deployId) { + String methodDescribe = getMethodDescribe("flowRecord"); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, flowTaskService.flowRecord(procInsId, deployId), methodDescribe); + } + + @ApiOperation(value = "取消申请", response = FlowTaskDto.class) + @PostMapping(value = "/stopProcess") + public HttpResult stopProcess(@RequestBody FlowTaskVo flowTaskVo) { + String methodDescribe = getMethodDescribe("stopProcess"); + flowTaskService.stopProcess(flowTaskVo); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS,true, methodDescribe); + } + + + @ApiOperation(value = "驳回任务") + @PostMapping(value = "/reject") + public HttpResult taskReject(@RequestBody FlowTaskVo flowTaskVo) { + String methodDescribe = getMethodDescribe("taskReject"); + flowTaskService.taskReject(flowTaskVo); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS,true, methodDescribe); + + } + } diff --git a/pqs-process/process-boot/src/main/java/com/njcn/process/mapper/FlowFormAssMapper.java b/pqs-process/process-boot/src/main/java/com/njcn/process/mapper/FlowFormAssMapper.java new file mode 100644 index 000000000..e35c26706 --- /dev/null +++ b/pqs-process/process-boot/src/main/java/com/njcn/process/mapper/FlowFormAssMapper.java @@ -0,0 +1,24 @@ +package com.njcn.process.mapper; + + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.njcn.process.pojo.dto.flowable.FlowProcDefDto; +import com.njcn.process.pojo.po.FlowFormAss; +import com.njcn.process.pojo.po.FlowableAss; +import org.apache.ibatis.annotations.Param; + +/** + *

+ * Mapper 接口 + *

+ * + * @author hongawen + * @since 2023-04-13 + */ +public interface FlowFormAssMapper extends BaseMapper { + + + + +} diff --git a/pqs-process/process-boot/src/main/java/com/njcn/process/mapper/FlowableAssMapper.java b/pqs-process/process-boot/src/main/java/com/njcn/process/mapper/FlowableAssMapper.java index e4c4b4ffe..24409ca57 100644 --- a/pqs-process/process-boot/src/main/java/com/njcn/process/mapper/FlowableAssMapper.java +++ b/pqs-process/process-boot/src/main/java/com/njcn/process/mapper/FlowableAssMapper.java @@ -2,7 +2,12 @@ package com.njcn.process.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.njcn.process.pojo.dto.flowable.FlowProcDefDto; import com.njcn.process.pojo.po.FlowableAss; +import org.apache.ibatis.annotations.Param; + +import java.util.List; /** *

@@ -14,4 +19,12 @@ import com.njcn.process.pojo.po.FlowableAss; */ public interface FlowableAssMapper extends BaseMapper { + + /** + * 流程定义列表 + * @param name + * @return + */ + Page selectDeployList(Page page, @Param("name") String name); + } diff --git a/pqs-process/process-boot/src/main/java/com/njcn/process/mapper/mapping/FlowableAssMapper.xml b/pqs-process/process-boot/src/main/java/com/njcn/process/mapper/mapping/FlowableAssMapper.xml new file mode 100644 index 000000000..f9cfbd2ea --- /dev/null +++ b/pqs-process/process-boot/src/main/java/com/njcn/process/mapper/mapping/FlowableAssMapper.xml @@ -0,0 +1,27 @@ + + + + + + + diff --git a/pqs-process/process-boot/src/main/java/com/njcn/process/service/RGeneralSurveyPlanPOService.java b/pqs-process/process-boot/src/main/java/com/njcn/process/service/RGeneralSurveyPlanPOService.java index 3c83268d3..62706388c 100644 --- a/pqs-process/process-boot/src/main/java/com/njcn/process/service/RGeneralSurveyPlanPOService.java +++ b/pqs-process/process-boot/src/main/java/com/njcn/process/service/RGeneralSurveyPlanPOService.java @@ -103,4 +103,7 @@ public interface RGeneralSurveyPlanPOService extends IMppService list(String name, Integer pageNum, Integer pageSize); + + + + /** + * 导入流程文件 + * 当每个key的流程第一次部署时,指定版本为1。对其后所有使用相同key的流程定义, + * 部署时版本会在该key当前已部署的最高版本号基础上加1。key参数用于区分流程定义 + * @param name + * @param category + * @param in + */ + void importFile(String name, String category, InputStream in); + + + /** + * 读取xml + * @param deployId + * @return + */ + String readXml(String deployId) throws IOException; + + + Boolean assFormWithDeploy(String deployId,String formId); + } diff --git a/pqs-process/process-boot/src/main/java/com/njcn/process/service/flowable/IFlowInstanceService.java b/pqs-process/process-boot/src/main/java/com/njcn/process/service/flowable/IFlowInstanceService.java new file mode 100644 index 000000000..c12994c69 --- /dev/null +++ b/pqs-process/process-boot/src/main/java/com/njcn/process/service/flowable/IFlowInstanceService.java @@ -0,0 +1,55 @@ +package com.njcn.process.service.flowable; + +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.process.pojo.vo.flowable.FlowTaskVo; + +import org.flowable.engine.history.HistoricProcessInstance; + +import java.util.Map; + +/** + * @author Tony + * @date 2021-04-03 14:40 + */ +public interface IFlowInstanceService { + + /** + * 结束流程实例 + * + * @param vo + */ + void stopProcessInstance(FlowTaskVo vo); + + /** + * 激活或挂起流程实例 + * + * @param state 状态 + * @param instanceId 流程实例ID + */ + void updateState(Integer state, String instanceId); + + /** + * 删除流程实例ID + * + * @param instanceId 流程实例ID + * @param deleteReason 删除原因 + */ + void delete(String instanceId, String deleteReason); + + /** + * 根据实例ID查询历史实例数据 + * + * @param processInstanceId + * @return + */ + HistoricProcessInstance getHistoricProcessInstanceById(String processInstanceId); + + /** + * 根据流程定义ID启动流程实例 + * + * @param procDefId 流程定义Id + * @param variables 流程变量 + * @return + */ + String startProcessInstanceById(String procDefId, Map variables); +} diff --git a/pqs-process/process-boot/src/main/java/com/njcn/process/service/flowable/IFlowTaskService.java b/pqs-process/process-boot/src/main/java/com/njcn/process/service/flowable/IFlowTaskService.java index 5d116ddfa..d9106e65e 100644 --- a/pqs-process/process-boot/src/main/java/com/njcn/process/service/flowable/IFlowTaskService.java +++ b/pqs-process/process-boot/src/main/java/com/njcn/process/service/flowable/IFlowTaskService.java @@ -2,7 +2,10 @@ package com.njcn.process.service.flowable; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.njcn.common.pojo.response.HttpResult; +import com.njcn.process.pojo.dto.flowable.FlowTaskDto; +import com.njcn.process.pojo.vo.flowable.FlowQueryVo; import com.njcn.process.pojo.vo.flowable.FlowTaskVo; import org.flowable.task.api.Task; @@ -36,6 +39,14 @@ public interface IFlowTaskService { Boolean complete(FlowTaskVo taskVo); + /** + * 给下一个任务指定执行人员 + * @author cdf + * @date 2024/3/29 + */ + Task toNextTaskUser(FlowTaskVo taskVo); + + /** * 获取任务 * @author cdf @@ -44,5 +55,64 @@ public interface IFlowTaskService { Task getTask(String proIndex); + /** + * 我发起的流程 + * @param queryVo 请求参数 + * @return + */ + Page myProcess(FlowQueryVo queryVo); + + + /** + * 已办任务列表 + * + * @param queryVo 请求参数 + * @return + */ + Page finishedList(FlowQueryVo queryVo); + + + /** + * 代办任务列表 + * + * @param queryVo 请求参数 + * @return + */ + Page todoList(FlowQueryVo queryVo); + + + /** + * 获取流程变量 + * @param taskId + * @return + */ + Map processVariables(String taskId); + + + /** + * 流程历史流转记录 + * + * @param procInsId 流程实例Id + * @return + */ + Map flowRecord(String procInsId,String deployId); + + + /** + * 取消申请 + * 目前实现方式: 直接将当前流程变更为已完成 + * @param flowTaskVo + * @return + */ + Boolean stopProcess(FlowTaskVo flowTaskVo); + + + /** + * 驳回任务 + * + * @param flowTaskVo + */ + void taskReject(FlowTaskVo flowTaskVo); + } diff --git a/pqs-process/process-boot/src/main/java/com/njcn/process/service/impl/RGeneralSurveyPlanPOServiceImpl.java b/pqs-process/process-boot/src/main/java/com/njcn/process/service/impl/RGeneralSurveyPlanPOServiceImpl.java index 362505bcf..9637b9109 100644 --- a/pqs-process/process-boot/src/main/java/com/njcn/process/service/impl/RGeneralSurveyPlanPOServiceImpl.java +++ b/pqs-process/process-boot/src/main/java/com/njcn/process/service/impl/RGeneralSurveyPlanPOServiceImpl.java @@ -1,8 +1,10 @@ package com.njcn.process.service.impl; import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; @@ -15,22 +17,28 @@ import com.njcn.minioss.bo.MinIoUploadResDTO; import com.njcn.oss.constant.OssPath; import com.njcn.oss.enums.OssResponseEnum; import com.njcn.oss.utils.FileStorageUtil; -import com.njcn.process.mapper.RGeneralSurveyPlanDetailMapper; -import com.njcn.process.mapper.RGeneralSurveyPlanPOMapper; -import com.njcn.process.mapper.RSurveyCycleMapper; +import com.njcn.process.enums.AuditProcessEnum; +import com.njcn.process.enums.FlowComment; +import com.njcn.process.mapper.*; import com.njcn.process.pojo.param.*; -import com.njcn.process.pojo.po.RGeneralSurveyPlanDetail; -import com.njcn.process.pojo.po.RGeneralSurveyPlanPO; -import com.njcn.process.pojo.po.RSurveyCyclePO; -import com.njcn.process.pojo.po.RSurveyPlanConfigPO; +import com.njcn.process.pojo.po.*; import com.njcn.process.pojo.vo.*; +import com.njcn.process.pojo.vo.flowable.FlowTaskVo; import com.njcn.process.service.RGeneralSurveyPlanDetailService; import com.njcn.process.service.RGeneralSurveyPlanPOService; +import com.njcn.process.service.flowable.IFlowDefinitionService; +import com.njcn.process.service.flowable.IFlowTaskService; +import com.njcn.process.service.impl.flowable.FlowDefinitionServiceImpl; + import com.njcn.user.api.DeptFeignClient; +import com.njcn.user.api.UserFeignClient; import com.njcn.user.pojo.po.Dept; +import com.njcn.user.pojo.po.User; import com.njcn.user.pojo.vo.PvTerminalTreeVO; import com.njcn.web.utils.RequestUtil; +import liquibase.pro.packaged.S; import lombok.RequiredArgsConstructor; +import org.flowable.task.api.Task; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -40,6 +48,7 @@ import org.springframework.util.StringUtils; import java.text.SimpleDateFormat; import java.time.LocalDate; import java.util.*; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -64,6 +73,16 @@ public class RGeneralSurveyPlanPOServiceImpl extends MppServiceImpl() - .eq(RGeneralSurveyPlanPO::getPlanNo, rGeneralSurveyPlanAddParm.getPlanNo()) - ); - boolean b; - if (count > 0) { - b = this.updateByMultiId(rGeneralSurveyPlanPO); + + + + RGeneralSurveyPlanPO generalSurveyPlanPO = this.getOne(new LambdaQueryWrapper() + .eq(RGeneralSurveyPlanPO::getPlanNo, rGeneralSurveyPlanAddParm.getPlanNo())); + + rGeneralSurveyPlanPO.setStatus(AuditProcessEnum.New.getStatus()); + if (Objects.isNull(generalSurveyPlanPO)) { + this.save(rGeneralSurveyPlanPO); } else { - /*todo 后期与工作流绑定*/ - rGeneralSurveyPlanPO.setStatus(0); - b = this.save(rGeneralSurveyPlanPO); + if(rGeneralSurveyPlanPO.getStatus() == AuditProcessEnum.New.getStatus() || rGeneralSurveyPlanPO.getStatus() == AuditProcessEnum.AuditRefuse.getStatus()){ + this.updateByMultiId(rGeneralSurveyPlanPO); + }else { + throw new BusinessException("存在相同普测计划编号,请勿重复新建"); + } } QueryWrapper queryWrapper = new QueryWrapper(); @@ -109,7 +131,7 @@ public class RGeneralSurveyPlanPOServiceImpl extends MppServiceImpl rGeneralSurveyPlanDetailList = new ArrayList<>(); - SubstationParam param =new SubstationParam(); + SubstationParam param = new SubstationParam(); param.setPowerIds(rGeneralSurveyPlanAddParm.getSubIds()); List stationList = commTerminalGeneralClient.tagOrIdGetSub(param).getData(); for (SubGetBase stat : stationList) { @@ -128,8 +150,9 @@ public class RGeneralSurveyPlanPOServiceImpl extends MppServiceImpl page = new Page<>(rGeneralSurveyPlanQueryParm.getCurrentPage(), rGeneralSurveyPlanQueryParm.getPageSize()); IPage returnpage = new Page<>(rGeneralSurveyPlanQueryParm.getCurrentPage(), rGeneralSurveyPlanQueryParm.getPageSize()); + String loginUserDept = RequestUtil.getDeptIndex(); + String loginUsrId = RequestUtil.getUserIndex(); + List childrenDeptIds = deptFeignClient.getDepSonIdtByDeptId(loginUserDept).getData(); + childrenDeptIds = childrenDeptIds.stream().filter(item -> !Objects.equals(item, loginUserDept)).distinct().collect(Collectors.toList()); + List userList = userFeignClient.getUserInfoByDeptIds(childrenDeptIds).getData(); + User my = new User(); + my.setId(loginUsrId); + my.setName(RequestUtil.getUserNickname()); + userList.add(my); + List userIds = userList.stream().map(User::getId).collect(Collectors.toList()); + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); /*type=1:新建页面:查看自己负责的计划;type=2:审核页面:审核者==当前用户;结果页面:查看自己负责的计划*/ if (type == "1" || type == "3") { - queryWrapper.eq(RGeneralSurveyPlanPO::getCreatePerson, RequestUtil.getUserIndex()); + queryWrapper.in(RGeneralSurveyPlanPO::getCreatePerson, userIds); } if (type == "2") { - queryWrapper.eq(RGeneralSurveyPlanPO::getCheckPerson, RequestUtil.getDeptIndex()); + queryWrapper.eq(RGeneralSurveyPlanPO::getCheckPerson, loginUsrId); } if (!StringUtils.isEmpty(rGeneralSurveyPlanQueryParm.getOrgNo())) { List data = deptFeignClient.getDepSonIdtByDeptId(rGeneralSurveyPlanQueryParm.getOrgNo()).getData(); @@ -180,10 +214,7 @@ public class RGeneralSurveyPlanPOServiceImpl extends MppServiceImpl dept = deptFeignClient.allDeptList().getData(); - Map pvTerminalTreeVOMap = dept.stream(). - collect(Collectors. - toMap(PvTerminalTreeVO::getId, - PvTerminalTreeVO::getName)); + Map pvTerminalTreeVOMap = dept.stream().collect(Collectors.toMap(PvTerminalTreeVO::getId, PvTerminalTreeVO::getName)); List collect = rGeneralSurveyPlanPOS.stream().map(RGeneralSurveyPlanPO::getPlanNo).collect(Collectors.toList()); @@ -191,6 +222,12 @@ public class RGeneralSurveyPlanPOServiceImpl extends MppServiceImpl rGeneralSurveyPlanDetails = rGeneralSurveyPlanDetailMapper.selectList(lambdaQueryWrapper); List rGeneralSurveyPlanVOList = new ArrayList<>(); + + Map userMap = userList.stream().collect(Collectors.toMap(User::getId, Function.identity())); + List checkIds = rGeneralSurveyPlanPOS.stream().map(RGeneralSurveyPlanPO::getCheckPerson).filter(Objects::nonNull).collect(Collectors.toList()); + List checkUserList = userFeignClient.getUserByIdList(checkIds).getData(); + Map checkMap = checkUserList.stream().collect(Collectors.toMap(User::getId, Function.identity())); + rGeneralSurveyPlanPOS.forEach(temp -> { RGeneralSurveyPlanVO rGeneralSurveyPlanVO = new RGeneralSurveyPlanVO(); BeanUtils.copyProperties(temp, rGeneralSurveyPlanVO); @@ -199,22 +236,14 @@ public class RGeneralSurveyPlanPOServiceImpl extends MppServiceImpl Objects.equals (surveyPlanDetail.getPlanNo ( ), temp.getPlanNo ( ))). - map (RGeneralSurveyPlanDetail::getSubId).distinct ( ).count ( );*/ -// rGeneralSurveyPlanVO.setBusCount (Busbarcount); -// List collect1 = rGeneralSurveyPlanDetails.stream ( ). -// filter (surveyPlanDetail -> Objects.equals (surveyPlanDetail.getPlanNo ( ), temp.getPlanNo ( ))). -// map (surveyPlanDetail -> { -// RGeneralSurveyPlanVO.RGeneralSurveyPlanDetailVO rGeneralSurveyPlanDetailVO = new RGeneralSurveyPlanVO.RGeneralSurveyPlanDetailVO ( ); -// BeanUtils.copyProperties (surveyPlanDetail, rGeneralSurveyPlanDetailVO); -// return rGeneralSurveyPlanDetailVO; -// }).collect (Collectors.toList ( )); + rGeneralSurveyPlanVO.setIsFileUpload(ObjectUtil.isNull(temp.getIsFileUpload()) ? 0 : temp.getIsFileUpload()); + rGeneralSurveyPlanVO.setOrgName(pvTerminalTreeVOMap.get(temp.getOrgNo())); //单位名称 -// -// rGeneralSurveyPlanVO.setRGeneralSurveyPlanDetailVOList (collect1); + rGeneralSurveyPlanVO.setCreatePersonName(userMap.get(temp.getCreatePerson()).getName()); + + if (StrUtil.isNotBlank(temp.getCheckPerson())) { + rGeneralSurveyPlanVO.setCheckPerson(checkMap.get(temp.getCheckPerson()).getName()); + } rGeneralSurveyPlanVOList.add(rGeneralSurveyPlanVO); }); @@ -249,12 +278,13 @@ public class RGeneralSurveyPlanPOServiceImpl extends MppServiceImpl updateWrapper = new UpdateWrapper(); updateWrapper.eq("plan_no", rGeneralSurveyPlanChcekParm.getPlanNo()); updateWrapper.set("check_comment", rGeneralSurveyPlanChcekParm.getCheckComment()); // updateWrapper.set ("check_person", rGeneralSurveyPlanChcekParm.getCheckPerson ( )); - updateWrapper.set("status", Objects.equals("1", rGeneralSurveyPlanChcekParm.getCheckResult()) ? 3 : 2); + updateWrapper.set("status", Objects.equals("1", rGeneralSurveyPlanChcekParm.getCheckResult()) ? AuditProcessEnum.Release.getStatus() : AuditProcessEnum.AuditRefuse.getStatus()); result = this.update(updateWrapper); + + //处理流程 + FlowableAss flowableAss = flowableAssMapper.selectOne(new LambdaQueryWrapper().eq(FlowableAss::getThsIndex,rGeneralSurveyPlanChcekParm.getPlanNo())); + + String auditResult = Objects.equals("1", rGeneralSurveyPlanChcekParm.getCheckResult()) ? "同意" : "拒绝"; + if(Objects.equals("0", rGeneralSurveyPlanChcekParm.getCheckResult())){ + //拒绝,则回退到申请步骤 + Task task = iFlowTaskService.getTask(flowableAss.getExecIndex()); + FlowTaskVo flowTaskVo = new FlowTaskVo(); + flowTaskVo.setTaskId(task.getId()); + flowTaskVo.setComment(rGeneralSurveyPlanChcekParm.getCheckComment()); + iFlowTaskService.taskReject(flowTaskVo); + + + }else { + Task task = iFlowTaskService.getTask(flowableAss.getExecIndex()); + FlowTaskVo flowTaskVo = new FlowTaskVo(); + flowTaskVo.setTaskId(task.getId()); + + flowTaskVo.setInstanceId(flowableAss.getExecIndex()); + flowTaskVo.setComment(RequestUtil.getUserNickname() + auditResult+"编号为"+rGeneralSurveyPlanChcekParm.getPlanNo()+"的计划,结论:"+rGeneralSurveyPlanChcekParm.getCheckComment()); + + + Map map = new HashMap<>(); + map.put("auditFlag",1); + flowTaskVo.setVariables(map); + iFlowTaskService.complete(flowTaskVo); + + } + + + return result; } @@ -538,7 +605,7 @@ public class RGeneralSurveyPlanPOServiceImpl extends MppServiceImpl list1 = commTerminalGeneralClient.tagOrIdGetSub(param).getData(); List children = deptSubstationVO.getChildren(); @@ -603,6 +670,70 @@ public class RGeneralSurveyPlanPOServiceImpl extends MppServiceImpl planIds = rGeneralSurveyPlanAuditUserParam.getPlanIds(); + + + Integer count = this.lambdaQuery().in(RGeneralSurveyPlanPO::getPlanNo, planIds).eq(RGeneralSurveyPlanPO::getCreatePerson, userId).count(); + if (count != planIds.size()) { + throw new BusinessException("只可以操作自己创建的计划!"); + } + + this.update(new LambdaUpdateWrapper() + .in(RGeneralSurveyPlanPO::getPlanNo, planIds) + .set(RGeneralSurveyPlanPO::getStatus, AuditProcessEnum.WaitAudit.getStatus()).set(RGeneralSurveyPlanPO::getCheckPerson, rGeneralSurveyPlanAuditUserParam.getAuditUser()) + ); + + //绑定工作流 + Map map = new HashMap<>(); + //map.put("applyUser",userId); + for (String planId : planIds) { + //需要判断那些流程已经启动,已经启动的则为驳回后的再次提交审核 + FlowableAss flowableAss = flowableAssMapper.selectOne(new LambdaQueryWrapper().eq(FlowableAss::getThsIndex,planId)); + if(Objects.nonNull(flowableAss)){ + //不为空则认为是驳回后的重新发起 + Task task = iFlowTaskService.getTask(flowableAss.getExecIndex()); + FlowTaskVo flowTaskVo = new FlowTaskVo(); + flowTaskVo.setTaskId(task.getId()); + flowTaskVo.setAssignee(userId); + flowTaskVo.setComment(RequestUtil.getUserNickname() + "重新发起普测计划申请"); + iFlowTaskService.complete(flowTaskVo); + + Task taskNext = iFlowTaskService.getTask(flowableAss.getExecIndex()); + FlowTaskVo flowTaskVoNext = new FlowTaskVo(); + flowTaskVoNext.setTaskId(taskNext.getId()); + flowTaskVoNext.setAssignee(rGeneralSurveyPlanAuditUserParam.getAuditUser()); + iFlowTaskService.toNextTaskUser(flowTaskVoNext); + }else { + //开始流程 + FlowFormAss flowFormAss = flowFormAssMapper.selectOne(new LambdaQueryWrapper().eq(FlowFormAss::getFormId,1)); + if(Objects.isNull(flowFormAss)){ + throw new BusinessException("当前功能未绑定流程,请先绑定流程"); + } + + String processId = iFlowDefinitionService.startProcessInstanceById(flowFormAss.getDefinitionId(), planId, map); + Task task = iFlowTaskService.getTask(processId); + FlowTaskVo flowTaskVo = new FlowTaskVo(); + flowTaskVo.setTaskId(task.getId()); + flowTaskVo.setInstanceId(processId); + flowTaskVo.setAssignee(RequestUtil.getUserIndex()); + flowTaskVo.setComment(RequestUtil.getUserNickname() + "发起普测计划申请"); + iFlowTaskService.complete(flowTaskVo); + + Task taskNext = iFlowTaskService.getTask(processId); + FlowTaskVo flowTaskVoNext = new FlowTaskVo(); + flowTaskVoNext.setTaskId(taskNext.getId()); + flowTaskVoNext.setAssignee(rGeneralSurveyPlanAuditUserParam.getAuditUser()); + iFlowTaskService.toNextTaskUser(flowTaskVoNext); + } + + + } + return true; + } + public List recursion(DeptSubstationVO result, String orgdid) { List deptSubstationVOList = new ArrayList<>(); if (Objects.equals(result.getId(), orgdid)) { diff --git a/pqs-process/process-boot/src/main/java/com/njcn/process/service/impl/flowable/FlowDefinitionServiceImpl.java b/pqs-process/process-boot/src/main/java/com/njcn/process/service/impl/flowable/FlowDefinitionServiceImpl.java index 1f2c006d6..5615e9bc9 100644 --- a/pqs-process/process-boot/src/main/java/com/njcn/process/service/impl/flowable/FlowDefinitionServiceImpl.java +++ b/pqs-process/process-boot/src/main/java/com/njcn/process/service/impl/flowable/FlowDefinitionServiceImpl.java @@ -8,7 +8,10 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.njcn.common.pojo.exception.BusinessException; import com.njcn.common.utils.PubUtils; import com.njcn.process.factory.FlowServiceFactory; +import com.njcn.process.mapper.FlowFormAssMapper; import com.njcn.process.mapper.FlowableAssMapper; +import com.njcn.process.pojo.dto.flowable.FlowProcDefDto; +import com.njcn.process.pojo.po.FlowFormAss; import com.njcn.process.pojo.po.FlowableAss; import com.njcn.process.service.flowable.IFlowDefinitionService; import com.njcn.web.utils.RequestUtil; @@ -53,6 +56,8 @@ public class FlowDefinitionServiceImpl extends FlowServiceFactory implements IFl private final FlowableAssMapper flowableAssMapper; + private final FlowFormAssMapper flowFormAssMapper; + @@ -75,7 +80,7 @@ public class FlowDefinitionServiceImpl extends FlowServiceFactory implements IFl // 设置流程发起人Id到流程中 identityService.setAuthenticatedUserId(RequestUtil.getUserIndex()); - variables.put("INITIATOR", RequestUtil.getUserIndex()); + //variables.put("applyUser", RequestUtil.getUserIndex()); ProcessInstance res = runtimeService.startProcessInstanceById(procDefId, variables); FlowableAss flowableAss = new FlowableAss(); @@ -84,7 +89,7 @@ public class FlowDefinitionServiceImpl extends FlowServiceFactory implements IFl flowableAss.setExecIndex(res.getProcessInstanceId()); flowableAssMapper.insert(flowableAss); - return res.getCallbackId(); + return res.getProcessInstanceId(); } catch (Exception e) { e.printStackTrace(); throw new BusinessException("开始流程出错!"); @@ -146,4 +151,103 @@ public class FlowDefinitionServiceImpl extends FlowServiceFactory implements IFl return historicProcessInstance; } + /** + * 流程定义列表 + * + * @param pageNum 当前页码 + * @param pageSize 每页条数 + * @return 流程定义分页列表数据 + */ + @Override + public Page list(String name, Integer pageNum, Integer pageSize) { +// // 流程定义列表数据查询 +// final ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery(); +// if (StringUtils.isNotEmpty(name)) { +// processDefinitionQuery.processDefinitionNameLike(name); +// } +//// processDefinitionQuery.orderByProcessDefinitionKey().asc(); +// page.setTotal(processDefinitionQuery.count()); +// List processDefinitionList = processDefinitionQuery.listPage(pageSize * (pageNum - 1), pageSize); +// +// List dataList = new ArrayList<>(); +// for (ProcessDefinition processDefinition : processDefinitionList) { +// String deploymentId = processDefinition.getDeploymentId(); +// Deployment deployment = repositoryService.createDeploymentQuery().deploymentId(deploymentId).singleResult(); +// FlowProcDefDto reProcDef = new FlowProcDefDto(); +// BeanUtils.copyProperties(processDefinition, reProcDef); +// SysForm sysForm = sysDeployFormService.selectSysDeployFormByDeployId(deploymentId); +// if (Objects.nonNull(sysForm)) { +// reProcDef.setFormName(sysForm.getFormName()); +// reProcDef.setFormId(sysForm.getFormId()); +// } +// // 流程定义时间 +// reProcDef.setDeploymentTime(deployment.getDeploymentTime()); +// dataList.add(reProcDef); +// } + + Page pageResult = flowableAssMapper.selectDeployList(new Page<>(pageNum,pageSize),name); + // 加载挂表单 + /* for (FlowProcDefDto procDef : dataList) { + SysForm sysForm = sysDeployFormService.selectSysDeployFormByDeployId(procDef.getDeploymentId()); + if (Objects.nonNull(sysForm)) { + procDef.setFormName(sysForm.getFormName()); + procDef.setFormId(sysForm.getFormId()); + } + }*/ + + return pageResult; + } + + + /** + * 导入流程文件 + * + * 当每个key的流程第一次部署时,指定版本为1。对其后所有使用相同key的流程定义, + * 部署时版本会在该key当前已部署的最高版本号基础上加1。key参数用于区分流程定义 + * @param name + * @param category + * @param in + */ + @Override + public void importFile(String name, String category, InputStream in) { + Deployment deploy = repositoryService.createDeployment().addInputStream(name + BPMN_FILE_SUFFIX, in).name(name).category(category).deploy(); + ProcessDefinition definition = repositoryService.createProcessDefinitionQuery().deploymentId(deploy.getId()).singleResult(); + repositoryService.setProcessDefinitionCategory(definition.getId(), category); + + } + + + /** + * 读取xml + * + * @param deployId + * @return + */ + @Override + public String readXml(String deployId) throws IOException { + ProcessDefinition definition = repositoryService.createProcessDefinitionQuery().deploymentId(deployId).singleResult(); + InputStream inputStream = repositoryService.getResourceAsStream(definition.getDeploymentId(), definition.getResourceName()); + String result = IOUtils.toString(inputStream, StandardCharsets.UTF_8.name()); + return result; + } + + @Override + public Boolean assFormWithDeploy(String deployId, String formId) { + + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(FlowFormAss::getDefinitionId,deployId); + FlowFormAss flowFormAss = flowFormAssMapper.selectOne(lambdaQueryWrapper); + + FlowFormAss po = new FlowFormAss(); + po.setDefinitionId(deployId); + po.setFormId(formId); + if(Objects.isNull(flowFormAss)){ + flowFormAssMapper.insert(po); + }else { + flowFormAssMapper.update(po,new LambdaQueryWrapper().eq(FlowFormAss::getDefinitionId,deployId)); + } + return true; + } + + } diff --git a/pqs-process/process-boot/src/main/java/com/njcn/process/service/impl/flowable/FlowInstanceServiceImpl.java b/pqs-process/process-boot/src/main/java/com/njcn/process/service/impl/flowable/FlowInstanceServiceImpl.java new file mode 100644 index 000000000..7a227627e --- /dev/null +++ b/pqs-process/process-boot/src/main/java/com/njcn/process/service/impl/flowable/FlowInstanceServiceImpl.java @@ -0,0 +1,121 @@ +package com.njcn.process.service.impl.flowable; + + + +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.process.factory.FlowServiceFactory; +import com.njcn.process.pojo.vo.flowable.FlowTaskVo; +import com.njcn.process.service.flowable.IFlowInstanceService; +import com.njcn.web.utils.RequestUtil; +import liquibase.pro.packaged.S; +import lombok.extern.slf4j.Slf4j; +import org.flowable.common.engine.api.FlowableObjectNotFoundException; +import org.flowable.engine.history.HistoricProcessInstance; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Map; +import java.util.Objects; + +/** + *

工作流流程实例管理

+ * + * @author Tony + * @date 2021-04-03 + */ +@Service +@Slf4j +public class FlowInstanceServiceImpl extends FlowServiceFactory implements IFlowInstanceService { + + /** + * 结束流程实例 + * + * @param vo + */ + @Override + public void stopProcessInstance(FlowTaskVo vo) { + String taskId = vo.getTaskId(); + + } + + /** + * 激活或挂起流程实例 + * + * @param state 状态 + * @param instanceId 流程实例ID + */ + @Override + public void updateState(Integer state, String instanceId) { + + // 激活 + if (state == 1) { + runtimeService.activateProcessInstanceById(instanceId); + } + // 挂起 + if (state == 2) { + runtimeService.suspendProcessInstanceById(instanceId); + } + } + + /** + * 删除流程实例ID + * + * @param instanceId 流程实例ID + * @param deleteReason 删除原因 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void delete(String instanceId, String deleteReason) { + + // 查询历史数据 + HistoricProcessInstance historicProcessInstance = getHistoricProcessInstanceById(instanceId); + if (historicProcessInstance.getEndTime() != null) { + historyService.deleteHistoricProcessInstance(historicProcessInstance.getId()); + return; + } + // 删除流程实例 + runtimeService.deleteProcessInstance(instanceId, deleteReason); + // 删除历史流程实例 + historyService.deleteHistoricProcessInstance(instanceId); + } + + /** + * 根据实例ID查询历史实例数据 + * + * @param processInstanceId + * @return + */ + @Override + public HistoricProcessInstance getHistoricProcessInstanceById(String processInstanceId) { + HistoricProcessInstance historicProcessInstance = + historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); + if (Objects.isNull(historicProcessInstance)) { + throw new FlowableObjectNotFoundException("流程实例不存在: " + processInstanceId); + } + return historicProcessInstance; + } + + /** + * 根据流程定义ID启动流程实例 + * + * @param procDefId 流程定义Id + * @param variables 流程变量 + * @return + */ + @Override + public String startProcessInstanceById(String procDefId, Map variables) { + + try { + // 设置流程发起人Id到流程中 + String userId = RequestUtil.getUserIndex(); +// identityService.setAuthenticatedUserId(userId.toString()); + variables.put("initiator",userId); + variables.put("_FLOWABLE_SKIP_EXPRESSION_ENABLED", true); + runtimeService.startProcessInstanceById(procDefId, variables); + return "流程启动成功"; + } catch (Exception e) { + e.printStackTrace(); + return "流程启动错误"; + } + } +} diff --git a/pqs-process/process-boot/src/main/java/com/njcn/process/service/impl/flowable/FlowTaskServiceImpl.java b/pqs-process/process-boot/src/main/java/com/njcn/process/service/impl/flowable/FlowTaskServiceImpl.java index 6c3497e72..a4fb4b2f4 100644 --- a/pqs-process/process-boot/src/main/java/com/njcn/process/service/impl/flowable/FlowTaskServiceImpl.java +++ b/pqs-process/process-boot/src/main/java/com/njcn/process/service/impl/flowable/FlowTaskServiceImpl.java @@ -2,13 +2,26 @@ package com.njcn.process.service.impl.flowable; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.process.enums.FlowComment; import com.njcn.process.factory.FlowServiceFactory; import com.njcn.process.pojo.dto.FlowViewerDto; +import com.njcn.process.pojo.dto.flowable.FlowCommentDto; +import com.njcn.process.pojo.dto.flowable.FlowTaskDto; +import com.njcn.process.pojo.vo.flowable.FlowQueryVo; import com.njcn.process.pojo.vo.flowable.FlowTaskVo; import com.njcn.process.service.flowable.IFlowTaskService; +import com.njcn.process.utils.FlowableUtils; +import com.njcn.user.api.UserFeignClient; +import com.njcn.user.pojo.po.User; +import com.njcn.user.pojo.vo.UserVO; +import com.njcn.web.utils.RequestUtil; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.io.IOUtils; @@ -46,6 +59,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * @author Tony @@ -53,8 +67,11 @@ import java.util.stream.Collectors; **/ @Service @Slf4j +@RequiredArgsConstructor public class FlowTaskServiceImpl extends FlowServiceFactory implements IFlowTaskService { + private final UserFeignClient userFeignClient; + @Override public Boolean getNextFlowNodeByStart(FlowTaskVo flowTaskVo) { @@ -124,16 +141,28 @@ public class FlowTaskServiceImpl extends FlowServiceFactory implements IFlowTask } if (DelegationState.PENDING.equals(task.getDelegationState())) { //taskService.addComment(taskVo.getTaskId(), taskVo.getInstanceId(), FlowComment.DELEGATE.getType(), taskVo.getComment()); - //taskService.resolveTask(taskVo.getTaskId(), taskVo.getVariables()); + taskService.resolveTask(taskVo.getTaskId(), taskVo.getVariables()); } else { - //taskService.addComment(taskVo.getTaskId(), taskVo.getInstanceId(), FlowComment.NORMAL.getType(), taskVo.getComment()); + taskService.addComment(taskVo.getTaskId(), taskVo.getInstanceId(), FlowComment.NORMAL.getType(), taskVo.getComment()); //Long userId = SecurityUtils.getLoginUser().getUser().getUserId(); - taskService.setAssignee(taskVo.getTaskId(), "1"); + if(StrUtil.isNotBlank(taskVo.getAssignee())){ + taskService.setAssignee(taskVo.getTaskId(), taskVo.getAssignee()); + } taskService.complete(taskVo.getTaskId(), taskVo.getVariables()); } return true; } + @Override + public Task toNextTaskUser(FlowTaskVo taskVo) { + Task task = taskService.createTaskQuery().taskId(taskVo.getTaskId()).singleResult(); + if (Objects.isNull(task)) { + throw new BusinessException("任务不存在"); + } + taskService.setAssignee(taskVo.getTaskId(), taskVo.getAssignee()); + return task; + } + @Override public Task getTask(String proIndex) { return taskService.createTaskQuery() @@ -141,4 +170,512 @@ public class FlowTaskServiceImpl extends FlowServiceFactory implements IFlowTask } + + + + /** + * 我发起的流程 + * + * @param queryVo 请求参数 + * @return + */ + @Override + public Page myProcess(FlowQueryVo queryVo) { + Page page = new Page<>(); + String userId = RequestUtil.getUserIndex(); + HistoricProcessInstanceQuery historicProcessInstanceQuery = historyService.createHistoricProcessInstanceQuery() + .startedBy(userId) + .orderByProcessInstanceStartTime() + .desc(); + List historicProcessInstances = historicProcessInstanceQuery.listPage(queryVo.getPageSize() * (queryVo.getPageNum() - 1), queryVo.getPageSize()); + page.setTotal(historicProcessInstanceQuery.count()); + List flowList = new ArrayList<>(); + for (HistoricProcessInstance hisIns : historicProcessInstances) { + FlowTaskDto flowTask = new FlowTaskDto(); + flowTask.setCreateTime(hisIns.getStartTime()); + flowTask.setFinishTime(hisIns.getEndTime()); + flowTask.setProcInsId(hisIns.getId()); + + // 计算耗时 + if (Objects.nonNull(hisIns.getEndTime())) { + long time = hisIns.getEndTime().getTime() - hisIns.getStartTime().getTime(); + flowTask.setDuration(getDate(time)); + } else { + long time = System.currentTimeMillis() - hisIns.getStartTime().getTime(); + flowTask.setDuration(getDate(time)); + } + // 流程定义信息 + ProcessDefinition pd = repositoryService.createProcessDefinitionQuery() + .processDefinitionId(hisIns.getProcessDefinitionId()) + .singleResult(); + flowTask.setDeployId(pd.getDeploymentId()); + flowTask.setProcDefName(pd.getName()); + flowTask.setProcDefVersion(pd.getVersion()); + flowTask.setCategory(pd.getCategory()); + flowTask.setProcDefVersion(pd.getVersion()); + // 当前所处流程 + List taskList = taskService.createTaskQuery().processInstanceId(hisIns.getId()).list(); + if (CollectionUtils.isNotEmpty(taskList)) { + flowTask.setTaskId(taskList.get(0).getId()); + flowTask.setTaskName(taskList.get(0).getName()); + if (StringUtils.isNotBlank(taskList.get(0).getAssignee())) { + // 当前任务节点办理人信息 + List userList = userFeignClient.getUserByIdList(Stream.of(taskList.get(0).getAssignee()).collect(Collectors.toList())).getData(); + if (CollectionUtil.isNotEmpty(userList)) { + flowTask.setAssigneeId(userList.get(0).getId()); + flowTask.setAssigneeName(userList.get(0).getName()); + flowTask.setAssigneeDeptName(Objects.nonNull(userList.get(0).getDeptId()) ? userList.get(0).getDeptId() : ""); + } + } + } else { + List historicTaskInstance = historyService.createHistoricTaskInstanceQuery().processInstanceId(hisIns.getId()).orderByHistoricTaskInstanceEndTime().desc().list(); + flowTask.setTaskId(historicTaskInstance.get(0).getId()); + flowTask.setTaskName(historicTaskInstance.get(0).getName()); + if (StringUtils.isNotBlank(historicTaskInstance.get(0).getAssignee())) { + // 当前任务节点办理人信息 + UserVO userVO = userFeignClient.getUserById(historicTaskInstance.get(0).getAssignee()).getData(); + if (Objects.nonNull(userVO)) { + flowTask.setAssigneeId(userVO.getId()); + flowTask.setAssigneeName(userVO.getName()); + flowTask.setAssigneeDeptName(Objects.nonNull(userVO.getDeptName()) ? userVO.getDeptName() : ""); + } + } + } + flowList.add(flowTask); + } + page.setRecords(flowList); + return page; + } + + + /** + * 已办任务列表 + * + * @param queryVo 请求参数 + * @return + */ + @Override + public Page finishedList(FlowQueryVo queryVo) { + Page page = new Page<>(); + String userId = RequestUtil.getUserIndex(); + HistoricTaskInstanceQuery taskInstanceQuery = historyService.createHistoricTaskInstanceQuery() + .includeProcessVariables() + .finished() + .taskAssignee(userId) + .orderByHistoricTaskInstanceEndTime() + .desc(); + List historicTaskInstanceList = taskInstanceQuery.listPage(queryVo.getPageSize() * (queryVo.getPageNum() - 1), queryVo.getPageSize()); + List hisTaskList = new ArrayList<>(); + 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(); + List userList = userFeignClient.getUserByIdList(Stream.of(historicProcessInstance.getStartUserId()).collect(Collectors.toList())).getData(); + flowTask.setStartUserId(userList.get(0).getLoginName()); + flowTask.setStartUserName(userList.get(0).getName()); + flowTask.setStartDeptName(userList.get(0).getDeptId()); + hisTaskList.add(flowTask); + } + page.setTotal(taskInstanceQuery.count()); + page.setRecords(hisTaskList); + return page; + } + + + /** + * 代办任务列表 + * + * @param queryVo 请求参数 + * @return + */ + @Override + public Page todoList(FlowQueryVo queryVo) { + Page page = new Page<>(); + // 只查看自己的数据 + String userId = RequestUtil.getUserIndex(); + UserVO sysUser = userFeignClient.getUserById(userId).getData(); + TaskQuery taskQuery = taskService.createTaskQuery() + .active() + .includeProcessVariables() + .taskCandidateGroupIn(sysUser.getRoleList()) + .taskCandidateOrAssigned(sysUser.getId()) + .orderByTaskCreateTime().desc(); + +// TODO 传入名称查询不到数据? +// if (StringUtils.isNotBlank(queryVo.getName())){ +// taskQuery.processDefinitionNameLike(queryVo.getName()); +// } + page.setTotal(taskQuery.count()); + List taskList = taskQuery.listPage(queryVo.getPageSize() * (queryVo.getPageNum() - 1), queryVo.getPageSize()); + List 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(); + UserVO startUser = userFeignClient.getUserById(historicProcessInstance.getStartUserId()).getData(); + flowTask.setStartUserId(startUser.getId()); + flowTask.setStartUserName(startUser.getName()); + flowTask.setStartDeptName(Objects.nonNull(startUser.getDeptName()) ? startUser.getDeptName() : ""); + flowList.add(flowTask); + } + + page.setRecords(flowList); + return page; + } + + + /** + * 获取流程变量 + * + * @param taskId + * @return + */ + @Override + public Map processVariables(String taskId) { +// HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery() +// .processInstanceId(task.getProcessInstanceId()) +// .singleResult(); +// SysUser startUser = sysUserService.selectUserById(Long.parseLong(historicProcessInstance.getStartUserId())); + + // 流程变量 + HistoricTaskInstance historicTaskInstance = historyService.createHistoricTaskInstanceQuery().includeProcessVariables().finished().taskId(taskId).singleResult(); + if (Objects.nonNull(historicTaskInstance)) { + return historicTaskInstance.getProcessVariables(); + } else { + Map variables = taskService.getVariables(taskId); + return variables; + } + } + + + /** + * 流程历史流转记录 + * + * @param procInsId 流程实例Id + * @return + */ + @Override + public Map flowRecord(String procInsId, String deployId) { + 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 ("startEvent".equals(histIns.getActivityType())) { +// FlowTaskDto flowTask = new FlowTaskDto(); +// // 流程发起人信息 +// HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery() +// .processInstanceId(histIns.getProcessInstanceId()) +// .singleResult(); +// SysUser startUser = sysUserService.selectUserById(Long.parseLong(historicProcessInstance.getStartUserId())); +// flowTask.setTaskName(startUser.getNickName() + "(" + startUser.getDept().getDeptName() + ")发起申请"); +// flowTask.setFinishTime(histIns.getEndTime()); +// hisFlowList.add(flowTask); +// } else if ("endEvent".equals(histIns.getActivityType())) { +// FlowTaskDto flowTask = new FlowTaskDto(); +// flowTask.setTaskName(StringUtils.isNotBlank(histIns.getActivityName()) ? histIns.getActivityName() : "结束"); +// flowTask.setFinishTime(histIns.getEndTime()); +// hisFlowList.add(flowTask); +// } else + 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())) { + UserVO sysUser = userFeignClient.getUserById(histIns.getAssignee()).getData(); + flowTask.setAssigneeId(sysUser.getId()); + flowTask.setAssigneeName(sysUser.getName()); + flowTask.setDeptName(Objects.nonNull(sysUser.getDeptName()) ? sysUser.getDeptName() : ""); + } + // 展示审批人员 + List linksForTask = historyService.getHistoricIdentityLinksForTask(histIns.getTaskId()); + StringBuilder stringBuilder = new StringBuilder(); + for (HistoricIdentityLink identityLink : linksForTask) { + // 获选人,候选组/角色(多个) + if ("candidate".equals(identityLink.getType())) { + if (StringUtils.isNotBlank(identityLink.getUserId())) { + UserVO sysUser = userFeignClient.getUserById(identityLink.getUserId()).getData(); + stringBuilder.append(sysUser.getName()).append(","); + } + if (StringUtils.isNotBlank(identityLink.getGroupId())) { + /* UserVO sysUser = userFeignClient.getUserById(identityLink.getUserId()).getData(); + + SysRole sysRole = sysRoleService.selectRoleById(Long.parseLong(identityLink.getGroupId())); + stringBuilder.append(sysRole.getRoleName()).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); + } + // 第一次申请获取初始化表单 + if (StringUtils.isNotBlank(deployId)) { + /* SysForm sysForm = sysInstanceFormService.selectSysDeployFormByDeployId(deployId); + if (Objects.isNull(sysForm)) { + return AjaxResult.error("请先配置流程表单"); + } + map.put("formData", JSONObject.parseObject(sysForm.getFormContent()));*/ + } + return map; + } + + + + /** + * 取消申请 + * 目前实现方式: 直接将当前流程变更为已完成 + * + * @param flowTaskVo + * @return + */ + @Override + public Boolean stopProcess(FlowTaskVo flowTaskVo) { + List task = taskService.createTaskQuery().processInstanceId(flowTaskVo.getInstanceId()).list(); + if (CollectionUtils.isEmpty(task)) { + throw new BusinessException("流程未启动或已执行完成,取消申请失败"); + } + // 获取当前流程实例 + ProcessInstance processInstance = runtimeService.createProcessInstanceQuery() + .processInstanceId(flowTaskVo.getInstanceId()) + .singleResult(); + BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId()); + if (Objects.nonNull(bpmnModel)) { + Process process = bpmnModel.getMainProcess(); + List endNodes = process.findFlowElementsOfType(EndEvent.class, false); + if (CollectionUtils.isNotEmpty(endNodes)) { + // TODO 取消流程为什么要设置流程发起人? +// SysUser loginUser = SecurityUtils.getLoginUser().getUser(); +// Authentication.setAuthenticatedUserId(loginUser.getUserId().toString()); + +// taskService.addComment(task.getId(), processInstance.getProcessInstanceId(), FlowComment.STOP.getType(), +// StringUtils.isBlank(flowTaskVo.getComment()) ? "取消申请" : flowTaskVo.getComment()); + // 获取当前流程最后一个节点 + String endId = endNodes.get(0).getId(); + List executions = runtimeService.createExecutionQuery() + .parentId(processInstance.getProcessInstanceId()).list(); + List executionIds = new ArrayList<>(); + executions.forEach(execution -> executionIds.add(execution.getId())); + // 变更流程为已结束状态 + runtimeService.createChangeActivityStateBuilder() + .moveExecutionsToSingleActivityId(executionIds, endId).changeState(); + } + } + + return true; + } + + /** + * 驳回任务 + * + * @param flowTaskVo + */ + @Override + public void taskReject(FlowTaskVo flowTaskVo) { + if (taskService.createTaskQuery().taskId(flowTaskVo.getTaskId()).singleResult().isSuspended()) { + throw new BusinessException("任务处于挂起状态!"); + } + // 当前任务 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) { + throw new BusinessException("当前节点为初始任务节点,不能驳回"); + } + // 获取活动 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) { + throw new BusinessException("任务出现多对多情况,无法撤回"); + } + + // 循环获取那些需要被撤回的节点的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) { + throw new BusinessException("未找到流程实例,流程可能已发生变化"); + } catch (FlowableException e) { + throw new BusinessException("无法取消或开始活动"); + } + + } + + /** + * 流程完成时间处理 + * + * @param ms + * @return + */ + private String getDate(long ms) { + + long day = ms / (24 * 60 * 60 * 1000); + long hour = (ms / (60 * 60 * 1000) - day * 24); + long minute = ((ms / (60 * 1000)) - day * 24 * 60 - hour * 60); + long second = (ms / 1000 - day * 24 * 60 * 60 - hour * 60 * 60 - minute * 60); + + if (day > 0) { + return day + "天" + hour + "小时" + minute + "分钟"; + } + if (hour > 0) { + return hour + "小时" + minute + "分钟"; + } + if (minute > 0) { + return minute + "分钟"; + } + if (second > 0) { + return second + "秒"; + } else { + return 0 + "秒"; + } + } + } diff --git a/pqs-process/process-boot/src/main/java/com/njcn/process/utils/FlowableUtils.java b/pqs-process/process-boot/src/main/java/com/njcn/process/utils/FlowableUtils.java new file mode 100644 index 000000000..3f4aa9c88 --- /dev/null +++ b/pqs-process/process-boot/src/main/java/com/njcn/process/utils/FlowableUtils.java @@ -0,0 +1,589 @@ +package com.njcn.process.utils; + +import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.model.*; +import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior; +import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior; +import org.flowable.task.api.history.HistoricTaskInstance; + +import java.util.*; + +/** + * @author Tony + * @date 2021-04-03 23:57 + */ +@Slf4j +public class FlowableUtils { + + /** + * 根据节点,获取入口连线 + * @param source + * @return + */ + public static List getElementIncomingFlows(FlowElement source) { + List sequenceFlows = null; + if (source instanceof FlowNode) { + sequenceFlows = ((FlowNode) source).getIncomingFlows(); + } else if (source instanceof Gateway) { + sequenceFlows = ((Gateway) source).getIncomingFlows(); + } else if (source instanceof SubProcess) { + sequenceFlows = ((SubProcess) source).getIncomingFlows(); + } else if (source instanceof StartEvent) { + sequenceFlows = ((StartEvent) source).getIncomingFlows(); + } else if (source instanceof EndEvent) { + sequenceFlows = ((EndEvent) source).getIncomingFlows(); + } + return sequenceFlows; + } + + /** + * 根据节点,获取出口连线 + * @param source + * @return + */ + public static List getElementOutgoingFlows(FlowElement source) { + List sequenceFlows = null; + if (source instanceof FlowNode) { + sequenceFlows = ((FlowNode) source).getOutgoingFlows(); + } else if (source instanceof Gateway) { + sequenceFlows = ((Gateway) source).getOutgoingFlows(); + } else if (source instanceof SubProcess) { + sequenceFlows = ((SubProcess) source).getOutgoingFlows(); + } else if (source instanceof StartEvent) { + sequenceFlows = ((StartEvent) source).getOutgoingFlows(); + } else if (source instanceof EndEvent) { + sequenceFlows = ((EndEvent) source).getOutgoingFlows(); + } + return sequenceFlows; + } + + /** + * 获取全部节点列表,包含子流程节点 + * @param flowElements + * @param allElements + * @return + */ + public static Collection getAllElements(Collection flowElements, Collection allElements) { + allElements = allElements == null ? new ArrayList<>() : allElements; + + for (FlowElement flowElement : flowElements) { + allElements.add(flowElement); + if (flowElement instanceof SubProcess) { + // 继续深入子流程,进一步获取子流程 + allElements = FlowableUtils.getAllElements(((SubProcess) flowElement).getFlowElements(), allElements); + } + } + return allElements; + } + + /** + * 迭代获取父级任务节点列表,向前找 + * @param source 起始节点 + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 + * @param userTaskList 已找到的用户任务节点 + * @return + */ + public static List iteratorFindParentUserTasks(FlowElement source, Set hasSequenceFlow, List userTaskList) { + userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList; + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; + + // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代 + if (source instanceof StartEvent && source.getSubProcess() != null) { + userTaskList = iteratorFindParentUserTasks(source.getSubProcess(), hasSequenceFlow, userTaskList); + } + + // 根据类型,获取入口连线 + List sequenceFlows = getElementIncomingFlows(source); + + if (sequenceFlows != null) { + // 循环找到目标元素 + for (SequenceFlow sequenceFlow: sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + // 类型为用户节点,则新增父级节点 + if (sequenceFlow.getSourceFlowElement() instanceof UserTask) { + userTaskList.add((UserTask) sequenceFlow.getSourceFlowElement()); + continue; + } + // 类型为子流程,则添加子流程开始节点出口处相连的节点 + if (sequenceFlow.getSourceFlowElement() instanceof SubProcess) { + // 获取子流程用户任务节点 + List childUserTaskList = findChildProcessUserTasks((StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements().toArray()[0], null, null); + // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续 + if (childUserTaskList != null && childUserTaskList.size() > 0) { + userTaskList.addAll(childUserTaskList); + continue; + } + } + // 继续迭代 + userTaskList = iteratorFindParentUserTasks(sequenceFlow.getSourceFlowElement(), hasSequenceFlow, userTaskList); + } + } + return userTaskList; + } + + /** + * 根据正在运行的任务节点,迭代获取子级任务节点列表,向后找 + * @param source 起始节点(退回节点) + * @param runTaskKeyList 正在运行的任务 Key,用于校验任务节点是否是正在运行的节点 + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 + * @param userTaskList 需要撤回的用户任务列表 + * @return + */ + public static List iteratorFindChildUserTasks(FlowElement source, List runTaskKeyList, Set hasSequenceFlow, List userTaskList) { + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; + userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList; + + // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代 + if (source instanceof EndEvent && source.getSubProcess() != null) { + userTaskList = iteratorFindChildUserTasks(source.getSubProcess(), runTaskKeyList, hasSequenceFlow, userTaskList); + } + + // 根据类型,获取出口连线 + List sequenceFlows = getElementOutgoingFlows(source); + + if (sequenceFlows != null) { + // 循环找到目标元素 + for (SequenceFlow sequenceFlow: sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + // 如果为用户任务类型,且任务节点的 Key 正在运行的任务中存在,添加 + if (sequenceFlow.getTargetFlowElement() instanceof UserTask && runTaskKeyList.contains((sequenceFlow.getTargetFlowElement()).getId())) { + userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement()); + continue; + } + // 如果节点为子流程节点情况,则从节点中的第一个节点开始获取 + if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) { + List childUserTaskList = iteratorFindChildUserTasks((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), runTaskKeyList, hasSequenceFlow, null); + // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续 + if (childUserTaskList != null && childUserTaskList.size() > 0) { + userTaskList.addAll(childUserTaskList); + continue; + } + } + // 继续迭代 + userTaskList = iteratorFindChildUserTasks(sequenceFlow.getTargetFlowElement(), runTaskKeyList, hasSequenceFlow, userTaskList); + } + } + return userTaskList; + } + + /** + * 迭代获取子流程用户任务节点 + * @param source 起始节点 + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 + * @param userTaskList 需要撤回的用户任务列表 + * @return + */ + public static List findChildProcessUserTasks(FlowElement source, Set hasSequenceFlow, List userTaskList) { + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; + userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList; + + // 根据类型,获取出口连线 + List sequenceFlows = getElementOutgoingFlows(source); + + if (sequenceFlows != null) { + // 循环找到目标元素 + for (SequenceFlow sequenceFlow: sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + // 如果为用户任务类型,且任务节点的 Key 正在运行的任务中存在,添加 + if (sequenceFlow.getTargetFlowElement() instanceof UserTask) { + userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement()); + continue; + } + // 如果节点为子流程节点情况,则从节点中的第一个节点开始获取 + if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) { + List childUserTaskList = findChildProcessUserTasks((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), hasSequenceFlow, null); + // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续 + if (childUserTaskList != null && childUserTaskList.size() > 0) { + userTaskList.addAll(childUserTaskList); + continue; + } + } + // 继续迭代 + userTaskList = findChildProcessUserTasks(sequenceFlow.getTargetFlowElement(), hasSequenceFlow, userTaskList); + } + } + return userTaskList; + } + + /** + * 从后向前寻路,获取所有脏线路上的点 + * @param source 起始节点 + * @param passRoads 已经经过的点集合 + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 + * @param targets 目标脏线路终点 + * @param dirtyRoads 确定为脏数据的点,因为不需要重复,因此使用 set 存储 + * @return + */ + public static Set iteratorFindDirtyRoads(FlowElement source, List passRoads, Set hasSequenceFlow, List targets, Set dirtyRoads) { + passRoads = passRoads == null ? new ArrayList<>() : passRoads; + dirtyRoads = dirtyRoads == null ? new HashSet<>() : dirtyRoads; + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; + + // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代 + if (source instanceof StartEvent && source.getSubProcess() != null) { + dirtyRoads = iteratorFindDirtyRoads(source.getSubProcess(), passRoads, hasSequenceFlow, targets, dirtyRoads); + } + + // 根据类型,获取入口连线 + List sequenceFlows = getElementIncomingFlows(source); + + if (sequenceFlows != null) { + // 循环找到目标元素 + for (SequenceFlow sequenceFlow: sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + // 新增经过的路线 + passRoads.add(sequenceFlow.getSourceFlowElement().getId()); + // 如果此点为目标点,确定经过的路线为脏线路,添加点到脏线路中,然后找下个连线 + if (targets.contains(sequenceFlow.getSourceFlowElement().getId())) { + dirtyRoads.addAll(passRoads); + continue; + } + // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代 + if (sequenceFlow.getSourceFlowElement() instanceof SubProcess) { + dirtyRoads = findChildProcessAllDirtyRoad((StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements().toArray()[0], null, dirtyRoads); + // 是否存在子流程上,true 是,false 否 + Boolean isInChildProcess = dirtyTargetInChildProcess((StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements().toArray()[0], null, targets, null); + if (isInChildProcess) { + // 已在子流程上找到,该路线结束 + continue; + } + } + // 继续迭代 + dirtyRoads = iteratorFindDirtyRoads(sequenceFlow.getSourceFlowElement(), passRoads, hasSequenceFlow, targets, dirtyRoads); + } + } + return dirtyRoads; + } + + /** + * 迭代获取子流程脏路线 + * 说明,假如回退的点就是子流程,那么也肯定会回退到子流程最初的用户任务节点,因此子流程中的节点全是脏路线 + * @param source 起始节点 + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 + * @param dirtyRoads 确定为脏数据的点,因为不需要重复,因此使用 set 存储 + * @return + */ + public static Set findChildProcessAllDirtyRoad(FlowElement source, Set hasSequenceFlow, Set dirtyRoads) { + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; + dirtyRoads = dirtyRoads == null ? new HashSet<>() : dirtyRoads; + + // 根据类型,获取出口连线 + List sequenceFlows = getElementOutgoingFlows(source); + + if (sequenceFlows != null) { + // 循环找到目标元素 + for (SequenceFlow sequenceFlow: sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + // 添加脏路线 + dirtyRoads.add(sequenceFlow.getTargetFlowElement().getId()); + // 如果节点为子流程节点情况,则从节点中的第一个节点开始获取 + if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) { + dirtyRoads = findChildProcessAllDirtyRoad((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), hasSequenceFlow, dirtyRoads); + } + // 继续迭代 + dirtyRoads = findChildProcessAllDirtyRoad(sequenceFlow.getTargetFlowElement(), hasSequenceFlow, dirtyRoads); + } + } + return dirtyRoads; + } + + /** + * 判断脏路线结束节点是否在子流程上 + * @param source 起始节点 + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 + * @param targets 判断脏路线节点是否存在子流程上,只要存在一个,说明脏路线只到子流程为止 + * @param inChildProcess 是否存在子流程上,true 是,false 否 + * @return + */ + public static Boolean dirtyTargetInChildProcess(FlowElement source, Set hasSequenceFlow, List targets, Boolean inChildProcess) { + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; + inChildProcess = inChildProcess != null && inChildProcess; + + // 根据类型,获取出口连线 + List sequenceFlows = getElementOutgoingFlows(source); + + if (sequenceFlows != null && !inChildProcess) { + // 循环找到目标元素 + for (SequenceFlow sequenceFlow: sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + // 如果发现目标点在子流程上存在,说明只到子流程为止 + if (targets.contains(sequenceFlow.getTargetFlowElement().getId())) { + inChildProcess = true; + break; + } + // 如果节点为子流程节点情况,则从节点中的第一个节点开始获取 + if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) { + inChildProcess = dirtyTargetInChildProcess((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), hasSequenceFlow, targets, inChildProcess); + } + // 继续迭代 + inChildProcess = dirtyTargetInChildProcess(sequenceFlow.getTargetFlowElement(), hasSequenceFlow, targets, inChildProcess); + } + } + return inChildProcess; + } + + /** + * 迭代从后向前扫描,判断目标节点相对于当前节点是否是串行 + * 不存在直接回退到子流程中的情况,但存在从子流程出去到父流程情况 + * @param source 起始节点 + * @param isSequential 是否串行 + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 + * @param targetKsy 目标节点 + * @return + */ + public static Boolean iteratorCheckSequentialReferTarget(FlowElement source, String targetKsy, Set hasSequenceFlow, Boolean isSequential) { + isSequential = isSequential == null || isSequential; + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; + + // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代 + if (source instanceof StartEvent && source.getSubProcess() != null) { + isSequential = iteratorCheckSequentialReferTarget(source.getSubProcess(), targetKsy, hasSequenceFlow, isSequential); + } + + // 根据类型,获取入口连线 + List sequenceFlows = getElementIncomingFlows(source); + + if (sequenceFlows != null) { + // 循环找到目标元素 + for (SequenceFlow sequenceFlow: sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + // 如果目标节点已被判断为并行,后面都不需要执行,直接返回 + if (!isSequential) { + break; + } + // 这条线路存在目标节点,这条线路完成,进入下个线路 + if (targetKsy.equals(sequenceFlow.getSourceFlowElement().getId())) { + continue; + } + if (sequenceFlow.getSourceFlowElement() instanceof StartEvent) { + isSequential = false; + break; + } + // 否则就继续迭代 + isSequential = iteratorCheckSequentialReferTarget(sequenceFlow.getSourceFlowElement(), targetKsy, hasSequenceFlow, isSequential); + } + } + return isSequential; + } + + /** + * 从后向前寻路,获取到达节点的所有路线 + * 不存在直接回退到子流程,但是存在回退到父级流程的情况 + * @param source 起始节点 + * @param passRoads 已经经过的点集合 + * @param roads 路线 + * @return + */ + public static List> findRoad(FlowElement source, List passRoads, Set hasSequenceFlow, List> roads) { + passRoads = passRoads == null ? new ArrayList<>() : passRoads; + roads = roads == null ? new ArrayList<>() : roads; + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; + + // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代 + if (source instanceof StartEvent && source.getSubProcess() != null) { + roads = findRoad(source.getSubProcess(), passRoads, hasSequenceFlow, roads); + } + + // 根据类型,获取入口连线 + List sequenceFlows = getElementIncomingFlows(source); + + if (sequenceFlows != null && sequenceFlows.size() != 0) { + for (SequenceFlow sequenceFlow: sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + // 添加经过路线 + if (sequenceFlow.getSourceFlowElement() instanceof UserTask) { + passRoads.add((UserTask) sequenceFlow.getSourceFlowElement()); + } + // 继续迭代 + roads = findRoad(sequenceFlow.getSourceFlowElement(), passRoads, hasSequenceFlow, roads); + } + } else { + // 添加路线 + roads.add(passRoads); + } + return roads; + } + + /** + * 历史节点数据清洗,清洗掉又回滚导致的脏数据 + * @param allElements 全部节点信息 + * @param historicTaskInstanceList 历史任务实例信息,数据采用开始时间升序 + * @return + */ + public static List historicTaskInstanceClean(Collection allElements, List historicTaskInstanceList) { + // 会签节点收集 + List multiTask = new ArrayList<>(); + allElements.forEach(flowElement -> { + if (flowElement instanceof UserTask) { + // 如果该节点的行为为会签行为,说明该节点为会签节点 + if (((UserTask) flowElement).getBehavior() instanceof ParallelMultiInstanceBehavior || ((UserTask) flowElement).getBehavior() instanceof SequentialMultiInstanceBehavior) { + multiTask.add(flowElement.getId()); + } + } + }); + // 循环放入栈,栈 LIFO:后进先出 + Stack stack = new Stack<>(); + historicTaskInstanceList.forEach(stack::push); + // 清洗后的历史任务实例 + List lastHistoricTaskInstanceList = new ArrayList<>(); + // 网关存在可能只走了部分分支情况,且还存在跳转废弃数据以及其他分支数据的干扰,因此需要对历史节点数据进行清洗 + // 临时用户任务 key + StringBuilder userTaskKey = null; + // 临时被删掉的任务 key,存在并行情况 + List deleteKeyList = new ArrayList<>(); + // 临时脏数据线路 + List> dirtyDataLineList = new ArrayList<>(); + // 由某个点跳到会签点,此时出现多个会签实例对应 1 个跳转情况,需要把这些连续脏数据都找到 + // 会签特殊处理下标 + int multiIndex = -1; + // 会签特殊处理 key + StringBuilder multiKey = null; + // 会签特殊处理操作标识 + boolean multiOpera = false; + while (!stack.empty()) { + // 从这里开始 userTaskKey 都还是上个栈的 key + // 是否是脏数据线路上的点 + final boolean[] isDirtyData = {false}; + for (Set oldDirtyDataLine : dirtyDataLineList) { + if (oldDirtyDataLine.contains(stack.peek().getTaskDefinitionKey())) { + isDirtyData[0] = true; + } + } + // 删除原因不为空,说明从这条数据开始回跳或者回退的 + // MI_END:会签完成后,其他未签到节点的删除原因,不在处理范围内 + if (stack.peek().getDeleteReason() != null && !"MI_END".equals(stack.peek().getDeleteReason())) { + // 可以理解为脏线路起点 + String dirtyPoint = ""; + if (stack.peek().getDeleteReason().contains("Change activity to ")) { + dirtyPoint = stack.peek().getDeleteReason().replace("Change activity to ", ""); + } + // 会签回退删除原因有点不同 + if (stack.peek().getDeleteReason().contains("Change parent activity to ")) { + dirtyPoint = stack.peek().getDeleteReason().replace("Change parent activity to ", ""); + } + FlowElement dirtyTask = null; + // 获取变更节点的对应的入口处连线 + // 如果是网关并行回退情况,会变成两条脏数据路线,效果一样 + for (FlowElement flowElement : allElements) { + if (flowElement.getId().equals(stack.peek().getTaskDefinitionKey())) { + dirtyTask = flowElement; + } + } + // 获取脏数据线路 + Set dirtyDataLine = FlowableUtils.iteratorFindDirtyRoads(dirtyTask, null, null, Arrays.asList(dirtyPoint.split(",")), null); + // 自己本身也是脏线路上的点,加进去 + dirtyDataLine.add(stack.peek().getTaskDefinitionKey()); + log.info(stack.peek().getTaskDefinitionKey() + "点脏路线集合:" + dirtyDataLine); + // 是全新的需要添加的脏线路 + boolean isNewDirtyData = true; + for (int i = 0; i < dirtyDataLineList.size(); i++) { + // 如果发现他的上个节点在脏线路内,说明这个点可能是并行的节点,或者连续驳回 + // 这时,都以之前的脏线路节点为标准,只需合并脏线路即可,也就是路线补全 + if (dirtyDataLineList.get(i).contains(userTaskKey.toString())) { + isNewDirtyData = false; + dirtyDataLineList.get(i).addAll(dirtyDataLine); + } + } + // 已确定时全新的脏线路 + if (isNewDirtyData) { + // deleteKey 单一路线驳回到并行,这种同时生成多个新实例记录情况,这时 deleteKey 其实是由多个值组成 + // 按照逻辑,回退后立刻生成的实例记录就是回退的记录 + // 至于驳回所生成的 Key,直接从删除原因中获取,因为存在驳回到并行的情况 + deleteKeyList.add(dirtyPoint + ","); + dirtyDataLineList.add(dirtyDataLine); + } + // 添加后,现在这个点变成脏线路上的点了 + isDirtyData[0] = true; + } + // 如果不是脏线路上的点,说明是有效数据,添加历史实例 Key + if (!isDirtyData[0]) { + lastHistoricTaskInstanceList.add(stack.peek().getTaskDefinitionKey()); + } + // 校验脏线路是否结束 + for (int i = 0; i < deleteKeyList.size(); i ++) { + // 如果发现脏数据属于会签,记录下下标与对应 Key,以备后续比对,会签脏数据范畴开始 + if (multiKey == null && multiTask.contains(stack.peek().getTaskDefinitionKey()) + && deleteKeyList.get(i).contains(stack.peek().getTaskDefinitionKey())) { + multiIndex = i; + multiKey = new StringBuilder(stack.peek().getTaskDefinitionKey()); + } + // 会签脏数据处理,节点退回会签清空 + // 如果在会签脏数据范畴中发现 Key改变,说明会签脏数据在上个节点就结束了,可以把会签脏数据删掉 + if (multiKey != null && !multiKey.toString().equals(stack.peek().getTaskDefinitionKey())) { + deleteKeyList.set(multiIndex , deleteKeyList.get(multiIndex).replace(stack.peek().getTaskDefinitionKey() + ",", "")); + multiKey = null; + // 结束进行下校验删除 + multiOpera = true; + } + // 其他脏数据处理 + // 发现该路线最后一条脏数据,说明这条脏数据线路处理完了,删除脏数据信息 + // 脏数据产生的新实例中是否包含这条数据 + if (multiKey == null && deleteKeyList.get(i).contains(stack.peek().getTaskDefinitionKey())) { + // 删除匹配到的部分 + deleteKeyList.set(i , deleteKeyList.get(i).replace(stack.peek().getTaskDefinitionKey() + ",", "")); + } + // 如果每组中的元素都以匹配过,说明脏数据结束 + if ("".equals(deleteKeyList.get(i))) { + // 同时删除脏数据 + deleteKeyList.remove(i); + dirtyDataLineList.remove(i); + break; + } + } + // 会签数据处理需要在循环外处理,否则可能导致溢出 + // 会签的数据肯定是之前放进去的所以理论上不会溢出,但还是校验下 + if (multiOpera && deleteKeyList.size() > multiIndex && "".equals(deleteKeyList.get(multiIndex))) { + // 同时删除脏数据 + deleteKeyList.remove(multiIndex); + dirtyDataLineList.remove(multiIndex); + multiIndex = -1; + multiOpera = false; + } + // pop() 方法与 peek() 方法不同,在返回值的同时,会把值从栈中移除 + // 保存新的 userTaskKey 在下个循环中使用 + userTaskKey = new StringBuilder(stack.pop().getTaskDefinitionKey()); + } + log.info("清洗后的历史节点数据:" + lastHistoricTaskInstanceList); + return lastHistoricTaskInstanceList; + } +}