对于流程设置不友好的问题,国内钉钉另行设计与实现了一套流程建模模式,跟bpmn规范无关,有人仿照实现了下,并做了开源(https://github.com/StavinLi/Workflow-Vue3),效果图如下:
实现大致原理是基于无限嵌套的子节点,输出json数据,传给后端,后端进行解析后,调用Camunda引擎的api,转换成流程模型后持久化。
**这种模式,相对于Camunda附带的bpmn2.0规范的流程建模,友好性大幅提升,但也引入了新的问题,没法设定环节之间的“跳跃式流转”。**这里说的“跳跃式流转”,实际包括两个方向的流转,一是从当前环节跳转至某个非直接后续环节,二是从当前环节回退到某个前序环节。
上面这种需求通过路由分支是没法解决的,对于原生的BPMN模型,可以通过在起始节点和目标节点绘制边,并且可以在边上设置条件,而仿钉钉的这套流程建模的模式,从根本上就令“绘制边”这种方案不具备可行性,怎么办?
在现有的流程建模的模式下,需要进行变通,来实现跳跃式流转,具体实现思路是自定义配置的方式来存储各环节可以回退或跳转的环节,将其作为作为环节配置的一部分。
既然是自己设计,自由度更高,完全可以通过增加属性的方式,来区分跳跃式流转的类型,是回退还是跳转。而原生的bpmn,绘制的边是没有类型的,正常边、默认边、返回边、跳转边均是同一实现,可以加条件,要区分回退还是跳转,只能通过设置约定的条件来间接实现,如设置${reject==true}这样的变量标记为回退,并不优雅。
通过配置方式实现回退可以限制用户,不允许任意回退。不过,回退到发起环节和上一环节是常见的需求,这两种情况是否由平台默认进行处理,即不需要配置也能回退,然后仅对其他需要进行处理的情况来配置回退环节即可?
系统进行默认配置,确实可以减少一部分配置的工作量,但是可能会破坏流程整体的可控性和规范性。例如,一个长流程,从管理上分为三个大阶段,在第二个阶段中,就不允许在回退到发起环节,一旦加了默认处理,则会破坏这种业务上的约束。另外,对于以下两种情况,也是不允许回退的:
1.多人会签环节,在所有参与人都处理完毕前
2.当前处于路由分支中的环节,直接回退到主分支上的环节
综合考虑上述场景和限制,最终决定系统不进行默认配置,由流程建模人员根据实际业务需要,进行配置。
流程建模时,配置某一环节能跳转和回退到的环节列表时,使用环节标识来做逻辑判断没问题,使用环节名称来做展示。涉及到一个问题,就是环节名称是不是要冗余存储。从友好性而言,需要冗余存储的。但是冗余存储意味着需要同步更新,但这点是不好实现的。用户先配置了环节跳转或回退,然后把目标环节的名称改了,环节跳转或回退配置这边实际是感知不到的。
为了解决该问题,有两个方案:
一是在跳转和回退到的环节列表配置里干脆就不对环节名称做冗余存储,只保存环节标识,动态查出环节名称,显示给用户,这样可以保证环节名称实时一致,不过处理起来比较繁琐,涉及到模型的解析和处理。
二是依旧进行冗余存储,在模型保存环节,遍历更新,即查询类型为发起或办理的环节,根据流程定义标识和环节标识,分别批量更新跳转和回退到的环节列表配置。
方案二有个问题在于,更新的是后端库表里数据,对于前端json格式的模型,实际并没有更新,如果将该模型导出再导入,则会导致错误的模型迁移结果。因此,本质上需要从前端去做更新,但是从前端去做,处理起来是相当麻烦。
该问题暂时搁置,先进行冗余存储,不做同步更新,环节名称修改的可能性较低,未实现同步更新的情况下可以人工通过配置方式来实现更新,工作量较小。
前端是将配置信息放到节点的config属性中,与人员设置personConfig和权限配置permissionConfig并列,扩展增加回退环节列表backNodeList和跳转环节列表jumpNodeList。理论上,发起环节与办理环节这两类节点是人工处理,涉及到“跳跃式流转”的需求。
回退环节列表backNodeList和跳转环节列表jumpNodeList的配置是相同的,因为流程可能允许当前环节向多个环节回退或跳转,因此是一个数组,包括目标环节的标识和名称两个关键属性即可。
示例如下:
{
"name": "填报",
"id": "root",
"type": "ROOT",
"config": {
"permissionConfig": [{
"areaCode": "applyArea",
"permission": "EDITABLE"
}, {
"areaCode": "organizationApproval",
"permission": "READONLY"
}, {
"areaCode": "hrApproval",
"permission": "INVISIBLE"
}],
"jumpNodeList": [{
"id": "node2268_3ea5_a5db_15b0",
"name": "人事审批"
}]
},
"branchList": [],
"child": {
"name": "部门审批",
"id": "node1938_8b28_c3ed_030f",
"type": "HANDLE",
"config": {
"personConfig": {
"mode": "COUNTERSIGN",
"setAssigneeFlag": "YES",
"userGroup": "99",
"userGroupName": "系统管理员"
},
"permissionConfig": [{
"areaCode": "applyArea",
"permission": "READONLY"
}, {
"areaCode": "organizationApproval",
"permission": "READONLY"
}, {
"areaCode": "hrApproval",
"permission": "READONLY"
}]
},
"child": {
"name": "人事审批",
"id": "node2268_3ea5_a5db_15b0",
"type": "HANDLE",
"config": {
"personConfig": {
"mode": "NORMAL",
"setAssigneeFlag": "YES",
"userGroup": "99",
"userGroupName": "系统管理员"
},
"permissionConfig": [{
"areaCode": "applyArea",
"permission": "READONLY"
}, {
"areaCode": "organizationApproval",
"permission": "READONLY"
}, {
"areaCode": "hrApproval",
"permission": "READONLY"
}],
"backNodeList": [{
"id": "root",
"name": "填报"
}, {
"id": "node1938_8b28_c3ed_030f",
"name": "部门审批"
}]
},
"child": {}
}
}
}
对于发起环节而言,是流程的起点,不存在回退问题。那跳转是否可以通过路由功能来实现呢?思考下,实际有多个场景。
场景1:一个单子,当满足某个规则时直接跳转到中间的某个环节,而这个规则未必是清晰的可供系统自动判断处理的,必须人为来判断是否应该进行跳转。
场景2:一个单子走到审批某个环节,发现有问题,退回到发起环节由申请人进行调整,调整完后,不希望将已经走过的环节再走一遍,而是希望直接跳转到驳回的环节,具体的场景类似于合同审批,多职能部门参与审核,如财务、法律等,法律审核环节发现有问题,需要调整,不希望再走一遍财务审批环节。
综上考虑,发起环节还是需要设置跳转环节设置的。
办理环节是人工处理环节,既需要回退功能,也需要跳转功能。
以跳转环节配置为例,如下图所示:
首先需要在“新增”按钮点击后,弹出对话框,显示所有可跳转的目标环节,该目标环节需要满足以下几个条件:
1.类型只能为发起环节和办理环节,不能跳转到路由节点去,若允许跳转到路由,将严重破坏工作流作为流程模板的规范性和约束性。
2.需要排除掉自身,自己跳转到自己,死循环……
3.后面扩展节点类型,如自动化处理环节(如发送邮件),则可视情况扩展,允许作为目标节点。
此外,这里还有个地方,对于办理环节,如果是会签,从业务角度考虑,应允许回退或跳转到该环节,但调用Camunda底层API时会报错,这里只是提一下,在后面篇章里会专门说一下该问题。
环节配置的回退环节和跳转环节的实体属性实际是完全相同的,如果用同一张库表存储,则需要实体再加一个属性来区分是回退还是跳转,库表多加一个字段,代码上附加逻辑判断。
基于平台低代码配置的高效率,先实现回退环节配置实体,然后通过复制新增功能来实现跳转环节配置。
以下是回退环节配置实体信息
属性列表如下:
在流程模型转换时,读取环节配合config属性下的backNodeList属性
private void configBackNodeList(String tempVersion, String id,String configString) {
if(StringUtils.isNoneBlank(configString)) {
List<WorkflowBackNodeConfig> backNodeList = new ArrayList<>();
JSONArray jsonArray = JSON.parseArray(configString);
for (int i = 0; i < jsonArray.size(); i++) {
JSONObject jsonObject = jsonArray.getJSONObject(i);
WorkflowBackNodeConfig entity = new WorkflowBackNodeConfig();
entity.setTargetNodeId(jsonObject.getString("id"));
entity.setTargetNodeName(jsonObject.getString("name"));
backNodeList.add(entity);
}
workflowBackNodeConfigService.updateConfig(tempVersion, id, backNodeList);
}
}
其中的服务方法,updateConfig如下,采用先清除配置,后创建的模式,顺便清理因节点变更遗留垃圾数据。
@Override
public void updateConfig(String processDefinitionId, String nodeId, List<WorkflowBackNodeConfig> jumpNodeList) {
// 先清空配置
QueryWrapper<WorkflowBackNodeConfig> queryWrapper=new QueryWrapper<>();
queryWrapper.lambda().eq(WorkflowBackNodeConfig::getProcessDefinitionId,processDefinitionId)
.eq(WorkflowBackNodeConfig::getNodeId,nodeId);
remove(queryWrapper);
// 后生成配置
int orderNo=0;
for(WorkflowBackNodeConfig entity:jumpNodeList){
orderNo++;
entity.setProcessDefinitionId(processDefinitionId);
entity.setNodeId(nodeId);
entity.setOrderNo(StringUtils.leftPad(String.valueOf(orderNo),2,"0"));
add(entity);
}
}
平台名称:一二三开发平台
简介: 企业级通用开发平台
设计资料:csdn专栏
开源地址:Gitee
开源协议:MIT
开源不易,欢迎收藏、点赞、评论。