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
+
+
+
+
+
+ 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
+
+
+
+
+
+ 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..394f4cc
--- /dev/null
+++ b/mis_builder_expimp/wizards/mis_builder_import_wizard.py
@@ -0,0 +1,162 @@
+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']
+
+ 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