1. 程式人生 > >吉特倉儲管系統(開源WMS)--Web線上報表以及列印模板分享

吉特倉儲管系統(開源WMS)--Web線上報表以及列印模板分享

  很早之前就想寫這篇文章與大家分享一下自己在吉特倉儲管理系統中開發列印和報表的功能,在GitHub(https://github.com/hechenqingyuan/gitwms)上公開下載的程式碼中很多人覺得線上設計報表這個功能比較不錯,但是很多人也會有疑問。這邊文章就簡單講解一下如何開發這個功能的,供大家學習參考,如果有任何疑問可以直接聯絡我,當然也有很多不足之處希望大家多多諒解和指點。

  一. 各種需求報表以及列印

    最開始之初在Web上做列印是每個列印也都會做一個頁面,利用的是瀏覽器自身帶的列印功能,當時做的也津津有味的感覺比較爽,但是後面做的列印頁面多了想死的心都有,特別是遇到了複雜的列印。後面就想著用一個列印元件試試,這樣開發列印可能方便很多,於是後面使用了lodop 列印元件(收費),這是一個非常不錯的列印元件,剛開始覺得這個元件也挺不錯的,後面用著也發現很多東西都有侷限性。於是後面專門弄了一個線上報表設計元件(FastReport),相信很多人都使用過這個元件,可以很方便的做線上報表以及列印功能。

    在吉特倉儲管理系統中涉及到列印的部分主要是如下部分:

    (1) 入庫單 (2) 出庫單[有些客戶喜歡用作送貨單] (3) 報損單 (4) 調撥單 (5) 銷售訂單 (6) 採購單 (7) 各種報表功能

    以單據為主的相關列印功能:

    

    另外一種是以表格為主的報表統計功能:

    

    還有一種以圖表為主的報表功能:

    

  二. 如何統一報表和單據的列印

    使用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 [email protected]

    假設我們定義報表的資料來源如上,當然也可以不適用引數的。以上是客戶實際案例中擇選的,更多的細節就不透露,反正可以說明問題。

    

    有幾個引數則新增幾個引數,不能多也不能少,必須唯一對應,否則在執行的過程中就無法得到正確的資料。基本資訊填好之後儲存則可以生成一個報表的資料行,接下來要走的就是設計模板了。

    

    

    線上設計器開啟之後就可以看到資料來源中包含的所有資料欄位了,這樣就可以設計我們想要的報表格式。至於如何設計報表這裡不做過的闡述,本文主要講解設計這邊功能的一個思路,報表工具的使用可以到網上查詢相關的資料學習。設計好報表之後儲存相關的設計即可,然後就可以開啟預覽查看了。

    

    開啟預覽頁面可以看到相應的引數輸入框,輸入引數點選搜尋即可展示報表的內容,完全根據自己的條件需要展示不同的資料。上面這個案例雖然有點簡單,但是能夠說明問題,在實際的客戶過程中肯定不只是顯示兩列資料的,可能有多個語句更加複雜的操作在裡面,但是都大同小異。

  五. 儲存過程的使用

    儲存過程相對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