diff --git a/base_report_assembler/__init__.py b/base_report_assembler/__init__.py new file mode 100644 index 00000000..f91d090e --- /dev/null +++ b/base_report_assembler/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Author: Yannick Vaucher +# Copyright 2013 Camptocamp SA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +from . import report_assembler +from . import assembled_report +from . import ir_report + diff --git a/base_report_assembler/__openerp__.py b/base_report_assembler/__openerp__.py new file mode 100644 index 00000000..53ccc32d --- /dev/null +++ b/base_report_assembler/__openerp__.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Author: Yannick Vaucher +# Copyright 2013 Camptocamp SA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +{'name': 'Base Report Assembler', + 'version': '1.0', + 'category': 'Report', + 'description': """ +Base Report Assembler +===================== + +Defines a new type of report which is an assemblage of multiple other reports +of the same object. + +For example you can merge the pdf output of a rml invoice report with the pdf +output of a webkit payment slip. + +To install this assemblage option for specific object you can install +the folling module(s): + +- Invoices: invoice_report_assemble (lp:account-invoice-report) + +""", + 'author': 'Camptocamp', + 'maintainer': 'Camptocamp', + 'website': 'http://www.camptocamp.com/', + 'depends': ['base'], + 'data': [], + 'test': [], + 'installable': True, + 'auto_install': False, + 'application': False, + } diff --git a/base_report_assembler/assembled_report.py b/base_report_assembler/assembled_report.py new file mode 100644 index 00000000..212e5bf2 --- /dev/null +++ b/base_report_assembler/assembled_report.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Author: Yannick Vaucher +# Copyright 2013 Camptocamp SA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +from openerp.osv import orm, fields + +class AssembledReport(orm.Model): + _name = 'assembled.report' + + _order = 'sequence' + + _columns = { + 'report_id': fields.many2one( + 'ir.actions.report.xml', 'Report', + domain="[('model', '=', model)," + "('report_type', '!=', 'assemblage')]", required=True), + 'model': fields.char('Object model'), + 'sequence': fields.integer('Sequence', required=True), + 'company_id': fields.many2one('res.company', 'Company', required=True), + } + + _defaults = { + 'sequence': 1, + 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get( + cr, uid, 'assembled.report', context=c) + } diff --git a/base_report_assembler/i18n/base_report_assembler.pot b/base_report_assembler/i18n/base_report_assembler.pot new file mode 100644 index 00000000..5b2331ef --- /dev/null +++ b/base_report_assembler/i18n/base_report_assembler.pot @@ -0,0 +1,36 @@ +# Translation of OpenERP Server. +# This file contains the translation of the following modules: +# * base_report_assembler +# +msgid "" +msgstr "" +"Project-Id-Version: OpenERP Server 7.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2013-11-05 14:55+0000\n" +"PO-Revision-Date: 2013-11-05 14:55+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: base_report_assembler +#: field:assembled.report,model:0 +msgid "Object model" +msgstr "" + +#. module: base_report_assembler +#: field:assembled.report,sequence:0 +msgid "Sequence" +msgstr "" + +#. module: base_report_assembler +#: field:assembled.report,company_id:0 +msgid "Company" +msgstr "" + +#. module: base_report_assembler +#: field:assembled.report,report_id:0 +msgid "Report" +msgstr "" diff --git a/base_report_assembler/i18n/fr.po b/base_report_assembler/i18n/fr.po new file mode 100644 index 00000000..6dee61e0 --- /dev/null +++ b/base_report_assembler/i18n/fr.po @@ -0,0 +1,37 @@ +# Translation of OpenERP Server. +# This file contains the translation of the following modules: +# * base_report_assembler +# +msgid "" +msgstr "" +"Project-Id-Version: OpenERP Server 7.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2013-11-05 14:55+0000\n" +"PO-Revision-Date: 2013-11-05 14:55+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: base_report_assembler +#: field:assembled.report,model:0 +msgid "Object model" +msgstr "Modèle d'object" + +#. module: base_report_assembler +#: field:assembled.report,sequence:0 +msgid "Sequence" +msgstr "Séquence" + +#. module: base_report_assembler +#: field:assembled.report,company_id:0 +msgid "Company" +msgstr "Société" + +#. module: base_report_assembler +#: field:assembled.report,report_id:0 +msgid "Report" +msgstr "Rapport" + diff --git a/base_report_assembler/ir_report.py b/base_report_assembler/ir_report.py new file mode 100644 index 00000000..6cc901e5 --- /dev/null +++ b/base_report_assembler/ir_report.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Author: Yannick Vaucher +# Copyright 2013 Camptocamp SA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +from openerp.osv import orm +from openerp import netsvc +from openerp.report.report_sxw import rml_parse +from report_assembler import PDFReportAssembler + + +def register_report(name, model, parser=rml_parse): + """ Register the report into the services """ + name = 'report.%s' % name + if netsvc.Service._services.get(name, False): + service = netsvc.Service._services[name] + if isinstance(service, PDFReportAssembler): + #already instantiated properly, skip it + return + if hasattr(service, 'parser'): + parser = service.parser + del netsvc.Service._services[name] + PDFReportAssembler(name, model, parser=parser) + + +class ReportAssembleXML(orm.Model): + + _name = 'ir.actions.report.xml' + _inherit = 'ir.actions.report.xml' + + def __init__(self, pool, cr): + super(ReportAssembleXML, self).__init__(pool, cr) + + def register_all(self, cursor): + value = super(ReportAssembleXML, self).register_all(cursor) + cursor.execute("SELECT * FROM ir_act_report_xml WHERE report_type = 'assemblage'") + records = cursor.dictfetchall() + for record in records: + register_report(record['report_name'], record['model']) + return value + + def unlink(self, cursor, user, ids, context=None): + """ Delete report and unregister it """ + trans_obj = self.pool.get('ir.translation') + trans_ids = trans_obj.search( + cursor, + user, + [('type', '=', 'report'), ('res_id', 'in', ids)] + ) + trans_obj.unlink(cursor, user, trans_ids) + + # Warning: we cannot unregister the services at the moment + # because they are shared across databases. Calling a deleted + # report will fail so it's ok. + + res = super(ReportAssembleXML, self).unlink( + cursor, + user, + ids, + context) + return res + + def create(self, cursor, user, vals, context=None): + """ Create report and register it """ + res = super(ReportAssembleXML, self).create(cursor, user, vals, context) + if vals.get('report_type', '') == 'assemblage': + # I really look forward to virtual functions :S + register_report( + vals['report_name'], + vals['model']) + return res + + def write(self, cr, uid, ids, vals, context=None): + """ Edit report and manage its registration """ + if isinstance(ids, (int, long)): + ids = [ids] + for rep in self.browse(cr, uid, ids, context=context): + if rep.report_type != 'assemblage': + continue + if (vals.get('report_name', False) + and vals['report_name'] != rep.report_name): + report_name = vals['report_name'] + else: + report_name = rep.report_name + + register_report( + report_name, + vals.get('model', rep.model), + False + ) + res = super(ReportAssembleXML, self).write(cr, uid, ids, vals, context) + return res + + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/base_report_assembler/report_assembler.py b/base_report_assembler/report_assembler.py new file mode 100644 index 00000000..08b5a9b9 --- /dev/null +++ b/base_report_assembler/report_assembler.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Author: Yannick Vaucher +# Copyright 2013 Camptocamp SA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +import time +import base64 +from PyPDF2 import PdfFileReader, PdfFileWriter +from StringIO import StringIO + +from openerp.netsvc import ExportService +from openerp.report import report_sxw +from openerp import pooler + +_POLLING_DELAY = 0.25 + + +def assemble_pdf(pdf_list): + """ + Assemble a list of pdf + """ + # Even though we are using PyPDF2 we can't use PdfFileMerger + # as this issue still exists in mostly used wktohtml reports version + # http://code.google.com/p/wkhtmltopdf/issues/detail?id=635 + #merger = PdfFileMerger() + #merger.append(fileobj=StringIO(invoice_pdf)) + #merger.append(fileobj=StringIO(bvr_pdf)) + + #with tempfile.TemporaryFile() as merged_pdf: + #merger.write(merged_pdf) + #return merged_pdf.read(), 'pdf' + + output = PdfFileWriter() + for pdf in pdf_list: + reader = PdfFileReader(StringIO(pdf)) + for page in range(reader.getNumPages()): + output.addPage(reader.getPage(page)) + s = StringIO() + output.write(s) + return s.getvalue() + + +class PDFReportAssembler(report_sxw.report_sxw): + """ PDFReportAssembler allows to put 2 pdf reports in one single pdf""" + + def _generate_all_pdf(self, cr, uid, ids, data, report_ids, context=None): + """ + Return a list of pdf encoded in base64 + """ + pool = pooler.get_pool(cr.dbname) + report_obj = pool.get('ir.actions.report.xml') + + spool = ExportService._services['report'] + + pdf_reports = [] + report_list = report_obj.browse(cr, uid, report_ids, context=context) + for report in report_list: + + report_key = spool.exp_report(cr.dbname, uid, report.report_name, + ids, datas=data, context=context) + while 1: + res = spool.exp_report_get(cr.dbname, uid, report_key) + if res.get('state'): + break + time.sleep(_POLLING_DELAY) + pdf = base64.b64decode(res.get('result')) + pdf_reports.append(pdf) + return pdf_reports + + def _get_report_ids(self, cr, uid, ids, context=None): + """ + Hook to define the list of report to print + """ + return [] + + def create_single_pdf(self, cr, uid, ids, data, report_xml, context=None): + """Call both report to assemble both pdf""" + + report_ids = self._get_report_ids(cr, uid, ids, context=context) + + pdf_reports = self._generate_all_pdf(cr, uid, ids, data, report_ids, context=context) + + pdf_assemblage = assemble_pdf(pdf_reports) + return pdf_assemblage, 'pdf' + + def create(self, cr, uid, ids, data, context=None): + """We override the create function in order to handle generator + Code taken from report openoffice. Thanks guys :) """ + pool = pooler.get_pool(cr.dbname) + ir_obj = pool.get('ir.actions.report.xml') + report_xml_ids = ir_obj.search(cr, uid, + [('report_name', '=', self.name[7:])], context=context) + if report_xml_ids: + + report_xml = ir_obj.browse(cr, + uid, + report_xml_ids[0], + context=context) + report_xml.report_rml = None + report_xml.report_rml_content = None + report_xml.report_sxw_content_data = None + report_xml.report_sxw_content = None + report_xml.report_sxw = None + else: + return super(PDFReportAssembler, self).create(cr, uid, ids, data, context) + if report_xml.report_type != 'assemblage': + return super(PDFReportAssembler, self).create(cr, uid, ids, data, context) + result = self.create_source_pdf(cr, uid, ids, data, report_xml, context) + if not result: + return (False, False) + return result