一套完整自定義工作流的實現
概述:
本工作流以一套金融軟件業務處理流程為例,實現功能包括:流程自定義、步驟自定義、步驟重復次數、步驟類型(順序/并行)、定義排序功能,完全使用數據庫實現,本文將詳細分析業務流程、系統設計及實現細節。
術語:
工作流(Workflow)[1],是對工作流程及其各操作步驟之間業務規則的抽象、概括、描述。工作流建模,即將工作流程中的工作如何前后組織在一起的邏輯和規則在計算機中以恰當的模型進行表示并對其實施計算。工作流要解決的主要問題是:為實現某個業務目標,在多個參與者之間,利用計算機,按某種預定規則自動傳遞文檔、信息或者任務。工作流管理系統(Workflow Management System, WfMS)的主要功能是通過計算機技術的支持去定義、執行和管理工作流,協調工作流執行過程中工作之間以及群體成員之間的信息交互。工作流需要依靠工作流管理系統來實現。
流程:工作流包含多個工作流程,處理時可任選一種流程進行處理,其包含步驟信息;
步驟:流程中每一環節的名稱,某一流程將包含多個步驟(其他工作流中也稱為節點)。
正文:
第一部分、業務邏輯分析
1、自定義工作流是指工作流各個環節及其參數完全自定義,常用于公文處理、業務流程簽批處理等。本系統來源于本人參與開發的一套金融管理軟件,業務處理人分不同角色擁有不同權限進行業務處理,將貸款數據庫從貸款調查一直到貸款簽批的完整流程。其中由于軟件功能要求,需要將貸款調查固定置為第一步驟,將貸款簽批置為最后一步驟。
其中數字表示當前步驟重復次數。
2、用戶業務處理部分包括:
1)、通過:當前步驟處理通過,(選擇下一處理人)進入當前流程中下一步驟,若為末步驟,則流程完成;
2)、退回:將步驟退回至上一步驟,即返回至上一處理人處,若為首步驟,則不可進行退回;
3)、否決:將步驟直接結束,不可再進行操作,或者回退至第一步驟;本系統中采用第二種方式;
4)、撤回:若當前步驟已處理,且在下一處理人未進行處理的情況下可進行撤回操作。
3、順序與并行
順序是指上一處理人指定某一處理人時,其他擁有此步驟權限的操作員不可進行查看和操作,必須當前處理人處理完畢后,流程才能繼續;并行是由上一處理人指定固定多個處理人時,由任一員工處理即可,不分前后順序,全部處理完成,進入下一步驟,此處理人數目由當前步驟重復次數確定。
兩者之間對應關系如下。
第二部分、系統設計
數據庫設計如下:
1)、流程信息表:S_flow_info(flow_id,flow_name)
2)、步驟信息表:S_action_info(action_id,action_name)
3)、流程-步驟信息表:S_step_info(step_id,action_id,flow_id,step_repeat_no,step_order_no,step_type)
(其中step_repeat_no為重復次數,step_order_no為排序號,step_type為類型:0為順序,1為并行)
4)、流程處理明細表:L_tranct_proc(proc_id,loan_id,step_id,step_action,step_emp_id)
(其中loan_id為數據主表主鍵,step_id關聯S_action_info,step_action
存儲處理結果:0--不通過,1--通過,2--退回,3--否決,4--撤回,step_emp_id為當前處理員工編號)
其中流程表、步驟表、流程步驟關系表為核心數據表,它們三者確定工作流的完全自定義。流程處理明細表為重要數據表,查詢數據主要通過此表進行連接查詢。
其他相關表包括:
1)、數據主表L_loan_info(loan_id,loan_name,flow_id,step_id,...)
(flow_id為流程編號,step_id關聯S_action_info)
2)、操作員工表E_emp_info(emp_id,emp_name,..)
3)、角色信息表E_role_info(role_id,role_name)
4)、員工-角色關系表E_emp_role(emp_role_id,emp_id,role_id)
(關聯角色表與員工表)
5)、步驟角色關系表S_action_role(action_role_id,action_id,role_id)
(關聯角色表與步驟表)
6)、下一處理人表L_loan_next_emp(loan_next_emp_id,loan_id,next_emp_id,step_id)
(其中next_emp_id關聯操作員工表E_emp_info,step_id關聯S_action_info)
其中數據流向如下:
1)業務最開始發生時,數據主表L_loan_info插入數據,其step_id為其所在流程的第一個步驟的編號,可根據下一步驟的重復次數來去將下一處理人的操作員編號插入到下一處理人表L_loan_next_emp中;插入流程處理明細表中數據,step_id為當前步驟編號;更新主表L_loan_info的step_id為下一步驟編號;
2)流程進入下一步驟;
3)下一處理人可查看當前待處理數據(以及本環節待處理數據),選定進行處理,將處理結果(0:不通過,1:通過,2:退回,3:否決)插入到流程處理明細表中,若為通過由更新主表L_loan_info的step_id為下一步驟編號,退回更新為上一步驟編號,否決則更新到第一個步驟編號;
4)在本人已處理數據中可查看已處理過的數據,若下一步驟中操作員還沒有進行操作,則可對數據操作進行撤回,撤回時將處理結果(4:撤回)插入到流程處理明細表中,其中的next_emp_id為本人操作員工編號);更新主表L_loan_info的step_id為流程的第一步驟的編號;
5)下一處理人繼續3)、4)的循環,直至流程的結束;
6)流程最后一個步驟,將處理結果中step_id值為0插入至流程處理明細中。
第三部分、實現細節
1、第一步驟中獲得數據列表代碼如下:
SELECT * FROM L_loan_info WHERE step_id=@step_id
其中@step_id使用代碼得到當前數據的第一步驟編號
2、第一步驟獲得已處理數據列表代碼:
SELECT * FROM L_loan_info WHERE loan_id IN (SELECT DISTINCT loan_id FROM l_tranct_proc WHERE step_id=@step_id)
3、獲得任一步驟數據列表(含未處理和已處理)
/// <summary> /// 方法:獲得審批(或簽批)數據列表 /// 開發:王洪劍http://www.cnblogs.com/walkingp http://www.51obj.cn/ /// 時間:2010-6-29 /// 最后修改時間:2010-6-29 /// 修改詳情: /// </summary> /// <param name="step_emp_id">簽批人(默認為當前操作員):0、本環節 其他、當前處理人</param> /// <param name="action_id">步驟</param> /// <param name="step_action">操作值:0:待審批/簽批 1:已審批/簽批 _:所有</param> /// <param name="version">版本:3.0</param> /// <returns></returns> public List<Hope.Model.L_loan_info> GetModelByProcess(int step_emp_id, int action_id, string step_action, int version) { #region string sql = "SELECT COUNT(*) FROM s_flow_info WHERE del_sign='1' and flow_id in(select flow_id from s_step_info where action_id=@action_id)"; SqlParameter[] para ={ new SqlParameter("@action_id", SqlDbType.Int, 4) }; para[0].Value = action_id; int count = int.Parse(DbHelperSQL.GetSingle(sql, para).ToString()); string[] arrFlowId = new string[count];//流程信息 sql = "SELECT flow_id FROM s_flow_info WHERE del_sign='1' and flow_id in(select flow_id from s_step_info where action_id=@action_id)"; DataTable dt = DbHelperSQL.Query(sql, para).Tables[0]; for (int i = 0; i < count; i++) { arrFlowId[i] = dt.Rows[i]["flow_id"].ToString(); } string[] pre_action_id = new string[count];//當前流程中上一流程id string[] next_action_id = new string[count];//當前流程中下一流程id for (int i = 0; i < count; i++) { sql = "SELECT TOP 1 action_id FROM s_step_info WHERE step_order_no<(SELECT order_no FROM S_action_info WHERE action_id=@action_id) AND flow_id=" + arrFlowId[i] + " ORDER BY s_step_info.step_order_no DESC"; SqlParameter[] paras ={ new SqlParameter("@action_id", SqlDbType.Int, 4) }; paras[0].Value = action_id; dt.Clear(); dt = DbHelperSQL.Query(sql, paras).Tables[0]; if (dt.Rows.Count > 0) pre_action_id[i] = dt.Rows[0][0].ToString(); sql = "SELECT TOP 1 action_id FROM s_step_info,l_loan_info WHERE step_order_no>(SELECT order_no FROM S_action_info WHERE action_id=@action_id) AND s_step_info.flow_id=" + arrFlowId[i] + " ORDER BY s_step_info.step_order_no"; SqlParameter[] paras_ ={ new SqlParameter("@action_id",SqlDbType.Int,4) }; paras_[0].Value = action_id; dt.Clear(); dt = DbHelperSQL.Query(sql, paras).Tables[0]; if (dt.Rows.Count > 0) next_action_id[i] = dt.Rows[0][0].ToString(); } DataSet ds = new DataSet(); for (int k = 0; k < count; k++) { StringBuilder sbTmp = new StringBuilder(); if (!string.IsNullOrEmpty(pre_action_id[k])) sbTmp.Append("(step_id=" + pre_action_id[k] + " AND step_action='1')"); if (!string.IsNullOrEmpty(pre_action_id[k]) && !string.IsNullOrEmpty(next_action_id[k])) sbTmp.Append(" OR "); if (!string.IsNullOrEmpty(next_action_id[k])) sbTmp.Append("(step_id=" + next_action_id[k] + " AND step_action='2')"); string strTemp = "1=1"; if (!string.IsNullOrEmpty(sbTmp.ToString())) strTemp += " AND " + sbTmp.ToString(); sql = "SELECT * FROM l_loan_info WHERE "; sql += "1=1"; if (step_action == "1")//已審批(簽批) { sql += " AND loan_id IN (SELECT loan_id FROM l_tranct_proc WHERE step_id=@action_id AND step_action='1'"; if (step_emp_id == 0) sql += ")"; else sql += " AND step_emp_id=@step_emp_id)"; } else if (step_action == "0")//待審批(簽批) { sql += " AND step_id=@action_id AND loan_id IN (SELECT loan_id FROM l_tranct_proc WHERE " + strTemp + ")"; if (step_emp_id == 0) sql += ""; else sql += " AND loan_id IN (select loan_id from l_loan_next_emp where next_emp_id=@step_emp_id)"; } else if (step_action == "")//所有 { sql += " AND loan_id IN (SELECT loan_id FROM l_tranct_proc WHERE step_id=@action_id AND step_action='1'"; if (step_emp_id == 0) sql += ")"; else sql += " AND step_emp_id=@step_emp_id)";//已審批(簽批) sql += " UNION "; sql += " AND step_id=@action_id AND loan_id IN (SELECT loan_id FROM l_tranct_proc WHERE " + strTemp + ")"; if (step_emp_id == 0) sql += ""; else sql += " AND loan_id IN (select loan_id from l_loan_next_emp where next_emp_id=@step_emp_id)";//待審批(簽批) } sql += " ORDER BY loan_id DESC"; SqlParameter[] parameters ={ new SqlParameter("@step_emp_id",SqlDbType.Int,4), new SqlParameter("@action_id",SqlDbType.Int,4)}; parameters[0].Value = step_emp_id; parameters[1].Value = action_id; if (ds.Tables.Count == 0) ds = DbHelperSQL.Query(sql, parameters); else { ds.Merge(DbHelperSQL.Query(sql, parameters), true, MissingSchemaAction.AddWithKey); } } dt = ds.Tables[0]; /*去除重復*/ DataView dv = new DataView(dt); string[] strCol ={ "loan_id" }; dt = dv.ToTable(true, strCol); Hope.Model.L_loan_info model; List<Hope.Model.L_loan_info> modelList = new List<Hope.Model.L_loan_info>(); if (dt.Rows.Count > 0) { #region for (int i = 0; i < dt.Rows.Count; i++) { model = new Hope.Model.L_loan_info(); model = GetModel(int.Parse(dt.Rows[i]["loan_id"].ToString())); modelList.Add(model); } #endregion return modelList; } else { return null; } }
以上為核心代碼,當然由于工作流的具體需求不同,可調整相應代碼。
第四部分、運行結果
1、系統參數配置
2、待處理列表
3、處理頁面
(注:當前為最后一處理,下一處理人選擇不可見)
4、撤回頁面
結語:
完整自定義工作流由于應用廣泛且業務邏輯復雜,要實現真正意義上通用的工作流還需要去做更多的分析和研究。
另本文不提供代碼及其他資料下載,請勿留言索取。
拋磚引玉,歡迎拍磚!
參考文檔:
[1]:維基百科:工作流技術 http://zh.wikipedia.org/zh-cn/工作流