使用本示例需通过docker容器,请先下拉jxTMS的docker镜像并按说明启动tms容器,并从helloWorld开始尝试。

jxTMS的简易流程

这一节我们做个复杂点的:一个申请审批流程。

一件事如果需要多人协作完成,业务管理系统一般是将其组织为一个流程来把各作业环节串联起来。但一般意义上的流程既较为复杂又门槛颇高,对于jxTMS的所期望的低成本开发来说,对开发者的要求有点高。所以jxTMS对一般意义上的流程进行了简化,称之为简易流程。

简易流程是面向企业中大量的常见流程,对一般性的流程做了些简化:

注:如果大家对一般性的流程是什么样的不太理解,那就先搞清楚jxTMS的简易流程,等熟悉了之后再针对性的添加上面所说的这两个被简化掉的内容,就容易理解一般的流程了

也就是说,简易流程就是针对大多数的线性作业,将作业环节按序依次推进,每节点就只有两个动作:

所有的申请审批都是此种模式的流程。

制作一个流程所需的工作内容

jxTMS已经为简易流程做了大量的支持工作,所以在jxTMS中制作一个简易流程的工作得到了大大的简化。

首先,需要协助业务部门进行流程的基本设计,包括:

然后需要做的只有:

但是呢,一个流程创建了,其执行情况如何我们还要提供列表查询的功能,所以我们还需要制作该流程列表查询的相关内容。

下面我们以一个最简单的三环节申请审批流程为例来演示如何制作该流程。该流程简述如下:

注:由于是演示,所有作业过程全部由manager来完成。以后我们会讲解如何将各作业环节分配给不同的岗位

制作流程页面

在web文件中添加:

web sfDemo type div; web sfDemoApply parent sfDemo type div cover=true; web sfDemoApplyt1 parent sfDemoApply type table title='申请人填写',width=900,alone=true; with sfDemoApplyt1 row 0 col c0 web n type text text='类型:',width=100,extra=[{'color':'red','text':'*'}]; with sfDemoApplyt1 row 0 col c1 web n bind demoType type input verify=[len>0] width=200,logChangedDisp='类型'; with sfDemoApplyt1 row 0 col c2 web n type text text='名称:',width=100,extra=[{'color':'red','text':'*'}]; with sfDemoApplyt1 row 0 col c3 web n bind demoName type input verify=[len>0] width=200,logChangedDisp='名称'; web sfDemoApplyt2 parent sfDemoApply type table title='申请人说明',width=900,forDisp=false,alone=true; with sfDemoApplyt2 row 0 col c0 web n type text text='意见:',width=100; with sfDemoApplyt2 row 0 col c1 web n bind applySuggestion type textarea width=800,logChangedDisp='意见'; with sfDemoApplyt2 row 1 col c0 web n type text text='签发时间:',width=100; with sfDemoApplyt2 row 1 col c1 web n bind applyDate type text width=200; with sfDemoApplyt2 row 1 col c2 web n type text text='签发人:',width=100; with sfDemoApplyt2 row 1 col c3 web n bind applyBy type text width=200; with sfDemoApplyt2 row 2 col c0 web n type button width=80,text='确认',needVerify=['demoType','demoName'],motion=cmd, demand=simpleFlowDual,onlyOnce=false,delay=2000, params={'flowName':'sfDemo','nodeName':'demoApply','active':'accept','signBy':'applyBy','signDate':'applyDate'}; web sfDemoConfirm parent sfDemo type div cover=true; web sfDemoConfirmt1 parent sfDemoConfirm type table title='审核',width=900,alone=true; with sfDemoConfirmt1 row 0 col c0 web n type text text='意见:',width=100; with sfDemoConfirmt1 row 0 col c1 web n bind confirmSuggestion type textarea width=800,logChangedDisp='意见'; with sfDemoConfirmt1 row 1 col c0 web n type text text='签发时间:',width=100; with sfDemoConfirmt1 row 1 col c1 web n bind confirmDate type text width=200; with sfDemoConfirmt1 row 1 col c2 web n type text text='签发人:',width=100; with sfDemoConfirmt1 row 1 col c3 web n bind confirmBy type text width=200; with sfDemoConfirmt1 row 2 col c0 web n type button width=80,text='同意',motion=cmd, demand=simpleFlowDual,params={'flowName':'sfDemo','nodeName':'demoConfirm','active':'accept','signBy':'confirmBy','signDate':'confirmDate'}; with sfDemoConfirmt1 row 2 col c2 web n type button width=80,text='拒绝',motion=cmd, demand=simpleFlowDual,params={'flowName':'sfDemo','nodeName':'demoConfirm','active':'reject','signBy':'ConfirmBy','signDate':'confirmDate'}; web sfDemoApprove parent sfDemo type div cover=true; web sfDemoApprovet1 parent sfDemoApprove type table title='审批',width=900,alone=true; with sfDemoApprovet1 row 0 col c0 web n type text text='意见:',width=100; with sfDemoApprovet1 row 0 col c1 web n bind approveSuggestion type textarea width=800,logChangedDisp='意见'; with sfDemoApprovet1 row 1 col c0 web n type text text='签发时间:',width=100; with sfDemoApprovet1 row 1 col c1 web n bind approveDate type text width=200; with sfDemoApprovet1 row 1 col c2 web n type text text='签发人:',width=100; with sfDemoApprovet1 row 1 col c3 web n bind approveBy type text width=200; with sfDemoApprovet1 row 2 col c0 web n type button width=80,text='同意',motion=cmd,demand=simpleFlowDual, params={'flowName':'sfDemo','nodeName':'demoApprove','active':'accept','signBy':'approveBy','signDate':'approveDate'}; with sfDemoApprovet1 row 2 col c1 web n type button width=80,text='拒绝',motion=cmd,demand=simpleFlowDual, params={'flowName':'sfDemo','nodeName':'demoApprove','active':'reject','signBy':'approveBy','signDate':'approveDate'}; with sfDemoApprovet1 row 2 col c3 web n bind exportAdditional type combobox width=80,values=[{'value':'demoApply','text':'退回申请人'},{'value':'demoConfirm','text':'退回审核'}],notExtant=true;

制作好的界面显示出来是如下的模样:

jfinal框架详解(jxTMS使用示例--简易流程)(1)

注:先不要管【日志、数据变动等】这三个工具条,以后我们会演示的

我们所定义的这个界面:sfDemo。其包括了三个子div【组容器】:

大家先不要管这个界面的细节,等完成了整个流程后,再对照着web界面的参考:web界面定义、web控件的一般定义、web界面中各控件的定义来搞明白界面的定义。

对各节点的逻辑处理分别编程

然后我们打开capa.py,在demo1类的定义中添加:

#申请节点的处理 #request在这里的参数表是:流程名、节点名、dual【指定为相应节点的逻辑处理】 @myModule.request('sfDemo', 'demoApply', 'dual') def sfDemoApply_dual(self, db, ctx,ca,active): #ca是记录当前流程各种状态与数据的对象事件,也是 #active是当前用户的操作,点击同意按钮是accept,点击否决按钮是reject ca.Type=self.getInput('demoType') ca.Name=self.getInput('demoName') ca.Purpose='demo' ca.State=0 ca.CreatorID=ctx.getCaller().id() ca.Info.set('creator',ctx.getCaller().abbreviation()) #提示并关闭界面 return True #流程结束后的处理 #虚拟节点end指示是整个流程结束后的逻辑处理 @myModule.request('sfDemo', 'end', 'dual') def sprjend_dual(self, db, ctx,ca,active): #结束 ca.State=1 db.update(ca,'State') #提示并关闭界面 return True

注:jxTMS在每个节点都执行了大量的默认操作,而审核与审批两节点并没有需要执行的额外处理,所以省略

编写流程

jxTMS中的简易流程是用文本进行定义的,大家在capa.py,在demo1类的定义中添加:

#指定本功能模块的默认显示界面【jxTMS在自动指派任务时需要此默认界面】 def viewWebInterface(self): return 'sfDemo' #定义一个名为sfDemo的简易流程 @myModule.simpleFlow('sfDemo') def sfDemo(): ''' web sfDemo; node demoApply 申请 web sfDemoApply ; node demoConfirm 审核 web sfDemoConfirm needRole manager notJump; node demoApprove 审批 web sfDemoApprove needRole manager notJump; ''' pass

简易流程的定义,是用myModule.simpleFlow(流程名)修饰的一个空函数的doc中进行定义的,大家可参考简易流程中的说明来理解。概要的说,就是定义了一个sfDemo的流程,该流程的web界面也叫sfDemo,其包括了三个节点:申请、审核、审批,指定了每个节点可操作的子界面,后两个节点都指派给manager角色的用户来执行【而且不可跳过】。

增加流程创建的入口

在op.py中添加:

@biz.Motion('demo.demo1','disp','sfDemo') @biz.OPDescr def op1(json): json.setShortcut('演示'.decode('utf-8'),'发起申请'.decode('utf-8')) json.setParam('notCover','sfDemoApply')

就是一个显示流程界面的sfDemo,唯一不同的是有一个notCover的参数被设置为了申请节点所对应的界面:sfDemoApply。意思就是不要遮挡sfDemoApply界面,什么意思呢?!等流程建好,大家多执行几次就会理解了的,所以现在先不管了。

我们将web、op.py、capa.py等文件按用sftp管理jxTMS的代码所述更新到/home/tms/codeDefine/demo/demo/demo1目录中。

然后执行一次热机刷新,由于快捷栏中的入口有变化,所以先要退出登录,再次登录后点击快捷栏中的【演示->发起申请】,然后看看显示出来的界面是什么样的。

遮罩

大家会看到【发起申请】这个界面,自上而下由四张表组成:申请人填写、申请人说明、审核、审批。在过了一小会之后,审核、审批两表会突然变灰了,而且里面的控件也无法再被点击到。

大家请回过头来,看一下sfDemo界面的定义,会发现sfDemoApply【参考simpleFlow('sfDemo')的定义,可以观察到其是demoApply即申请节点所对应的web操作界面】、sfDemoConfirm【参考simpleFlow('sfDemo')的定义,可以观察到其是demoConfirm即审核节点所对应的web操作界面】、sfDemoApprove【参考simpleFlow('sfDemo')的定义,可以观察到其是demoApprove即审批节点所对应的web操作界面】都有一个之前从没看到过的属性:

cover=true

大家看一下组控件中对cover属性的讲解。当一个组容器设置了cover=true后,其在界面初始化与数据装定之后,就会被用一个罩子遮挡起来,从而使得用户能看到其内容但无法对其包含的控件进行操作。

大家再看一下op.py文件中【发起申请】这个入口,我们设置了一个参数:

json.setParam('notCover','sfDemoApply')

意思也很明显,就是在打开通过【发起申请】这个入口打开sfDemo界面后,取消sfDemoApply界面的遮挡,也就是将sfDemoApply的遮罩关闭掉,从而使得用户可以填写申请信息以及进行说明,并点击【确认】按钮。

注:大家有没有想到一种攻击方式:在某种情况下【如请款被退回重新填写】,通过按【tab】键将焦点移动到总经理的意见那一栏后,输入同意支付一百万,然后再按【tab】键将焦点移动到总经理对应的同意按钮上,敲击【回车】,如果财务人员未注意签发人或签发人正好同名【这就是jxTMS为什么要用简称的原因】,就完成了一次非法的申请审批。针对此种情况,jxTMS中禁用了【tab】键,用户是无法通过按动【tab】键来切换焦点的

取消【tab】键、加遮罩、取消遮罩,这岂不是很麻烦啊,jxTMS为什么要这么做呢?!大家看一下sfDemo界面的web定义,是不是觉得的很麻烦,可sfDemo流程只有简单的三个步骤,而且每个步骤都非常简单。那么大家想一下,如果是一个复杂的流程,其界面是不是就会非常的麻烦?!

那么,如果不使用遮罩的话,任何一个节点就必须同时设计一个写界面、一个只显示的读界面,然后在流转到自己的那个节点的时候,其它节点的界面都是读界面、自己的这个节点的界面是写界面。而这样一来,界面开发的工作量就大了一倍,管控只会更复杂。因为,如果是其他人查看呢?如果是自己已经做完了想再看看呢?

注:另一种实现方案是将该节点所对应的组容器中的所有子控件禁用掉,但jxTMS的控件都是动态生成的、界面管理本身就非常复杂,如果采用本方法,会导致界面管理进一步复杂化,这对于只有一个开发者的jxTMS来说,笔者实在不想出现该禁用又被错误的使能,或是该使能的时候却没有正确的使能

开发过程可能需要修改流程界面啊,那么如果有修改,则还必须保持两个界面的同步。所以呢,思来想去,笔者认为简单才是硬道理,jxTMS的实现再复杂,只要测试完备就不怕,换来的是开发者成本、风险的双双下降,还是一半以上的下降,最终还是采用了遮挡的方案。

结语

简易流程比较复杂,而本节内容就已经较多了,所以就不再继续,大家对照显示出来的【发起申请】界面,仔细回顾一下本节内容,这样可以加深对简易流程的理解。我们下一节开始讲解简易流程的流转。

,