网关:处理工作流同一级别,节点不同分支条件场景
互斥网关:其中一条分支审核通过进入下一节点
并行网关:所有分支同时审核,全部通过进入下一节点
包容网关:满足条件分支同时审核,全部通过进入下一节点
指派模式:上一节点审核用户指定下一节点的审核人
抢占模式:同一节点多个审核人,任何一人审核通过,就会进入下一节点
会签模式(并行):所有审核人同时看到审核任务,全部通过后进入下一个节点
会签模式(串行):审核人依次看到审核任务,全部通过后进入下一个节点
流程实例数据表 ACT_HI_PROCINST ,每个单据都是一个独立的审核流程,整个流程的数据信息就保存在此表中
运行时任务节点 ACT_RU_TASK ,此表记录了正在运行中的节点(即:正在待审核的节点数据) ,每个审核流程都是由多个节点逻辑连接组合而成,每个流程中正在运行时的任务节点信息都会存储到此表格中来
字段 PROC_INST_ID 对应到该节点所属流程实例的流程实例id
字段 PROC_DEF_ID 流程定义ID,通过该字段可以获取到流程模板的定义数据
字段 TASK_DEF_ID_ 任务节点ID,通过该字段可以获取到该节点的定义数据
每种类型单据都会有对应的流程模板,在U8Cloud中的持久化位置在流程模板数据表 ACT_DE_MODEL中
*注:在流程模型表中每种单据的MODEL_EDITOR_JSON字段下的数据格式是比较复杂的,层级关系为object -->childShapes -->properties -->participants 中可查询到具体的节点代理人
在同一流程模板的前提基础下,我们每个单据在不同的条件情况中所执行的流程也是不同的,每个单据的流程会出现不同的情况,所有U8Cloud中存在流程定义数据表ACT_RE_PROCDEF来区分不同的流程
每一个流程实例下所包含的每个流程节点同一会有对应的节点定义数据,ACT_PROC_RE_NODE流程定义节点表就保存了每一个节点中的定义信息
流程模型数据逻辑关系图如下所示:
*注:此节重点对流程定义数据相关的内容介绍,其他表格和所涉及表格的其他字段后续继续章节中介绍
一个流程是由不同的任务节点组成。在审核流程时,不同的职员在面对相同流程的同一个节点时可能会出现不同的操作状态,这就涉及到操作人员与流程节点之间的关系判断。节点所包含的操作状态包含一下几种情况:送审、收回、审核、销审、无权审核等。
送审权限首先是建立在单据录入人/制单人的基础上的,只有单据的制单人/单据录入人才会拥有送审权限。
每个单据模块(eg:指标单据、预算单据、经费申请单据等)都包含我的审核 和我的单据,当操作员进行我的单据查询时,首先获取到该模块下的所有单据,然后依据操作员信息过滤掉非该操作员录入的单据,过滤条件为单据的填单人 /录入人为当前操作员,而非单据的申请人。
在 我的单据 模块中单据还会根据其审核状态被分为两大类(未送审/审核中),一类是单据还未被制单人送审的,另一类是单据已经被送审的。送审权限的判断就是对未送审单据的送审条件判断:
未送审单据集合的送审条件判断
条件1:单据状态是否为未送审状态,对应单据数据中的 ’zt‘字段(zt=0)
条件2:通过单据所处流程的相关数据信息判断操作员是否具有权限
判断当前用户是否具有送审条件,有两种判断方式,如下图所有所示。两种查询方式是逻辑或关系,只要满足一种即表示当前操作员具有送审权限!
declare @userId int, @businessKey nvarchar(255)
select @userId = ID from GL_CZY where CzyCode ='20190003' -- #{zydm} 20190003
set @businessKey ='D721E2B6-4758-4255-87FE-5056DEB20932 '-- #{djguid}
if ('unchecked' = 'unchecked') --送审条件查询
BEGIN
declare @taskId nvarchar(64),@isAssignee int;
select @taskId = ID_ from ACT_RU_TASK where PROC_INST_ID_ = (select PROC_INST_ID_ from ACT_HI_PROCINST where BUSINESS_KEY_ = @businessKey)
select @isAssignee = iif(count(1) > 0, 1, 0) from ACT_RU_TASK where ID_ = @taskId and ASSIGNEE_ = @userId
if (@isAssignee = 1)
begin
select 'true' havePermission
end
else
begin
select @isAssignee = iif(count(1) > 0, '1', '0')from (
select distinct iif(USER_ID_ is null or len(rtrim(USER_ID_)) = 0, r.CZYID, USER_ID_) id
from ACT_RU_IDENTITYLINK i left join GL_CZY_ROLE r on r.ROLEID = i.GROUP_ID_
where TASK_ID_ = @taskId) a
where a.id = @userId
if (@isAssignee = 1)
begin
select 'true' havePermission
end
else
begin
select 'false' havePermission
end
end
END
销审权限与送审权限类似,也是建立在 制单人/单据录入人 的基础上的。一条单据送审之后(‘zt=1’),会被依据单据的 ’zt’ 字段将此单据分配到 我的单据模块下单已送审类别中。
方式一:在判断操作员是否具有收回权限时,需要满足下面两个条件
条件1:当前操作员为本条单据的制单人
条件2:当前单据的审核状态为 1(zt=1)
方式二:通过获取流程节点逻辑顺序判断销审权限
在为解决上一种方式产生的问题,所以为了准确的查询到逻辑上的上一节点,所以需要对流程的部署逻辑进行查询,查询出对应节点的逻辑上一节点,然后进行条件对比后获取到对应的审核权限。
declare @zydm varchar(20) ,@djguid varchar(200),@procinstid varchar(200),@procdefid varchar(200),@currentnodekey varchar(200),@nodeName varchar(200),@isAssignee int
select @zydm='20010003' ,@djguid='522bad1dd9304199888f7f73140e66c4' --20200018 20010003
select @procinstid = PROC_INST_ID_ from ACT_HI_PROCINST where BUSINESS_KEY_=@djguid
select @procdefid=PROC_DEF_ID_,@currentnodekey=TASK_DEF_KEY_ from ACT_RU_TASK where PROC_INST_ID_=@procinstid
select @nodeName = node_name from ACT_PROC_RE_NODE where proc_def_id=@procdefid and node_id = (select physics_parent_id from ACT_PROC_RE_NODE where proc_def_id=@procdefid and node_id=@currentnodekey)
select @nodeName nodename,@procdefid
select @isAssignee = iif(count(1) > 0, 1, 0) from ACT_HI_TASKINST where PROC_INST_ID_=@procinstid and ASSIGNEE_=(select ID from GL_CZY where CzyCode =@zydm) and NAME_= @nodeName
if (@isAssignee = 1)
begin
select 'true' havePermission
end
else
begin
select 'false' havePermission
end
操作员在进入我的审批模块时,首先需要将该模块下所属当前操作员名下的两大类数据查询获区分出来(待审核单据/ 已审核单据)
待审核单据数据查询方式:
1.查询出该模块下的所有单据集合
2.过滤掉单据集合中未送审单据(zt=0)。过滤目的:审核权限也包含了送审权限,但是在执行送审和审核操作时是不同的API接口执行,并且前操作员的送审单据应该存在于我的单据模块中。
3.根据操作员信息筛选出集合中的每条单据操作员是否具有审核权限,其中的筛选条件与 2.1 中的制单人送审条件的判断相同。
通过上面三个步骤,就可以得到单据类型下 我的审批 模块中 待审核单据数据。待审核单据就是通过判断单据是否具有审核权限而过滤出来的,存在于待审核列表中的单据都是要满足当前审核人进行审批的权限。审核权限的判断与 2.1 送审条件的判断相同!
declare
@userId int,
@businessKey nvarchar(255)
select @userId = ID from GL_CZY where CzyCode =#{zydm}
set @businessKey =#{djguid}
<!-- if ('unchecked' = 'unchecked') 审核条件查询 -->
declare @taskId nvarchar(64),@isAssignee int;
select @taskId = ID_ from ACT_RU_TASK where PROC_INST_ID_ = (select PROC_INST_ID_ from ACT_HI_PROCINST where BUSINESS_KEY_ = @businessKey)
select @isAssignee = iif(count(1) > 0, 1, 0) from ACT_RU_TASK where ID_ = @taskId and ASSIGNEE_ = @userId
if (@isAssignee = 1)
begin
select 'true' havePermission
end
else
begin
select @isAssignee = iif(count(1) > 0, '1', '0')
from (
select distinct iif(USER_ID_ is null or len(rtrim(USER_ID_)) = 0, r.CZYID, USER_ID_) id
from ACT_RU_IDENTITYLINK i
left join GL_CZY_ROLE r on r.ROLEID = i.GROUP_ID_
where TASK_ID_ = @taskId) a
where a.id = @userId
if (@isAssignee = 1)
begin
select 'true' havePermission
end
else
begin
select 'false' havePermission
end
end
被审核过的单据都会从待审核列中转移到已经审核列表中去,而销审操作就是建立在已审核单据的基础上的。所以在判断操作员是否具有销审权限之前需要将操作员参与过的审核单据过滤出来即已处理单据列中,然后在判断操作员对于某条单据是否具有销审权限。
已审核单据数据查询方式:
1.查询出所以该类型的单据
2.然后通过如下所示的过滤方式,将操作员参与过审核的单据进行过滤处理即可
declare
@userId int,
@businessKey nvarchar(255)
select @userId = ID from GL_CZY where CzyCode =#{zydm}
set @businessKey =#{djguid}
<!--select * from ACT_HI_taskinst where ASSIGNEE_ = @userId and PROC_INST_ID_ = (select PROC_INST_ID_ from ACT_HI_PROCINST where BUSINESS_KEY_ = @businessKey ) -->
declare @isAssignee int
select @isAssignee = iif(count(1) > 0, 1, 0) from ACT_HI_taskinst where ASSIGNEE_ = @userId and PROC_INST_ID_ = (select PROC_INST_ID_ from ACT_HI_PROCINST where BUSINESS_KEY_ = @businessKey )
if (@isAssignee = 1)
begin
select 'true' havePermission
end
else
begin
select 'false' havePermission
end
已审核模块下单据销审条件判断
1.首先要判断该节点是否为最后一个节点:通过历史流程实例表获取到该流程的 END_ACT_ID_ ,判断该字段是否为 null,就可以确定该流程是否已经执行完毕!
--判断当前节点是否为当前流程的最后一个已审核节点
select end_act_id_ as endid from act_hi_procinst where BUSINESS_KEY_=#{djguid}
select end_act_id_ as endid from act_hi_procinst where BUSINESS_KEY_=#{djguid}
select @endId =#{END_ACT_ID}
begin
select @isAssignee = iif(count(1) > 0, 1, 0) from ACT_HI_TASKINST where ASSIGNEE_ = (select top 1 ID from GL_CZY where CzyCode=#{zydm}) and EXECUTION_ID_=(select top 1 execution_id_ from ACT_HI_ACTINST where act_id_=@endId order by END_TIME_ desc)
if (@isAssignee = 1)
begin
select 'true' havePermission
end
else
begin
select 'falese' havePermission
end
end
通过遍历的方式。首先查询出该流程下所有执行过的历史流程节点,然后获取到里流程中上一节点的代理人ASSIGNEE_的id,然后与当前操作员的id进行对比判断是否具有审核权限
declare @userId int, @businessKey nvarchar(255)
select @userId = ID from GL_CZY where CzyCode ='20160002' -- #{zydm} 20190003
set @businessKey ='046CCA4AC68D4B369C0FD74BE306A402'-- #{djguid}
if ('checked' = 'checked') --消审条件查询
BEGIN
declare @taskDefKey nvarchar(255), @id nvarchar(64), @assignee nvarchar(255), @lastAssignee nvarchar(255), @taskAssignee nvarchar(255), @procInstId nvarchar(64), @i int
set @i = 0
select @procInstId = PROC_INST_ID_ from ACT_HI_PROCINST where BUSINESS_KEY_ = @businessKey
-- 如果流程最后一个节点未审核,则遍历获取到流程的经办人(送审人),否则是获取到流程的最后一人
declare cursorQueryLastAssignee cursor for
select ASSIGNEE_ from ACT_HI_TASKINST where PROC_INST_ID_ = @procInstId order by START_TIME_ desc --降序获取流程历史节点数据
open cursorQueryLastAssignee fetch next from cursorQueryLastAssignee into @lastAssignee
while (@@FETCH_STATUS = 0)
begin
print '111@lastAssignee='+@lastAssignee
set @i = @i + 1
--第一次才会进入判断是否需要break
if (@i = 1)
begin
print '@i=1执行一次'
--历史节点中除了流程审核完毕,其他环节第一个历史节点都为null所以不会结束遍历游标
--这样是为了排除最后一个节点已经被审核,即流程已经结束
if (@lastAssignee is not null)
begin
print 'break'
break
end
end
fetch next from cursorQueryLastAssignee into @lastAssignee
end
close cursorQueryLastAssignee DEALLOCATE cursorQueryLastAssignee
--
declare cursorQueryPermission cursor for
select ID_, TASK_DEF_KEY_, ASSIGNEE_ from ACT_HI_TASKINST where PROC_INST_ID_ = @procInstId order by START_TIME_ desc
open cursorQueryPermission fetch next from cursorQueryPermission into @id, @taskDefKey, @assignee
while (@@FETCH_STATUS = 0) BEGIN
print '22@assignee: ' + @assignee
print '22@id='+@id
--遍历出流程下历史节点数据,找到desc降序排序下,第一个不为null的人(且不等于制单人)即为上一节点审核人
--获取流程节点中的第一个已审核节点审核人,null会自动跳过此循环(正在运行时节点审核人为null会自动跳过)
if (@lastAssignee != @assignee)
begin
print '@lastAssignee != @assignee'
set @taskAssignee = @assignee break
end
fetch next from cursorQueryPermission into @id, @taskDefKey, @assignee
end
close cursorQueryPermission DEALLOCATE cursorQueryPermission
print '=================='
print '@lastAssignee='+@lastAssignee
print '@assignee: ' + @assignee
print '@taskAssignee='+@taskAssignee
if (@taskAssignee = @userId)
begin
select 'true' havePermission
end
else
begin
select 'false' havePermission
end
END
在送审环节中,最基本的操作就是通过单据的业务代码 BUSINESS_KEY 从历史流程表 ACT_HI_PROCINST 中获取到对应流程的流程实例id PROC_INST_ID_ 然后在通过流程实例id来获取对应流程下的相关节点任务,从而进行后续的操作!
在执行设置任务节点委托人获取最终委托人id值的具体流程如下
在单据审核操作中,涉及到三种类型的操作,分别是 审核通过、退回上一步、退回编制人。
审核通过操做和送审操作的流程执行逻辑是大同小异的。在审核通过操作中涉及到一种特殊情况,即当前节点为最后一个节点时的审核通过操作。对于这种情况会做出相应的特殊处理。
驳回上一岗位操在就是通过业务标识代码获取到流程节点相关的消息后,取得当前节点的上一岗位数据后操作流程。需要注意当前节点位置位于第一岗和第二岗这两种特殊的情况。位于第一岗时,当前节点是不允许驳回操作的,在位于第二岗时执行驳回操作就是执行了驳回第一岗操作。
declare @piid varchar(100)
select @piid = PROC_INST_ID_ from ACT_HI_PROCINST where BUSINESS_KEY_ ='046CCA4AC68D4B369C0FD74BE306A402'
--select top 1 DJGUID from OER_DJML where GSDM ='001' and KJND ='2022' and DJLXID ='160' and MLID = '67'
--根据不同类型的指标类型:查询不同的数据表
--select top 1 DJGUID from GBI_TZDML where TZDid=60 and GSDM='001' and KJnd='2022'
--@DJGUID
select CONVERT(NVARCHAR(20), a.START_TIME_, 120) SDT, ACT_NAME_ NODENAME,
iif((select NAME from GL_CZY where ID = h.ASSIGNEE_) is null or (select NAME from GL_CZY where ID = h.ASSIGNEE_) = '',
iif((select NAME from GL_CZY where ID in (select USER_ID_ from ACT_RU_IDENTITYLINK i where i.TASK_ID_ = a.TASK_ID_)) is null or
(select NAME from GL_CZY where ID in (select USER_ID_ from ACT_RU_IDENTITYLINK i where i.TASK_ID_ = a.TASK_ID_)) = '',
(select NAME + '' from GL_CZY where ID in (select CZYID from GL_CZY_ROLE where ROLEID =(select GROUP_ID_ from ACT_RU_IDENTITYLINK i where i.TASK_ID_ = a.TASK_ID_)) for xml path ('')),
(select NAME from GL_CZY where ID in (select USER_ID_ from ACT_RU_IDENTITYLINK i where i.TASK_ID_ = a.TASK_ID_))),
(select NAME from GL_CZY where ID = h.ASSIGNEE_)) AUDITOR,
iif((select count(1) from ACT_RU_TASK t where t.ID_ = a.TASK_ID_) > 0, '', N'已处理') ATYPE,
(select MESSAGE_ from ACT_HI_COMMENT c where c.PROC_INST_ID_ = a.PROC_INST_ID_ and c.TASK_ID_ = a.TASK_ID_ and not exists(
select TIME_ from ACT_HI_COMMENT c1 where c1.PROC_INST_ID_ = c.PROC_INST_ID_ and c1.TASK_ID_ = c.TASK_ID_ and c1.TIME_ > c.TIME_)) REMARK,
'' IMG
from ACT_HI_ACTINST a
left join ACT_HI_TASKINST h on h.ID_ = a.TASK_ID_
where a.PROC_INST_ID_ = @piid and (ACT_TYPE_ = 'startEvent' or ACT_TYPE_ = 'userTask' or ACT_TYPE_ = 'endEvent')
order by a.START_TIME_,
case ACT_TYPE_ when 'startEvent' then 0 when 'userTask' then 1 end
select LOCKED , * from ACT_CO_DATABASECHANGELOGLOCK where ID=1 --查看LOCKED字段数据需要为0
update ACT_CO_DATABASECHANGELOGLOCK set LOCKED='0' where ID=1
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.4.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.11</version>
</dependency>
//----------FlowAble相关jar包1----------
import org.flowable.task.api.Task; [extends TaskInfo]
String getId();
String getName();
String getAssignee();//获取节点代理人
String getProcessDefinitionId();//获取节点所属流程的流程实例定义id
String getTaskDefinitionKey(); //获取任务节点定义key
.....
//----------FLowAble相关jar包2----------
import org.flowable.engine.TaskService;
//获取当前节点 方式一(流程实例id+当前节点审核人)
taskService.createTaskQuery().processInstanceId(processInstanceId).taskCandidateUser(userId).singleResult();
//获取当前节点 方式二(通过流程实例id:processInstanceId)
taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
//向任务或流程实例添加注释
Comment addComment(String taskId, String processInstanceId, String message);
//保存添加流程任务的注释信息
void saveComment(Comment comment);
//当前节点设置代理人,移交任务权限
void setAssignee(String taskId, String userId);
//组任务设置代理人,移交任务权限
void addCandidateUser(String taskId, String userId);
//结束当前节点任务
void complete(String taskId,HashMap args);
args.put("bizTitle",bizTitle); //节点标题描述
args.put("operateType","next");//节点操作类型
args.put("taskId", taskId);//当前节点id
//通过流程实例id获取到该流程下的所有任务节点
taskService.createTaskQuery().processInstanceId(processInstanceId).list()
//----------FlowAble相关jar包3----------
import org.flowable.engine.HistoryService;
//通过业务场景码获取流程实例的实例id(processInstanceId)[对应历史流程实例表:ACT_HI_PROCINST]
historyService.createHistoricProcessInstanceQuery().processInstanceBusinessKey(businessKey).singleResult().getId();
//通过流程实例id获取流程下所有历史节点,按开始顺序降序获取历史节点数据
historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().desc().list();
//----------FLowAble相关jar包4
import org.flowable.engine.RuntimeService;
//通过流程实例id去获取当前流程运行节点的流程定义id(processDefinitionId)
runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult().getProcessDefinitionId();
//通过流程实例id更改流程当前运行时节点(驳回操作中用到)
runtimeService.createChangeActivityStateBuilder().processInstanceId(processInstanceId)
//更改运行时节点流程id
.moveActivityIdTo(currentNodeId, newNodeId).changeState();
//通过流程实例id,获取该流程的流程定义id
runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult().getProcessDefinitionId();
//----------FlowAble相关jar包5、6、7
import org.flowable.engine.RepositoryService;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
//通过流程定义id(processDefinitionId)去获取对应流程的业务流程模型BpmnModel
BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
//流程模型通过节点定义id(taskDefinitionKey/flowElementId)获取流程中对应节点的定义数据
FlowElement flowElement = bpmnModel.getMainProcess().getFlowElement(TaskDefinitionKey)
flowElement.getId() //流程节点id
flowElement.getName() //流程节点名称
//获取流程节点当前有关参与人设置的参数信息!!
flowElement.getAttributes().get("participants").get(0).getValue();