diff --git a/account_ebics/__manifest__.py b/account_ebics/__manifest__.py index 5080e83..0e15ad3 100644 --- a/account_ebics/__manifest__.py +++ b/account_ebics/__manifest__.py @@ -3,7 +3,7 @@ { "name": "EBICS banking protocol", - "version": "16.0.1.3.2", + "version": "16.0.1.4.0", "license": "LGPL-3", "author": "Noviat", "website": "https://www.noviat.com", diff --git a/account_ebics/models/account_bank_statement.py b/account_ebics/models/account_bank_statement.py index 64616e1..89c5eaf 100644 --- a/account_ebics/models/account_bank_statement.py +++ b/account_ebics/models/account_bank_statement.py @@ -8,3 +8,4 @@ class AccountBankStatement(models.Model): _inherit = "account.bank.statement" ebics_file_id = fields.Many2one(comodel_name="ebics.file", string="EBICS Data File") + import_format = fields.Char(readonly=True) diff --git a/account_ebics/models/ebics_config.py b/account_ebics/models/ebics_config.py index d84eb25..a7b6fe5 100644 --- a/account_ebics/models/ebics_config.py +++ b/account_ebics/models/ebics_config.py @@ -91,14 +91,6 @@ class EbicsConfig(models.Model): "between customer and financial institution. " "The human user also can authorise orders.", ) - ebics_files = fields.Char( - string="EBICS Files Root", - required=True, - readonly=True, - states={"draft": [("readonly", False)]}, - default=lambda self: self._default_ebics_files(), - help="Root Directory for EBICS File Transfer Folders.", - ) # We store the EBICS keys in a separate directory in the file system. # This directory requires special protection to reduce fraude. ebics_keys = fields.Char( @@ -253,14 +245,3 @@ class EbicsConfig(models.Model): ) % dirname ) - - def _check_ebics_files(self): - dirname = self.ebics_files or "" - if not os.path.exists(dirname): - raise UserError( - _( - "EBICS Files Root Directory %s is not available." - "\nPlease contact your system administrator." - ) - % dirname - ) diff --git a/account_ebics/models/ebics_file.py b/account_ebics/models/ebics_file.py index 20ac47e..7ef3514 100644 --- a/account_ebics/models/ebics_file.py +++ b/account_ebics/models/ebics_file.py @@ -16,6 +16,8 @@ from odoo.addons.base.models.res_bank import sanitize_account_number _logger = logging.getLogger(__name__) +DUP_CHECK_FORMATS = ["cfonb120", "camt053"] + class EbicsFile(models.Model): _name = "ebics.file" @@ -224,11 +226,21 @@ class EbicsFile(models.Model): res["notifications"].append({"type": "error", "message": message}) return (currency, journal) - def _process_download_result(self, res): + def _process_download_result(self, res, file_format=None): + """ + We perform a duplicate statement check after the creation of the bank + statements since we rely on Odoo Enterprise or OCA modules for the + bank statement creation. + From a development standpoint (code creation/maintenance) a check after + creation is the easiest way. + """ statement_ids = res["statement_ids"] notifications = res["notifications"] statements = self.env["account.bank.statement"].sudo().browse(statement_ids) - st_cnt = len(statement_ids) + if statements: + statements.write({"import_format": file_format}) + statements = self._statement_duplicate_check(res, statements) + st_cnt = len(statements) warning_cnt = error_cnt = 0 if notifications: errors = [] @@ -270,11 +282,11 @@ class EbicsFile(models.Model): date=statement.date, cpy=statement.company_id.name, ) - if statement_ids: - self.sudo().bank_statement_ids = [(4, x) for x in statement_ids] + if statements: + self.sudo().bank_statement_ids = [(4, x) for x in statements.ids] company_ids = self.sudo().bank_statement_ids.mapped("company_id").ids self.company_ids = [(6, 0, company_ids)] - ctx = dict(self.env.context, statement_ids=statement_ids) + ctx = dict(self.env.context, statement_ids=statements.ids) module = __name__.split("addons.")[1].split(".")[0] result_view = self.env.ref("%s.ebics_file_view_form_result" % module) return { @@ -289,13 +301,47 @@ class EbicsFile(models.Model): "type": "ir.actions.act_window", } + def _statement_duplicate_check(self, res, statements): + """ + This check is required for import modules that do not + set the 'unique_import_id' on the statement lines. + E.g. OCA camt import + """ + to_unlink = self.env["account.bank.statement"] + for statement in statements.filtered( + lambda r: r.import_format in DUP_CHECK_FORMATS + ): + dup = self.env["account.bank.statement"].search_count( + [ + ("id", "!=", statement.id), + ("name", "=", statement.name), + ("company_id", "=", statement.company_id.id), + ("date", "=", statement.date), + ("import_format", "=", statement.import_format), + ] + ) + if dup: + message = _( + "Statement %(st_name)s dated %(date)s has already been imported.", + st_name=statement.name, + date=statement.date, + ) + res["notifications"].append({"type": "warning", "message": message}) + to_unlink += statement + res["statement_ids"] = [ + x for x in res["statement_ids"] if x not in to_unlink.ids + ] + statements -= to_unlink + to_unlink.unlink() + return statements + def _process_cfonb120(self): import_module = "account_statement_import_fr_cfonb" self._check_import_module(import_module) res = {"statement_ids": [], "notifications": []} st_datas = self._split_cfonb(res) self._process_bank_statement_oca(res, st_datas) - return self._process_download_result(res) + return self._process_download_result(res, file_format="cfonb120") def _unlink_cfonb120(self): """ @@ -341,7 +387,7 @@ class EbicsFile(models.Model): def _process_camt052(self): import_module = "account_statement_import_camt" self._check_import_module(import_module) - return self._process_camt053(self) + return self._process_camt053(file_format="camt052") def _unlink_camt052(self): """ @@ -352,7 +398,7 @@ class EbicsFile(models.Model): def _process_camt054(self): import_module = "account_statement_import_camt" self._check_import_module(import_module) - return self._process_camt053(self) + return self._process_camt053(file_format="camt054") def _unlink_camt054(self): """ @@ -360,7 +406,7 @@ class EbicsFile(models.Model): EBICS data file and its related bank statements. """ - def _process_camt053(self): + def _process_camt053(self, file_format=None): """ The Odoo standard statement import is based on manual selection of a financial journal before importing the electronic statement file. @@ -395,7 +441,8 @@ class EbicsFile(models.Model): self._process_bank_statement_oca(res, st_datas) else: self._process_bank_statement_oe(res, st_datas) - return self._process_download_result(res) + file_format = file_format or "camt053" + return self._process_download_result(res, file_format=file_format) def _process_bank_statement_oca(self, res, st_datas): for st_data in st_datas: diff --git a/account_ebics/models/ebics_userid.py b/account_ebics/models/ebics_userid.py index 2e1cfb3..9fdd034 100644 --- a/account_ebics/models/ebics_userid.py +++ b/account_ebics/models/ebics_userid.py @@ -255,7 +255,6 @@ class EbicsUserID(models.Model): Create new keys and certificates for this user """ self.ensure_one() - self.ebics_config_id._check_ebics_files() if self.state != "draft": raise UserError( _("Set state to 'draft' before Bank Key (re)initialisation.") @@ -442,7 +441,6 @@ class EbicsUserID(models.Model): must be downloaded and checked for consistency. """ self.ensure_one() - self.ebics_config_id._check_ebics_files() if self.state != "get_bank_keys": raise UserError(_("Set state to 'Get Keys from Bank'.")) try: diff --git a/account_ebics/views/ebics_config_views.xml b/account_ebics/views/ebics_config_views.xml index f7220d9..15735fe 100644 --- a/account_ebics/views/ebics_config_views.xml +++ b/account_ebics/views/ebics_config_views.xml @@ -52,7 +52,6 @@ - diff --git a/account_ebics/wizards/ebics_admin_order.py b/account_ebics/wizards/ebics_admin_order.py index 3f28d0e..d0ffedf 100644 --- a/account_ebics/wizards/ebics_admin_order.py +++ b/account_ebics/wizards/ebics_admin_order.py @@ -27,7 +27,6 @@ class EbicsAdminOrder(models.TransientModel): def ebics_admin_order(self): self.ensure_one() - self.ebics_config_id._check_ebics_files() client = self._setup_client() if not client: self.note += ( diff --git a/account_ebics/wizards/ebics_xfer.py b/account_ebics/wizards/ebics_xfer.py index 44a5504..f5a9f6b 100644 --- a/account_ebics/wizards/ebics_xfer.py +++ b/account_ebics/wizards/ebics_xfer.py @@ -10,7 +10,6 @@ logging.basicConfig( import base64 import logging -import os from sys import exc_info from traceback import format_exception @@ -193,7 +192,6 @@ class EbicsXfer(models.TransientModel): def ebics_download(self): self.ensure_one() - self.ebics_config_id._check_ebics_files() ctx = self.env.context.copy() self.note = "" err_cnt = 0 @@ -532,43 +530,17 @@ class EbicsXfer(models.TransientModel): return ebics_files def _create_ebics_file(self, data, file_format, docname=None): - """ - Write the data as received over the EBICS connection - to a temporary file so that is is available for - analysis (e.g. in case formats are received that cannot - be handled in the current version of this module). - - TODO: add code to clean-up /tmp on a regular basis. - - After saving the data received we call the method to perform - file format specific processing. - """ - ebics_files_root = self.ebics_config_id.ebics_files - tmp_dir = os.path.normpath(ebics_files_root + "/tmp") - if not os.path.isdir(tmp_dir): - os.makedirs(tmp_dir, mode=0o700) fn_parts = [self.ebics_config_id.ebics_host, self.ebics_config_id.ebics_partner] if docname: fn_parts.append(docname) else: fn_date = self.date_to or fields.Date.today() fn_parts.append(fn_date.isoformat()) - base_fn = "_".join(fn_parts) - n = 1 - full_tmp_fn = os.path.normpath(tmp_dir + "/" + base_fn) - while os.path.exists(full_tmp_fn): - n += 1 - tmp_fn = base_fn + "_" + str(n).rjust(3, "0") - full_tmp_fn = os.path.normpath(tmp_dir + "/" + tmp_fn) - - with open(full_tmp_fn, "wb") as f: - f.write(data) - + fn = "_".join(fn_parts) ff_methods = self._file_format_methods() if file_format.name in ff_methods: data = ff_methods[file_format.name](data) - fn = base_fn suffix = file_format.suffix if suffix and not fn.endswith(suffix): fn = ".".join([fn, suffix]) diff --git a/account_ebics_oca_statement_import/wizards/account_statement_import.py b/account_ebics_oca_statement_import/wizards/account_statement_import.py index 9615723..09dcded 100644 --- a/account_ebics_oca_statement_import/wizards/account_statement_import.py +++ b/account_ebics_oca_statement_import/wizards/account_statement_import.py @@ -53,7 +53,7 @@ class AccountStatementImport(models.TransientModel): self._set_statement_name(st_vals) if st_vals.get("transactions"): transactions = True - super()._create_bank_statements(stmts_vals, result) + super()._create_bank_statements([st_vals], result) if result["statement_ids"] == statement_ids: # no statement has been created, this is the case # when all transactions have been imported already