1. 程式人生 > >金蝶EAS BOS工作流開發(附帶JAVA指令碼)

金蝶EAS BOS工作流開發(附帶JAVA指令碼)

1.  流程配置基本知識及示例

1.1. 重要概念

1.1.1.流程變數

流程變數是工作流引擎和業務系統的資料互動的橋樑。

工作流承載業務,驅動業務流程,但是不會執行業務。工作流中的業務執行,全部都會委託給具體的業務模組執行。那麼,這些被工作流分割的業務功能,在工作流中被呼叫執行的時候,如何保證做操作的資料的一致性?

通過流程變數,在每一步的業務功能執行的過程中,將業務資料儲存在流程變數中,那麼整個流程的後續活動中,都可以引用該流程變數,來完成業務功能,保證業務資料的一致性。

例如:在憑證的審批流程中,新增憑證審批,審批的時候為什麼可以正確定為到剛剛提交的那張憑證,而不是別的憑證?流程變數起到了重要的作用。在憑證新增之後,將可唯一標示憑證的ID儲存到流程變數中,在審批的時候將流程變數中的值傳遞給憑證,就可以根據這個ID獲取到一張憑證了。

流程變數在流程例項的生命週期內都是有效的。

1.1.2.任務輸入輸出

         任務輸入:在業務發生之前,有工作流傳遞給業務的資料。以憑證的過賬為例,某個使用者收到一條憑證過賬的訊息,雙擊訊息處理。在雙擊之後、憑證的介面彈出之前,工作流會把定義的入口引數變數中的值傳遞給憑證,憑證根據這個值來展現特定的單據,展現特定的功能。

         任務輸出:在業務發生之後,由業務系統傳遞給工作流,需要儲存在流程變數中的資料。仍以憑證過賬為例,在使用者操作完憑證過賬之後,工作流繼續流轉之前,如果流程定義中定義了任務的輸出,那麼,就會將憑證對應的屬性的資料,儲存在流程變數中。已備在後續的流程活動中使用。

1.2. 注意事項

l   繪畫工作流圖之前一定要先將業務流整理清楚,分析業務流的特性,提取可以抽象出來公用的東西,分析是否可以進行優化等,好的業務流程可以直接對映為工作流流程。

l   利用“流程變數”的威力,建立單據和流程之間資料交換的橋樑。輸入輸出引數用來在流程和單據之間進行資料的傳遞。輸入輸出引數和流程變數搭建了流程和單據之間的資料聯絡通道。

l   流程變數賦值時需注意:變數是否在另外地方被改變,有子流程時變數關係如何匹配,對應的是否正確,每個節點對應的變數是否正確,不同的節點可能對應不同的單據id,給變數賦值時需特別細心。

l   如存在一些系統預定義功能無法滿足的需求,可以採用自己開發功能,根據輸入引數和輸出引數來與工作流互動,如在單據中增加function,繫結到自動節點執行,或者是利用指令碼節點,獲取一些有用的資訊輸出到流程變數中在工作流中使用。還可以利用工作流的一些新增功能,比如利用函式節點,BOTP節點等來執行特殊需求。

l   參與人動態變化或根據條件變化時,可以充分利用“參與人變數”作為動態值,變數的值可以通過各種方式獲取,比如指令碼式(後置指令碼或指令碼活動)、或者任務輸出屬性方式等。(流程變數中可以定義型別為參與人的變數)

l   利用條件參與人設定一些動態的參與人場景。比如當某條件滿足時,設定為某些參與人,當條件不滿足或為另外情況時,設定為另外的參與人,通過條件參與人和參與人變數可以滿足大部分複雜的參與人場景。條件參與人的參與人範圍還可以做為執行期指定下一步活動的參與人範圍。

l   善於利用路由節點,除了可設定模式外,還可以對流程圖進行美化。

l   一些公用的業務邏輯可以單獨抽取出來配置為子流程給其他流程共用,減少維護的流程數量,比如一些常用的審批流程等。

1.3. 基本流程的配置示例

1.3.1.單流程

說明:流程只有一個人工型活動,完成憑證提交的任務,流程結束。沒有具體的業務含義。

以憑證提交的業務為例。流程圖如下:


定義步驟:

1、 拖入開始、結束活動

2、 拖入人工型活動,用連線弧連線起來。

3、 定義人工型活動

首先,定義人工型活動的任務,選擇任務

 

選定任務後,定製任務的輸入輸出。

         輸入引數是由任務定義帶出的

        

根據任務輸入的意義,指的是在憑證提交之前,由工作流告知憑證的資料。新建一個流程變數,繫結該輸入。

 

這裡繫結的意思是:在提交之前,工作流會將billID這個流程變數中的資料傳遞給業務。業務拿到這個值之後,會根據業務需要做出判斷。

         [說明]所有人工型任務的輸入引數,全部是在定義任務的時候就定義好的。每個任務的輸出引數可能不同,是由於各個不同的業務系統對於業務開始之前,所需要的資料不同導致。但是在EAS系統中,基本上任務的輸入引數只有一個BOID型別的引數。這是因為一般來說,通過這樣一個型別的值,就可以完全定位一個業務單據,並且拿到這個業務單據,就可以滿足大部分的業務需求了。

         這裡,將ID屬性輸出,並且選定輸出的流程變數是billID

        

憑證提交完畢後,將可以唯一標示一張憑證的ID屬性儲存在流程變數billID中,在後續的活動中,如果還需要操作這張憑證,就可以通過billID來唯一定位這張憑證,保證業務的一致性。

         參與人定義中,分為了預設參與人和條件參與人。工作流在獲取執行人的時候,首先根據條件來逐個掃描條件參與人,發現沒有符合,那麼會取預設參與人。

         這裡簡單處理,選擇任意人。

 

         提交就可以匹配到該流程

         到現在為止,這條簡單的流程就已經定義完畢。釋出。到EAS中提交憑證,然後到工作流監控中,會發現有一條流程例項,並且狀態是已完成。

1.3.2.審批流程

說明:單據提交之後,經過一層審批,流程結束。

以憑證為示例。流程圖如下

 

定義步驟:

1、  同場景1.3.1拖入活動

2、  增加一個審批活動,如圖畫連線弧

3、  配置提交活動。和場景1.3.1中一樣選擇任務,參與人也是任意人。但是任務的輸出奪一項。由於在後續的訊息中想展現出單據的編碼,所以多輸出一個單據編碼到一個流程變數number中

 

4、  配置審批活動。為了方便測試,參與人設定為流程發起人的本人

 


這一部分,就是任務輸入。對於現在的場景,審批憑證,那麼在業務單據內碼這一欄選中billID。此時billID已經在提交之後,儲存了剛剛提交的憑證的ID。一旦這個審批任務發生執行,那麼,在執行前,工作流會將billID這個變數中儲存的值傳給業務系統。那麼審批時就可以唯一定位到一條業務單據。

任務輸出,選擇將審批結果輸出到一個列舉型的流程變數 審批結果 中。

         定製審批訊息

 

流程定義完畢。釋出,在EAS中執行。

         提交憑證,在訊息中心收到一條訊息,審批,通過。然後回到憑證序時簿,察看該流程,發現,憑證的狀態還是“提交”而不是“稽核”。

                  這是因為工作流中的多極審批,只是單純的驅動流程,做一個選擇而已,不會修改業務資料。

         為了能夠讓憑證打上審批標記,按照如下方式修改流程定義

 

                  最後的這個自動活動,就完成給憑證打稽核標記的功能。

         任務選擇如下:

儲存、釋出,再到EAS中執行一下。發現審批狀態打上了。

1.3.3.帶分支的審批流程

說明:審批通過,則打審批狀態。審批不通過,返回修改。

仍以憑證為例。流程定義如下:

        

定義步驟

1、  提交、審批、自動節點的設定和場景2中一樣。

2、  增加一個人工型活動,修改。選擇的任務和“提交”一樣。但是由於單據的ID和單據的編碼是新增的時候就定好的,無法修改。所以只需要定義任務輸入就可以了,不需要定義任務輸出。

         誰提交的誰修改,參與人設定為流程發起人本人

         定製修改訊息

 

3、  編輯連線弧

首先編輯“審批”到“自動”的連線弧。

 

建模工具會自動根據之前的定義識別列舉,然後將列舉的值也會自動列在選擇範圍內。

然後編輯“審批”到“修改”的連線弧。按照如下方式設定條件

儲存。流程定製完畢。釋出。

         [說明]業務單據一旦進入工作流,就要受到工作流的約束。例如,剛剛提交完憑證,流程執行到審批節點。這個時候工作流要求的行為是“某個人執行審批操作”。如果這個時候修改憑證,會提示:“已在工作流處理中,任務不匹配”。

1.4. 常見的流程配置需求

1.4.1.配置參與人


1.4.2.配置多級審批

管理員收到申請單後,傳給多個領導。由各領導分別進行審批,審批不通過的要能夠來回進行審批。直到審批通過,也即是多級審批,每級審批不通過回到相應的審批節點繼續進行審批。

流程配置

面對此種問題首先想到的應該是通過流程變數來設定審批節點的標誌,在每級審批節點完成後設定一個標誌,標誌目前走到那個審批級別,如果不通過,根據流程變數上審批標誌的值直接回到此審批節點。具體配置請檢視下面截圖:


完整流程圖


綜合處審批節點後置指令碼

總結:充分利用流程變數作為流程流轉的條件

 


1.4.3.函式節點

需要重複使用某些指令碼,希望能減低指令碼維護的複雜度,同時提高指令碼的複用率和使用效率。

解決辦法

已經內建了函式節點的支援,函式節點本質上和指令碼節點沒有任何區別,所處理的任務和職責都是一樣,但指令碼節點指令碼散落在流程中各個地方,維護和使用非常困難。函式節點的目的就是為了減低流程配置中指令碼維護的複雜度和可重用性低的問題。具體使用請參考如下的說明:

在工具選單 視窗->顯示檢視,查詢選擇工作流檢視組->函式定義

 

點選確定後會出現函式定義的介面,其中已經內建了一些函式,雙擊選擇的某個函式可以開啟函式編輯介面

 

函式編輯介面,在此可以定義函式的輸入輸出以及一些描述性的說明,並可在編輯框內輸入具體的程式碼(目前支援KScript和java程式碼),程式碼其實就是指令碼。

 

然後定義工作流,在流程定義中增加一個函式節點。

 

在函式任務介面選擇函式定義,會彈出函式定義選擇介面,選擇合適的函式即可。

 

在函式任務介面配置好函式的輸入輸出引數即可在流程中使用此函式的輸出值。

如需自己定義函式,直接在函式檢視中按照右鍵選單提示進行操作即可。

 

1.4.4.用流程變數配置條件參予人

員工提交單據後,進行第一級審批,第一級審批參與人設定條件:如果是提交單據的是財務部員工,則參與人為財務部某一職員進行審批,如果不是財務部提交的單據,則提交人所在部門(歸口部門)的直接上級進行審批,如果直接上級是部門負責人則上級審批完成流程就繼續往下走,如果直接上級不是部門的負責人則還需要部門的負責人進行審批。

配置步驟:

首先畫出流程草圖,將大致的圖形先畫出來。

在提交單據節點輸出提交人的部門到流程變數“歸口部門”,並在後置指令碼中根據歸口部門的值設定流程變數“是否財務部”的值。

在審批節點設定條件參與人,根據流程變數 “是否財務部”來設定相關的條件參與人,並將審批人輸出到流程變數“審批人”中。


在審批節點後設置一指令碼節點->負責人判斷,用來判斷上級審批的人員是否是部門負責人。如果是則流程結束。如果不是則繼續在部門內多級審批,直到部門負責人審批完成(迴圈過程)。負責人判斷的指令碼內容為:

是否部門負責人=

com.kingdee.bos.workflow.participant.ParticipantHelper.isOrgPrincipal(

__bosContext,審批人,歸口部門);

其中,是否為部門負責人,審批人,歸口部門為流程變數。

 

最終流程如下:

總結:充分利用了條件參與人與流程變數,並通過指令碼來獲取流程必須的資訊。

2.  工作流指令碼

2.1.JAVA指令碼

在工作流中經常會需要使用到一些指令碼來獲取單據的資訊或者是執行一些特殊的操作,指令碼應如何寫?需要注意那些地方?有沒有更好的定義和使用方法?

解決方法

工作流指令碼利用的是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 < n; 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.     宣告變數
var_principal  外部資料型別
var_orgUnitId  內碼(BOID)

2.  在做業務單據時(稽核節點之前)將單據上的“費用的承擔部門”的ID關聯到var_orgUnitId變數中 ,並在該節點的後繼指令碼中填入如下內容:

com.kingdee.eas.basedata.org.AdminOrgUnitInfo adminInfo = com.kingdee.eas.basedata.org.AdminOrgUnitFactory.getRemoteInstance().getAdminOrgUnit(var_orgUnitId);
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<pColl.size(); i++) {
com.kingdee.eas.basedata.person.PersonInfo pi = pColl.get(i);
var_principal[i] = pi.getId().toString();
   }
}

3.   在稽核節點中定義參與人,選擇為參與人變數,並把var_princial新增進去。

l  根據分錄資訊迴圈傳送訊息給業務員

收集:指令碼一

    java.lang.String commonStr = data +"," + billNumber + "," + chauffeur + "," +regnumber + "," + contact + ".";
    java.lang.HashMap map = newjava.lang.HashMap();
    for (int i=0; i<records.size(); i++) {
        //請替換成出車單的分錄物件

        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<list.size(); 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);

    }

2.2.多UI相同實體需要觸發不同的流程,條件啟動流程

在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("一般員工");