diff --git a/account_ebics_batch/README.rst b/account_ebics_batch/README.rst new file mode 100644 index 0000000..80f2d09 --- /dev/null +++ b/account_ebics_batch/README.rst @@ -0,0 +1,50 @@ +.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png + :target: https://www.gnu.org/licenses/agpl + :alt: License: AGPL-3 + +============================================ +Module to enable batch import of EBICS files +============================================ + +This module adds a cron job for the automated import of EBICS files. + +| + +A Log is created during the import in order to document import errors. +If errors have been detected, the Batch Import Log state is set to 'error'. + +When all EBICS Files have been imported correctly, the Batch Import Log state is set to 'done'. + +| + +The user can reprocess the imported EBICS files in status 'draft' via the Log object 'REPROCESS' button until all errors have been cleared. + +As an alternative, the user can force the Batch Import Log state to 'done' +(e.g. when the errors have been circumvented via manual encoding or the reprocessing of a single EBICS file). + +| + +Configuration +============= + +Adapt the 'EBICS Batch Import' ir.cron job created during the module installation. + +The cron job calls the following python method: + +| + +.. code-block:: python + + _batch_import() + + +The EBICS download will be performed on all confirmed EBICS connections. + +You can limit the automated operation to a subset of your EBICS connections via the ebics_config_ids parameter, e.g. + +| + +.. code-block:: python + + _batch_import(ebics_config_ids=[1,3]) + diff --git a/account_ebics_batch/__init__.py b/account_ebics_batch/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/account_ebics_batch/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/account_ebics_batch/__manifest__.py b/account_ebics_batch/__manifest__.py new file mode 100644 index 0000000..86060ca --- /dev/null +++ b/account_ebics_batch/__manifest__.py @@ -0,0 +1,21 @@ +# Copyright 2009-2024 Noviat. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "EBICS Files batch import", + "version": "18.0.1.0.0", + "license": "AGPL-3", + "author": "Noviat", + "website": "https://www.noviat.com", + "category": "Accounting & Finance", + "summary": "EBICS Files automated import and processing", + "depends": ["account_ebics"], + "data": [ + "security/ir.model.access.csv", + "data/ir_cron_data.xml", + "views/ebics_batch_log_views.xml", + "views/menu.xml", + ], + "installable": True, + "images": ["static/description/cover.png"], +} diff --git a/account_ebics_batch/data/ir_cron_data.xml b/account_ebics_batch/data/ir_cron_data.xml new file mode 100644 index 0000000..9ea3f24 --- /dev/null +++ b/account_ebics_batch/data/ir_cron_data.xml @@ -0,0 +1,15 @@ + + + + + EBICS Batch Import + + code + model._batch_import() + + 1 + days + + + + diff --git a/account_ebics_batch/i18n/account_ebics_batch.pot b/account_ebics_batch/i18n/account_ebics_batch.pot new file mode 100644 index 0000000..d6c1582 --- /dev/null +++ b/account_ebics_batch/i18n/account_ebics_batch.pot @@ -0,0 +1,222 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_ebics_batch +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0+e\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: account_ebics_batch +#: model:ir.model.fields,field_description:account_ebics_batch.field_ebics_batch_log__file_ids +msgid "Batch Import EBICS Files" +msgstr "" + +#. module: account_ebics_batch +#: model:ir.model.fields,field_description:account_ebics_batch.field_ebics_batch_log_item__note +#: model_terms:ir.ui.view,arch_db:account_ebics_batch.ebics_batch_log_view_form +msgid "Batch Import Log" +msgstr "" + +#. module: account_ebics_batch +#: model:ir.model.fields,field_description:account_ebics_batch.field_ebics_batch_log__log_ids +msgid "Batch Import Log Items" +msgstr "" + +#. module: account_ebics_batch +#: model_terms:ir.ui.view,arch_db:account_ebics_batch.ebics_batch_log_view_form +msgid "Batch Import Logs" +msgstr "" + +#. module: account_ebics_batch +#: model:ir.model.fields,field_description:account_ebics_batch.field_ebics_batch_log_item__log_id +msgid "Batch Object" +msgstr "" + +#. module: account_ebics_batch +#: model:ir.model.fields,field_description:account_ebics_batch.field_ebics_batch_log__create_uid +#: model:ir.model.fields,field_description:account_ebics_batch.field_ebics_batch_log_item__create_uid +msgid "Created by" +msgstr "" + +#. module: account_ebics_batch +#: model:ir.model.fields,field_description:account_ebics_batch.field_ebics_batch_log__create_date +#: model:ir.model.fields,field_description:account_ebics_batch.field_ebics_batch_log_item__create_date +msgid "Created on" +msgstr "" + +#. module: account_ebics_batch +#: model:ir.model.fields,field_description:account_ebics_batch.field_ebics_batch_log__date_from +msgid "Date From" +msgstr "" + +#. module: account_ebics_batch +#: model:ir.model.fields,field_description:account_ebics_batch.field_ebics_batch_log__date_to +msgid "Date To" +msgstr "" + +#. module: account_ebics_batch +#: model:ir.model.fields,field_description:account_ebics_batch.field_ebics_batch_log__display_name +#: model:ir.model.fields,field_description:account_ebics_batch.field_ebics_batch_log_item__display_name +msgid "Display Name" +msgstr "" + +#. module: account_ebics_batch +#: model:ir.model.fields.selection,name:account_ebics_batch.selection__ebics_batch_log__state__done +#: model:ir.model.fields.selection,name:account_ebics_batch.selection__ebics_batch_log_item__state__done +msgid "Done" +msgstr "" + +#. module: account_ebics_batch +#: model:ir.model.fields.selection,name:account_ebics_batch.selection__ebics_batch_log__state__draft +#: model:ir.model.fields.selection,name:account_ebics_batch.selection__ebics_batch_log_item__state__draft +msgid "Draft" +msgstr "" + +#. module: account_ebics_batch +#: model:ir.actions.server,name:account_ebics_batch.ir_cron_ebics_batch_import_ir_actions_server +msgid "EBICS Batch Import" +msgstr "" + +#. module: account_ebics_batch +#: model_terms:ir.ui.view,arch_db:account_ebics_batch.ebics_batch_log_view_form +msgid "EBICS Batch Import Log" +msgstr "" + +#. module: account_ebics_batch +#: model:ir.actions.act_window,name:account_ebics_batch.ebics_batch_log_action +#: model:ir.ui.menu,name:account_ebics_batch.ebics_batch_log_menu +msgid "EBICS Batch Import Logs" +msgstr "" + +#. module: account_ebics_batch +#: model:ir.model.fields,field_description:account_ebics_batch.field_ebics_batch_log__ebics_config_ids +msgid "EBICS Configurations" +msgstr "" + +#. module: account_ebics_batch +#: model_terms:ir.ui.view,arch_db:account_ebics_batch.ebics_batch_log_view_form +msgid "EBICS Files" +msgstr "" + +#. module: account_ebics_batch +#: model:ir.model.fields,field_description:account_ebics_batch.field_ebics_batch_log__file_count +msgid "EBICS Files Count" +msgstr "" + +#. module: account_ebics_batch +#: model:ir.model.fields.selection,name:account_ebics_batch.selection__ebics_batch_log__state__error +#: model:ir.model.fields.selection,name:account_ebics_batch.selection__ebics_batch_log_item__state__error +msgid "Error" +msgstr "" + +#. module: account_ebics_batch +#. odoo-python +#: code:addons/account_ebics_batch/models/ebics_batch_log.py:0 +#, python-format +msgid "Error while processing EBICS connection '%s' :\n" +msgstr "" + +#. module: account_ebics_batch +#: model_terms:ir.ui.view,arch_db:account_ebics_batch.ebics_batch_log_view_search +msgid "Group By" +msgstr "" + +#. module: account_ebics_batch +#: model:ir.model.fields,field_description:account_ebics_batch.field_ebics_batch_log__has_draft_files +msgid "Has Draft Files" +msgstr "" + +#. module: account_ebics_batch +#: model:ir.model.fields,field_description:account_ebics_batch.field_ebics_batch_log__id +#: model:ir.model.fields,field_description:account_ebics_batch.field_ebics_batch_log_item__id +msgid "ID" +msgstr "" + +#. module: account_ebics_batch +#: model:ir.model.fields,field_description:account_ebics_batch.field_ebics_batch_log__write_uid +#: model:ir.model.fields,field_description:account_ebics_batch.field_ebics_batch_log_item__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: account_ebics_batch +#: model:ir.model.fields,field_description:account_ebics_batch.field_ebics_batch_log__write_date +#: model:ir.model.fields,field_description:account_ebics_batch.field_ebics_batch_log_item__write_date +msgid "Last Updated on" +msgstr "" + +#. module: account_ebics_batch +#: model_terms:ir.ui.view,arch_db:account_ebics_batch.ebics_batch_log_view_form +msgid "Mark Done" +msgstr "" + +#. module: account_ebics_batch +#. odoo-python +#: code:addons/account_ebics_batch/models/ebics_batch_log.py:0 +#, python-format +msgid "" +"No EBICS UserID with stored passphrase found.\n" +"You should configure such a UserID for automated downloads." +msgstr "" + +#. module: account_ebics_batch +#: model:ir.model.fields,field_description:account_ebics_batch.field_ebics_batch_log_item__error_count +msgid "Number of Errors" +msgstr "" + +#. module: account_ebics_batch +#: model:ir.model,name:account_ebics_batch.model_ebics_batch_log_item +msgid "Object to store EBICS Batch Import Log Items" +msgstr "" + +#. module: account_ebics_batch +#: model:ir.model,name:account_ebics_batch.model_ebics_batch_log +msgid "Object to store EBICS Batch Import Logs" +msgstr "" + +#. module: account_ebics_batch +#. odoo-python +#: code:addons/account_ebics_batch/models/ebics_batch_log.py:0 +#, python-format +msgid "Only log objects in state 'draft' can be deleted !" +msgstr "" + +#. module: account_ebics_batch +#. odoo-python +#: code:addons/account_ebics_batch/models/ebics_batch_log.py:0 +#, python-format +msgid "Please set state to 'Confirm' and Reprocess this EBICS Import Log." +msgstr "" + +#. module: account_ebics_batch +#: model_terms:ir.ui.view,arch_db:account_ebics_batch.ebics_batch_log_view_form +msgid "Reprocess" +msgstr "" + +#. module: account_ebics_batch +#: model_terms:ir.ui.view,arch_db:account_ebics_batch.ebics_batch_log_view_form +msgid "Reprocess 'draft' EBICS Files" +msgstr "" + +#. module: account_ebics_batch +#: model_terms:ir.ui.view,arch_db:account_ebics_batch.ebics_batch_log_view_search +msgid "Search EBICS Batch Import Log Files" +msgstr "" + +#. module: account_ebics_batch +#: model_terms:ir.ui.view,arch_db:account_ebics_batch.ebics_batch_log_view_form +msgid "Set to Draft" +msgstr "" + +#. module: account_ebics_batch +#: model:ir.model.fields,field_description:account_ebics_batch.field_ebics_batch_log__state +#: model:ir.model.fields,field_description:account_ebics_batch.field_ebics_batch_log_item__state +#: model_terms:ir.ui.view,arch_db:account_ebics_batch.ebics_batch_log_view_search +msgid "State" +msgstr "" diff --git a/account_ebics_batch/models/__init__.py b/account_ebics_batch/models/__init__.py new file mode 100644 index 0000000..740bafa --- /dev/null +++ b/account_ebics_batch/models/__init__.py @@ -0,0 +1 @@ +from . import ebics_batch_log diff --git a/account_ebics_batch/models/ebics_batch_log.py b/account_ebics_batch/models/ebics_batch_log.py new file mode 100644 index 0000000..8600a40 --- /dev/null +++ b/account_ebics_batch/models/ebics_batch_log.py @@ -0,0 +1,207 @@ +# Copyright 2009-2024 Noviat. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from sys import exc_info +from traceback import format_exception + +from odoo import api, fields, models +from odoo.exceptions import UserError + + +class EbicsBatchLog(models.Model): + _name = "ebics.batch.log" + _description = "Object to store EBICS Batch Import Logs" + _order = "create_date desc" + + date_from = fields.Date() + date_to = fields.Date() + ebics_config_ids = fields.Many2many( + comodel_name="ebics.config", string="EBICS Configurations" + ) + log_ids = fields.One2many( + comodel_name="ebics.batch.log.item", + inverse_name="log_id", + string="Batch Import Log Items", + readonly=True, + ) + file_ids = fields.Many2many( + comodel_name="ebics.file", + string="Batch Import EBICS Files", + readonly=True, + ) + file_count = fields.Integer( + string="EBICS Files Count", compute="_compute_ebics_files_fields", readonly=True + ) + has_draft_files = fields.Boolean(compute="_compute_ebics_files_fields") + state = fields.Selection( + selection=[("draft", "Draft"), ("error", "Error"), ("done", "Done")], + required=True, + readonly=True, + default="draft", + ) + + @api.depends("file_ids") + def _compute_ebics_files_fields(self): + for rec in self: + rec.has_draft_files = "draft" in rec.file_ids.mapped("state") + rec.file_count = len(rec.file_ids) + + def unlink(self): + for log in self: + if log.state != "draft": + raise UserError( + self.env._("Only log objects in state 'draft' can be deleted !") + ) + return super().unlink() + + def button_draft(self): + self.state = "draft" + + def button_done(self): + self.state = "done" + + def reprocess(self): + import_dict = {"errors": []} + self._ebics_process(import_dict) + self._finalise_processing(import_dict) + + def view_ebics_files(self): + action = self.env["ir.actions.actions"]._for_xml_id( + "account_ebics.ebics_file_action_download" + ) + action["domain"] = [("id", "in", self.file_ids.ids)] + return action + + def _batch_import(self, ebics_config_ids=None, date_from=None, date_to=None): + """ + Call this method from a cron job to automate the EBICS import. + """ + log_model = self.env["ebics.batch.log"] + import_dict = {"errors": []} + configs = self.env["ebics.config"].browse(ebics_config_ids) or self.env[ + "ebics.config" + ].search( + [ + ("company_ids", "in", self.env.user.company_ids.ids), + ("state", "=", "confirm"), + ] + ) + log = log_model.create( + { + "ebics_config_ids": [(6, 0, configs.ids)], + "date_from": date_from, + "date_to": date_to, + } + ) + ebics_file_ids = [] + for config in configs: + err_msg = ( + self.env._("Error while processing EBICS connection '%s' :\n") + % config.name + ) + if config.state == "draft": + import_dict["errors"].append( + err_msg + + self.env._( + "Please set state to 'Confirm' and " + "Reprocess this EBICS Import Log." + ) + ) + continue + if not any(config.mapped("ebics_userid_ids.ebics_passphrase_store")): + import_dict["errors"].append( + err_msg + + self.env._( + "No EBICS UserID with stored passphrase found.\n" + "You should configure such a UserID for automated downloads." + ) + ) + continue + try: + with self.env.cr.savepoint(): + ebics_file_ids += self._ebics_import( + config, date_from, date_to, import_dict + ) + except UserError as e: + import_dict["errors"].append(err_msg + " ".join(e.args)) + except Exception: + tb = "".join(format_exception(*exc_info())) + import_dict["errors"].append(err_msg + tb) + log.file_ids = [(6, 0, ebics_file_ids)] + try: + log._ebics_process(import_dict) + except UserError as e: + import_dict["errors"].append(err_msg + " ".join(e.args)) + except Exception: + tb = "".join(format_exception(*exc_info())) + import_dict["errors"].append(err_msg + tb) + log._finalise_processing(import_dict) + + def _finalise_processing(self, import_dict): + log_item_model = self.env["ebics.batch.log.item"] + state = self.has_draft_files and "draft" or "done" + note = "" + error_count = 0 + if import_dict["errors"]: + state = "error" + note = "\n\n".join(import_dict["errors"]) + error_count = len(import_dict["errors"]) + log_item_model.create( + { + "log_id": self.id, + "state": state, + "note": note, + "error_count": error_count, + } + ) + self.state = state + + def _ebics_import(self, config, date_from, date_to, import_dict): + ebics_userids = config.ebics_userid_ids.filtered( + lambda r: r.ebics_passphrase_store + ) + t_userids = ebics_userids.filtered(lambda r: r.signature_class == "T") + ebics_userid = t_userids and t_userids[0] or ebics_userids[0] + xfer_wiz = ( + self.env["ebics.xfer"] + .with_context(ebics_download=True) + .create( + { + "ebics_config_id": config.id, + "date_from": date_from, + "date_to": date_to, + } + ) + ) + xfer_wiz._onchange_ebics_config_id() + xfer_wiz.ebics_userid_id = ebics_userid + res = xfer_wiz.ebics_download() + file_ids = res["context"].get("ebics_file_ids", []) + if res["context"]["err_cnt"]: + import_dict["errors"].append(xfer_wiz.note) + return file_ids + + def _ebics_process(self, import_dict): + to_process = self.file_ids.filtered(lambda r: r.state == "draft") + for ebics_file in to_process: + ebics_file.process() + + +class EbicsBatchLogItem(models.Model): + _name = "ebics.batch.log.item" + _description = "Object to store EBICS Batch Import Log Items" + _order = "create_date desc" + + log_id = fields.Many2one( + comodel_name="ebics.batch.log", + string="Batch Object", + ondelete="cascade", + readonly=True, + ) + state = fields.Selection( + selection=[("draft", "Draft"), ("error", "Error"), ("done", "Done")], + required=True, + readonly=True, + ) + note = fields.Text(string="Batch Import Log", readonly=True) + error_count = fields.Integer(string="Number of Errors", required=True, default=0) diff --git a/account_ebics_batch/pyproject.toml b/account_ebics_batch/pyproject.toml new file mode 100644 index 0000000..4231d0c --- /dev/null +++ b/account_ebics_batch/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/account_ebics_batch/security/ir.model.access.csv b/account_ebics_batch/security/ir.model.access.csv new file mode 100644 index 0000000..af76670 --- /dev/null +++ b/account_ebics_batch/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_ebics_batch_log,ebics.batch.log,model_ebics_batch_log,account.group_account_invoice,1,1,1,1 +access_ebics_batch_log_item,ebics.batch.log.item,model_ebics_batch_log_item,account.group_account_invoice,1,1,1,1 diff --git a/account_ebics_batch/static/description/cover.png b/account_ebics_batch/static/description/cover.png new file mode 100644 index 0000000..41c4cec Binary files /dev/null and b/account_ebics_batch/static/description/cover.png differ diff --git a/account_ebics_batch/static/description/icon.png b/account_ebics_batch/static/description/icon.png new file mode 100644 index 0000000..889d129 Binary files /dev/null and b/account_ebics_batch/static/description/icon.png differ diff --git a/account_ebics_batch/static/description/index.html b/account_ebics_batch/static/description/index.html new file mode 100644 index 0000000..c5ccb4c --- /dev/null +++ b/account_ebics_batch/static/description/index.html @@ -0,0 +1,406 @@ + + + + + +README.rst + + + +
+ + +License: AGPL-3 +
+

Module to enable batch import of EBICS files

+

This module adds a cron job for the automated import of EBICS files.

+
+

+
+

A Log is created during the import in order to document import errors. +If errors have been detected, the Batch Import Log state is set to 'error'.

+

When all EBICS Files have been imported correctly, the Batch Import Log state is set to 'done'.

+
+

+
+

The user can reprocess the imported EBICS files in status 'draft' via the Log object 'REPROCESS' button until all errors have been cleared.

+

As an alternative, the user can force the Batch Import Log state to 'done' +(e.g. when the errors have been circumvented via manual encoding or the reprocessing of a single EBICS file).

+
+

+
+
+

Configuration

+

Adapt the 'EBICS Batch Import' ir.cron job created during the module installation.

+

The cron job calls the following python method:

+
+

+
+
+_batch_import()
+
+

The EBICS download will be performed on all confirmed EBICS connections.

+

You can limit the automated operation to a subset of your EBICS connections via the ebics_config_ids parameter, e.g.

+
+

+
+
+_batch_import(ebics_config_ids=[1,3])
+
+
+
+
+ + diff --git a/account_ebics_batch/views/ebics_batch_log_views.xml b/account_ebics_batch/views/ebics_batch_log_views.xml new file mode 100644 index 0000000..1d20341 --- /dev/null +++ b/account_ebics_batch/views/ebics_batch_log_views.xml @@ -0,0 +1,117 @@ + + + + + ebics.batch.log.search + ebics.batch.log + + + + + + + + + + + + + + + + ebics.batch.log.list + ebics.batch.log + + + + + + + + + + + ebics.batch.log.form + ebics.batch.log + +
+
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ + + EBICS Batch Import Logs + ir.actions.act_window + ebics.batch.log + list,form + + + + +
diff --git a/account_ebics_batch/views/menu.xml b/account_ebics_batch/views/menu.xml new file mode 100644 index 0000000..21343b8 --- /dev/null +++ b/account_ebics_batch/views/menu.xml @@ -0,0 +1,12 @@ + + + + + +