diff --git a/mis_builder_expimp/__init__.py b/mis_builder_expimp/__init__.py new file mode 100644 index 0000000..326c3db --- /dev/null +++ b/mis_builder_expimp/__init__.py @@ -0,0 +1 @@ +from . import wizards \ No newline at end of file diff --git a/mis_builder_expimp/__manifest__.py b/mis_builder_expimp/__manifest__.py new file mode 100644 index 0000000..ee9971c --- /dev/null +++ b/mis_builder_expimp/__manifest__.py @@ -0,0 +1,17 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "MIS Builder Import / Export", + "summary": """Import / Export Reports with all dependencies""", + "version": "2.0.1.0.0", + "license": "AGPL-3", + "author": "Jamotion GmbH", + "website": "https://gitlab.com/flectra-community/mis-builder", + "depends": ["mis_builder"], + "data": [ + "wizards/mis_builder_export_views.xml", + "wizards/mis_builder_import_views.xml", + "security/ir.model.access.csv", + ], + "installable": True, +} diff --git a/mis_builder_expimp/security/ir.model.access.csv b/mis_builder_expimp/security/ir.model.access.csv new file mode 100644 index 0000000..2408cf4 --- /dev/null +++ b/mis_builder_expimp/security/ir.model.access.csv @@ -0,0 +1,3 @@ +"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" +access_mis_builder_import_wizard,access_mis_builder_import_wizard,model_mis_builder_import_wizard,account.group_account_manager,1,1,1,1 +access_mis_builder_export_wizard,access_mis_builder_export_wizard,model_mis_builder_export_wizard,account.group_account_manager,1,1,1,1 \ No newline at end of file diff --git a/mis_builder_expimp/static/description/icon.png b/mis_builder_expimp/static/description/icon.png new file mode 100644 index 0000000..1afa781 Binary files /dev/null and b/mis_builder_expimp/static/description/icon.png differ diff --git a/mis_builder_expimp/static/description/index.html b/mis_builder_expimp/static/description/index.html new file mode 100644 index 0000000..b3c1c35 --- /dev/null +++ b/mis_builder_expimp/static/description/index.html @@ -0,0 +1,69 @@ +
+
+
+
+ +

MIS Builder - Import / Export

+

Function to import / export MIS Report Templates

+
+
+
+ +
+
+

Full export and import of report template

+
+ +
+
+

+ When exporting a report, all related data are exported too: +

+
    +
  • KPIs
  • +
  • SubKPIs
  • +
  • Expressions
  • +
  • Queries
  • +
  • Sub Reports
  • +
  • Styles
  • +
+
+
+
+ +
+
+

Quick Start

+
+

Installation

+
+

+ There are no dependencies other than flectra base modules, so you can simply install the module. +

+
+
+
+

Configuration

+
+

+ No configuration options available. +

+
+
+
+

Usage

+
+

+ You will find two new menu items at Finance -> Configuration -> MIS Reporting: +

+
    +
  • Export MIS Report -> shows a PopUp to select the report to export
  • +
  • Import MIS Report -> shows a PopUp to select the file of exported report to import
  • +
+
+
+ +
+
+ +
\ No newline at end of file diff --git a/mis_builder_expimp/static/description/screenshot.png b/mis_builder_expimp/static/description/screenshot.png new file mode 100644 index 0000000..9cbb176 Binary files /dev/null and b/mis_builder_expimp/static/description/screenshot.png differ diff --git a/mis_builder_expimp/wizards/__init__.py b/mis_builder_expimp/wizards/__init__.py new file mode 100644 index 0000000..f3eef5c --- /dev/null +++ b/mis_builder_expimp/wizards/__init__.py @@ -0,0 +1,2 @@ +from . import mis_builder_export_wizard +from . import mis_builder_import_wizard \ No newline at end of file diff --git a/mis_builder_expimp/wizards/mis_builder_export_views.xml b/mis_builder_expimp/wizards/mis_builder_export_views.xml new file mode 100644 index 0000000..24f99f5 --- /dev/null +++ b/mis_builder_expimp/wizards/mis_builder_export_views.xml @@ -0,0 +1,45 @@ + + + + + mis.builder.export.wizard.view + mis.builder.export.wizard + +
+ + +
+

This wizard will export a MIS Report including all dependencies. +
After export you can import this file in another system to have the same MIS Report. +

+
+ + + + + + + + +
+
+ +
+
+ + Export MIS Report + ir.actions.act_window + mis.builder.export.wizard + form + new + + +
+
\ No newline at end of file diff --git a/mis_builder_expimp/wizards/mis_builder_export_wizard.py b/mis_builder_expimp/wizards/mis_builder_export_wizard.py new file mode 100644 index 0000000..1cc8cbe --- /dev/null +++ b/mis_builder_expimp/wizards/mis_builder_export_wizard.py @@ -0,0 +1,178 @@ +# Copyright 2014 ACSONE SA/NV () +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +import base64 +import json + +from lxml import etree + +from flectra import api, fields, models, _ + +QUERY_FIELDS = ['name', 'model_id', 'field_ids', 'aggregate', 'date_field', 'query_context', 'domain'] + +REPORT_FIELDS = ['name', 'description', 'account_model', 'subreport_ids'] + +KPI_FIELDS = [ + 'name', 'description', 'multi', 'auto_expand_accounts', 'style_expression', + 'type', 'compare_method', 'accumulation_method', 'sequence', + 'expression' +] + +STYLE_FIELDS = [ + 'name', + 'color_inherit', 'color', + 'background_color_inherit', 'background_color', + 'font_style_inherit', 'font_style', + 'font_weight_inherit', 'font_weight', + 'font_size_inherit', 'font_size', + 'indent_level_inherit', 'indent_level', + 'prefix_inherit', 'prefix', + 'suffix_inherit', 'suffix', + 'dp_inherit', 'dp', + 'divider_inherit', 'divider', + 'hide_empty_inherit', 'hide_empty', + 'hide_always_inherit', 'hide_always', +] + + +class MisBuilderExportWizard(models.TransientModel): + _name = "mis.builder.export.wizard" + _description = "Export MIS Builder Report" + + report_id = fields.Many2one( + comodel_name='mis.report', + string='Report', + required=True, + ) + + name = fields.Char( + string='File Name', + default='mis_report.json', + ) + + file_save = fields.Binary( + string='Settings File', + readonly=True, + ) + + state = fields.Selection([ + ('draft', 'Draft'), + ('download', 'Download') + ], default='draft') + + def export(self): + self.ensure_one() + + report_data = self._read_subreport_data(self.report_id.subreport_ids) + report_data.append(self._read_report_data(self.report_id)) + + json_data = json.dumps(report_data, indent=2) + # change state of the wizard + self.write({ + 'name': '%s.json' % self.report_id.name, + 'file_save': base64.b64encode(json_data.encode()), + 'state': 'download' + }) + + return { + 'name': _('Save'), + 'view_type': 'form', + 'view_mode': 'form', + 'res_model': self._name, + 'type': 'ir.actions.act_window', + 'target': 'new', + 'res_id': self.id, + } + + @api.model + def _read_subreport_data(self, subreport_ids): + report_data = [] + for subreport_id in subreport_ids: + report_data.extend(self._read_subreport_data(subreport_id.subreport_id.subreport_ids)) + report_data.append(self._read_report_data(subreport_id.subreport_id)) + + return report_data + + @api.model + def _read_report_data(self, report_id): + report_data = report_id.read(self._get_report_fields())[0] + del report_data['id'] + report_data['extid'] = '__export__.mis_report_%s' % report_id.id + report_data['move_lines_source'] = report_id.move_lines_source.model + if report_id.style_id: + report_data['style_id'] = report_id.style_id.read(self._get_style_fields())[0] + del report_data['style_id']['id'] + report_data['style_id']['extid'] = '__export__.mis_report_style_%s' % report_id.style_id.id + if report_id.query_ids: + report_data['query_ids'] = [] + for query_id in report_id.query_ids: + query_data = query_id.read(self._get_query_fields())[0] + del query_data['id'] + query_data['model_id'] = query_id.model_id.model + query_data['field_ids'] = query_id.field_ids.mapped('name') + query_data['extid'] = '__export__.mis_report_query_%s' % query_id.id + report_data['query_ids'].append(query_data) + if report_id.kpi_ids: + report_data['kpi_ids'] = [] + last_kpi_sequence = -1 + for kpi_id in report_id.kpi_ids.sorted(key=lambda s: s.sequence): + kpi_data = kpi_id.read(self._get_kpi_fields())[0] + # Fix of sequences to be unique - otherwise there are problems on importing reports with subkpis + if kpi_id.sequence <= last_kpi_sequence: + kpi_data['sequence'] = last_kpi_sequence + 1 + last_kpi_sequence = kpi_data['sequence'] + del kpi_data['id'] + if kpi_id.style_id: + kpi_data['style_id'] = kpi_id.style_id.read(self._get_style_fields())[0] + del kpi_data['style_id']['id'] + kpi_data['style_id']['extid'] = '__export__.mis_report_style_%s' % kpi_id.style_id.id + if kpi_id.auto_expand_accounts_style_id: + kpi_data['auto_expand_accounts_style_id'] = kpi_id.auto_expand_accounts_style_id.read(self._get_style_fields())[0] + del kpi_data['auto_expand_accounts_style_id']['id'] + kpi_data['auto_expand_accounts_style_id'][ + 'extid'] = '__export__.mis_report_style_%s' % kpi_id.auto_expand_accounts_style_id.id + kpi_data['extid'] = '__export__.mis_report_kpi_%s' % kpi_id.id + kpi_data['expression_ids'] = [] + for expression_id in kpi_id.expression_ids: + expression_data = expression_id.read(['sequence', 'name'])[0] + del expression_data['id'] + expression_data['extid'] = '__export__.mis_report_kpi_expression_%s' % expression_id.id + kpi_data['expression_ids'].append(expression_data) + report_data['kpi_ids'].append(kpi_data) + if report_id.subkpi_ids: + report_data['subkpi_ids'] = [] + for subkpi_id in report_id.subkpi_ids: + subkpi_data = subkpi_id.read(['sequence', 'name', 'description'])[0] + del subkpi_data['id'] + subkpi_data['extid'] = '__export__.mis_report_subkpi_%s' % subkpi_id.id + subkpi_data['expression_ids'] = [] + for expression_id in subkpi_id.expression_ids: + expression_data = expression_id.read(['sequence', 'name'])[0] + del expression_data['id'] + expression_data['kpi_id'] = '__export__.mis_report_kpi_%s' % expression_id.kpi_id.id + expression_data['extid'] = '__export__.mis_report_kpi_expression_%s' % expression_id.id + subkpi_data['expression_ids'].append(expression_data) + report_data['subkpi_ids'].append(subkpi_data) + if report_id.subreport_ids: + report_data['subreport_ids'] = [] + for subreport_id in report_id.subreport_ids: + subreport_data = subreport_id.read(['name'])[0] + del subreport_data['id'] + subreport_data['subreport_id'] = '__export__.mis_report_%s' % subreport_id.subreport_id.id + report_data['subreport_ids'].append(subreport_data) + return report_data + + @api.model + def _get_query_fields(self): + return QUERY_FIELDS + + @api.model + def _get_report_fields(self): + return REPORT_FIELDS + + @api.model + def _get_kpi_fields(self): + return KPI_FIELDS + + @api.model + def _get_style_fields(self): + return STYLE_FIELDS diff --git a/mis_builder_expimp/wizards/mis_builder_import_views.xml b/mis_builder_expimp/wizards/mis_builder_import_views.xml new file mode 100644 index 0000000..b2f57ab --- /dev/null +++ b/mis_builder_expimp/wizards/mis_builder_import_views.xml @@ -0,0 +1,34 @@ + + + + + mis.builder.import.wizard.view + mis.builder.import.wizard + +
+
+

This wizard will import a MIS report from json File.

+
+ +
+
+ +
+
+ + Import MIS Report + ir.actions.act_window + mis.builder.import.wizard + form + new + + +
+
\ No newline at end of file diff --git a/mis_builder_expimp/wizards/mis_builder_import_wizard.py b/mis_builder_expimp/wizards/mis_builder_import_wizard.py new file mode 100644 index 0000000..ce6d5d6 --- /dev/null +++ b/mis_builder_expimp/wizards/mis_builder_import_wizard.py @@ -0,0 +1,166 @@ +import copy +import json +import logging +import subprocess +import sys +from io import StringIO +from pathlib import Path + +from flectra import models, fields, api, _ +from flectra.tools import config, etree, base64, convert_xml_import, tempfile + +import os + +from flectra.exceptions import ValidationError + +_logger = logging.getLogger(__name__) + + +class MisBuilderImportWizard(models.TransientModel): + # Private attributes + _name = "mis.builder.import.wizard" + _description = "Import MIS Builder Report" + + file = fields.Binary( + string='Settings File', + required=True, + ) + + def __int__(self): + super(MisBuilderImportWizard, self).__int__() + + def import_report(self): + self.ensure_one() + + json_data = base64.b64decode(self.file) + report_data = json.loads(json_data) + + for report_entry in report_data: + report_values = self.__prepare_report_values(report_entry) + + self.__prepare_style_values(report_entry, report_values) + + self.__prepare_subreport_values(report_entry, report_values) + + self.__prepare_query_values(report_values) + + self.__prepare_kpi_values(report_values) + + subkpi_values = [] + if report_values.get('subkpi_ids'): + for subkpi_value in report_values['subkpi_ids']: + subkpi_values.append(subkpi_value) + del report_values['subkpi_ids'] + + new_report = self.env['mis.report'].create(report_values) + self._create_external_reference(new_report, report_entry['extid']) + + self.__update_subkpi_values(new_report, report_entry, subkpi_values) + + def __prepare_style_values(self, report_entry, report_values): + if report_entry.get('style_id'): + report_values['style_id'] = self._create_style(report_values['style_id']).id + + def __prepare_report_values(self, report_entry): + result = self.env['ir.model.data'].xmlid_to_res_id(report_entry['extid']) + if result: + raise ValidationError('Report already exists! Please delete it first before importing') + report_values = copy.deepcopy(report_entry) + + del report_values['extid'] + for kpi in report_values['kpi_ids']: + del kpi['extid'] + for expr in kpi['expression_ids']: + del expr['extid'] + + for kpi in report_values['subkpi_ids']: + del kpi['extid'] + for expr in kpi['expression_ids']: + del expr['extid'] + report_values['move_lines_source'] = self.env['ir.model'].search([('model', '=', report_values['move_lines_source'])]).id + return report_values + + def __update_subkpi_values(self, new_report, report_entry, subkpi_values): + temporary_extids = self.env['ir.model.data'] + subkpi_updates = [] + if subkpi_values: + for kpi_id in new_report.kpi_ids: + for kpi_data in report_entry['kpi_ids']: + if kpi_id.sequence == kpi_data['sequence']: + temporary_extids |= self._create_external_reference(kpi_id, kpi_data['extid']) + for subkpi_value in subkpi_values: + expression_updates = [] + for expression_value in subkpi_value['expression_ids']: + expression_value['kpi_id'] = self.env.ref(expression_value['kpi_id']).id + expression_updates.append((0, False, expression_value)) + subkpi_value['expression_ids'] = expression_updates + subkpi_updates.append((0, False, subkpi_value)) + if subkpi_updates: + new_report.write({'subkpi_ids': subkpi_updates}) + if temporary_extids: + temporary_extids.unlink() + + def __prepare_kpi_values(self, report_values): + if report_values.get('kpi_ids'): + kpi_values = [] + for kpi_value in report_values['kpi_ids']: + self.__prepare_style_values(kpi_value, kpi_value) + if kpi_value.get('auto_expand_accounts_style_id'): + kpi_value['auto_expand_accounts_style_id'] = self._create_style(kpi_value['auto_expand_accounts_style_id']).id + if report_values.get('subkpi_ids'): + del kpi_value['expression'] + del kpi_value['expression_ids'] + else: + if kpi_value.get('expression_ids'): + expression_values = [(0, False, v) for v in kpi_value['expression_ids']] + kpi_value['expression_ids'] = expression_values + kpi_values.append((0, False, kpi_value)) + report_values['kpi_ids'] = kpi_values + + def __prepare_query_values(self, report_values): + if report_values.get('query_ids'): + query_values = [] + for query_value in report_values['query_ids']: + query_model = self.env['ir.model'].search([('model', '=', query_value['model_id'])]) + query_value['model_id'] = query_model.id + query_value['field_ids'] = [(6, 0, [f.id for f in query_model.field_id if f.name in query_value['field_ids']])] + query_values.append((0, False, query_value)) + report_values['query_ids'] = query_values + + def __prepare_subreport_values(self, report_entry, report_values): + if report_entry.get('subreport_ids'): + subreport_values = [] + for subreport_value in report_entry['subreport_ids']: + subreport_value['subreport_id'] = self.env.ref(subreport_value['subreport_id']).id + subreport_values.append((0, False, subreport_value)) + report_values['subreport_ids'] = subreport_values + + @api.model + def _create_style(self, style_data): + exit_id = style_data['extid'] + result = self.env['ir.model.data'].xmlid_to_object(exit_id) + if result: + return result + + del style_data['extid'] + new_style = self.env['mis.report.style'].create(style_data) + self._create_external_reference(new_style, exit_id) + return new_style + + def _create_external_reference(self, obj, extid, temporary=False): + module, name = extid.split('.', 1) + existing = self.env['ir.model.data'].search([ + ('module', '=', module), + ('model', '=', obj._name), + ('name', '=', name), + ], limit=1) + if existing: + existing.res_id = obj.id + return existing + + return self.env['ir.model.data'].create({ + 'module': module, + 'model': obj._name, + 'name': name, + 'res_id': obj.id, + }) diff --git a/mis_builder_expimp_budget/__init__.py b/mis_builder_expimp_budget/__init__.py new file mode 100644 index 0000000..326c3db --- /dev/null +++ b/mis_builder_expimp_budget/__init__.py @@ -0,0 +1 @@ +from . import wizards \ No newline at end of file diff --git a/mis_builder_expimp_budget/__manifest__.py b/mis_builder_expimp_budget/__manifest__.py new file mode 100644 index 0000000..d44c2a4 --- /dev/null +++ b/mis_builder_expimp_budget/__manifest__.py @@ -0,0 +1,14 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "MIS Builder Import / Export", + "summary": """Import / Export Reports with all dependencies""", + "version": "2.0.1.0.0", + "license": "AGPL-3", + "author": "Jamotion GmbH", + "website": "https://gitlab.com/flectra-community/mis-builder", + "depends": ["mis_builder_expimp", "mis_builder_budget"], + "data": [], + "installable": True, + "auto_install": True, +} diff --git a/mis_builder_expimp_budget/security/ir.model.access.csv b/mis_builder_expimp_budget/security/ir.model.access.csv new file mode 100644 index 0000000..2408cf4 --- /dev/null +++ b/mis_builder_expimp_budget/security/ir.model.access.csv @@ -0,0 +1,3 @@ +"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" +access_mis_builder_import_wizard,access_mis_builder_import_wizard,model_mis_builder_import_wizard,account.group_account_manager,1,1,1,1 +access_mis_builder_export_wizard,access_mis_builder_export_wizard,model_mis_builder_export_wizard,account.group_account_manager,1,1,1,1 \ No newline at end of file diff --git a/mis_builder_expimp_budget/static/description/icon.png b/mis_builder_expimp_budget/static/description/icon.png new file mode 100644 index 0000000..1afa781 Binary files /dev/null and b/mis_builder_expimp_budget/static/description/icon.png differ diff --git a/mis_builder_expimp_budget/static/description/index.html b/mis_builder_expimp_budget/static/description/index.html new file mode 100644 index 0000000..b3c1c35 --- /dev/null +++ b/mis_builder_expimp_budget/static/description/index.html @@ -0,0 +1,69 @@ +
+
+
+
+ +

MIS Builder - Import / Export

+

Function to import / export MIS Report Templates

+
+
+
+ +
+
+

Full export and import of report template

+
+ +
+
+

+ When exporting a report, all related data are exported too: +

+
    +
  • KPIs
  • +
  • SubKPIs
  • +
  • Expressions
  • +
  • Queries
  • +
  • Sub Reports
  • +
  • Styles
  • +
+
+
+
+ +
+
+

Quick Start

+
+

Installation

+
+

+ There are no dependencies other than flectra base modules, so you can simply install the module. +

+
+
+
+

Configuration

+
+

+ No configuration options available. +

+
+
+
+

Usage

+
+

+ You will find two new menu items at Finance -> Configuration -> MIS Reporting: +

+
    +
  • Export MIS Report -> shows a PopUp to select the report to export
  • +
  • Import MIS Report -> shows a PopUp to select the file of exported report to import
  • +
+
+
+ +
+
+ +
\ No newline at end of file diff --git a/mis_builder_expimp_budget/static/description/screenshot.png b/mis_builder_expimp_budget/static/description/screenshot.png new file mode 100644 index 0000000..9cbb176 Binary files /dev/null and b/mis_builder_expimp_budget/static/description/screenshot.png differ diff --git a/mis_builder_expimp_budget/wizards/__init__.py b/mis_builder_expimp_budget/wizards/__init__.py new file mode 100644 index 0000000..207bc97 --- /dev/null +++ b/mis_builder_expimp_budget/wizards/__init__.py @@ -0,0 +1 @@ +from . import mis_builder_export_wizard diff --git a/mis_builder_expimp_budget/wizards/mis_builder_export_wizard.py b/mis_builder_expimp_budget/wizards/mis_builder_export_wizard.py new file mode 100644 index 0000000..5c69157 --- /dev/null +++ b/mis_builder_expimp_budget/wizards/mis_builder_export_wizard.py @@ -0,0 +1,33 @@ +# Copyright 2014 ACSONE SA/NV () +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +import base64 +import json + +from lxml import etree + +from flectra import api, fields, models, _ + + +class MisBuilderExportWizard(models.TransientModel): + _inherit = "mis.builder.export.wizard" + + @api.model + def _get_query_fields(self): + result = super(MisBuilderExportWizard, self)._get_query_fields() + return result + + @api.model + def _get_report_fields(self): + result = super(MisBuilderExportWizard, self)._get_report_fields() + return result + + @api.model + def _get_kpi_fields(self): + result = super(MisBuilderExportWizard, self)._get_kpi_fields() + result.append('budgetable') + return result + + @api.model + def _get_style_fields(self): + result = super(MisBuilderExportWizard, self)._get_style_fields() + return result