很早以前就想写这篇文章与你们分享一下本身在吉特仓储管理系统中开发打印和报表的功能,在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