天天看点

jxTMS使用示例--合规性保证

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

合规性保证

任何一个业务都需要符合相应的业务规则,如我们在导入excel一节中导入了销售折扣权限,则当销售在提交订单时,我们就要检查成交明细中各产品的成交价是否高于提交者的折扣权限,如果高于,则通过;如果低于,则按业务规则所要求的进行处置,比如,将不符合权限的项目标红,以提示后继的审批人员】,或者设置流程数据对象的某个属性,然后根据此属性来控制流程流转,如折扣都要于权限,则只审核,如果有折扣低于权限,则流转到审批环节等。

业务的数字化当然也包括了业务规则的数字化,但业务规则的数字化很多都是依靠编程来实现的,这就导致业务方所拟定的业务规则是否被开发人员准确的实现了只能通过全面的测试才能加以验证,但这个全面测试的要求既推高了开发成本也会大大限制业务随动环境变化、战略调整等的变更。

jxTMS以中文文本规则的形式提供了业务规则的描述与执行检查的能力,从而得以让业务人员在开发者配合下自行编写业务规则,在业务的合规性检查方面也实在了低成本快速定制。

也就是说,我们把对业务的合规性的审查,从业务编码中抽离出来,以文本的形式使之易读、易理解,最好是能让业务人员书写这些业务的合规性规则,由于jxTMS保证了这些规则的稳定、可靠执行,所以业务合规性的保证成本就得以大幅度的降低而且调整起来也非常简便。

在jxTMS中,实现这一目标的手段是:业务规则表。就是用文本定义一组业务规则,然后在需要的检查点运行这些规则进行检查。

现在我们以之前演示的简易流程sfDemo来演示如何在jxTMS应用业务规则表来提供合规性保证。

定义业务规则

jxTMS的业务规则很简单,就是一组由英文分号相分隔的中文产生式,只不过由于担心中文分词出现问题,强制要求词与词之间用空格隔开。所以业务规则就是条件语句:

如果 条件组 则 干什么 否则  干什么
           

其中条件组,是用【且】连接起来的一组判断条件,且,就是英文的and,即同时成立。

注:由于and与or具有等价性,而且引入or后还存在一个优先级的问题,会增加规则编写者的水平要求,否则可能会导致实际成立的条件不是编写者所预想的。所以建议大家尽量只使用and连接

条件有三种,我们现在介绍今天会用到的一种,其它等有机会再说:

#值比较
值 操作符 表达式 
           

其中的操作符是:==【等于】、!=【不等】、>【大于】、>=【大于等于】、<【小于】、<=【小于等于】、like【仅用于字符串,左操作数是否以右操作数开头】。调用函数较为复杂,等我们用到时用例子进行讲解就容易理解了。

而干什么则是一组用英文分号分隔的一组语句【每个都会被执行】,这些语句也有三种,我们主要介绍最常用的赋值语句,其它两种暂时用不到:

#赋值
值 = 表达式
           

注1:jxTMS中所有的赋值是一个英文等号,而比较中的等于,则是两个英文等号

注2:jxTMS中所有的标点符号都是英文符合,一定不要使用中文符号

好了,我们现在开始编写我们的第一条规则。我们在capa.py文件中添加:

#定义名为testDemoRule的规则表
@myModule.rule('testDemoRule')
def testDemoRule():
	'''
	/* 如果申请人填写的类型是demo,则将其标红 */
	如果 a0:field.Type == 'demo' 则 outAttr.demoType.boder = '红' ;
	'''
	pass
           

然后在申请人所对应的操作响应函数【sfDemoApply_dual】中增加testDemoRule规则表的运行语句:

#运行testDemoRule规则表对当前上下文进行合规性检查
self.restrict(db,ctx,'testDemoRule')
           

则sfDemoApply_dual现在应该是:

@myModule.request('sfDemo', 'demoApply', 'dual')
def sfDemoApply_dual(self, db, ctx,ca,active):
	self.currentAffair.Name=self.getInput('demoName')
	self.currentAffair.Type=self.getInput('demoType')
	self.currentAffair.Purpose='demo'
	self.currentAffair.State=0
	self.currentAffair.CreatorID=ctx.getCaller().id()

	#业务合规性检查
	self.restrict(db,ctx,'testDemoRule')
	
	#提示并关闭界面
	return True
           

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

然后执行一次热机刷新后,然后点击快捷栏中的【演示->发起申请】,然后在类型栏中填入demo,其它随便输。然后点击确认按钮。然后点击快捷栏中的【消息->我在做的事情】,打开刚刚指派给自己的流程,然后看看有什么变化。

大家可以看到类型后的输入框被标红了:

jxTMS使用示例--合规性保证

看完效果,我们回过头来对上面的testDemoRule规则表进行更详细的讲解。

规则表中的条件

我们对testDemoRule的定义是:

@myModule.rule('testDemoRule')
def testDemoRule():
	'''
	/* 如果申请人填写的类型是demo,则将其标红 */
	如果 a0:field.Type == 'demo' 则 outAttr.demoType.boder = '红' ;
	'''
	pass
           

可以看到,我们用myModule.rule修饰了一个空函数【只有一个pass】,然后rule的参数就是规则表的名字。而规则表中的规则是以文本的形式定义在该函数的__doc__中的。

用框起来的就是注释,在加载时会被过滤掉。

具体的规则以英文分号进行分隔,所以testDemoRule中就定义了一条规则。该规则的条件是:

a0:field.Type == 'demo'
           

意为检测a0数据对象的Type属性是否等于demo。即比较符的左侧是一个取值,右侧则是一个表达式【这里是字符串表达式demo】。取值语句的格式是:

[对象序号:]类型.[用途.]名称
           

注:方括号括起来的意味着某些情况下可以省略

对象序名指的是在运行规则表是提供的一组数据对象,jxTMS会将其按提供的顺序,依次命名为a0、a1、a2…,如果没有提供数据对象,则默认送入self.currentAffair【环境不同,可能self.currentAffair是空,所以一定要搞清楚规则表使用时的上下文】。即规则表调用时的完整的函数签名应该是:

public final Boolean restrict(IDBop db,context ctx,String ruleTableName,orm... ormObjArray) throws Exception;
           

即调用时,可以送入所有需要检测的数据对象,jxTMS会将其按顺序命名为a0、a1、a2…。我们上面调用时,没有送任何数据对象,所以jxTMS会默认将self.currentAffair送入,而self.currentAffair恰恰就是申请人在申请时所创建的流程数据对象。所以【a0:field.Type】就是说要读取的是self.currentAffair中的相关数据。

取值时的类型包括:

  • field:如:a1:field:Name,从数据对象的属性中读取
  • ctx:如:ctx:something,从上下文中读取something的值
  • input:如:input:something,从运行规则表的功能模块的输入中读取something的值
  • cache:如:cache:something,从规则表运行期间的临时存储区读取something的值

注:临时存储区主要用于在两条规则之间传递数据

还有很多其它类型,我们还是等用到时讲解。因此【a0:field.Type】的意思就是:读取self.currentAffair的Type属性的值。

规则表中的动作

上述规则的动作是:

outAttr.demoType.boder = '红'
           

效果大家看到了,就是名为demoType的输入框被标红了。所以大家也能猜到这条语句的意思就是设置demoType的边为红色。赋值语句的格式是:

[对象序号:]类型.[用途.]名称 = 表达式
           

对象序号上面讲解过,赋值时的类型包括:

  • field:如:a1:field:Name = ‘myName’,设数据对象的属性,相应的修改会自动提交到数据库中
  • ctx:如:ctx:something = ‘what’,写上下文中的变量
  • cache:如:cache:something = ‘what’,写入规则表运行期间的临时存储区

注:临时存储区主要用于在两条规则之间传递数据

  • out:如:out:demoName = ‘myName’,输出到运行规则表的功能模块的输出中,即相当于执行了self.setOutput(‘demoName’,‘myName’)
  • outAttr:如:outAttr:demoName.boder = ‘橙’,输出到运行规则表的功能模块当前打开的web界面的某数据控件【绑定了数据名】的控件属性。目前的属性只有boder

注:我们上面的演示表明:业务规则中设置的框红,还会被记录到流程对象中,让之后的操作人员都可以看到

  • result:如:result:type = ‘what’,输出到一个json对象中,然后可以获取这个json对象来使用
  • error:如:error:类型 = ‘类型只能是demo’,立刻掷出一个异常,其异常消息为:【类型错误:类型只能是demo】

注:异常会导致规则表的执行被打断,同时调用restrict函数的事件响应函数也被打断,而最重要的是,相应的数据库事务会被回滚,但由于相应的功能模块并未被丢弃,所以由于执行了该事件响应函数而导致的上下文变化得以保留。典型的,申请界面点击了确认按钮后,当异常掷出时,self.currentAffair已经被创建完毕了,但由于数据库事务的回滚,就会导致该对象实际上并未被保存到数据库中,这就出现了状态的不一致

  • local:如:local.result = false,对规则表本身进行控制。规则表运行后会返回一个检测结果,默认是true;但可以用local.result = false使得检测结果为假。local.stop = true,使得本规则执行完毕后停止表中其它规则的继续检测。local.disp = ‘一个说明字符串’,会将这个说明字符串放入整个规则表运行解释串中

注:上述的error掷出异常可能会导致状态的不一致,而local.result = false,则会使得restrict函数的返回值为false,这样一来,我们就可以在后续的代码中,手动消除事件响应函数所造成的不一致【如丢弃已创建但未保存到数据库中的数据对象:self.currentAffair = None】。即,error的处理非常简单,不需要额外做什么,但可能会导致状态的不一致;local.result需要手动来清理现场,比较麻烦,但胜在可控

还有很多其它类型,我们还是等用到时讲解。

因此【outAttr.demoType.boder = ‘红’】的意思,就是将demoType的边框设为红色,而boder的取值可以是:红、橙、绿、蓝。

规则表预检测

大家有没有发现一个问题:审核等节点可以看到testDemoRule的检测结果,但申请人是看不到的。只有被打回或自己查询时才能看到合规性检查的结果。如果能让申请人也提前看到,就可以当时修改或做针对性的说明,避免了被审核打回后再次修改提交的浪费。

我们针对这个问题,制作一个预检测的功能。首先在web文件中增加:

/* 规则表运行结果 */;
web rtResult type div;
web rtResultt1 parent rtResult type table title="规则表运行结果";
with rtResultt1 row 0 col c0 web n type text text='运行结果:',width=100;
with rtResultt1 row 0 col c1 web n bind rtResult type text width=500;
with rtResultt1 row 1 col c0 web n type text text='执行说明:',width=100;
with rtResultt1 row 1 col c1 web n bind execDisp type textarea width=500;
with rtResultt1 row 2 col c0 web n type text text='结果输出:',width=100;
with rtResultt1 row 2 col c1 web n bind resultList type codeEditor width=500,height=300;
           

即增加一个规则表运行结果的显示界面,大家想象一下这个界面的构成情况。注意一下界面的总宽度,看看和之前定义的界面有什么不同?

然后在op.py文件中增加:

@biz.Motion('demo.demo1','disp','rtResult')
@biz.OPDescr
def op1(json):
	json.setA().text('用规则表检测'.decode('utf-8')).dispInDialog()

@biz.OPDescr
def op1(json):
	json.setBtnList('demo.demo1.disp.sfDemo', 'demo.demo1.disp.rtResult')
           

即新增了一个【用规则表检测】的工具条,并将其链接到sfDemo流程的作业与查看界面。注意,该入口有一个新的特性:dispInDialog。如其名字所示,应该是显示到对话框中。一会大家看下显示效果。

然后在capa.py文件中增加:

@myModule.rule('demoRule')
def demoRule():
	'''
	/* 如果申请人填写的类型是demo,则将其标红 */
	如果 input.demoType != 'demo' 则 outAttr.demoType.boder = '红',local.disp = '类型不是demo' outAttr.demoType.boder = '绿',local.disp = '类型是demo',result.type = 'demo'  ;
	如果 input.demoName == 'test' 则 outAttr.demoName.boder = '红',local.disp = '名字是test',result.name = 'test' 否则 outAttr.demoName.boder = '绿',local.disp = '名字不是test'  ;
	'''
	pass

@myModule.event('prepareDisp', 'rtResult')
def rtResult(self, db, ctx):
	rs = self.restrictResult(db,ctx,True,'demoRule')
	self.setOutput('rtResult',rs.get('result'))
	self.setOutput('execDisp',rs.get('disp'))
	js = rs.getSubObject('resultList')
	if not js is None:
		self.setOutput('resultList',B64.encode(js.KVPair('\n')))
           

我们新定义了一个新的demoRule规则,然后在rtResult显示时,调用restrictResult函数来执行该规则表,然后将执行结果依次显示到rtResult中。

我们还是先看效果,然后再来讲解。将web、op.py、capa.py等文件按用sftp管理jxTMS的代码所述更新到/home/tms/codeDefine/demo/demo/demo1目录中。

然后执行一次热机刷新后,然后点击快捷栏中的【演示->发起申请】,然后在类型栏中填入demo,在名称栏中填入test,然后点击工具条中的【用规则表检测】,看看发生了什么:

jxTMS使用示例--合规性保证

咦?为什么是一绿一红呢?!大家仔细看一下demoRule规则表,注意两个规则的条件中,一个是不等,一个是等于哦。然后大家在两个输入项后面分别再多输入一个2,然后再次点击工具条中的【用规则表检测】,看看发生了什么?是不是红绿发生了反转。

同时大家再看一下执行说明,然后仔细体会一下,应该就会理解规则表的执行情况了。我们再说明一下:

  • testDemoRule表中的条件中,取值是通过【a0:field.Type】,而demoRule中则是【input.demoType】,这是为什么呢?因为两个表的执行位置不一样:testDemoRule表是在申请人提交时执行的,这个时候self.currentAffair已经被创建了,而demoRule则是申请人还没提交前执行的,这时候self.currentAffair还没有被创建,所以就改用input从输入中取值来检测
  • estDemoRule表我们是用restrict函数来执行,而demoRule表我们则是用restrictResult函数执行的,这两者的区别就是返回值不同,restrictResult会返回一个json对象,其中包含了更丰富的执行结果信息,而restrict函数只返回规则表的检测结果用于业务逻辑的流转
  • demoRule表中的动作都是多个,用英文分号分隔,当条件符合时,都会被执行
  • demoRule表中的规则还都定义了else分支【否则】,在条件不成立时,该分支会得到执行
  • demoRule表中的动作还对local.disp做了赋值,而这些说明文字作为每条规则执行的结果说明,和输入条件、检测结果组合后,统一放到了restrictResult得到的json中的disp字段