使用本示例需通過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,其它随便輸。然後點選确認按鈕。然後點選快捷欄中的【消息->我在做的事情】,打開剛剛指派給自己的流程,然後看看有什麼變化。
大家可以看到類型後的輸入框被标紅了:

看完效果,我們回過頭來對上面的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,然後點選工具條中的【用規則表檢測】,看看發生了什麼:
咦?為什麼是一綠一紅呢?!大家仔細看一下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字段