吉特仓储管系统(开源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 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);

默认处理数据源结构

     其实处理的方式都比较简单,只是在一定程度上做了技巧性的问题,当时这个问题也困扰了我很久,在设计的时候如果带有参数加载不出数据源的结构,又要达到共性所以才想了这样一个比较笨的办法。

 

  八. 总结

    吉特仓储管理系统中的打印达到目前这个程度是经过很多次的改版和总结达到的,之前每个打印都需要做一个新的页面,当时感觉挺好的,随着业务的复杂度增加已经远远不能停留在过去的人工一个个制作打印的阶段了。目前的打印仍然存在很多的问题,也还有很多的需求点不能满足,这个需要更近一步的去理解,抽象和总结。

    仓储系统中如果涉及到条码等要求快速打印的需求,那么目前的打印方式是肯定不能满足的,这一点可以参考之前写过的两篇文章:

    吉特仓库管理系统-.NET打印问题总结

           吉特仓库管理系统- 斑马打印机 ZPL语言的腐朽和神奇

    

    这里不是标榜自己这个做的有多好,但是自己能够做到这个程度也还挺高兴的,自认为是用心在做这个事情,而不是为了糊弄炫技术。在此过程中很重要的一点就是理解业务需求,找出共性,用最少的代码解决更多的问题。

    

    题外话:前些天博客园路过秋天的几篇文章反响很大,很是佩服他有这样的想法(非贬义),能够将想法付诸行动。不过他那个目标融资300W,其实挺希望自己能够利用这个软件做到300W,当然这里需要更的人一起来参与,2016吉特仓储算是开了一个头吧,希望接下来的一年做的更好,也希望更多的人能够给我指导意见。

作者:情缘

出处:http://www.cnblogs.com/qingyuan/

关于作者:从事仓库,生产软件方面的开发,在项目管理以及企业经营方面寻求发展之路
版权声明:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

联系方式: 个人QQ  821865130 ; 仓储技术QQ群 88718955,142050808 ;

吉特仓储管理系统 开源地址: https://github.com/hechenqingyuan/gitwms

 

时间: 2024-07-30 08:25:13

吉特仓储管系统(开源WMS)--Web在线报表以及打印模板分享的相关文章

吉特仓储管系统(开源WMS)--分享两月如何做到10W+的项目

  在此文开篇之处先特别申明,此文在有些人的眼中会有广告的嫌疑,但是本人不想将其作为一个广告宣传的文章,在此提到软件内容部分请大家予以谅解和包含,作为时间不算短的程序员给大家分享一些自己开发吉特仓储管理软件相关的经验和坑,当然还有一些自己从中获利的方式,不能说给大家指条明路吧,算是作为程序开发人的相互经验交流. 此文本来想写在国庆假期之前的,但是那段时间公司事情刚好很忙,所以没有来得及写此文.当时要搞Solr搜索引擎,因为自己不熟悉java程序所以在弄得过程中有些费力,而且自己本身也不是一心苦心

吉特仓储管系统(开源)--使用Grunt压缩JS文件

  在吉特仓储管理系统开发的过程中大量使用到了JS,随着JS文件的增多我们需要对JS进行有效的管理,同时也要对JS文件进行一些压缩.文本用于记录一下使用grunt压缩JS的操作步骤,便于遗忘之后记录查找,文章内容非常浅显.   一. 什么是grunt JavaScript世界的构建工具,官网上是这么描述的,姑且这么描述,个人感觉还比较好用.官网地址: http://www.gruntjs.net/  更多的参考资料也可以查询官网   二. 安装grunt Grunt和Grunt插件是通过npm安

[开源]吉特仓储管系统--2017年底应该写一些东西(一)

  又到2017年年底了,今年文章产出数量特别少,年底了觉得还是要写一些什么,毕竟为此目标奋斗了一年,为分享也好为纪念也好,终究是一年过去了,有辛酸,有收获也还要期待.2016年底,也就是2017年元旦上海出发前往山西,巍巍太行山,绵延八百里,大雪纷飞从山西太行山段四天时间徒步穿越到河南,虽说路线不是很难,一路上我就在想2017年我要干什么.   一. 2016年回顾 2016年一波三折的经历,小儿出生了,突然觉得自己要干些什么,其实不是觉得要干些什么,应该说是干什么能够赚钱,能够快速的赚钱.出

吉特仓库管理系统-ORM框架的使用

  最近在园子里面连续看到几篇关于ORM的文章,其中有两个印象比较深刻<<SqliteSugar>>,另外一篇文章是<<我的开发框架之ORM框架>>, 第一个做的ORM是相当的不错的,第二个也是相当的不错, 至少在表面上看起来是这么一回事.至于具体的用法和实践我没有深入的去测试过,所以也不便发表更多的意见,不过这种造轮子的精神我个人还是比较佩服的, 虽说有时候造轮子是闲的蛋疼的事情,但是如果你没有早过轮子你也体会不到造轮子给你带来的感官感受.目前比较受欢迎的

吉特仓库管理系统-.NET4.0环境安装不上问题解决

  在给客户实施软件的过程中要,要安装.NET 4.0 环境,而且是在XP的系统上. 目前的客户中仍然有大量使用XP的机器,而且极为不稳定,在安装吉特仓库管理系统客户端的时候出现了如下问题: 产品: Microsoft .NET Framework 4 Client Profile -- 错误 1402.无法打开键: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_ZONE_E

html-如何才能系统的学习web前端呢

问题描述 如何才能系统的学习web前端呢 我是一个培训出来的学生,学的是net但是现在想转做web前端,我对js,css,html都有了解.,但是不是精通.求大神给一个方法.谢谢了 解决方案 精通不精通无所谓,前端是一个熟能生巧的技艺,而没有高深的技术.一方面,多向你的同事请教,另一方面,要有一颗精益求精把活做到完美的心,多观察,多想想为什么. 解决方案二: 除了大量的动手实践,大概没有其他办法了.自己架个空间,自己维护空间特效,让它一点点变漂亮,把学到的每一个知识点都写成blog记录下来.一年

吉特仓库管理系统-.SQL Server 2012 升级企业版

  随着业务数据的不断增大,单表的数量已经上亿,查询的数据越来越慢,所以考虑到将数据库表分区,同时也将数据库升级到SQL Server 2012. 当时没有考虑更多,在服务器上安装了 SQL Server 2012 standard edition 版本.在测试数据的时候发现不支持分区,必须要企业版, 这里记录一下如何将 Standard edition 升级到企业版.   1. 找到原先的安装文件,运行Setup.exe  运行如上界面,点击维护,点击右侧版本升级.   2.  紧接着下一步

Win7系统中unity web player是什么程序?

  Win7系统中unity web player是什么程序?          如果你安装好Win7没有安装过软件出现该程序,可能系统不够纯净 建议使用:最新纯净版系统 unity web player是什么程序? 1.在win7的程序列表中并没有对该程序的详细说明,而我们使用一 些软件管理软件,如电脑管家的管理软件就可以看到,该程序是一个浏览器的扩展插件如下图: 2.其实是一款浏览器运行Unity3D游戏引擎发布的游戏的插件,和Flash Player很像,安全无毒 3.应该是你玩某款网页游

基于Web在线考试系统的设计与实现

这是一个课程设计的文档,源码及文档数据库我都修改过了,貌似这里复制过来的时候图片不能贴出,下载地址:http://download.csdn.net/detail/sdksdk0/9361973   数据库原理课程设计说明书              基于Web在线考试系统的设计与实现             目  录   1 课题背景与意义.3 1.1课题开发背景.3 1.2 课题开发意义.3 2 系统需求分析.4 2.1 项目要求.4 2.2 开发方案.5 2.3开发环境.5 3 总体开发.