很早以前就想寫這篇文章與你們分享一下本身在吉特倉儲管理系統中開發打印和報表的功能,在GitHub(https://github.com/hechenqingyuan/gitwms)上公開下載的代碼中不少人以爲在線設計報表這個功能比較不錯,可是不少人也會有疑問。這邊文章就簡單講解一下如何開發這個功能的,供你們學習參考,若是有任何疑問能夠直接聯繫我,固然也有不少不足之處但願你們多多諒解和指點。html
一. 各類需求報表以及打印git
最開始之初在Web上作打印是每一個打印也都會作一個頁面,利用的是瀏覽器自身帶的打印功能,當時作的也津津有味的感受比較爽,可是後面作的打印頁面多了想死的心都有,特別是遇到了複雜的打印。後面就想着用一個打印組件試試,這樣開發打印可能方便不少,因而後面使用了lodop 打印組件(收費),這是一個很是不錯的打印組件,剛開始以爲這個組件也挺不錯的,後面用着也發現不少東西都有侷限性。因而後面專門弄了一個在線報表設計組件(FastReport),相信不少人都使用過這個組件,能夠很方便的作在線報表以及打印功能。github
在吉特倉儲管理系統中涉及到打印的部分主要是以下部分:sql
(1) 入庫單 (2) 出庫單[有些客戶喜歡用做送貨單] (3) 報損單 (4) 調撥單 (5) 銷售訂單 (6) 採購單 (7) 各類報表功能json
以單據爲主的相關打印功能:瀏覽器
另一種是以表格爲主的報表統計功能:ide
還有一種以圖表爲主的報表功能:工具
二. 如何統一報表和單據的打印post
使用FastReport 能夠方便的作在線Web的打印,因此這裏打印功能就不是問題了,不管是作單據的打印仍是作報表的打印直接利用這個組件便可。在分析一下打印以及報表的過程:學習
(1) 獲取數據源:數據可能來自多方面的,好比入庫單,出庫單,以及本身定義的數據源
(2) 數據顯示到頁面中:不一樣單據顯示不同,這裏就須要不一樣的打印模板
(3) 打印頁面中內容:打印功能比較單一,打印顯示的內容便可
只要可以解決上面上個問題,那麼作一個公共的打印功能就比較方便了,提供一個公共格式的數據源,根據公共格式的數據源可以在線設計不一樣形式的模板,打印顯示的內容。
不一樣的單據其字段是不同的,因此在統計數據格式的時候可使用DataTable ,DataTable是比較方便的可以解決這種問題的,至於打印模板無非就是一個在線設計的問題,FastReport已經徹底解決了這種問題。最關鍵點就是提供數據源了, 在吉特倉儲管理系統中數據源都是使用的實體模型, 相信各位作開發的也可以理解在開發過程當中使用DataTable 帶來的不便,單是在這裏咱們要反其道而行將實體模型轉換爲DataTable 。
三. 分類處理
本文章只作幾個分類的處理,這個分類比較零散,這裏以其中一個客戶實際案例做爲講解。
在報表的分類中咱們定義了 入庫單,出庫單,盤點單,報表和施工單幾個分類, 都是定義的枚舉值。 咱們能夠根據具體的業務需求來修改這裏的值。固然這裏也制定了數據源提供的方式,主要是兩種數據源:SQL 語句以及存儲過程。其實這裏咱們有第三種數據源,那就是各類單據數據,由於系統中已經提供了各類單據數據訪問的接口,咱們沒有必要去再次寫SQL語句以及存儲過程,存儲過程和SQL語句只是給自定義報表來使用的,由於自定義報表不清楚系統是否已經提供了響應的接口。
FastReport利用的數據源其實也就是DataSet,這裏咱們能夠很方便的銜接起來。若是使用SQL語句或者存儲過程咱們就只須要將返回的結果集指定爲DataSet,而其餘的單據則只須要將List<T>集合轉換爲DataTable 便可。 這裏就解決了字段不肯定的問題,在技術上解決以下幾個問題:
(1) List<T> 轉換爲DataTable 的問題: 這種問題很是容易解決,有過.NET開發經驗的應該都知道
(2) SQL 語句中帶有參數的問題: 這裏規定SQL語句中能夠帶參數或者不帶參數,若是有參數那必須使用@佔位符
(3) 存儲過程當中帶有參數的問題:存儲過程一樣能夠帶參數或者不帶參數
(4) 調用SQL語句和存儲過程執行問題: 如何肯定調用的是SQL語句仍是存儲過程, 這裏在數據源格式上作了分類,上圖能夠看得出
四. SQL數據源
使用SQL數據源遵循以下幾個步驟:
(1) 編寫SQL語句,SQL語句中若是有參數則必須使用佔位符參數
(2) 添加佔位符參數信息,佔位符參數必須和SQL語句中的佔位符參數一致
SELECT * FROM [dbo].[ConDetail] WHERE BarCode=@BarCode
假設咱們定義報表的數據源如上,固然也能夠不適用參數的。以上是客戶實際案例中擇選的,更多的細節就不透露,反正能夠說明問題。
有幾個參數則添加幾個參數,不能多也不能少,必須惟一對應,不然在執行的過程當中就沒法獲得正確的數據。基本信息填好以後保存則能夠生成一個報表的數據行,接下來要走的就是設計模板了。
在線設計器打開以後就能夠看到數據源中包含的全部數據字段了,這樣就能夠設計咱們想要的報表格式。至於如何設計報表這裏不作過的闡述,本文主要講解設計這邊功能的一個思路,報表工具的使用能夠到網上查找相關的資料學習。設計好報表以後保存相關的設計便可,而後就能夠打開預覽查看了。
打開預覽頁面能夠看到相應的參數輸入框,輸入參數點擊搜索便可展現報表的內容,徹底根據本身的條件須要展現不一樣的數據。上面這個案例雖然有點簡單,可是可以說明問題,在實際的客戶過程當中確定不僅是顯示兩列數據的,可能有多個語句更加複雜的操做在裏面,可是都大同小異。
五. 存儲過程的使用
存儲過程相對SQL語句來講是同樣的,這裏惟一一個比較偏門的就是如何或者存儲過程的參數問題,咱們以入庫單審覈存儲過程爲例: Proc_AuditeInStorage
數據源類型選擇存儲過程,在數據源中輸入存儲過程的名稱,而後回車則會自動加載存儲過程當中的全部參數信息,而後本身將這些信息補充完整便可,好比顯示的名稱,頁面顯示的元素類型。
SELECT [SPECIFIC_CATALOG],[SPECIFIC_NAME],[ORDINAL_POSITION],[PARAMETER_MODE],[PARAMETER_NAME],[DATA_TYPE],[CHARACTER_MAXIMUM_LENGTH] FROM [INFORMATION_SCHEMA].[PARAMETERS] WHERE [SPECIFIC_NAME]=@SPECIFIC_NAME
上面這段SQL語句是獲取存儲過程的相關參數信息的,若是有相似的功能需求能夠參考利用一下這個SQL語句。SQL以及存儲過程的相關執行都是依賴於Git.Framework.ORM 這個組件,固然你也可使用其餘的方式來實現。
六. 單據打印的數據源
單據打印的數據源有點特殊,他不須要本身寫SQL或者存儲過程來提供數據源,固然你執意要這麼作也是沒問題的。
public override DataSet GetPrint(string argOrderNum) { DataSet ds = new DataSet(); InStorageEntity entity = new InStorageEntity(); entity.SnNum = argOrderNum; entity = GetOrder(entity); if (entity != null) { List<InStorageEntity> list = new List<InStorageEntity>(); list.Add(entity); DataTable tableOrder = list.ToDataTable(); ds.Tables.Add(tableOrder); InStorDetailEntity detail = new InStorDetailEntity(); detail.OrderSnNum = argOrderNum; List<InStorDetailEntity> listDetail = GetOrderDetail(detail); listDetail = listDetail.IsNull() ? new List<InStorDetailEntity>() : listDetail; DataTable tableDetail = listDetail.ToDataTable(); ds.Tables.Add(tableDetail); } else { List<InStorageEntity> list = new List<InStorageEntity>(); List<InStorDetailEntity> listDetail = new List<InStorDetailEntity>(); DataTable tableOrder = list.ToDataTable(); ds.Tables.Add(tableOrder); DataTable tableDetail = listDetail.ToDataTable(); ds.Tables.Add(tableDetail); } return ds; }
入庫單打印的數據都是List<T> ,咱們這裏須要將其轉換爲DataTable
public DataSet GetPrint(string SnNum) { DataSet ds = new DataSet(); ConBookEntity entity = GetBook(SnNum); if (entity != null) { List<ConBookEntity> listBook = new List<ConBookEntity>(); listBook.Add(entity); DataTable tableBook = listBook.ToDataTable(); ds.Tables.Add(tableBook); List<ConDetailEntity> listDetail = GetDetailList(SnNum); listDetail = listDetail.IsNull() ? new List<ConDetailEntity>() : listDetail; DataTable tableDetail = listDetail.ToDataTable(); ds.Tables.Add(tableDetail); List<BookMaterialEntity> listMaterial = GetMaterialList(SnNum); listMaterial = listMaterial.IsNull() ? new List<BookMaterialEntity>() : listMaterial; DataTable tableMaterial = listMaterial.ToDataTable(); ds.Tables.Add(tableMaterial); } else { List<ConBookEntity> listBook = new List<ConBookEntity>(); entity = new ConBookEntity(); listBook.Add(entity); DataTable tableBook = listBook.ToDataTable(); ds.Tables.Add(tableBook); List<ConDetailEntity> listDetail = null; listDetail = listDetail.IsNull() ? new List<ConDetailEntity>() : listDetail; DataTable tableDetail = listDetail.ToDataTable(); ds.Tables.Add(tableDetail); List<BookMaterialEntity> listMaterial = null; listMaterial = listMaterial.IsNull() ? new List<BookMaterialEntity>() : listMaterial; DataTable tableMaterial = listMaterial.ToDataTable(); ds.Tables.Add(tableMaterial); } return ds; }
下載過github上代碼看過的人,其實一看就明白這裏都是套路,套路。 全部的單據都是這個套路,同時也遵循這個套路。
在報表管理的頁面中新建施工單打印的模板,報表類型要選擇施工單,這裏不能隨意選必定要選擇正確,相關的數據源均可以不指定,或者隨意制定如下SQL或者存儲過程便可。
各類單據的打印咱們須要提供的參數就是單據的惟一編號,這裏是須要明確的,在吉特倉儲系統中惟一編號使用的是GUID,其實這樣能夠很方便的解決這個問題。
; (function ($) { $.fn.CusReportDialog = function (options) { var defaultOption = { title:"選擇打印模板", data: {}, Mult: false, EventName: "click", callBack: undefined, ReportType:undefined }; defaultOption = $.extend(defaultOption, options); var current=undefined; var target=$(this); var DataServer={ Server: function () { var config = (function () { var URL_GetList = "/Report/ManagerAjax/GetList"; return { URL_GetList: URL_GetList }; })(); //數據操做服務 var dataServer = (function ($, config) { //查詢分頁列表 var GetList=function(data,callback){ $.gitAjax({ url: config.URL_GetList, data: data, type: "post", dataType: "json", success: function (result) { if(callback!=undefined && typeof callback=="function"){ callback(result); } } }); } return { GetList: GetList } })($, config); return dataServer; }, SetTable:function(result){ current.find("#tabInfo").DataTable({ destroy: true, data:result.Result, paging:false, searching:false, scrollX: false, bAutoWidth:true, bInfo:false, ordering:false, columns: [ { data: 'SnNum' ,render:function(data, type, full, meta){ return "<input type='checkbox' name='item_report' value='"+data+"' data-full='"+JSON.stringify(full)+"'/>"; }}, { data: 'ReportNum'}, { data: 'ReportName'}, { data: 'Remark'} ], aoColumnDefs:[ { "sWidth": "15px", "aTargets": [0] } ], oLanguage:{ sEmptyTable:"沒有查詢到任何數據" } }); var pageInfo=result.PageInfo; if(pageInfo!=undefined){ current.find("#myMinPager").minpager({ pagenumber: pageInfo.PageIndex, recordCount: pageInfo.RowCount, pageSize: pageInfo.PageSize, buttonClickCallback: DataServer.PageClick }); } DataServer.BindEvent(); }, BindEvent:function(){ if(defaultOption.Mult){ current.find("#tabInfo").find("input[name='item_all']").click(function(event) { var flag=$(this).attr("checked"); if(flag){ current.find("#tabInfo").find("input[name='item_report']").attr("checked",true); }else{ current.find("#tabInfo").find("input[name='item_report']").attr("checked",false); } }); } else{ current.find("#tabInfo").find("input[name='item_all']").hide(); current.find("#tabInfo").find("input[name='item_report']").click(function(event) { current.find("#tabInfo").find("input[name='item_report']").attr('checked', false); $(this).attr("checked",true); }); } }, GetSelect:function(){ var list=[]; current.find("#tabInfo").find("input[name='item_report']").each(function(i,item){ var flag=$(item).attr("checked"); if(flag){ var value=$(item).attr("data-full"); var item=JSON.parse(value); list.push(item); } }); return list; } } var submit = function (v, h, f) { if (v == 1) { var list=DataServer.GetSelect(); if (defaultOption.callBack != undefined && typeof (defaultOption.callBack) == "function") { if(defaultOption.Mult){ defaultOption.callBack.call(target,list); }else{ defaultOption.callBack.call(target,list[0]); } } } }; $(this).bind(defaultOption.EventName, function () { var Server=DataServer.Server(); var search={}; search["ReportType"]=defaultOption.ReportType; Server.GetList(search,function(result){ var data=result.Result; if(data!=undefined && data.length>1){ $.jBox.open("get:/Report/Manager/Dialog", defaultOption.title, 650, 400, { buttons: { "選擇": 1, "關閉": 2 }, submit: submit, loaded: function (h) { current=h; DataServer.SetTable(result); } }); }else{ defaultOption.callBack.call(target,data[0]); } }); }); }; })(jQuery);
不一樣的單據再打印的時候能夠選擇不一樣的模板,這裏可使用這個插件來實現
$('#tabList').find('a.print').each(function(i,item){ $(item).CusReportDialog({ ReportType:1, callBack:function(result){ if(result!=undefined){ var SN=data[i].OrderSnNum; var SnNum=result.SnNum; var url="/Report/Manager/Show?SnNum="+SnNum+"&OrderNum="+SN; window.location.href=url; } } }); });
七. 模板設計過程當中如何加載結構
有個很顯示的問題,在模板設計的過程當中若是數據源爲空,那模板設計就失去了設計的基礎數據源結構,這樣是毫無心義的。而不少狀況下不少數據在後臺過程當中只有獲得了數據才能獲得結構,這是要命的問題。因此在這裏走必定技巧性的處理,若是查詢的數據源結果集爲空,那麼久默認添加一個空的數據進去,填充其結構,這裏須要特別注意。
List<InStorageEntity> list = new List<InStorageEntity>(); List<InStorDetailEntity> listDetail = new List<InStorDetailEntity>(); DataTable tableOrder = list.ToDataTable(); ds.Tables.Add(tableOrder); DataTable tableDetail = listDetail.ToDataTable(); ds.Tables.Add(tableDetail);
其實處理的方式都比較簡單,只是在必定程度上作了技巧性的問題,當時這個問題也困擾了我好久,在設計的時候若是帶有參數加載不出數據源的結構,又要達到共性因此纔想了這樣一個比較笨的辦法。
八. 總結
吉特倉儲管理系統中的打印達到目前這個程度是通過不少次的改版和總結達到的,以前每一個打印都須要作一個新的頁面,當時感受挺好的,隨着業務的複雜度增長已經遠遠不能停留在過去的人工一個個製做打印的階段了。目前的打印仍然存在不少的問題,也還有不少的需求點不能知足,這個須要更近一步的去理解,抽象和總結。
倉儲系統中若是涉及到條碼等要求快速打印的需求,那麼目前的打印方式是確定不能知足的,這一點能夠參考以前寫過的兩篇文章:
這裏不是標榜本身這個作的有多好,可是本身可以作到這個程度也還挺高興的,自認爲是用心在作這個事情,而不是爲了糊弄炫技術。在此過程當中很重要的一點就是理解業務需求,找出共性,用最少的代碼解決更多的問題。
題外話:前些天博客園路過秋天的幾篇文章反響很大,非常佩服他有這樣的想法(非貶義),可以將想法付諸行動。不過他那個目標融資300W,其實挺但願本身可以利用這個軟件作到300W,固然這裏須要更的人一塊兒來參與,2016吉特倉儲算是開了一個頭吧,但願接下來的一年作的更好,也但願更多的人可以給我指導意見。
做者:情緣
出處:http://www.cnblogs.com/qingyuan/
關於做者:從事倉庫,生產軟件方面的開發,在項目管理以及企業經營方面尋求發展之路
版權聲明:本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文連接。
聯繫方式: 我的QQ 821865130 ; 倉儲技術QQ羣 88718955,142050808 ;
吉特倉儲管理系統 開源地址: https://github.com/hechenqingyuan/gitwms