diff --git a/requirements.txt b/requirements.txt index c208f5bc3f..27f438546a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ # generated from manifests external_dependencies +openpyxl xlrd xlsxwriter diff --git a/sql_export_excel/README.rst b/sql_export_excel/README.rst new file mode 100644 index 0000000000..7a01f226d8 --- /dev/null +++ b/sql_export_excel/README.rst @@ -0,0 +1,90 @@ +================ +SQL Export Excel +================ + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:140000da7d6e4acfcacee34bf261cfa64e2d76d8e01e4aaccfc11cf3bc93b288 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Freporting--engine-lightgray.png?logo=github + :target: https://github.com/OCA/reporting-engine/tree/17.0/sql_export_excel + :alt: OCA/reporting-engine +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/reporting-engine-17-0/reporting-engine-17-0-sql_export_excel + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/reporting-engine&target_branch=17.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +Add the possibility to extract data from a sql query toward an excel +file. It is also possible to provide an template excel file for a query. +In this case, the data will be inserted in the specified sheet of the +provided excel file. This is usefull when doing a lot of calculation in +excel and the data is coming from Odoo. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +If you want Odoo to update an existing excel file, you should create an +attachment with the excel file and configure this attachment on the +query. Then, you can configure the query to indicate if Odoo should +export the header and where it should insert the data. By default, it +will insert it in the first sheet, at first row/column. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Akretion + +Contributors +------------ + +- Florian da Costa +- Helly kapatel + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/reporting-engine `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/sql_export_excel/__init__.py b/sql_export_excel/__init__.py new file mode 100644 index 0000000000..0650744f6b --- /dev/null +++ b/sql_export_excel/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/sql_export_excel/__manifest__.py b/sql_export_excel/__manifest__.py new file mode 100644 index 0000000000..b9e1802c19 --- /dev/null +++ b/sql_export_excel/__manifest__.py @@ -0,0 +1,22 @@ +# Copyright 2019 Akretion +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +{ + "name": "SQL Export Excel", + "version": "17.0.1.0.0", + "author": "Akretion,Odoo Community Association (OCA)", + "website": "https://github.com/OCA/reporting-engine", + "license": "AGPL-3", + "category": "Generic Modules/Others", + "summary": "Allow to export a sql query to an excel file.", + "depends": ["sql_export"], + "external_dependencies": { + "python": [ + "openpyxl", + ], + }, + "data": [ + "views/sql_export_view.xml", + ], + "installable": True, +} diff --git a/sql_export_excel/i18n/ca.po b/sql_export_excel/i18n/ca.po new file mode 100644 index 0000000000..8f64f3b39b --- /dev/null +++ b/sql_export_excel/i18n/ca.po @@ -0,0 +1,122 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sql_export_excel +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2022-06-15 18:05+0000\n" +"Last-Translator: jabelchi \n" +"Language-Team: none\n" +"Language: ca\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.3.2\n" + +#. module: sql_export_excel +#: model:ir.model.fields,field_description:sql_export_excel.field_sql_export__col_position +msgid "Column Position" +msgstr "Posició columna" + +#. module: sql_export_excel +#: model:ir.model.fields.selection,name:sql_export_excel.selection__sql_export__file_format__excel +msgid "Excel" +msgstr "Excel" + +#. module: sql_export_excel +#: model:ir.model.fields,field_description:sql_export_excel.field_sql_export__attachment_id +msgid "Excel Template" +msgstr "Plantilla Excel" + +#. module: sql_export_excel +#: model:ir.model.fields,field_description:sql_export_excel.field_sql_export__file_format +msgid "File Format" +msgstr "Format de fitxer" + +#. module: sql_export_excel +#: model:ir.model.fields,field_description:sql_export_excel.field_sql_export__header +msgid "Header" +msgstr "Capçalera" + +#. module: sql_export_excel +#: model:ir.model.fields,help:sql_export_excel.field_sql_export__attachment_id +msgid "" +"If you configure an excel file (in xlsx format) here, the result of the query will be injected in it.\n" +"It is usefull to feed data in a excel file pre-configured with calculation" +msgstr "" +"Si configureu aquí un fitxer Excel (en format xlsx), s'hi injectarà el " +"resultat de la consulta.\n" +"Això és útil per a carregar dades en un fitxer Excel pre-configurat amb " +"càlculs" + +#. module: sql_export_excel +#: model:ir.model.fields,help:sql_export_excel.field_sql_export__col_position +msgid "Indicate from which column the result of the query should be injected." +msgstr "" +"Indiqueu des de quina columna ha de carregar-se el resultat de la consulta." + +#. module: sql_export_excel +#: model:ir.model.fields,help:sql_export_excel.field_sql_export__row_position +msgid "Indicate from which row the result of the query should be injected." +msgstr "" +"Indiqueu des de quina fila ha de carregar-se el resultat de la consulta." + +#. module: sql_export_excel +#: model:ir.model.fields,help:sql_export_excel.field_sql_export__header +msgid "Indicate if the header should be exported to the file." +msgstr "Indiqueu si la capçalera ha d'exportar-se al fitxer." + +#. module: sql_export_excel +#: model:ir.model.fields,help:sql_export_excel.field_sql_export__sheet_position +msgid "" +"Indicate the sheet's position of the excel template where the result of the " +"sql query should be injected." +msgstr "" +"Indiqueu la posició del full de la plantilla Excel on s'ha de carregar el " +"resultat de la consulta SQL." + +#. module: sql_export_excel +#: model:ir.model.fields,field_description:sql_export_excel.field_sql_export__row_position +msgid "Row Position" +msgstr "Posició fila" + +#. module: sql_export_excel +#: model:ir.model,name:sql_export_excel.model_sql_export +msgid "SQL export" +msgstr "Exportació SQL" + +#. module: sql_export_excel +#: model:ir.model.fields,field_description:sql_export_excel.field_sql_export__sheet_position +msgid "Sheet Position" +msgstr "Posició del full" + +#. module: sql_export_excel +#: code:addons/sql_export_excel/models/sql_export.py:0 +#, python-format +msgid "" +"The Excel Template file contains less than %s sheets Please, adjust the " +"Sheet Position parameter." +msgstr "" +"La plantilla Excel conté menys de %s fulls. Si us plau, corregiu el " +"paràmetre de la posició del full." + +#. module: sql_export_excel +#: code:addons/sql_export_excel/models/sql_export.py:0 +#, python-format +msgid "The column position can't be less than 1." +msgstr "La posició de la columna no pot ser menor que 1." + +#. module: sql_export_excel +#: code:addons/sql_export_excel/models/sql_export.py:0 +#, python-format +msgid "The row position can't be less than 1." +msgstr "La posició de la fila no pot ser menor que 1." + +#. module: sql_export_excel +#: code:addons/sql_export_excel/models/sql_export.py:0 +#, python-format +msgid "The sheet position can't be less than 1." +msgstr "La posició del full no pot ser menor que 1." diff --git a/sql_export_excel/i18n/es.po b/sql_export_excel/i18n/es.po new file mode 100644 index 0000000000..733e13409d --- /dev/null +++ b/sql_export_excel/i18n/es.po @@ -0,0 +1,126 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sql_export_excel +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2023-10-15 19:36+0000\n" +"Last-Translator: Ivorra78 \n" +"Language-Team: none\n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.17\n" + +#. module: sql_export_excel +#: model:ir.model.fields,field_description:sql_export_excel.field_sql_export__col_position +msgid "Column Position" +msgstr "Posición de la columna" + +#. module: sql_export_excel +#: model:ir.model.fields.selection,name:sql_export_excel.selection__sql_export__file_format__excel +msgid "Excel" +msgstr "Excel" + +#. module: sql_export_excel +#: model:ir.model.fields,field_description:sql_export_excel.field_sql_export__attachment_id +msgid "Excel Template" +msgstr "Plantilla Excel" + +#. module: sql_export_excel +#: model:ir.model.fields,field_description:sql_export_excel.field_sql_export__file_format +msgid "File Format" +msgstr "Formato del archivo" + +#. module: sql_export_excel +#: model:ir.model.fields,field_description:sql_export_excel.field_sql_export__header +msgid "Header" +msgstr "Cabecera" + +#. module: sql_export_excel +#: model:ir.model.fields,help:sql_export_excel.field_sql_export__attachment_id +msgid "" +"If you configure an excel file (in xlsx format) here, the result of the query will be injected in it.\n" +"It is usefull to feed data in a excel file pre-configured with calculation" +msgstr "" +"Si configura aquí un fichero excel (en formato xlsx), el resultado de la " +"consulta se inyectará en él.\n" +"Es útil para alimentar los datos en un archivo excel preconfigurado con " +"cálculo" + +#. module: sql_export_excel +#: model:ir.model.fields,help:sql_export_excel.field_sql_export__col_position +msgid "Indicate from which column the result of the query should be injected." +msgstr "" +"Indique a partir de qué columna debe inyectarse el resultado de la consulta." + +#. module: sql_export_excel +#: model:ir.model.fields,help:sql_export_excel.field_sql_export__row_position +msgid "Indicate from which row the result of the query should be injected." +msgstr "" +"Indica a partir de qué fila debe inyectarse el resultado de la consulta." + +#. module: sql_export_excel +#: model:ir.model.fields,help:sql_export_excel.field_sql_export__header +msgid "Indicate if the header should be exported to the file." +msgstr "Indique si la cabecera debe exportarse al fichero." + +#. module: sql_export_excel +#: model:ir.model.fields,help:sql_export_excel.field_sql_export__sheet_position +msgid "" +"Indicate the sheet's position of the excel template where the result of the " +"sql query should be injected." +msgstr "" +"Indique la posición de la hoja de la plantilla excel donde debe inyectarse " +"el resultado de la consulta sql." + +#. module: sql_export_excel +#: model:ir.model.fields,field_description:sql_export_excel.field_sql_export__row_position +msgid "Row Position" +msgstr "Posición de la fila" + +#. module: sql_export_excel +#: model:ir.model,name:sql_export_excel.model_sql_export +msgid "SQL export" +msgstr "Exportar SQL" + +#. module: sql_export_excel +#: model:ir.model.fields,field_description:sql_export_excel.field_sql_export__sheet_position +msgid "Sheet Position" +msgstr "Posición de la hoja" + +#. module: sql_export_excel +#. odoo-python +#: code:addons/sql_export_excel/models/sql_export.py:0 +#, python-format +msgid "" +"The Excel Template file contains less than %s sheets Please, adjust the " +"Sheet Position parameter." +msgstr "" +"El archivo Plantilla Excel contiene menos de %s hojas Por favor, ajuste el " +"parámetro Posición de la Hoja." + +#. module: sql_export_excel +#. odoo-python +#: code:addons/sql_export_excel/models/sql_export.py:0 +#, python-format +msgid "The column position can't be less than 1." +msgstr "La posición de la columna no puede ser inferior a 1." + +#. module: sql_export_excel +#. odoo-python +#: code:addons/sql_export_excel/models/sql_export.py:0 +#, python-format +msgid "The row position can't be less than 1." +msgstr "La posición de la fila no puede ser inferior a 1." + +#. module: sql_export_excel +#. odoo-python +#: code:addons/sql_export_excel/models/sql_export.py:0 +#, python-format +msgid "The sheet position can't be less than 1." +msgstr "La posición de la hoja no puede ser inferior a 1." diff --git a/sql_export_excel/i18n/it.po b/sql_export_excel/i18n/it.po new file mode 100644 index 0000000000..41cda522a9 --- /dev/null +++ b/sql_export_excel/i18n/it.po @@ -0,0 +1,114 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sql_export_excel +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +#. module: sql_export_excel +#: model:ir.model.fields,field_description:sql_export_excel.field_sql_export__col_position +msgid "Column Position" +msgstr "" + +#. module: sql_export_excel +#: model:ir.model.fields.selection,name:sql_export_excel.selection__sql_export__file_format__excel +msgid "Excel" +msgstr "" + +#. module: sql_export_excel +#: model:ir.model.fields,field_description:sql_export_excel.field_sql_export__attachment_id +msgid "Excel Template" +msgstr "" + +#. module: sql_export_excel +#: model:ir.model.fields,field_description:sql_export_excel.field_sql_export__file_format +msgid "File Format" +msgstr "" + +#. module: sql_export_excel +#: model:ir.model.fields,field_description:sql_export_excel.field_sql_export__header +msgid "Header" +msgstr "" + +#. module: sql_export_excel +#: model:ir.model.fields,help:sql_export_excel.field_sql_export__attachment_id +msgid "" +"If you configure an excel file (in xlsx format) here, the result of the query will be injected in it.\n" +"It is usefull to feed data in a excel file pre-configured with calculation" +msgstr "" + +#. module: sql_export_excel +#: model:ir.model.fields,help:sql_export_excel.field_sql_export__col_position +msgid "Indicate from which column the result of the query should be injected." +msgstr "" + +#. module: sql_export_excel +#: model:ir.model.fields,help:sql_export_excel.field_sql_export__row_position +msgid "Indicate from which row the result of the query should be injected." +msgstr "" + +#. module: sql_export_excel +#: model:ir.model.fields,help:sql_export_excel.field_sql_export__header +msgid "Indicate if the header should be exported to the file." +msgstr "" + +#. module: sql_export_excel +#: model:ir.model.fields,help:sql_export_excel.field_sql_export__sheet_position +msgid "" +"Indicate the sheet's position of the excel template where the result of the " +"sql query should be injected." +msgstr "" + +#. module: sql_export_excel +#: model:ir.model.fields,field_description:sql_export_excel.field_sql_export__row_position +msgid "Row Position" +msgstr "" + +#. module: sql_export_excel +#: model:ir.model,name:sql_export_excel.model_sql_export +msgid "SQL export" +msgstr "" + +#. module: sql_export_excel +#: model:ir.model.fields,field_description:sql_export_excel.field_sql_export__sheet_position +msgid "Sheet Position" +msgstr "" + +#. module: sql_export_excel +#. odoo-python +#: code:addons/sql_export_excel/models/sql_export.py:0 +#, python-format +msgid "" +"The Excel Template file contains less than %s sheets Please, adjust the " +"Sheet Position parameter." +msgstr "" + +#. module: sql_export_excel +#. odoo-python +#: code:addons/sql_export_excel/models/sql_export.py:0 +#, python-format +msgid "The column position can't be less than 1." +msgstr "" + +#. module: sql_export_excel +#. odoo-python +#: code:addons/sql_export_excel/models/sql_export.py:0 +#, python-format +msgid "The row position can't be less than 1." +msgstr "" + +#. module: sql_export_excel +#. odoo-python +#: code:addons/sql_export_excel/models/sql_export.py:0 +#, python-format +msgid "The sheet position can't be less than 1." +msgstr "" diff --git a/sql_export_excel/i18n/sql_export_excel.pot b/sql_export_excel/i18n/sql_export_excel.pot new file mode 100644 index 0000000000..f9758ca58f --- /dev/null +++ b/sql_export_excel/i18n/sql_export_excel.pot @@ -0,0 +1,113 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sql_export_excel +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \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: sql_export_excel +#: model:ir.model.fields,field_description:sql_export_excel.field_sql_export__col_position +msgid "Column Position" +msgstr "" + +#. module: sql_export_excel +#: model:ir.model.fields.selection,name:sql_export_excel.selection__sql_export__file_format__excel +msgid "Excel" +msgstr "" + +#. module: sql_export_excel +#: model:ir.model.fields,field_description:sql_export_excel.field_sql_export__attachment_id +msgid "Excel Template" +msgstr "" + +#. module: sql_export_excel +#: model:ir.model.fields,field_description:sql_export_excel.field_sql_export__file_format +msgid "File Format" +msgstr "" + +#. module: sql_export_excel +#: model:ir.model.fields,field_description:sql_export_excel.field_sql_export__header +msgid "Header" +msgstr "" + +#. module: sql_export_excel +#: model:ir.model.fields,help:sql_export_excel.field_sql_export__attachment_id +msgid "" +"If you configure an excel file (in xlsx format) here, the result of the query will be injected in it.\n" +"It is usefull to feed data in a excel file pre-configured with calculation" +msgstr "" + +#. module: sql_export_excel +#: model:ir.model.fields,help:sql_export_excel.field_sql_export__col_position +msgid "Indicate from which column the result of the query should be injected." +msgstr "" + +#. module: sql_export_excel +#: model:ir.model.fields,help:sql_export_excel.field_sql_export__row_position +msgid "Indicate from which row the result of the query should be injected." +msgstr "" + +#. module: sql_export_excel +#: model:ir.model.fields,help:sql_export_excel.field_sql_export__header +msgid "Indicate if the header should be exported to the file." +msgstr "" + +#. module: sql_export_excel +#: model:ir.model.fields,help:sql_export_excel.field_sql_export__sheet_position +msgid "" +"Indicate the sheet's position of the excel template where the result of the " +"sql query should be injected." +msgstr "" + +#. module: sql_export_excel +#: model:ir.model.fields,field_description:sql_export_excel.field_sql_export__row_position +msgid "Row Position" +msgstr "" + +#. module: sql_export_excel +#: model:ir.model,name:sql_export_excel.model_sql_export +msgid "SQL export" +msgstr "" + +#. module: sql_export_excel +#: model:ir.model.fields,field_description:sql_export_excel.field_sql_export__sheet_position +msgid "Sheet Position" +msgstr "" + +#. module: sql_export_excel +#. odoo-python +#: code:addons/sql_export_excel/models/sql_export.py:0 +#, python-format +msgid "" +"The Excel Template file contains less than %s sheets Please, adjust the " +"Sheet Position parameter." +msgstr "" + +#. module: sql_export_excel +#. odoo-python +#: code:addons/sql_export_excel/models/sql_export.py:0 +#, python-format +msgid "The column position can't be less than 1." +msgstr "" + +#. module: sql_export_excel +#. odoo-python +#: code:addons/sql_export_excel/models/sql_export.py:0 +#, python-format +msgid "The row position can't be less than 1." +msgstr "" + +#. module: sql_export_excel +#. odoo-python +#: code:addons/sql_export_excel/models/sql_export.py:0 +#, python-format +msgid "The sheet position can't be less than 1." +msgstr "" diff --git a/sql_export_excel/models/__init__.py b/sql_export_excel/models/__init__.py new file mode 100644 index 0000000000..0144620622 --- /dev/null +++ b/sql_export_excel/models/__init__.py @@ -0,0 +1 @@ +from . import sql_export diff --git a/sql_export_excel/models/sql_export.py b/sql_export_excel/models/sql_export.py new file mode 100644 index 0000000000..68194c52ec --- /dev/null +++ b/sql_export_excel/models/sql_export.py @@ -0,0 +1,122 @@ +# Copyright 2019 Akretion +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +import base64 +import logging +from io import BytesIO + +from odoo import _, api, exceptions, fields, models + +_logger = logging.getLogger(__name__) + +try: + import openpyxl +except ImportError: + _logger.debug("Can not import openpyxl") + + +class SqlExport(models.Model): + _inherit = "sql.export" + + file_format = fields.Selection( + selection_add=[("excel", "Excel")], ondelete={"excel": "set default"} + ) + header = fields.Boolean( + default=True, help="Indicate if the header should be exported to the file." + ) + attachment_id = fields.Many2one( + "ir.attachment", + string="Excel Template", + help="If you configure an excel file (in xlsx format) here, the " + "result of the query will be injected in it.\nIt is usefull to " + "feed data in a excel file pre-configured with calculation", + ) + sheet_position = fields.Integer( + default=1, + help="Indicate the sheet's position of the excel template where the " + "result of the sql query should be injected.", + ) + row_position = fields.Integer( + default=1, + help="Indicate from which row the result of the query should be " "injected.", + ) + col_position = fields.Integer( + string="Column Position", + default=1, + help="Indicate from which column the result of the query should be " + "injected.", + ) + + @api.constrains("sheet_position") + def check_sheet_position(self): + for export in self: + if export.sheet_position < 1: + raise exceptions.ValidationError( + _("The sheet position can't be less than 1.") + ) + + @api.constrains("row_position") + def check_row_position(self): + for export in self: + if export.row_position < 1: + raise exceptions.ValidationError( + _("The row position can't be less than 1.") + ) + + @api.constrains("col_position") + def check_column_position(self): + for export in self: + if export.col_position < 1: + raise exceptions.ValidationError( + _("The column position can't be less than 1.") + ) + + def _get_file_extension(self): + self.ensure_one() + if self.file_format == "excel": + return "xlsx" + else: + return super()._get_file_extension() + + def excel_get_data_from_query(self, variable_dict): + self.ensure_one() + res = self._execute_sql_request( + params=variable_dict, mode="fetchall", header=self.header + ) + # Case we insert data in an existing excel file. + if self.attachment_id: + datas = self.attachment_id.datas + infile = BytesIO() + infile.write(base64.b64decode(datas)) + infile.seek(0) + wb = openpyxl.load_workbook(filename=infile) + sheets = wb.worksheets + try: + ws = sheets[self.sheet_position - 1] + except IndexError as err: + raise exceptions.ValidationError( + _( + "The Excel Template file contains less than %s sheets " + "Please, adjust the Sheet Position parameter." + ) + ) from err + row_position = self.row_position or 1 + col_position = self.col_position or 1 + # Case of excel file creation + else: + wb = openpyxl.Workbook() + ws = wb.active + row_position = 1 + col_position = 1 + for index, row in enumerate(res, row_position): + for col, val in enumerate(row, col_position): + # manage jsonb field as dict are not writable on the excel cell + if isinstance(val, dict): + val = str(val) + ws.cell(row=index, column=col).value = val + output = BytesIO() + wb.save(output) + output.getvalue() + output_datas = base64.b64encode(output.getvalue()) + output.close() + return output_datas diff --git a/sql_export_excel/pyproject.toml b/sql_export_excel/pyproject.toml new file mode 100644 index 0000000000..4231d0cccb --- /dev/null +++ b/sql_export_excel/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/sql_export_excel/readme/CONFIGURE.md b/sql_export_excel/readme/CONFIGURE.md new file mode 100644 index 0000000000..b630ec9d48 --- /dev/null +++ b/sql_export_excel/readme/CONFIGURE.md @@ -0,0 +1,5 @@ +If you want Odoo to update an existing excel file, you should create an +attachment with the excel file and configure this attachment on the +query. Then, you can configure the query to indicate if Odoo should +export the header and where it should insert the data. By default, it +will insert it in the first sheet, at first row/column. diff --git a/sql_export_excel/readme/CONTRIBUTORS.md b/sql_export_excel/readme/CONTRIBUTORS.md new file mode 100644 index 0000000000..17ad1ef72d --- /dev/null +++ b/sql_export_excel/readme/CONTRIBUTORS.md @@ -0,0 +1,2 @@ +- Florian da Costa \<\> +- Helly kapatel \<\> diff --git a/sql_export_excel/readme/DESCRIPTION.md b/sql_export_excel/readme/DESCRIPTION.md new file mode 100644 index 0000000000..0f86e1a7e4 --- /dev/null +++ b/sql_export_excel/readme/DESCRIPTION.md @@ -0,0 +1,5 @@ +Add the possibility to extract data from a sql query toward an excel +file. It is also possible to provide an template excel file for a query. +In this case, the data will be inserted in the specified sheet of the +provided excel file. This is usefull when doing a lot of calculation in +excel and the data is coming from Odoo. diff --git a/sql_export_excel/static/description/icon.png b/sql_export_excel/static/description/icon.png new file mode 100644 index 0000000000..3a0328b516 Binary files /dev/null and b/sql_export_excel/static/description/icon.png differ diff --git a/sql_export_excel/static/description/index.html b/sql_export_excel/static/description/index.html new file mode 100644 index 0000000000..4d682c8e6b --- /dev/null +++ b/sql_export_excel/static/description/index.html @@ -0,0 +1,435 @@ + + + + + + +SQL Export Excel + + + +
+

SQL Export Excel

+ + +

Beta License: AGPL-3 OCA/reporting-engine Translate me on Weblate Try me on Runboat

+

Add the possibility to extract data from a sql query toward an excel +file. It is also possible to provide an template excel file for a query. +In this case, the data will be inserted in the specified sheet of the +provided excel file. This is usefull when doing a lot of calculation in +excel and the data is coming from Odoo.

+

Table of contents

+ +
+

Configuration

+

If you want Odoo to update an existing excel file, you should create an +attachment with the excel file and configure this attachment on the +query. Then, you can configure the query to indicate if Odoo should +export the header and where it should insert the data. By default, it +will insert it in the first sheet, at first row/column.

+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Akretion
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/reporting-engine project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/sql_export_excel/tests/__init__.py b/sql_export_excel/tests/__init__.py new file mode 100644 index 0000000000..6d89d7607e --- /dev/null +++ b/sql_export_excel/tests/__init__.py @@ -0,0 +1 @@ +from . import test_sql_query_excel diff --git a/sql_export_excel/tests/test_sql_query_excel.py b/sql_export_excel/tests/test_sql_query_excel.py new file mode 100644 index 0000000000..7dc03c5a91 --- /dev/null +++ b/sql_export_excel/tests/test_sql_query_excel.py @@ -0,0 +1,112 @@ +# Copyright (C) 2019 Akretion () +# @author: Florian da Costa +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import base64 +import logging +from io import BytesIO + +from odoo.tests.common import TransactionCase + +_logger = logging.getLogger(__name__) + +try: + import openpyxl +except ImportError: + _logger.debug("Can not import openpyxl") + + +class TestExportSqlQueryExcel(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.wizard_obj = cls.env["sql.file.wizard"] + + def get_workbook_from_query(self, wizard): + wizard.export_sql() + decoded_data = base64.b64decode(wizard.binary_file) + xlsx_file = BytesIO(decoded_data) + return openpyxl.load_workbook(xlsx_file) + + def test_excel_file_generation(self): + test_query = "SELECT 'testcol1' as firstcol, 2 as second_col" + query_vals = { + "name": "Test Query Excel", + "query": test_query, + "file_format": "excel", + } + query = self.env["sql.export"].create(query_vals) + query.button_validate_sql_expression() + wizard = self.wizard_obj.create( + { + "sql_export_id": query.id, + } + ) + workbook = self.get_workbook_from_query(wizard) + ws = workbook.active + # Check values, header should be here by default + self.assertEqual(ws.cell(row=1, column=1).value, "firstcol") + self.assertEqual(ws.cell(row=2, column=1).value, "testcol1") + self.assertEqual(ws.cell(row=2, column=2).value, 2) + + query.write({"header": False}) + wb2 = self.get_workbook_from_query(wizard) + ws2 = wb2.active + # Check values, the header should not be present + self.assertEqual(ws2.cell(row=1, column=1).value, "testcol1") + self.assertEqual(ws2.cell(row=1, column=2).value, 2) + + def test_excel_file_insert(self): + # Create excel file with 2 sheets. Create a header in second sheet + # where data will be inserted + wb = openpyxl.Workbook() + ws = wb.active + ws.cell(row=1, column=1, value="My Test Value") + ws2 = wb.create_sheet("data") + ws2.cell(row=1, column=1, value="Partner Id") + ws2.cell(row=1, column=2, value="Partner Name") + output = BytesIO() + wb.save(output) + data = output.getvalue() + + # Create attachment with the created xlsx file which will be used as + # template in the sql query + attachmnent_vals = { + "name": "template xlsx sql export Res Partner", + "datas": base64.b64encode(data), + } + attachment = self.env["ir.attachment"].create(attachmnent_vals) + + # Create the query and configure it to insert the data in the second + # sheet of the xlsx template file and start inserting data at the + # second row, ignoring header (because the template excel file + # already contains a header) + test_query = "SELECT id, name FROM res_partner" + query_vals = { + "name": "Test Query Excel", + "query": test_query, + "file_format": "excel", + "attachment_id": attachment.id, + "sheet_position": 2, + "header": False, + "row_position": 2, + } + query = self.env["sql.export"].create(query_vals) + query.button_validate_sql_expression() + wizard = self.wizard_obj.create( + { + "sql_export_id": query.id, + } + ) + + # Check the generated excel file. The first sheet should still contain + # the same data and the second sheet should have kept the header and + # inserted data from the query + wb2 = self.get_workbook_from_query(wizard) + sheets = wb2.worksheets + ws1 = sheets[0] + # Check values, header should be here by default + self.assertEqual(ws1.cell(row=1, column=1).value, "My Test Value") + ws2 = sheets[1] + self.assertEqual(ws2.cell(row=1, column=1).value, "Partner Id") + self.assertTrue(ws2.cell(row=2, column=1).value) diff --git a/sql_export_excel/views/sql_export_view.xml b/sql_export_excel/views/sql_export_view.xml new file mode 100644 index 0000000000..c4dda1de49 --- /dev/null +++ b/sql_export_excel/views/sql_export_view.xml @@ -0,0 +1,16 @@ + + + + sql.export + + + + + + + + + + + +