基于Lodop的报表打印模块
- 
前段时间写了个小模块,来解决OE中报表打印不方便的问题。 
 借鉴了 @buke 兄的 openerp-web-pdf-preview-print 模块的部分代码。
 介绍:
 Lodop是一款优秀的国产打印控件(activeX): [检测到链接无效,已移除]
 ActiveX只支持windows,所以本控件不适用linux,mac osx.
 模块使用mako标签,html的模版。
 我只贴代码,不加附件,这样各位会体会更深。
 模块结构:
 [attach=1]
 openerp.py<br />{<br />    "name": "Lodop控件报表",<br />    "category": "web",<br />    "description":<br />        """<br />        Lodop控件模块, 针对于报表。<br />        """,<br />    "version": "6.0.5.6",<br />    "depends": [],<br />    "js": ["static/lib/Lodop6.145/*.js", "static/js/*.js"],<br />    'active':True,<br />    'installable': True,<br />    'active': False,<br />    'application':False,<br />}<br /><br />
 服务端的controller(没啥一样的,继续借鉴@buke):<br /># -*- coding: utf-8 -*-<br /><br /><br />import openerp.addons.web.http as openerpweb<br />from openerp.addons.web.controllers.main import View<br /><br />import urllib2<br />import simplejson<br />import base64<br />import time<br />import zlib<br />import cPickle<br />import hashlib<br /><br /><br />class LodopReport(View):<br />    _cp_path = "/web/lodop/report"<br />    POLLING_DELAY = 0.25<br /><br />    @openerpweb.jsonrequest<br />    def index(self, req, action):<br />        action = simplejson.loads(action)<br />        report_srv = req.session.proxy("report")<br />        context = dict(req.context)<br />        context.update(action["context"])<br />        report_data = {}<br />        report_ids = context["active_ids"]<br />        if 'report_type' in action:<br />            report_data['report_type'] = action['report_type']<br />        if 'datas' in action:<br />            if 'ids' in action['datas']:<br />                report_ids = action['datas'].pop('ids')<br />            report_data.update(action['datas'])<br />        report_id = report_srv.report(<br />            req.session._db, req.session._uid, req.session._password,<br />            action["report_name"], report_ids,<br />            report_data, context)<br />        report_struct = None<br />        while True:<br />            report_struct = report_srv.report_get(<br />                req.session._db, req.session._uid, req.session._password, report_id)<br />            if report_struct["state"]:<br />                break<br />            time.sleep(self.POLLING_DELAY)<br /><br />        report = base64.b64decode(report_struct['result'])<br />        return dict(report = report)<br /><br />
 主要部分是js部分:<br /><br />openerp.fg_lodop = function(instance) {<br /><br />    instance.web.ActionManager = instance.web.ActionManager.extend({<br /><br />        init: function (parent, action) {<br />            this._super(parent);<br />            //activex的标签放在页面里。<br />            var obj_string = '<object style="width:0px;height:0px;" id="LODOP_OB" classid="clsid:2105C259-1E0C-4534-8141-A753534CB4CA" width=0 height=0><embed id="LODOP_EM" type="application/x-print-lodop" width=0 height=0 pluginspage="/fg_lodop/static/lib/lodop6.145/install_lodop32.exe"></embed></object>';<br />            $(obj_string).appendTo("body");<br />        },<br />        <br />        ir_actions_report_xml: function(action, options) {<br />            var self = this;<br />            instance.web.blockUI();<br />            return instance.web.pyeval.eval_domains_and_contexts({<br />                contexts: [action.context],<br />                domains: []<br />            }).then(function(res) {<br />                action = _.clone(action);<br />                action.context = res.context;<br />                var os = navigator.platform || "Unknown OS";<br />                linux = os.indexOf("Linux") > -1;<br />                mac = os.indexOf("Mac") > -1;<br /><br />                self.rpc("/web/lodop/report", {<br />                    action: JSON.stringify(action)<br />                }).done(function(result) {<br />                    if(result.error){<br />                        instance.web.unblockUI();<br />                        self.dialog_stop();<br />                        return;<br />                    }<br />                    instance.web.unblockUI();<br />                    self.dialog_stop();<br />                    if(linux || mac) { <br />                        //不支持linux, mac, 这点没考虑过。<br />                        report_window=window.open('','','width=600,height=500');<br />                        report_window.document.write(result.report);<br />                        report_window.focus();<br />                    }<br />                    else {<br />                        //do magic.<br />                        // 等会解释这个由来。<br />                        format_obj = action.attachment.split(',');<br /><br />                        LODOP=getLodop(document.getElementById('LODOP'),document.getElementById('LODOP_EM'));  <br />                        LODOP.SET_LICENSES("","xxxxxx","","");  //不设置授权码照样可以打印。<br />                        LODOP.PRINT_INIT("FG ERP Order");<br />                        LODOP.SET_PRINT_PAGESIZE(1, 2300, 1390, 'fg_lodop_print_job'); //公司用的各种单据的打印纸都是统一规格,所以写死了。<br />                        <br />                        var tables = $.parseHTML(result.report);<br />                        $.each( tables, function( i, el ) {<br />                            if(el.nodeName == "TABLE"){<br />                                LODOP.ADD_PRINT_TABLE(format_obj[0],format_obj[1],format_obj[2], format_obj[3], el.outerHTML);<br />                                LODOP.NEWPAGE();<br />                            }<br />                        });<br />                        LODOP.PREVIEW();<br />                    }<br />                });<br />            });<br />        },<br />    });<br /><br /><br />};<br /><br /><br /><br />
 安装后,本模块将会替代系统默认的报表动作。
 使用方法:<br /><br /><report auto="False" id="report_fg_sale_cust_order_html" model="fg_sale.cust.order"<br />                name="fg_sale.cust.order.html" rml="fg_sale/report/cust_order.html"<br />                string="定制单" report_type="mako2html" attachment="0mm,0mm,220mm,98mm"/><br /><br />
 因为需要确定打印的范围,所以借用了attachment这个属性----实在是不想修改系统的rng文件了。
 * 这就是刚才代码 “format_obj = action.attachment.split(',');” 这一行的原因。
 mako的html模版大概是这样的:<br /># -*- coding: utf-8 -*-<br />    % for o in objects:<br />        % if o.state == 'review':<br />                    <table border="0" cellspacing="2" cellpadding="2" bordercolor="#000000" style="font-size:14px;width:850px;"><br />                      <thead><br />                          <tr><br />                            <td colspan="8" align="center"><br />                              <span style="font-size:18px;font-weight:bold;">定制清单  </span> ${ o.name }</td><br />                          </tr><br />                          <tr><br />                              <td colspan="1"  height="18">客户名称: <br />                                ${ o.partner_id.name }<br />                              </td><br />                              <td colspan="3"  height="18">要求到货日期: ${ o.date_arrival_req or '' }</td><br />                              <td colspan="4" height="18"><br />                                  交货日期: ${ o.date_delivery or '' }<br />                              </td><br />                          </tr><br />                          % if o.contact or o.phone or o.delivery_addr:<br />                          <tr><br />                            <td colspan="1">联系人: ${ o.contact or '' }</td><br />                            <td colspan="3">联系电话: ${ o.phone or '' }</td><br />                            <td colspan="3">交货地址: ${ o.delivery_addr or '' }</td><br />                          </tr><br />                          % endif<br />                          <tr><br />                            <td colspan="1">已付金额: ${ o.amount_paid or '' }</td><br />                            <td colspan="3">付款方式: ${ o.amount_paid_method or '' }</td><br />                            <td colspan="3">发票: <br />                              % if o.invoice_type == 'common':<br />                              普通发票<br />                              % elif o.invoice_type == 'va':<br />                              增值发票<br />                              % else:<br />                              暂不开票<br />                              % endif<br />                            </td><br />                          </tr><br />                          <tr><br />                            <td colspan="1">定制版面:  ${ o.client }</td><br />                            <td colspan="3">运费承担方: ${ o.delivery_fee or '' }</td><br />                            <td colspan="3">送货方式: ${ o.delivery_method or '' }</td><br />                          </tr><br />                          <tr height="18"><br />                        <td style="BORDER-COLLAPSE: collapse; BORDER:groove 1px;" align="center">品名</td><br />                        <td style="BORDER-COLLAPSE: collapse; BORDER:groove 1px;" align="center">数量(只)</td><br />                            <td style="BORDER-COLLAPSE: collapse; BORDER:groove 1px;" align="center">开票价</td><br />                            <td style="BORDER-COLLAPSE: collapse; BORDER:groove 1px;" align="center">版费</td><br />                            <td style="BORDER-COLLAPSE: collapse; BORDER:groove 1px;" align="center">已发货</td><br />                            <td style="BORDER-COLLAPSE: collapse; BORDER:groove 1px;" align="center">小计</td><br />                            <td style="BORDER-COLLAPSE: collapse; BORDER:groove 1px;" align="center">附注</td><br />                          </tr><br />                      </thead><br />                      <tbody><br />                          % for line in o.order_line:<br />                          <tr><br />                              <td height="18" style="BORDER-COLLAPSE: collapse; BORDER:groove 1px;" >${ line.product_id.name }</td><br />                              <td width="10%" height="18" style="BORDER-COLLAPSE: collapse; BORDER:groove 1px;">${ line.product_uom_qty }</td><br />                              <td width="10%" height="18" style="BORDER-COLLAPSE: collapse; BORDER:groove 1px;" >${ line.unit_price }</td><br />                              <td width="10%" height="18" style="BORDER-COLLAPSE: collapse; BORDER:groove 1px;">${ line.cust_price }</td><br />                              <td width="10%" height="18" style="BORDER-COLLAPSE: collapse; BORDER:groove 1px;">${ line.delivered and '是' or '否' }</td><br />                              <td width="10%" height="18" style="BORDER-COLLAPSE: collapse; BORDER:groove 1px;">${ line.subtotal_amount }</td><br />                              <td width="20%" height="18" style="BORDER-COLLAPSE: collapse; BORDER:groove 1px;">${ line.note or '' }</td><br />                            </tr><br />                          % endfor<br />                      </tbody><br />                      <tfoot><br />                          <tr><br />                            <td colspan="1" style="BORDER-COLLAPSE: collapse; BORDER:groove 1px;text-align:right;" tdata="allSum" format="#,##0.00" tindex="6"><br />                                共计: #<br />                            </td><br />                            <td colspan="4" style="BORDER-COLLAPSE: collapse; BORDER:groove 1px;" tdata="allSum" format="UpperMoney" tindex="6"><br />                                #<br />                            </td><br />                            <td colspan="2" style="BORDER-COLLAPSE: collapse; BORDER:groove 1px;" tdata="subSum" format="#,##0.00"><br />                                本页小计: #<br />                            </td><br />                          </tr><br />                          <tr><br />                              <td colspan="2"><br />                                开单人: ${ o['user_id']['name'] } &nbsp;&nbsp;<br />                                开单日期:${ o.date_order } <br />                              </td><br />                              <td colspan="4"><br />                                业务部确认: ${ o['confirmer_id']['name'] }&nbsp;&nbsp;<br />                                业务经办人:${ o.employee_id.name }<br />                              </td><br />                              <td colspan="1" style="font-size:14px;height:18px;text-align:right;">第<span tdata="pageNO" format="#">#</span>页-共<span tdata="pageCount" format="#">#</span>页</td><br />                          </tr><br />                      </tfoot><br />                    </table><br />            % endif<br />      % endfor<br />
 注意:
 1. 模版只包含table标签,支持多table(多单打印)。
 2. lodop的使用方法请参看其文档。
 大功告成。
 献丑了。如需改进,有问题请 @杨振宇_
- 
[quote author=d_yang link=topic=7397.msg16907#msg16907 date=1373119368] 
 [quote author=Joshua link=topic=7397.msg16902#msg16902 date=1373109235]
 mako模板能重复表头么?
 [/quote]
 lodop里,addprinttable方法可以把table里面 <theader>标签转为你说的,表头,tfoot标签转换为页脚。
 tbody里,就是明细部分了,自动根据页面高度分页。
 另外lodop还支持一些标签,比如,总页数,当前页数,数字大写转换,统计,等。
 [/quote]
 好东西。谢谢@d_yang分享。
- 
mark,马上研究报表开发了 
- 
首先,非常感谢 LZ 的 分享大作。 刚好在弄打印这块,而且还真的是要用lodop来做这个。 所以受益匪浅。 
 然后,这里有个问题,想跟LZ请教下,
 在使用以下我的sample.mako模板代码时,发现maktohtml2html.py文件中的 方法 format_body中,有一个问题。
 body[:-1]得到是一个空的list, 因为 body = html.findall("body"), 对于一个Html文件来说, <body>标签只有1个。 所以这个我觉得是个问题,不知道LZ是什么解决的? 我一个小打算,是直接提bug,修改openerp的源码来搞定这件事情。 不知道还有其他的方法没有?
 openerp 的源码部分:
 def format_body(self, html):
 body = html.findall('body')
 body_list = []
 footer = self.format_footer(body[-1].getchildren())
 for b in body[:-1]:
 body_list.append(etree.tostring(b).replace('\t', '').replace('\n',''))
 mako文件的代码。(文件名不是html,在openerp报告中没有问题。)
 <html>
 <head>
 <title>test mako template</title>
 </head>
 <body>
 <table>
 order_number: 12345
 </table>
 <footer>
 </footer>
 </body>
 </html>

