三、Activiti——请假流程实现

作者: 小疯子 分类: Activiti 发布时间: 2020-03-11 11:32

一、bpmn请假流程绘制

我是在resources/processes目录下进行绘制,图如下

详细流程跳转条件看一下xml中设置的

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" xmlns:tns="http://www.activiti.org/test" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" expressionLanguage="http://www.w3.org/1999/XPath" id="m1582185353972" name="" targetNamespace="http://www.activiti.org/test" typeLanguage="http://www.w3.org/2001/XMLSchema">
<process id="Vacation" isClosed="false" isExecutable="true" processType="None">
<startEvent id="_2" name="StartEvent"/>
<userTask activiti:candidateGroups="employee" activiti:exclusive="true" id="_3" name="填写请假申请"/>
<userTask activiti:candidateGroups="manager" activiti:exclusive="true" id="_4" name="经理审批"/>
<userTask activiti:candidateGroups="director" activiti:exclusive="true" id="_5" name="总监审批"/>
<userTask activiti:candidateGroups="hr" activiti:exclusive="true" id="_6" name="人力资源审批"/>
<endEvent id="_7" name="EndEvent"/>
<sequenceFlow id="_8" sourceRef="_2" targetRef="_3"/>
<exclusiveGateway gatewayDirection="Unspecified" id="_9" name="ExclusiveGateway"/>
<sequenceFlow id="_10" sourceRef="_3" targetRef="_9"/>
<sequenceFlow id="_11" sourceRef="_9" targetRef="_5">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[
${arg.days > 3}
]]>
</conditionExpression>
</sequenceFlow>
<sequenceFlow id="_12" sourceRef="_9" targetRef="_4">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[
${arg.days <= 3}
]]>
</conditionExpression>
</sequenceFlow>
<sequenceFlow id="_13" sourceRef="_5" targetRef="_6"/>
<sequenceFlow id="_14" sourceRef="_4" targetRef="_6"/>
<sequenceFlow id="_15" sourceRef="_6" targetRef="_7"/>
<textAnnotation id="_16" textFormat="text/plain">
<text>小于等于3天?</text>
</textAnnotation>
<textAnnotation id="_17" textFormat="text/plain">
<text>大于3天?</text>
</textAnnotation>
</process>
...
</definitions>

重点就是签名的process id设置了流程的id,还有activiti:candidateGroups代表了此任务的候选用户组(目前是这个任务所有候选用户组的用户都可以进行处理,后期待开发成只有和申请请假相同一个部门的用户组才可以处理领取)
还有arg这个参数,这个参数包含了关于请假申请的一坨信息:申请时间、申请人、申请请假日期、请假天数,也就是一开始填写的申请请假表单的相关信息

二、开始开发

2.1 基本信息添加

首先后台添加员工、总监、经理、hr的相关用户和角色(角色名需要和activiti:candidateGroups对应好)

2.2 员工申请请假

step1. 员工登录后台,填写请假申请单

前端是通过vue iview实现的,前端这里就不谈了,因为我也是小白边查边扒拉的前端不误导别人了就
step2. 后端进行请假申请的接口编写

public ProcessInstance startVacation(VacationRequestForm vacation) {
        SysUser currentUser = UserUtils.getCurrentUser();
        if(currentUser == null || ObjectUtils.isEmpty(currentUser.getId()) || ObjectUtils.isEmpty(currentUser.getActUserId())){
            return null;
        }
        vacation.setUserId(currentUser.getActUserId());
        vacation.setSysUserId(String.valueOf(currentUser.getId()));
        // 设置标题
        vacation.setUserName(currentUser.getUsername());
        vacation.setTitle(currentUser.getUsername() + "的申请");
        vacation.setBusinessType("请假申请");
        // 查找请假流程定义,这里的vacation就是上面的process的id
        ProcessDefinition pd = repositoryService.createProcessDefinitionQuery().processDefinitionKey("Vacation").singleResult();
        // 初始化任务参数
        Map<String, Object> vars = new HashMap<>();
        vars.put("arg", vacation);

        // 启动流程
        ProcessInstance pi = runtimeService.startProcessInstanceByKey(pd.getKey());// 根据流程定义启动流程产生流程实例
        // 查询第一个任务
        Task firstTask = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
        // 设置任务受理人
        firstTask.setAssignee(vacation.getUserId());
        // 完成任务
        taskService.complete(firstTask.getId(), vars);
        // 记录请假数据
        vacationProcessService.saveVacation(vacation, pi.getId());
        return pi;
    }

2.3 总监审批(or经理审批)

假设我们上面的请假申请天数是5天,大于3了,由此就是arg中的days>3,流程会跳转到候选组为总监的那个审批任务上

step1. 总监登录后台

查看我的任务

说明:我的任务模块分两部分,一个是作为候选人可以领取的待办任务,如上图,当点击的“领取”按钮之后,这个任务就claim分派给此总监用户了,此总监用户再对这个任务进行“审批”操作,“查看”按钮可以查看目前流程审批进行到哪里了,后面再细说

step2. 任务列表

关于上图中内容的获取,采用如下代码
控制器层:

 /**
     * 查询用户待办任务 or 受理任务
     * @param taskType 待办or受理
     * @return 任务列表
     */
    @PostMapping(value = "/get_tasks")
    public Result<IPage> getTasks(@RequestParam String taskType, @RequestBody(required = false) PageVo pageVo, @CurrentUser SysUser user) {
//        SysUser currentUser = UserUtils.getCurrentUser();
        if(user == null || ObjectUtils.isEmpty(user.getId()) || ObjectUtils.isEmpty(user.getActUserId())){
            return ResultHelper.buildFail(null);
        }
        if(ObjectUtils.isEmpty(pageVo)) {
            pageVo = new PageVo();
            pageVo.setPageNumber(1);
            pageVo.setPageSize(10);
        }
        String userId = user.getActUserId(); // 流程用户id
        IPage tasks = null;
        if(TaskResponseVO.CANDIDATE.equals(taskType)) {
            tasks = vacationBusiness.getAgendaTasks(userId, pageVo);
        }else if(TaskResponseVO.ASSIGNEE.equals(taskType)){
            tasks = vacationBusiness.getAssigneeTasks(userId, pageVo);
        }
        return ResultHelper.buildSuccess(tasks);
    }

business层:

// 获取待办任务
public IPage getAgendaTasks(String userId, PageVo pageVo) {
        int pageNumber = pageVo.getPageNumber();
        int pageSize = pageVo.getPageSize();
        // pageNumber和pageSize都需要验证一下
        IPage page = new Page<>(pageNumber, pageSize);
        // 找到候选人对应的tasks
        Map<String, Object> maps = vacationProcessService.getAgendaTasks(userId, pageNumber, pageSize);
        // 然后进行转换
        List taskVoList = vacationProcessService.createTaskVoList((List)maps.get("records"));
        page.setRecords(taskVoList);
        page.setPages((Long) maps.get("pages"));
        page.setTotal((Long) maps.get("total"));
        return page;
    }

    // 获取已领取的分派给自己的任务
    public IPage getAssigneeTasks(String userId, PageVo pageVo) {
        // pageNumber和pageSize都需要验证一下
        int pageNumber = pageVo.getPageNumber();
        int pageSize = pageVo.getPageSize();
        IPage page = new Page<>(pageNumber, pageSize);

        Map<String, Object> maps = vacationProcessService.getAssigneeTasks(userId,pageNumber,pageSize);
        List taskVoList = vacationProcessService.createTaskVoList((List)maps.get("records"));
        page.setRecords(taskVoList);
        page.setPages((Long) maps.get("pages"));
        page.setTotal((Long) maps.get("total"));
        return page;
    }

service层:

//获取候选用户所拥有的任务列表
public Map<String, Object> getAgendaTasks(String userId, int pageNumber, int pageSize) {
        /**
         * 此时不能直接使用userId来作为候选人id选项查询任务列表,因为目前流程定义中只是定义了候选组为group:manager
         * 所以需要根据userId找到对应所在的用户组,在根据其用户组进行查询
         */
        List groups = identityService.createGroupQuery().groupMember(userId).list();
        List groupIds = groups.stream().map(x -> x.getId()).collect(Collectors.toList());
        int first = (pageNumber-1)*pageSize;
        TaskQuery taskQuery = taskService.createTaskQuery().taskCandidateGroupIn(groupIds);
        long total = getTaskCount(taskQuery);
        long totalPages = total / pageSize;
        if (total % pageSize != 0) {
            totalPages++;
        }
        List tasks = taskQuery.listPage(first, pageSize);
        Map<String, Object> map = new HashMap<>();
        map.put("records", tasks);
        map.put("total", total);
        map.put("pages", totalPages);
        return map;
    }
// 查询用户所受理的全部任务
public Map<String, Object> getAssigneeTasks(String userId,int pageNumber, int pageSize) {
        TaskQuery taskQuery = taskService.createTaskQuery().taskAssignee(userId);
        int first = (pageNumber-1)*pageSize;
        List list = taskQuery.listPage(first, pageSize);
        long total = getTaskCount(taskQuery);
        long totalPages = total / pageSize;
        if (total % pageSize != 0) {
            totalPages++;
        }
        Map<String, Object> map = new HashMap<>();
        map.put("records", list);
        map.put("total", total);
        map.put("pages", totalPages);
        return map;
    }

step3. 领取任务

也就是上图中的领取按钮,领取了之后这个任务就属于这个总监了,其他总监角色用户的候选任务里就没有此任务了(这个领取操作也可以直接省了,这里先留着了方便我自己理清楚流程)
后台代码:

// 领取任务,任务id和activiti user id
public void claim(String taskId, String userId) {
        taskService.claim(taskId, userId);
    }

step4. 任务审批

领取完任务之后我们看看前端截图

关于代码在上面的任务列表里面实现了,和待办任务的一样,只不过传入的请求参数来区分是待办还是受理的
a. 审批内容显示
接下来点击审批操作进行任务的审批,审批呢肯定需要看到这个请假申请的相关信息,所以之前请假的信息参数arg需要获取内容并返回,如下图所示

请假信息返回

// business
public Map<String, Object> getFormData(String taskId) {
        List formFields = vacationProcessService.getFormFields(taskId);
        List commentVos = vacationProcessService.getComments(taskId);
        Map<String, Object> map = new HashMap<>();
        map.put("formFields", formFields); // 获取请假信息
        map.put("comments", commentVos); // 获取此流程实例的所有评论
        return map;
    }
// service
@Override
    public List getFormFields(String taskId) {
        // 查询流程实例
        ProcessInstance pi = getProcessInstanceByTaskId(taskId);
        // 获取流程参数
        VacationBaseForm baseForm = (VacationBaseForm)runtimeService.getVariable(pi.getId(), "arg");
        List formFields = baseForm.getFormFields();
        return formFields;
    }

    @Override
    public List getComments(String taskId) {
        // 查询流程实例
        ProcessInstance pi = getProcessInstanceByTaskId(taskId);
        List result = new ArrayList<>();
        List comments = taskService.getProcessInstanceComments(pi.getId());
        for(Comment c :comments) {
            // 查询用户
            User user = identityService.createUserQuery().userId(c.getUserId()).singleResult();
            CommentVo vo = new CommentVo();
            vo.setContent(c.getFullMessage());
            vo.setTime(DateUtils.getDateString(c.getTime()));
            vo.setUserName(user.getLastName());
            result.add(vo);
        }
        return result;

    }

b. 审批操作
这里就是操作者进行同意还是拒绝的审批意见了,同时也可以添加自己的意见comment评论,上面代码的getComments就是获取所有审批者的评论

public void audit(AuditRequestVO vo, SysUser user) {
        if(vo.getTaskId() == null) {
            throw new IllegalArgumentException("参数有误");
        }
        Task task = taskService.createTaskQuery().taskId(vo.getTaskId()).singleResult();
        // 利用任务对象,获取流程实例id
        String processInstancesId = task.getProcessInstanceId();
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("operateApprove", vo.isOperate());
        identityService.setAuthenticatedUserId(user.getActUserId()); // 添加评论的时候需要先整上这个,好像是会根据这个来绑定当前谁的评论
        String content = user.getUsername() + (vo.isOperate()?"同意 ":"拒绝 ")+vo.getTitle()+";";
        if(!ObjectUtils.isEmpty(vo.getContent())){
            content += vo.getContent();
        }
        taskService.addComment(vo.getTaskId(), processInstancesId,content);
        taskService.complete(vo.getTaskId(), map);
    }

审批完之后,这个登录总监的受理任务列表下此条任务就清了,因为流程往下走了,走到了hr那里,因此我们退出此登陆,采用hr的用户登录

2.4 Hr审批

hr登录后的页面和总监一样,操作也一致,先领取,再审批
界面如下

可以发现流程跑到hr这里了,我们再看一下“查看”按钮看目前的流程图:

可见走到人力资源这里了也是,关于流程图的展示见Vue获取并显示后端Feign返回的Activiti图片流
hr领取后审批

可以看到之前总监审批进行的评论
操作完之后,这个流程实例就complete完成了

0