接下来,我们对出差申请,做下改造,加入多实例会签
,就是多个人同时审批一个任务的意思
打开我们的流程设计器flowable-ui,在原理的基础上加入总经理并行会签
注意,如果你的会签里面加了回退,那么会签任务必须要和结束节点挨着,
不能有其他的会签任务,如果你的会签没有回退,那么可以设置多个
如果你的会签设置多个,并且有回退,那么他会自动给你添加一个下一个任务的数据
多实例类型: 我们这里选择并行
集合(多实例): 输入userIds 用于传用户的集合名称
在代码里面对应这一部分
基数(多实例): 输入3,就是你要几个人审批,我这里输入3个人审批
元素变量(多实例) :输入userId 这里不要加s啊
分配用户:输入表达式 ${userId} 和要和 元素变量(多实例)的字段一样
然后保存流程图,部署,重新走出差流程
我们在提交出差单,和审批出差单这块坐下改造
package com.dmg.service.impl;
@Service
public class TravelServiceImpl implements TravelService {
@Autowired
private TravelMapper travelMapper;
@Autowired
private FlowService flowService;
/**
* 提交出差单
* @param req
*/
@Transactional
@Override
public boolean submitApplication(Req req) {
//从数据库查询当前信息
Travel travel=travelMapper.selectById(req.getId());
if(travel==null){
throw new RuntimeException("出差单不存在");
}
if("3".equals(travel.getApprovalStatus())){
throw new RuntimeException("已审批通过,不能提交");
}
//业务key 用于待办已办 扩展字段 展示使用
//我这里以 流程定义key + 主键 + 申请单名称 的方式命名
String businessKey="cc."+travel.getId()+"."+travel.getName();
//因为我们增加了会签的审批 所以要提前吧会签人传入
Map<String,Object>map=new HashMap<>();
//多个用户,你这里可以从前端传过来 让用户选择会签人
// 人数就是你在流程图设置的 基数(多实例)
List<String> userIds=new ArrayList<>();
userIds.add("zhangsan");
userIds.add("lisi");
userIds.add("wangwu");
//userIds就是 集合(多实例) 那个位置填写的数据
map.put("userIds",userIds);
//如果流程实例为空 启动一个新的流程实例
String instanceId = flowService.startProcessInstance(businessKey, map,travel.getInstanceId());
//保存instanceId到数据库
travel.setInstanceId(instanceId);
//审批状态改为 2:审批中
travel.setApprovalStatus("2");
travelMapper.updateById(travel);
return true;
}
/**
* 审批出差单
* @param req
*/
@Transactional
@Override
public boolean approval(Req req) {
//从数据库查询当前信息
Travel travel=travelMapper.selectById(req.getId());
if(travel==null){
throw new RuntimeException("出差单不存在");
}
//2:审批中
if(!"2".equals(travel.getApprovalStatus())){
throw new RuntimeException("状态不是审批中,不能审批");
}
//审批条件 用于会签使用
Map<String,Object>map=new HashMap<>();
map.put("flag",req.getFlag());
String res = flowService.complete(travel.getInstanceId(), map, travel.getId(), req.getApprovalComments(), req.getApprover());
if("end".equals(res)){
//如果流程已经走完了 那么吧审批状态修改为 3:审批通过
travel.setApprovalStatus("3");
travelMapper.updateById(travel);
}
return true;
}
}
提交出差单主要增加这部分代码
审批出差单,主要加了这部分代码
完成条件(多实例):输入 ${compCondition.getComCondition(execution)}
就是对应箭头的位置,接下来我们在写下条件类
package com.dmg.condition;
/**
* 条件类 ${compCondition.getComCondition(execution)}
*/
@Slf4j
@Component("compCondition")
public class ZongJingLiCondition implements Serializable {
@Autowired
private RepositoryService repositoryService;
@Autowired
private RuntimeService runtimeService;
public boolean getComCondition(DelegateExecution execution){
//实例总数
Object nrOfInstances = execution.getVariable("nrOfInstances");
//未完成的实例
Object nrOfActiveInstances = execution.getVariable("nrOfActiveInstances");
//已完成实例
Object nrOfCompletedInstances = execution.getVariable("nrOfCompletedInstances");
//map里面传过来的流程变量 审批通过 还是驳回
String flag = execution.getVariable("flag").toString();
log.info("总实例数量:{}",nrOfInstances.toString());
log.info("未完成的实例:{}",nrOfActiveInstances.toString());
log.info("已完成实例:{}",nrOfCompletedInstances.toString());
if("false".equals(flag)) {
//如果是拒绝 那么回退到上一个节点
//获取当前节点的id
String currentActivityId = execution.getCurrentActivityId();
//根据流程定义id 查询bpmn模型
BpmnModel bpmnModel = repositoryService.getBpmnModel(execution.getProcessDefinitionId());
//查询当前流节点
FlowNode flowNode = (FlowNode) bpmnModel.getFlowElement(currentActivityId);
//获取前面的连线
SequenceFlow sequenceFlow = flowNode.getIncomingFlows().get(0);
//获取上一个节点的id
String firstId = sequenceFlow.getSourceRef();
//判断上一个节点是不是并行网关
FlowNode firstNode = (FlowNode) bpmnModel.getFlowElement(firstId);
//如果是并行网关,那么回退到并行网关 所有的分支
if (firstNode instanceof ParallelGateway) {
//并行网关上一个节点id集合
List<String> activityIds = new ArrayList<>();
List<SequenceFlow> incomingFlows = firstNode.getIncomingFlows();
for (SequenceFlow x : incomingFlows) {
//并行网关上一个节点的id
String sourceRef = x.getSourceRef();
activityIds.add(sourceRef);
}
if (CollectionUtils.isNotEmpty(activityIds)) {
//如果集合不为空 回退到上一个节点 多个任务
runtimeService.createChangeActivityStateBuilder()
.processInstanceId(execution.getProcessInstanceId())
.moveSingleActivityIdToActivityIds(currentActivityId, activityIds)
.changeState();
}
}else {
//如果是串行,直接回退上一个节点
runtimeService.createChangeActivityStateBuilder()
.processInstanceId(execution.getProcessInstanceId())
.moveActivityIdTo(currentActivityId,firstId)
.changeState();
}
//结束当前所有任务
return true;
}
//我们也可以设置百分比 有多少人审批通过 就能结束当前任务 业务场景 由你来控制
//已审批的数量 / 总的数量 > 一半 那么就可以往下走
if (Float.parseFloat(nrOfCompletedInstances.toString())/Float.parseFloat(nrOfInstances.toString()) > 0.5){
//如果完成的比例高于50%就返回ture,代表会签结束,没有完成的任务就自动结束了
return true;
}else {
//如果完成的比例不高于50%就返回false,还需要继续会签
return false;
}
}
}
nrOfInstances
nrOfActiveInstances
nrOfCompletedInstances
上面三个字段是flowable自带的,不要改动
我们这里设置的会签,2个人成功才能往下走,1个人审批不通过,就退回到上一个节点
我们来看下效果
http://localhost:8080/travel/approval
{
"id":"003",
"approvalComments":"我不同意",
"flag":"false",
"approver":"zhangsan"
}
我们可以看到,在总经理审批不通过,就回退到并行网关里面的任务了
然后再审批通过,回到了我们的总经理审批这里
有2个人审批通过,那么我们的流程就结束了
那么接下来,我们还可以做前加签,后加签的操作
前加签:在张三审批之前,选择李四先去审批,李四审批完了,张三在审批
后加签:张三审批完了,选择李四在审批一次
我们对请假申请代码改造一下
package com.dmg.vo.req;
import lombok.Data;
@Data
public class Req {
/**
* 主键
*/
private String id;
/**
* 审批意见
*/
private String approvalComments;
/**
* 通过:true 驳回false
*/
private String flag;
/**
* 审批人
*/
private String approver;
/**
* 加签人
*/
private String signatory;
/**
* 加签类型 1:前加签 2:后加签 如果为空 正常审批流程
*/
private String signatureType;
}
package com.dmg.service.impl;
@Service
public class QjServiceImpl implements QjService {
@Autowired
private QjMapper qjMapper;
@Autowired
private FlowService flowService;
/**
* 审批请假单
* @param req
*/
@Transactional
@Override
public boolean approval(Req req) {
//从数据库查询当前信息
Qj qj=qjMapper.selectById(req.getId());
if(qj==null){
throw new RuntimeException("请假单不存在");
}
//2:审批中
if(!"2".equals(qj.getApprovalStatus())){
throw new RuntimeException("状态不是审批中,不能审批");
}
//返回结果
String res ="";
//参数 分支条件
Map<String, Object> map=new HashMap<>();
//通过:true 驳回false
map.put("flag",req.getFlag());
if("1".equals(req.getSignatureType())){
//如果是前加签 我们可以给张三 审批之前 先设置一个其他的审批人 ,让他审批完了 张三才审批
res = flowService.frontEndorsement(qj.getInstanceId(),req);
}else if("2".equals(req.getSignatureType())){
//如果是后加签 那就是张三审批完成之后, 还需要一个人来审批
res =flowService.postEndorsement(qj.getInstanceId(),req);
}else {
//正常审批
res = flowService.complete(qj.getInstanceId(), map, qj.getId(), req.getApprovalComments(), req.getApprover());
}
if("end".equals(res)){
//如果流程已经走完了 那么吧审批状态修改为 3:审批通过
qj.setApprovalStatus("3");
qjMapper.updateById(qj);
}
if("false".equals(req.getFlag())){
//如果是驳回 那么修改状态为 未提交
qj.setApprovalStatus("1");
qjMapper.updateById(qj);
}
return true;
}
}
package com.dmg.service;
public interface FlowService {
public String frontEndorsement(String processInstanceId, Req req);
public String postEndorsement(String processInstanceId, Req req);
}
package com.dmg.service.impl;
/**
* 流程定义服务实现类
*/
@Service
public class FlowServiceImpl extends FlowServiceFactory implements FlowService {
@Autowired
private HisApprovalMapper hisApprovalMapper;
@Autowired
private QjMapper qjMapper;
/**
* 审批
* @param businessId 业务表主键
* @param map 分支条件
* @param approvalComments 审批意见
* @param instanceId 流程实例id
* @param assignee 审批人
*/
@Override
public String complete(String instanceId,Map<String,Object>map,
String businessId,String approvalComments,
String assignee){
//根据流程实例id 获取任务
Task task = taskService.createTaskQuery()
.processInstanceId(instanceId)
.taskAssignee(assignee)
.singleResult();
if(task==null){
throw new RuntimeException("任务不存在,或者不是当前任务的审批人");
}
//判断当前任务 是否被委托过
if(StringUtils.isNotEmpty(task.getOwner())
&& !task.getOwner().equals(task.getAssignee())){
//如果不是空 并且和签收人 不一样 那么解决前加签的任务
taskService.resolveTask(task.getId(),map);
}else {
//完成任务
taskService.complete(task.getId(),map);
}
//审批完成后 再次查询任务是否还有数据
long count = taskService.createTaskQuery()
.processInstanceId(instanceId)
.count();
//添加历史审批
insertHisApproval(approvalComments, assignee, businessId, task.getName());
if(count==0){
//返回结束 修改表单的状态
return "end";
}
return "ok";
}
/**
* 前加签
* @param processInstanceId
* @param req
* @return
*/
@Override
public String frontEndorsement(String processInstanceId, Req req) {
//根据流程实例id 获取任务
Task task = taskService.createTaskQuery()
.processInstanceId(processInstanceId)
.taskAssignee(req.getApprover())
.singleResult();
if(task==null){
throw new RuntimeException("任务不存在,或者不是当前任务的审批人");
}
//把张三的任务 委托给 另一个人审批 当另一个人审批完了,自动回到张三手里
taskService.delegateTask(task.getId(),req.getSignatory());
//添加历史审批
insertHisApproval( "前加签", req.getApprover(), req.getId(), task.getName());
return "ok";
}
/**
* 后加签
* @param processInstanceId
* @param req
* @return
*/
@Override
public String postEndorsement(String processInstanceId, Req req) {
//根据流程实例id 获取任务
Task task = taskService.createTaskQuery()
.processInstanceId(processInstanceId)
.taskAssignee(req.getApprover())
.singleResult();
if(task==null){
throw new RuntimeException("任务不存在,或者不是当前任务的审批人");
}
//把张三的任务 设置为 李四审批
taskService.setAssignee(task.getId(),req.getSignatory());
//添加历史审批
insertHisApproval( "后加签", req.getApprover(), req.getId(), task.getName());
return "ok";
}
}
我们操作下审批 来看下结果,先设置下前加签
http://localhost:8080/qj/approval
{
"id":"001",
"approvalComments":"我同意",
"flag":"true",
"approver":"zhangsan",
"signatory":"xiaoming",
"signatureType":"1"
}
前加签主要是在这里通过委托 把OWNER_设置数据
可以看到OWNER_变成了张三,ASSIGNEE_变成了xiaoming
接下来我们用xiaoming审批一下
http://localhost:8080/qj/approval
{
"id":"001",
"approvalComments":"我同意",
"flag":"true",
"approver":"xiaoming"
}
在任务审批之前,通过前加签委托
然后再审批这里 这样审批
可以看到任务又回到张三手里了
我们这次来看下 后加签 的结果
http://localhost:8080/qj/approval
{
"id":"001",
"approvalComments":"我同意",
"flag":"true",
"approver":"lisi",
"signatory":"xiaobai",
"signatureType":"2"
}
主要就是在这里,吧当前审批人 转办给加签人
在数据库中 签收人已经变成了xiaobai,这就是我们的后加签