微服务项目学习:
cloud.macrozheng.com
来源:juejin.cn/post/7401773397312782399
工作流审批功能是办公OA系统核心能力,如果让你设计一个工作流审批系统,你会吗?千万不要小瞧OA内部系统的复杂性,大家可以头脑风暴思考一下实现方案。
要明白工作流审批涉及多个用户的任务流转,多个流程分支跳转,虽然是办公内部系统,但是这个系统并不简单如果没有强大的工作流引擎,难以高效扩展旧流程,难以增加新流程,工作流审批将成为公司所有人的噩梦
但是在使用 activiti开源工作流引擎后,一切痛苦与噩梦均烟消云散~
activiti 支持新增流程非常简单,只需要两步1)画个流程图 2)搭配前端页面
首先画一个流程图
将文章开头的需求,转化为 activiti 流程图,使用Idea 安装 actiBPM 插件,创建该流程图,文件命名apply.mpmn,实现请假流程的二级审批能力
-
-
这或许是一个对你有用的开源项目
,mall项目是一套基于
SpringBoot3
+ JDK 17 + Vue 实现的电商系统(
Github标星60K
),采用Docker容器化部署,后端支持多模块和微服务架构。包括前台商城项目和后台管理系统,能支持完整的订单流程!涵盖商品、订单、购物车、权限、优惠券、会员、支付等功能!
-
Boot项目:
https://github.com/macrozheng/mall
-
Cloud项目:
https://github.com/macrozheng/mall-swarm
-
视频教程:
https://www.macrozheng.com/video/
项目演示:
测试流程图
首先创建工作流引擎,部署流程图
//创建工作流引擎
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
//RepositoryService用于部署流程图
RepositoryService repositoryService = engine.getRepositoryService();
//部署请假流程图
repositoryService.createDeployment().addClasspathResource("processes/apply.bpmn").deploy();
部署流程图,这部分工作一般放在工作流的后台系统,
开发创建好流程图以后,上传部署到系统中。无需开发修改代码
repositoryService.createDeployment().addClasspathResource("processes/apply.bpmn").deploy();
这行代码负责部署流程图到流程引擎。 工作流引擎会解析该流程图文件,创建流程模版,接下来就可以在使用该流程模版,发起流程实例了。
员工zhang3,发起新流程,设置审批人
Map variableMap = new HashMap<>();
variableMap.put("applyUser", "zhang3");
variableMap.put("supervisor", "li4");
variableMap.put("upperSupervisor", "wang5");
员工zhang3 提出请假申请,在发起新流程时,通过OA其他系统,查到zhang3的一级主管是li4,二级主管是wang5,于是设置上审批人。 有人疑问,以下变量
applyUser
等,是系统默认的,还是在哪里指定的? 在创建主管审批节点时,指定审批人变量
${applyUser}
想象一下,如果请假类的流程均可能需要一二级主管审批,是不是可以在发起流程时,统一填充一二级主管 审批人变量。这部分代码是不是就是通用的,新增流程时无需二次修改了。
发起一个新流程
ProcessInstance instance = runtimeService.startProcessInstanceByKey("apply_processor_1", variableMap);
如上代码指定了全流程审批人,发起了一个新流程。 有人会疑问
apply_processor_1
是什么?在哪里指定的,这是流程模版的 Key,在使用Idea插件画流程图时,需要指定流程图的Key
申请人设置请假天数
TaskService taskService = engine.getTaskService();
Task firstTask = taskService.createTaskQuery().taskAssignee("zhang3").singleResult();
taskService.complete(firstTask.getId(), Maps.newHashMap("day", 4));
创建并开启流程实例后,工作流引擎相当于帮你执行了 流程图的 开始节点,然后流程执行到 请假申请节点,此时通过 taskService 查询 zhang3的 处理任务。TaskService是通过第一步ProcessEngine 获取到的,主要用于任务查询。
有人会疑问,为什么要区分创建流程、处理申请人审批任务两个步骤,我们在提请假申请时,只需要提申请一步就完成了。 实际上 提请假申请时,系统会帮你创建好流程,然后自动替你完成审批。 为什么工作流引擎要区分为两步呢?所有的流程图都需要经过 开始节点,也都需要结束节点,如此设计方式,可以让工作流引擎的抽象层次更高。它可以在开始和结束时点建立事件通知,维护流程状态的完整性。
接下来,zhang3 通过 taskService完成该任务,并且设置变量 day=4,即请假天数是4天。
一级主管审批任务
Task secondTask = taskService.createTaskQuery().taskAssignee("li4").singleResult();
taskService.setVariable(secondTask.getId(), "result1", true);
接下来,zhang3和 领导li4 说,”我家里有事要请假,辛苦4哥审批一下“,领导在自己的审批后台查询 审批任务,查到后,通过了审批任务。
有人会疑问,怎么标识 审批通过和不通过呢? result1 是什么东西?系统默认的,还是在何处甚至的变量? 在流程图上配置的
工作流引擎没有审批通过不通过的概念。当流程上存在 A 和 B 两个分支时,流程图上可以使用排他网关进行分支判定。如请假流程图中,一级审批结果就是一个排他网关。在网关的下游分支上配置如果要走A分支,应该满足哪些条件;走B分支,要满足哪个条件; 而排他网关上并没有配置路由条件。例如在一级主管审批后,流程上设置新的变量 result1,经过排他网关时,A分支 是
#{result1==true}
判定通过,于是走了A分支。
工作流引擎负责检查网关的下级分支的条件是否满足,哪个条件满足走哪个分支。
也就是说上一个任务在处理时,并不知道接下来走哪个分支,也没有指定走哪个分支,而是将自己的处理结果放到流程变量中,在排他网关的下游分支条件上根据流程变量进行判断,接下来走哪个分支,这就是工作流引擎对于流程的抽象。
工作流引擎负责驱动流程到排他网关,至于走哪个分支,由分支上的条件决定!
这个思维一定要记住哦~
继续剩余的流程
一级主管审批通过后,需要判断是否需要二级审批,这时又有一个排他网关,使用的条件就是
#{day>3}
,或者
#{day<=3}
在第2步中,zhang3 申请了 4天的假期,于是走到了二级主管审批页面。二级主管wang5 在审批任务列表中,找到了zhang3的请假申请,然后点击了通过。
Task thirdTask = taskService.createTaskQuery().taskAssignee("wang5").singleResult();
if (thirdTask != null) {
taskService.setVariable(thirdTask.getId(), "result2", true);
log.warn("用户任务完成,流程ID:{}, 任务名称:{}, id:{}, assignee:{}", thirdTask.getProcessInstanceId(), thirdTask.getName(), thirdTask.getId(), thirdTask.getAssignee(), thirdTask.getDelegationState());
taskService.complete(thirdTask.getId());
} else {
log.warn("没有查到二级主管审批任务");
}
接下来流程上没有其他审批任务,但是引擎依然会继续驱动流程,如下图中二级主管审批结果通过或拒绝,分别进入到两个不同的终点。
值得一提的是,上面的代码仅仅是各个审批人在处理审批任务时,必要的代码、通用的代码。如审批是否通过这一流程变量,完全可以统一规范,无需二次开发。例如 二级主管审批通过设置了 result2,实际上可以使用 二级主管审批这个节点的id 后缀,如 result_10来代表执行结果,规范以后,流程审批代码更为统一。新增流程模版时,在审批任务节点,审批通过或审批拒绝均不需要再次开发代码。
惊喜的事情是:我们没有开发任何一行
流程驱动
和
分支判定
相关的代码。
但是我们依然需要处理前端页面。因为请假申请页面上,不同的假期类型需要的表单参数不同,需要新增前端页面,新增发起流程的后端接口。
相比整体的流程控制代码,这部分开发工作量大大降低,难度更是大大降低。
接下来,我提出一个问题,如果新增一个需求,要求请病假的时候,需要HR审批,你知道如何修改流程图,支持新的处理流程吗?
很简单,在审批通过终节点的前面,再加一个排他网关,判断请假类型== ”带薪病假“,如果条件通过则增加HR审批节点,如果条件不通过,则直接走到 审批通过的 终点。不需要开发一行代码就能支持需求哦~
有了工作流引擎 activiti ,新增和修改流程 都变得非常简单,它帮我们完成了所有的流程驱动和分支判定工作。
这就是工作流引擎的价值之一。
如何查看完整的执行流程图
通过流程引擎 ProcessEngine获取HistoryService;通过流程实例id,拿到流程执行的所有节点。执行记录如下图,可完整看到流程执行的完整过程。
List activityInstanceList =
historyService.createHistoricActivityInstanceQuery().processInstanceId(instance.getId()).list();
for (HistoricActivityInstance historicActivityInstance : activityInstanceList) {
log.warn("activityName:{},activityType:{}, assignee:{}, taskId:{}",
historicActivityInstance.getActivityName(),
historicActivityInstance.getActivityType(),
historicActivityInstance.getAssignee(),
historicActivityInstance.getTaskId());
activiti 项目基础配置
首先引入activiti pom
<dependency>
<groupId>org.activitigroupId>
<artifactId>activiti-spring-boot-starter-basicartifactId>
<version>5.23.0version>
dependency>
我使用的是activiti 5.x,配套的 Springboot starter parent是
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.5.4version>
<relativePath/>
parent>
配置数据源和工作流引擎配置类
activiti 需要使用方提供数据库配置,在项目启动时,activiti 会自动检查数据库是否包含activiti 相关的表,如果不包含,会自动帮你建表。
在学习activiti 的过程中,我没有安装MySQL,而是使用H2 内存数据库,该数据库在Java进程中,随JVM同生共死,无需担心重复启动,数据被污染等问题,非常适合学习activiti 的时候使用
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="org.h2.Driver"/>
<property name="url" value="jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;mode=MySQL;"/>
bean>
<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="dataSource" ref="dataSource"/>
<property name="databaseSchemaUpdate" value="true"/>
bean>
<context:component-scan base-package="com.muppet.activiti"/>
beans>
H2 需要的POM如下
xml 代码解读复制代码<dependency
>
<groupId>com.h2databasegroupId>
<artifactId>h2artifactId>
<scope>runtimescope>
dependency>
全部示例代码
@SpringBootTest(classes = {ActivitiStartApplication.class})
class ActivitiStartApplicationTests {
public static final Logger log = LoggerFactory.getLogger(ActivitiStartApplicationTests.class);
@Autowired
private ApplyTaskListener applyTaskListener;
@Test
void contextLoads() {
System.out.println("启动成功");
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = engine.getRepositoryService();
RuntimeService runtimeService = engine.getRuntimeService();
TaskService taskService = engine.getTaskService();
HistoryService historyService = engine.getHistoryService();
repositoryService.createDeployment().addClasspathResource("processes/apply.bpmn").deploy();
Map variableMap = new HashMap<>();
variableMap.put("applyUser", "zhang3");
variableMap.put("supervisor", "li4");
variableMap.put("upperSupervisor", "wang5");
ProcessInstance instance = runtimeService.startProcessInstanceByKey("apply_processor_1", variableMap);
Task firstTask = taskService.createTaskQuery().taskAssignee("zhang3").singleResult();
log.warn("用户任务完成,流程ID:{}, 任务名称 :{}, id:{}, assignee:{}", firstTask.getProcessInstanceId(), firstTask.getName(), firstTask.getId(), firstTask.getAssignee());
taskService.complete(firstTask.getId(), Maps.newHashMap("day", 4));
Task secondTask = taskService.createTaskQuery().taskAssignee("li4").singleResult();
taskService.setVariable(secondTask.getId(), "result1", true);
log.warn("用户任务完成流程ID:{}, 任务名称 :{}, id:{}, assignee:{}", secondTask.getProcessInstanceId(), secondTask.getName(), secondTask.getId(), secondTask.getAssignee());
taskService.complete(secondTask.getId());
Task thirdTask = taskService.createTaskQuery().taskAssignee("wang5").singleResult();
if (thirdTask != null) {
taskService.setVariable(thirdTask.getId(), "result2", true);
log.warn("用户任务完成,流程ID:{}, 任务名称:{}, id:{}, assignee:{}", thirdTask.getProcessInstanceId(), thirdTask.getName(), thirdTask.getId(), thirdTask.getAssignee(), thirdTask.getDelegationState());
taskService.complete(thirdTask.getId());
} else {
log.warn("没有查到二级主管审批任务");
}
log.warn("流程执行过程如下");
List activityInstanceList = historyService.createHistoricActivityInstanceQuery().processInstanceId(instance.getId()).list();
for (HistoricActivityInstance historicActivityInstance : activityInstanceList) {
log.warn("activityName:{},activityType:{}, assignee:{}, taskId:{}",
historicActivityInstance.getActivityName(),
historicActivityInstance.getActivityType(),
historicActivityInstance.getAssignee(),
historicActivityInstance.getTaskId());
}
}
}
activiti 工作流引擎数据库设计
为了保证流程的可靠性和可恢复性,工作流引擎通常会将流程实例的状态和数据持久化存储到中。在流程执行过程中,引擎会不断地更新数据库中的状态数据。,activiti 共包含了一系列用于存储流程定义、运行时数据以及历史记录的表。
1.流程定义相关表
-
ACT_RE_*系列表:主要包括流程定义(Process Definitions)、流程资源(Resources)和其他静态信息的存储。
2.运行时数据表
-
ACT_RU_*系列表:这些表存放了流程实例执行过程中的实时数据,如任务(Tasks)、流程实例(Process Instances)、变量(Variables)、执行对象(Executions)等。
3. 历史数据表
-
ACT_HI_*系列表:当流程实例结束或达到特定条件时,相关的运行时数据会被迁移到历史表中,以供后期审计、报告分析之用。
4. 身份和权限表
-
ACT_ID_*系列表:主要用于存储用户、组以及相关的身份和权限信息。
5. 其他辅助表
-
包括事件日志表:(Event Log)、作业及定时器表(Job and Timer entities)等,它们服务于调度、异步处理等功能需求。
工作流引擎API 介绍
-
ProcessEngine
: 表示Activiti工作流引擎的入口,用于获取各种管理API操作的对象。
-
RepositoryService
: 用于管理流程定义的API,包括流程的部署和删除等操作。
-
RuntimeService
: 用于管理流程实例的API,包括启动、暂停和删除流程实例等操作。
-
TaskService
: 用于管理任务的API,包括创建、完成和查询任务等操作。
-
HistoryService
: 用于查询历史记录的API,包括查询已完成的任务、流程实例和变量等信息。
这5个Service我们已经很熟悉了,现在跟大家介绍这部分API,大家应该更容易理解了。
其中 ProcessEngine我们用来获取各类Service类,RepositoryService 用来部署流程图,RuntimeService用来创建流程图实例、TaskService用来查询任务和完成任务;HistoryService用来查看流程执行过程。
什么是BPMN流程图
BPMN(Business Process Modeling Notation)是一种流程建模的通用和标准语言,用来绘制业务流程图,以便更好地让各部门之间理解业务流程和相互关系。
BPMN 1.0规范于2004年5月对外发布,而BPMN 2.0标准由OMG于2011年推出,对BPMN进行了重新定义。
Activiti 是由 jBPM 的创建者 Tom Baeyens 离开 JBoss 之后建立的项目,构建在开发 jBPM 版本 1 到 4 时积累的多年经验的基础之上,旨在创建下一代的 BPM 解决方案。同时 Activiti 选择了 Apache 许可,一方面是希望 Activiti 能有更长久的生命力,因为它不受任何个人或是公司的控制而是属于整个社区,另一方面更是希望这个宽松的许可能够让 Activiti BPM 引擎和 BPMN2.0 被更广泛的采纳、使用和商业化。
Idea actiBPM 插件如何安装
https://www.cnblogs.com/No2-explorer/p/11032469.html
Idea新建的流程图是什么
本质是一个XML,可以通过BPMN 可视化工具解析,如下XML代码是 请假流程图
创建一个二级主管审批节点。
<userTask activiti:assignee="${upperSupervisor}" activiti:async="false" activiti:exclusive="true" id="_10" name="二级主管审批"/>
创建一个审批结果的分支
分支上包含连接了哪两个节点,以及分支的条件表达式
#{result2==true}
<sequenceFlow id="_19" sourceRef="_17" targetRef="_8">
<conditionExpression xsi:type="tFormalExpression">conditionExpression>
sequenceFlow>
<sequenceFlow id="_20" sourceRef="_17" targetRef="_11">
<conditionExpression xsi:type="tFormalExpression">conditionExpression>
sequenceFlow>
请假流程图完整XML
<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="m1723259512248" name="" targetNamespace="http://www.activiti.org/test" typeLanguage="http://www.w3.org/2001/XMLSchema">
<process id="apply_processor_1" isClosed="false" isExecutable="true" processType="None">
<startEvent id="_4" name="开始">
<extensionElements>
<activiti:executionListener class="com.muppet.activiti.listener.ApplyTaskListener" event="end"/>
extensionElements>
startEvent>
<userTask activiti:assignee="#{applyUser}" activiti:async="false" activiti:exclusive="true" id="_5" name="请假申请"/>
<userTask activiti:assignee="${supervisor}" activiti:async="false" activiti:exclusive="true" id="_6" name="主管审批">
<extensionElements>
<activiti:executionListener class="com.muppet.activiti.listener.ApplyTaskListener" event="end"/>
extensionElements>
userTask>
<exclusiveGateway gatewayDirection="Unspecified" id="_7" name="一级审批结果"/>
<endEvent id="_8" name="审批不通过">
<extensionElements>
<activiti:executionListener class="com.muppet.activiti.listener.ApplyTaskListener" event="end"/>
extensionElements>
endEvent>
<exclusiveGateway gatewayDirection="Unspecified" id="_9" name="天数验证2"/>
<userTask activiti:assignee="${upperSupervisor}" activiti:async="false" activiti:exclusive="true" id="_10" name="二级主管审批"/>
<endEvent id="_11" name="审批通过">
<extensionElements>
<activiti:executionListener class="com.muppet.activiti.listener.ApplyTaskListener" event="end"/>
extensionElements>
endEvent>
<sequenceFlow id="_2" sourceRef="_4" targetRef="_5"/>
<sequenceFlow id="_3" sourceRef="_5" targetRef="_6"/>
<sequenceFlow id="_12" sourceRef="_6" targetRef="_7"/>
<sequenceFlow id="_13" sourceRef="_7" targetRef="_8">
<conditionExpression xsi:type="tFormalExpression">conditionExpression>
sequenceFlow>
<sequenceFlow id
="_14" sourceRef="_7" targetRef="_9">
<conditionExpression xsi:type="tFormalExpression">conditionExpression>
sequenceFlow>
<sequenceFlow id="_15" sourceRef="_9" targetRef="_10">
<conditionExpression xsi:type="tFormalExpression">3}]]>conditionExpression>
sequenceFlow>
<sequenceFlow id="_16" sourceRef="_9" targetRef="_11">
<conditionExpression xsi:type="tFormalExpression">conditionExpression>
sequenceFlow>
<exclusiveGateway gatewayDirection="Unspecified" id="_17" name="审批结果2"/>
<sequenceFlow id="_18" sourceRef="_10" targetRef="_17"/>
<sequenceFlow id="_19" sourceRef="_17" targetRef="_8">
<conditionExpression xsi:type="tFormalExpression">conditionExpression>
sequenceFlow>
<sequenceFlow id="_20" sourceRef="_17" targetRef="_11">
<conditionExpression xsi:type="tFormalExpression">conditionExpression>
sequenceFlow>
process>
<bpmndi:BPMNDiagram documentation="background=#3C3F41;count=1;horizontalcount=1;orientation=0;width=842.4;height=1195.2;imageableWidth=832.4;imageableHeight=1185.2;imageableX=5.0;imageableY=5.0" id="Diagram-_1" name="New Diagram">
<bpmndi:BPMNPlane bpmnElement="apply_processor_1">
<bpmndi:BPMNShape bpmnElement="_4" id="Shape-_4">
<omgdc:Bounds height="32.0" width="32.0" x="525.0" y="170.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
bpmndi:BPMNLabel>
bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_5" id="Shape-_5">
<omgdc:Bounds height="55.0" width="85.0" x="495.0" y="285.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
bpmndi:BPMNLabel>
bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_6" id="Shape-_6">
<omgdc:Bounds height="55.0" width="85.0" x="500.0" y="390.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
bpmndi:BPMNLabel>
bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_7" id="Shape-_7" isMarkerVisible="false">
<omgdc:Bounds height="32.0" width="32.0" x="520.0" y="495.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
bpmndi:BPMNLabel>
bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_8" id="Shape-_8">
<omgdc:Bounds height="32.0" width="32.0" x="295.0" y="825.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
bpmndi:BPMNLabel>
bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_9" id="Shape-_9" isMarkerVisible="false">
<omgdc:Bounds height="32.0" width="32.0" x="515.0" y="600.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
bpmndi:BPMNLabel>
bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_10" id="Shape-_10">
<omgdc:Bounds height="55.0" width="95.0" x="485.0" y="720.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="55.0" width="95.0" x="0.0" y="0.0"/>
bpmndi:BPMNLabel>
bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_11" id="Shape-_11">
<omgdc:Bounds height="32.0" width="32.0" x="720.0" y="600.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
bpmndi:BPMNLabel>
bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_17" id="Shape-_17" isMarkerVisible="false">
<omgdc:Bounds height="32.0" width="32.0" x="510.0" y="810.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
bpmndi:BPMNLabel>
bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="_13" id="BPMNEdge__13" sourceElement="_7" targetElement="_8">
<omgdi:waypoint x="520.1953352769677" y="511.0000000000001"/>
<omgdi:waypoint x="310.0" y="700.0"/>
<omgdi:waypoint x="310.0" y="825.0808012104277"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
bpmndi:BPMNLabel>
bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="_12" id="BPMNEdge__12" sourceElement="_6" targetElement="_7">
<omgdi:waypoint x="535.9999999999999" y="444.97894395853575"/>
<omgdi:waypoint x="535.9999999999999" y="495.19533527696785"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
bpmndi:BPMNLabel