流程变量是工作流引擎和业务系统的数据交互的桥梁。
工作流承载业务,驱动业务流程,但是不会执行业务。工作流中的业务执行,全部都会委托给具体的业务模块执行。那么,这些被工作流分割的业务功能,在工作流中被调用执行的时候,如何保证做操作的数据的一致性?
通过流程变量,在每一步的业务功能执行的过程中,将业务数据保存在流程变量中,那么整个流程的后续活动中,都可以引用该流程变量,来完成业务功能,保证业务数据的一致性。
例如:在凭证的审批流程中,新增凭证审批,审批的时候为什么可以正确定为到刚刚提交的那张凭证,而不是别的凭证?流程变量起到了重要的作用。在凭证新增之后,将可唯一标示凭证的ID保存到流程变量中,在审批的时候将流程变量中的值传递给凭证,就可以根据这个ID获取到一张凭证了。
流程变量在流程实例的生命周期内都是有效的。
任务输入:在业务发生之前,有工作流传递给业务的数据。以凭证的过账为例,某个用户收到一条凭证过账的消息,双击消息处理。在双击之后、凭证的界面弹出之前,工作流会把定义的入口参数变量中的值传递给凭证,凭证根据这个值来展现特定的单据,展现特定的功能。
任务输出:在业务发生之后,由业务系统传递给工作流,需要保存在流程变量中的数据。仍以凭证过账为例,在用户操作完凭证过账之后,工作流继续流转之前,如果流程定义中定义了任务的输出,那么,就会将凭证对应的属性的数据,保存在流程变量中。已备在后续的流程活动中使用。
l 绘画工作流图之前一定要先将业务流整理清楚,分析业务流的特性,提取可以抽象出来公用的东西,分析是否可以进行优化等,好的业务流程可以直接映射为工作流流程。
l 利用“流程变量”的威力,建立单据和流程之间数据交换的桥梁。输入输出参数用来在流程和单据之间进行数据的传递。输入输出参数和流程变量搭建了流程和单据之间的数据联系通道。
l 流程变量赋值时需注意:变量是否在另外地方被改变,有子流程时变量关系如何匹配,对应的是否正确,每个节点对应的变量是否正确,不同的节点可能对应不同的单据id,给变量赋值时需特别细心。
l 如存在一些系统预定义功能无法满足的需求,可以采用自己开发功能,根据输入参数和输出参数来与工作流交互,如在单据中增加function,绑定到自动节点执行,或者是利用脚本节点,获取一些有用的信息输出到流程变量中在工作流中使用。还可以利用工作流的一些新增功能,比如利用函数节点,BOTP节点等来执行特殊需求。
l 参与人动态变化或根据条件变化时,可以充分利用“参与人变量”作为动态值,变量的值可以通过各种方式获取,比如脚本式(后置脚本或脚本活动)、或者任务输出属性方式等。(流程变量中可以定义类型为参与人的变量)
l 利用条件参与人设置一些动态的参与人场景。比如当某条件满足时,设置为某些参与人,当条件不满足或为另外情况时,设置为另外的参与人,通过条件参与人和参与人变量可以满足大部分复杂的参与人场景。条件参与人的参与人范围还可以做为运行期指定下一步活动的参与人范围。
l 善于利用路由节点,除了可设置模式外,还可以对流程图进行美化。
l 一些公用的业务逻辑可以单独抽取出来配置为子流程给其他流程共用,减少维护的流程数量,比如一些常用的审批流程等。
说明:流程只有一个人工型活动,完成凭证提交的任务,流程结束。没有具体的业务含义。
以凭证提交的业务为例。流程图如下:
定义步骤:
1、 拖入开始、结束活动
2、 拖入人工型活动,用连接弧连接起来。
3、 定义人工型活动
首先,定义人工型活动的任务,选择任务
选定任务后,定制任务的输入输出。
输入参数是由任务定义带出的
根据任务输入的意义,指的是在凭证提交之前,由工作流告知凭证的数据。新建一个流程变量,绑定该输入。
这里绑定的意思是:在提交之前,工作流会将billID这个流程变量中的数据传递给业务。业务拿到这个值之后,会根据业务需要做出判断。
[说明]所有人工型任务的输入参数,全部是在定义任务的时候就定义好的。每个任务的输出参数可能不同,是由于各个不同的业务系统对于业务开始之前,所需要的数据不同导致。但是在EAS系统中,基本上任务的输入参数只有一个BOID类型的参数。这是因为一般来说,通过这样一个类型的值,就可以完全定位一个业务单据,并且拿到这个业务单据,就可以满足大部分的业务需求了。
这里,将ID属性输出,并且选定输出的流程变量是billID
凭证提交完毕后,将可以唯一标示一张凭证的ID属性保存在流程变量billID中,在后续的活动中,如果还需要操作这张凭证,就可以通过billID来唯一定位这张凭证,保证业务的一致性。
参与人定义中,分为了默认参与人和条件参与人。工作流在获取执行人的时候,首先根据条件来逐个扫描条件参与人,发现没有符合,那么会取默认参与人。
这里简单处理,选择任意人。
提交就可以匹配到该流程
到现在为止,这条简单的流程就已经定义完毕。发布。到EAS中提交凭证,然后到工作流监控中,会发现有一条流程实例,并且状态是已完成。
说明:单据提交之后,经过一层审批,流程结束。
以凭证为示例。流程图如下
定义步骤:
1、 同场景1.3.1拖入活动
2、 增加一个审批活动,如图画连接弧
3、 配置提交活动。和场景1.3.1中一样选择任务,参与人也是任意人。但是任务的输出夺一项。由于在后续的消息中想展现出单据的编码,所以多输出一个单据编码到一个流程变量number中
4、 配置审批活动。为了方便测试,参与人设定为流程发起人的本人
这一部分,就是任务输入。对于现在的场景,审批凭证,那么在业务单据内码这一栏选中billID。此时billID已经在提交之后,保存了刚刚提交的凭证的ID。一旦这个审批任务发生执行,那么,在执行前,工作流会将billID这个变量中保存的值传给业务系统。那么审批时就可以唯一定位到一条业务单据。
任务输出,选择将审批结果输出到一个枚举型的流程变量 审批结果 中。
定制审批消息
流程定义完毕。发布,在EAS中执行。
提交凭证,在消息中心收到一条消息,审批,通过。然后回到凭证序时簿,察看该流程,发现,凭证的状态还是“提交”而不是“审核”。
这是因为工作流中的多极审批,只是单纯的驱动流程,做一个选择而已,不会修改业务数据。
为了能够让凭证打上审批标记,按照如下方式修改流程定义
最后的这个自动活动,就完成给凭证打审核标记的功能。
任务选择如下:
保存、发布,再到EAS中执行一下。发现审批状态打上了。
说明:审批通过,则打审批状态。审批不通过,返回修改。
仍以凭证为例。流程定义如下:
定义步骤
1、 提交、审批、自动节点的设置和场景2中一样。
2、 增加一个人工型活动,修改。选择的任务和“提交”一样。但是由于单据的ID和单据的编码是新增的时候就定好的,无法修改。所以只需要定义任务输入就可以了,不需要定义任务输出。
谁提交的谁修改,参与人设置为流程发起人本人
定制修改消息
3、 编辑连接弧
首先编辑“审批”到“自动”的连接弧。
建模工具会自动根据之前的定义识别枚举,然后将枚举的值也会自动列在选择范围内。
然后编辑“审批”到“修改”的连接弧。按照如下方式设置条件
保存。流程定制完毕。发布。
[说明]业务单据一旦进入工作流,就要受到工作流的约束。例如,刚刚提交完凭证,流程执行到审批节点。这个时候工作流要求的行为是“某个人执行审批操作”。如果这个时候修改凭证,会提示:“已在工作流处理中,任务不匹配”。
管理员收到申请单后,传给多个领导。由各领导分别进行审批,审批不通过的要能够来回进行审批。直到审批通过,也即是多级审批,每级审批不通过回到相应的审批节点继续进行审批。
流程配置
面对此种问题首先想到的应该是通过流程变量来设置审批节点的标志,在每级审批节点完成后设置一个标志,标志目前走到那个审批级别,如果不通过,根据流程变量上审批标志的值直接回到此审批节点。具体配置请查看下面截图:
完整流程图
综合处审批节点后置脚本
总结:充分利用流程变量作为流程流转的条件
需要重复使用某些脚本,希望能减低脚本维护的复杂度,同时提高脚本的复用率和使用效率。
解决办法
已经内置了函数节点的支持,函数节点本质上和脚本节点没有任何区别,所处理的任务和职责都是一样,但脚本节点脚本散落在流程中各个地方,维护和使用非常困难。函数节点的目的就是为了减低流程配置中脚本维护的复杂度和可重用性低的问题。具体使用请参考如下的说明:
在工具菜单 窗口->显示视图,查找选择工作流视图组->函数定义
点击确定后会出现函数定义的界面,其中已经内置了一些函数,双击选择的某个函数可以打开函数编辑界面
函数编辑界面,在此可以定义函数的输入输出以及一些描述性的说明,并可在编辑框内输入具体的代码(目前支持KScript和java代码),代码其实就是脚本。
然后定义工作流,在流程定义中增加一个函数节点。
在函数任务界面选择函数定义,会弹出函数定义选择界面,选择合适的函数即可。
在函数任务界面配置好函数的输入输出参数即可在流程中使用此函数的输出值。
如需自己定义函数,直接在函数视图中按照右键菜单提示进行操作即可。
员工提交单据后,进行第一级审批,第一级审批参与人设置条件:如果是提交单据的是财务部员工,则参与人为财务部某一职员进行审批,如果不是财务部提交的单据,则提交人所在部门(归口部门)的直接上级进行审批,如果直接上级是部门负责人则上级审批完成流程就继续往下走,如果直接上级不是部门的负责人则还需要部门的负责人进行审批。
配置步骤:
首先画出流程草图,将大致的图形先画出来。
在提交单据节点输出提交人的部门到流程变量“归口部门”,并在后置脚本中根据归口部门的值设置流程变量“是否财务部”的值。
在审批节点设置条件参与人,根据流程变量 “是否财务部”来设置相关的条件参与人,并将审批人输出到流程变量“审批人”中。
在审批节点后设置一脚本节点->负责人判断,用来判断上级审批的人员是否是部门负责人。如果是则流程结束。如果不是则继续在部门内多级审批,直到部门负责人审批完成(循环过程)。负责人判断的脚本内容为:
是否部门负责人=
com.kingdee.bos.workflow.participant.ParticipantHelper.isOrgPrincipal(
__bosContext,审批人,归口部门);
其中,是否为部门负责人,审批人,归口部门为流程变量。
最终流程如下:
总结:充分利用了条件参与人与流程变量,并通过脚本来获取流程必须的信息。
2. 工作流脚本
在工作流中经常会需要使用到一些脚本来获取单据的信息或者是执行一些特殊的操作,脚本应如何写?需要注意那些地方?有没有更好的定义和使用方法?
解决方法
工作流脚本利用的是java语法,所有的java代码都可以被工作流解析,但所有的类必须是全路径名称(除了java.lang.*包下的类),如List 必须写成java.util.List。在工作流中内置了一些变量和函数可以进行使用,如:“__bosContext”内置变量表示服务端的context。在工作流脚本中可以直接使用定义好的流程变量,脚本执行过程中会将脚本的值自动赋值给相应的流程变量。在内置函数节点可以用来封装脚本,减轻脚本维护的复杂度和提高可重用性。
工作流中内置的一些函数
如下是一些脚本的示例:
l 根据人获取对应主负责的行政组织脚本:
首先新建一个流程变量 BOID类型 例如 orgId
String userId = ""; //获取User对象的远程控制接口 com.kingdee.eas.base.permission.IUser iUser =com.kingdee.eas.base.permission.UserFactory .getLocalInstance(__bosContext); com.kingdee.eas.base.permission.IUser iUser =com.kingdee.eas.base.permission.UserFactory .getLocalInstance(__bosContext); com.kingdee.eas.basedata.person.PersonInfo info =iUser.getUserInfo( new com.kingdee.bos.dao.ormapping.ObjectUuidPK( com.kingdee.bos.util.BOSUuid.read(userId))).getPerson(); if (info != null) { String persOnId= info.getId().toString(); com.kingdee.eas.basedata.org.IPositiOnMemberiPositionMember= com.kingdee.eas.basedata.org.PositionMemberFactory .getLocalInstance(__bosContext); com.kingdee.eas.basedata.org.PositiOnMemberInfopositionMemberInfo= iPositionMember .getPositionMemberInfo("select position.adminOrgUnit.id where person.id = '"+personId + "' and isPrimary = 1"); orgId =positionMemberInfo.getPosition().getAdminOrgUnit().getId(); }
l 根据组织的id获取公司的名称,然后在条件参与人中根据此进行判断
1、 增加一个流程变量adminId、companyId、companyNum。都是字符串型
2、 在新增节点的任务输出中,输出组织的ID到adminId变量中
3、 增加脚本节点,其中脚本如下:
companyId =
com.kingdee.eas.basedata.person.app.PersonToWFAdapter.getCompanyIdByAdminId(__bosContext,adminId);
com.kingdee.eas.basedata.org.INewOrgUnitFacadeiOrg =
com.kingdee.eas.basedata.org.NewOrgUnitFacadeFactory.getLocalInstance(__bosContext);
com.kingdee.eas.basedata.org.CompanyOrgUnitInfofiInfo =
(com.kingdee.eas.basedata.org.CompanyOrgUnitInfo)
iOrg.getDelegateUnit(companyId,com.kingdee.eas.basedata.org.OrgType.Company);
if (fiInfo != null) {
companyNum =fiInfo.getNumber();
}
l 判断有无直接上下级的脚本
首先定义一流程变量,类型为布尔:有直接上级
com.kingdee.bos.workflow.service.ormrpc.IEnactmentServiceservice = new
com.kingdee.bos.workflow.service.ormrpc.EnactmentService(__bosContext);
com.kingdee.bos.workflow.ProcessInstInfo[]procInstInfos =
service.getProcessInstanceByHoldedObjectId(billID.toString());
com.kingdee.bos.workflow.ProcessInstInfocurProcInst = null;
for (int i = 0, n =procInstInfos.length; i if(procInstInfos[i].getState().startsWith("open.run")) { curProcInst =procInstInfos[i]; } } if (curProcInst != null) { initUserId =curProcInst.getInitiatorId(); com.kingdee.eas.basedata.person.app.PersOnToWFAdapteradapter= new com.kingdee.eas.basedata.person.app.PersonToWFAdapter(); com.kingdee.bos.workflow.participant.Person[]persOns= adapter.getSupervisor(__bosContext, initUserId); if (persons != null&& persons.length > 0 && persons[0] != null) { 有直接上级 = true; } else { 有直接上级 = false; } } l 委托时,判断审批人是否是部门负责人 判断审批人是否是部门负责人,如果审批人是委托任务处理人,则判断委托人是否是部门负责人 变量声明: 下述脚本使用了如下变量: __bosContext: 系统内置变量 单据Id : 单据内码,一般由单据直接输出 归口部门: 类型为字符串,为部门id 审批人: 类型为字符串,为审批任务的处理人,在审批节点中输出 审批节点名称: 类型为字符串,为节点定义名称 是否部门负责人: 类型为布尔,为返回的结果值,用来在流程中判断 com.kingdee.bos.workflow.enactment.WfEngineengine = com.kingdee.bos.workflow.enactment.WfEngine.getEngine(__bosContext); com.kingdee.bos.workflow.ProcessInstInfo[]infos = engine.getProcessInstanceByHoldedObjectId(单据Id); if(infos!= null && infos.length>0){ ProcessCOntrolDataKScriptAdapteradapter= newProcessControlDataKScriptAdapter(infos[0],engine.getLocale(),engine); String cOnstituentUserId=adapter.getTopConstituent(审批节点名称); if(constituentUserId !=null&& !constituentUserId.trim().equals("")){ //有委托人,判断委托人是否是部门负责人 是否部门负责人 =com.kingdee.bos.workflow.participant.ParticipantHelper.isOrgPrincipal ( __bosContext ,constituentUserId ," 归口部门) ; }else{//没有委托人,直接判断审批人是否有直接上级 是否部门负责人 =com.kingdee.bos.workflow.participant.ParticipantHelper.isOrgPrincipal ( __bosContext ,审批人 ,归口部门) ; } } else{ 是否部门负责人 =com.kingdee.bos.workflow.participant.ParticipantHelper.isOrgPrincipal ( __bosContext ,审批人 ,归口部门) ; } l 在脚本中执行SQL语句 java.lang.StringBuffersql = java.lang.new StringBuffer(); //将SQL语句保存到sql对象中 //… java.sql.COnnectioncon= com.kingdee.bos.framework.ejb.EJBFactory.getConnection(__bosContext); java.sql.StatementbatchStatement = con.createStatement(); batchStatement.execute(sql.toString()); com.kingdee.util.db.SQLUtils.cleanup(batchStatement,con); com.kingdee.util.db.SQLUtils.cleanup(con); l 将活动的参与人设为单据对应部门的负责人 场景:在单据上有个“费用的承担部门”的属性,现在要将审批活动的参与人设置为该部门的负责人。 1. 声明变量 2. 在做业务单据时(审核节点之前)将单据上的“费用的承担部门”的ID关联到var_orgUnitId变量中 ,并在该节点的后继脚本中填入如下内容: com.kingdee.eas.basedata.org.AdminOrgUnitInfo adminInfo = com.kingdee.eas.basedata.org.AdminOrgUnitFactory.getRemoteInstance().getAdminOrgUnit(var_orgUnitId); 3. 在审核节点中定义参与人,选择为参与人变量,并把var_princial添加进去。 l 根据分录信息循环发送消息给业务员 收集:脚本一 java.lang.String commOnStr= data +"," + billNumber + "," + chauffeur + "," +regnumber + "," + contact + "."; com.kingdee.eas.fi.gl.VoucherEntryInforecord = records.get(i); java.lang.String s =record.get("wareName") + "(" + record.get("wareNumber") + ")已发往" + record.get("customer") + ";"; java.langString op =record.("operator").getId().toString(); if (map.get(op) == null) { map.put(op, s); } else { map.put(map.get(op) + s); } } userList = new java.util.ArrayList(); msgList = new java.util.ArrayList(); //此段已废弃 begin for (java.lang.Iterator i = map.getKeySet.iterator();i.hasNext();) { Object key = i.next(); Object msg = map.get(key); userList.add(key); msgList.add(commonStr + msg); } //此段已废弃 end 修改为: list = new java.util.ArrayList(); list.addAll(map.keySet()); for (int i=0; i Object key = list.get(i); Object msg = map.get(key); userList.add(key); msgList.add(commonStr + msg); } pos = 0; count = userList.size(); if (count > 0) { user = userList.get(0); msg = msgList.get(0); } 在EAS中经常碰到这种情形,有两套UI对应一个实体,此两个UI绑定的功能(Function)和操作(Operate)是一样。由于工作流是根据功能和操作来进行流程匹配的,这两个ui执行后都启用了同一个流程,需要在两套UI上提交的时候触发不同的工作流,如何实现? 解决办法 工作流目前支持两种方式的流程匹配方式:一种是根据Function和Operate以及用户信息来进行流程的匹配,如果两套UI使用的用户范围是不一样的,可以通过在不同的流程的第一个人工节点根据用户范围设置参与人,根据参与人来区分是要匹配那条流程。但如果两条流程使用的用户范围是一致的,此种方式不可用;第二种方式是根据流程启动条件来进行流程的匹配,通过在流程的第一个连接符上设置启动条件,可以根据一些绑定的业务单据对象的某个属性或其他内容来设置此流程启动的条件和场景。工作流系统中内置了一个__processTrigger变量,代表绑定的业务单据,可以使用此变量输出相关的业务属性来进行判断,详细的请查看如下图示: 图一.设置启动条件 图二.双击链接符,弹出设置条件界面 图三.内置变量支持打点弹出业务对象的所有属性特征 图四.最终设置条件,示例为只有当单据的number为121的时候才启动此流程,此处根据业务需求来设定 可以使用一些比较复杂的条件来做判断,只要最后返回一个boolean值即可,如下述代码: com.kingdee.bos.dao.ormapping.ObjectUuidPK pk = new com.kingdee.bos.dao.ormapping.ObjectUuidPK(__processTrigger.getProposer().getId()); com.kingdee.bos.metadata.entity.SelectorItemCollection sic = new com.kingdee.bos.metadata.entity.SelectorItemCollection(); sic.add(new com.kingdee.bos.metadata.entity.SelectorItemInfo("id")); sic.add(new com.kingdee.bos.metadata.entity.SelectorItemInfo("name")); sic.add(new com.kingdee.bos.metadata.entity.SelectorItemInfo("employeeClassify.id")); sic.add(new com.kingdee.bos.metadata.entity.SelectorItemInfo("employeeClassify.name")); com.kingdee.eas.basedata.person.PersonInfo person = com.kingdee.eas.basedata.person.PersonFactory.getLocalInstance(__bosContext).getPersonInfo(pk,sic); return person.getEmployeeClassify().getName().equals("一般员工");
var_principal 外部数据类型
var_orgUnitId 内码(BOID)
if(adminInfo != null && adminInfo.getResponPosition != null)
{
com.kingdee.eas.basedata.org.IPosition iPosition = com.kingdee.eas.basedata.org.PositionFactory.getRemoteInstance();
com.kingdee.eas.basedata.person.PersonCollection pColl = iPosition.getAllPersons(adminInfo.getResponPosition.getId());
var_principal = new String[pColl.size()];
for (int i=0; i
var_principal[i] = pi.getId().toString();
}
}
java.lang.HashMap map = newjava.lang.HashMap();
for (int i=0; i2.2.多UI相同实体需要触发不同的流程,条件启动流程