From deebf75d1a9f876e0dd9f769d96a187969314d1b Mon Sep 17 00:00:00 2001 From: Luc De Meyer Date: Sun, 12 Feb 2023 17:07:49 +0100 Subject: [PATCH] add support for 16.0 Odoo OE camt parser --- .pre-commit-config.yaml | 3 +- account_ebics/README.rst | 12 + .../models/account_bank_statement.py | 2 +- account_ebics/models/ebics_config.py | 2 +- account_ebics/models/ebics_file.py | 264 ++++++++++++------ account_ebics/models/ebics_file_format.py | 2 +- account_ebics/models/ebics_userid.py | 2 +- .../wizards/ebics_change_passphrase.py | 2 +- account_ebics/wizards/ebics_xfer.py | 2 +- account_ebics_batch/__manifest__.py | 2 +- account_ebics_batch/data/ir_cron_data.xml | 2 +- account_ebics_batch/models/ebics_batch_log.py | 2 +- account_ebics_batch_payment/__manifest__.py | 2 +- .../models/account_batch_payment.py | 2 +- .../__manifest__.py | 6 +- account_ebics_oe/__manifest__.py | 2 +- account_ebics_oe_statement_import/README.rst | 22 -- account_ebics_oe_statement_import/__init__.py | 1 - .../__manifest__.py | 18 -- .../static/description/icon.png | Bin 17137 -> 0 bytes .../wizards/__init__.py | 1 - .../wizards/account_bank_statement_import.py | 60 ---- account_ebics_payment_order/__manifest__.py | 6 +- .../models/account_payment_order.py | 2 +- .../addons/account_ebics_oca_statement_import | 1 - .../setup.py | 6 - .../addons/account_ebics_oe_statement_import | 1 - .../setup.py | 6 - .../odoo/addons/account_ebics_payment_order | 1 - setup/account_ebics_payment_order/setup.py | 6 - 30 files changed, 207 insertions(+), 233 deletions(-) delete mode 100644 account_ebics_oe_statement_import/README.rst delete mode 100644 account_ebics_oe_statement_import/__init__.py delete mode 100644 account_ebics_oe_statement_import/__manifest__.py delete mode 100644 account_ebics_oe_statement_import/static/description/icon.png delete mode 100644 account_ebics_oe_statement_import/wizards/__init__.py delete mode 100644 account_ebics_oe_statement_import/wizards/account_bank_statement_import.py delete mode 120000 setup/account_ebics_oca_statement_import/odoo/addons/account_ebics_oca_statement_import delete mode 100644 setup/account_ebics_oca_statement_import/setup.py delete mode 120000 setup/account_ebics_oe_statement_import/odoo/addons/account_ebics_oe_statement_import delete mode 100644 setup/account_ebics_oe_statement_import/setup.py delete mode 120000 setup/account_ebics_payment_order/odoo/addons/account_ebics_payment_order delete mode 100644 setup/account_ebics_payment_order/setup.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 137d792..4904dc9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,8 @@ exclude: | (?x) # NOT INSTALLABLE ADDONS - ^server_environment_files/| + ^account_ebics_oca_statement_import/| + ^account_ebics_payment_order/| # END NOT INSTALLABLE ADDONS # Files and folders generated by bots, to avoid loops ^setup/|/static/description/index\.html$| diff --git a/account_ebics/README.rst b/account_ebics/README.rst index ccaf717..dd9e9d7 100644 --- a/account_ebics/README.rst +++ b/account_ebics/README.rst @@ -59,6 +59,16 @@ We also recommend to consider the installation of the following modules: | +- account_usability + + Recommended if you have multiple financial journals. + This module adds a number of accounting menu entries such as bank statement list view + which allows to see all statements downloaded via the ir.cron automated EBICS download. + + Cf. https://github.com/OCA/account-financial-tools + +| + - account_ebics_payment_order Recommended if you are using the OCA account_payment_order module. @@ -204,3 +214,5 @@ Known Issues / Roadmap ====================== - add support to import externally generated keys & certificates (currently only 3SKey signature certificate) +- For Odoo 16.0 the interaction with the OCA payment order and bank statement import modules (e.g. french CFONB) is not yet available. + diff --git a/account_ebics/models/account_bank_statement.py b/account_ebics/models/account_bank_statement.py index 6b6540f..e5eb289 100644 --- a/account_ebics/models/account_bank_statement.py +++ b/account_ebics/models/account_bank_statement.py @@ -1,4 +1,4 @@ -# Copyright 2009-2022 Noviat. +# Copyright 2009-2023 Noviat. # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). from odoo import fields, models diff --git a/account_ebics/models/ebics_config.py b/account_ebics/models/ebics_config.py index 1e6e3cb..1726ae2 100644 --- a/account_ebics/models/ebics_config.py +++ b/account_ebics/models/ebics_config.py @@ -1,4 +1,4 @@ -# Copyright 2009-2022 Noviat. +# Copyright 2009-2023 Noviat. # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). import logging diff --git a/account_ebics/models/ebics_file.py b/account_ebics/models/ebics_file.py index cf23a16..7f960a3 100644 --- a/account_ebics/models/ebics_file.py +++ b/account_ebics/models/ebics_file.py @@ -3,13 +3,18 @@ import base64 import logging +from copy import deepcopy from sys import exc_info from traceback import format_exception +from lxml import etree + from odoo import _, fields, models from odoo.exceptions import UserError from odoo.tools.safe_eval import safe_eval +from odoo.addons.base.models.res_bank import sanitize_account_number + _logger = logging.getLogger(__name__) @@ -65,10 +70,14 @@ class EbicsFile(models.Model): readonly=True, ) note = fields.Text(string="Notes") - note_process = fields.Text(string="Notes") + note_process = fields.Text( + string="Notes", + readonly=True, + ) company_ids = fields.Many2many( comodel_name="res.company", string="Companies", + readonly=True, help="Companies sharing this EBICS file.", ) @@ -100,7 +109,7 @@ class EbicsFile(models.Model): ff = self.format_id.download_process_method if ff in ff_methods: if ff_methods[ff].get("process"): - res = ff_methods[ff]["process"](self) + res = ff_methods[ff]["process"]() self.state = "done" return res else: @@ -111,9 +120,8 @@ class EbicsFile(models.Model): action = self.env["ir.actions.act_window"]._for_xml_id( "account.action_bank_statement_tree" ) - domain = safe_eval(action.get("domain") or "[]") - domain += [("id", "in", self._context.get("statement_ids"))] - action.update({"domain": domain}) + domain = [("id", "in", self.env.context.get("statement_ids"))] + action["domain"] = domain return action def button_close(self): @@ -169,50 +177,26 @@ class EbicsFile(models.Model): return False return True - def _process_result_action(self, res_action): - notifications = [] - st_line_ids = [] - statement_ids = [] + def _process_download_result(self, res): + statement_ids = res["statement_ids"] + notifications = res["notifications"] sts_data = [] - if res_action.get("type") and res_action["type"] == "ir.actions.client": - st_line_ids = res_action["context"].get("statement_line_ids", []) - if st_line_ids: - self.env.flush_all() - self.env.cr.execute( - """ - SELECT DISTINCT - absl.statement_id, - abs.name, abs.date, abs.company_id, - rc.name AS company_name - FROM account_bank_statement_line absl - INNER JOIN account_bank_statement abs - ON abs.id = absl.statement_id - INNER JOIN res_company rc - ON rc.id = abs.company_id - WHERE absl.id IN %s - ORDER BY date, company_id - """, - (tuple(st_line_ids),), - ) - sts_data = self.env.cr.dictfetchall() - else: - if res_action.get("res_id"): - st_ids = res_action["res_id"] - else: - st_ids = res_action["domain"][0][2] - statements = self.env["account.bank.statement"].browse(st_ids) - for statement in statements: - sts_data.append( - { - "statement_id": statement.id, - "date": statement.date, - "name": statement.name, - "company_name": statement.company_id.name, - } - ) - st_cnt = len(sts_data) + if statement_ids: + self.env.flush_all() + self.env.cr.execute( + """ + SELECT abs.name, abs.date, abs.company_id, rc.name AS company_name + FROM account_bank_statement abs + JOIN res_company rc ON rc.id = abs.company_id + WHERE abs.id in %s + ORDER BY abs.date, rc.id + """, + (tuple(res["statement_ids"]),), + ) + sts_data = self.env.cr.dictfetchall() + + st_cnt = len(statement_ids) warning_cnt = error_cnt = 0 - notifications = res_action["context"].get("notifications", []) if notifications: for notif in notifications: if notif["type"] == "error": @@ -234,7 +218,11 @@ class EbicsFile(models.Model): ) if st_cnt: self.note_process += "\n\n" - self.note_process += _("%s bank statements have been imported: ") % st_cnt + self.note_process += _( + "%(st_cnt)s bank statement%(sp)s been imported: ", + st_cnt=st_cnt, + sp=st_cnt == 1 and _(" has") or _("s have"), + ) self.note_process += "\n" for st_data in sts_data: self.note_process += ("\n%s, %s (%s)") % ( @@ -242,7 +230,6 @@ class EbicsFile(models.Model): st_data["name"], st_data["company_name"], ) - statement_ids = [x["statement_id"] for x in sts_data] if statement_ids: self.sudo().bank_statement_ids = [(4, x) for x in statement_ids] company_ids = self.sudo().bank_statement_ids.mapped("company_id").ids @@ -262,8 +249,13 @@ class EbicsFile(models.Model): "type": "ir.actions.act_window", } - @staticmethod def _process_cfonb120(self): + """ + Disable this code while waiting on OCA cfonb release for 16.0 + """ + # pylint: disable=W0101 + raise NotImplementedError + import_module = "account_statement_import_fr_cfonb" self._check_import_module(import_module) wiz_model = "account.statement.import" @@ -356,51 +348,56 @@ class EbicsFile(models.Model): result_action["domain"] = [("id", "in", statement_ids)] return self._process_result_action(result_action) - @staticmethod def _unlink_cfonb120(self): """ Placeholder for cfonb120 specific actions before removing the EBICS data file and its related bank statements. """ - @staticmethod def _process_camt052(self): import_module = "account_statement_import_camt" self._check_import_module(import_module) return self._process_camt053(self) - @staticmethod def _unlink_camt052(self): """ Placeholder for camt052 specific actions before removing the EBICS data file and its related bank statements. """ - @staticmethod def _process_camt054(self): import_module = "account_statement_import_camt" self._check_import_module(import_module) return self._process_camt053(self) - @staticmethod def _unlink_camt054(self): """ Placeholder for camt054 specific actions before removing the EBICS data file and its related bank statements. """ - @staticmethod - def _process_camt053(self): + def _process_camt053(self): # noqa C901 + """ + The Odoo standard statement import is based on manual selection + of a financial journal before importing the electronic statement file. + An EBICS download may return a single file containing a large number of + statements from different companies/journals. + Hence we need to split the CAMT file into + single statement CAMT files before we can call the logic + implemented by the Odoo OE or Community CAMT parsers. + + TODO: refactor method to enable removal of noqa C901 + """ modules = [ ("oca", "account_statement_import_camt"), ("oe", "account_bank_statement_import_camt"), ] - found = False - for _src, mod in modules: - if self._check_import_module(mod, raise_if_not_found=False): - found = True + author = False + for entry in modules: + if self._check_import_module(entry[1], raise_if_not_found=False): + author = entry[0] break - if not found: + if not author: raise UserError( _( "The module to process the '%(ebics_format)s' format is " @@ -410,15 +407,125 @@ class EbicsFile(models.Model): modules=", ".join([x[1] for x in modules]), ) ) - if _src == "oca": + res = {"statement_ids": [], "notifications": []} + try: + with self.env.cr.savepoint(): + msg_hdr = _("{} : Import failed for file %(fn)s:\n", fn=self.name) + file_data = base64.b64decode(self.data) + root = etree.fromstring(file_data, parser=etree.XMLParser(recover=True)) + if root is None: + message = msg_hdr.format(_("Error")) + message += _("Invalid XML file.") + res["notifications"].append({"type": "error", "message": message}) + ns = {k or "ns": v for k, v in root.nsmap.items()} + for i, stmt in enumerate(root[0].findall("ns:Stmt", ns), start=1): + msg_hdr = _( + "{} : Import failed for statement number %(index)s, filename %(fn)s:\n", + index=i, + fn=self.name, + ) + acc_number = sanitize_account_number( + stmt.xpath( + "ns:Acct/ns:Id/ns:IBAN/text() | ns:Acct/ns:Id/ns:Othr/ns:Id/text()", + namespaces=ns, + )[0] + ) + if not acc_number: + message = msg_hdr.format(_("Error")) + message += _("No bank account number found.") + res["notifications"].append( + {"type": "error", "message": message} + ) + continue + currency_code = stmt.xpath( + "ns:Acct/ns:Ccy/text() | ns:Bal/ns:Amt/@Ccy", namespaces=ns + )[0] + currency = self.env["res.currency"].search( + [("name", "=ilike", currency_code)], limit=1 + ) + if not currency: + message = msg_hdr.format(_("Error")) + message += _("Currency %(cc) not found.", cc=currency_code) + res["notifications"] = {"type": "error", "message": message} + continue + journal = self.env["account.journal"].search( + [ + ("type", "=", "bank"), + ( + "bank_account_id.sanitized_acc_number", + "ilike", + acc_number, + ), + ] + ) + journal_currency = ( + journal.currency_id or journal.company_id.currency_id + ) + if journal_currency != currency: + message = msg_hdr.format(_("Error")) + message += _( + "No financial journal found for Account Number %(nbr)s, " + "Currency %(cc)", + nbr=acc_number, + cc=currency_code, + ) + res["notifications"].append( + {"type": "error", "message": message} + ) + continue + + root_new = deepcopy(root) + for j, el in enumerate(root_new[0].findall("ns:Stmt", ns), start=1): + if j != i: + el.getparent.remove(el) + data = base64.b64encode(etree.tostring(root_new)) + + if author == "oca": + # TODO: implement _process_camt053_oca() once OCA camt is + # released for 16.0 + raise NotImplementedError + else: + self.env.company = journal.company_id + attachment = self.env["ir.attachment"].create( + {"name": self.name, "datas": data, "store_fname": self.name} + ) + act = journal._import_bank_statement(attachment) + for entry in act["domain"]: + if ( + isinstance(entry, tuple) + and entry[0] == "statement_id" + and entry[1] == "in" + ): + res["statement_ids"].extend(entry[2]) + break + notifications = act["context"]["notifications"] + if notifications: + res["notifications"].append(act["context"]["notifications"]) + + except UserError as e: + message = msg_hdr.format(_("Error")) + message += "".join(e.args) + res["notifications"].append({"type": "error", "message": message}) + except Exception: + tb = "".join(format_exception(*exc_info())) + message = msg_hdr.format(_("Error")) + message += tb + res["notifications"].append({"type": "error", "message": message}) + + if author == "oca": + # TODO: implement _process_camt053_oca() once OCA camt is + # released for 16.0 return self._process_camt053_oca() else: - return self._process_camt053_oe() + return self._process_download_result(res) def _process_camt053_oca(self): """ - TODO: merge common logic of this method and _process_cfonb120 + Disable this code while waiting on OCA CAMT parser for 16.0 """ + # pylint: disable=W0101 + raise NotImplementedError + wiz_model = "account.statement.import" wiz_vals = { "statement_filename": self.name, @@ -487,38 +594,12 @@ class EbicsFile(models.Model): result_action["domain"] = [("id", "in", statement_ids)] return self._process_result_action(result_action) - def _process_camt053_oe(self): - wiz_model = "account.bank.statement.import" - wiz_vals = { - "attachment_ids": [ - ( - 0, - 0, - {"name": self.name, "datas": self.data, "store_fname": self.name}, - ) - ] - } - wiz = ( - self.env[wiz_model].with_context(active_model="ebics.file").create(wiz_vals) - ) - res = wiz.import_file() - if res.get("res_model") == "account.bank.statement.import.journal.creation": - if res.get("context"): - bank_account = res["context"].get("default_bank_acc_number") - raise UserError( - _("No financial journal found for Company Bank Account %s") - % bank_account - ) - return self._process_result_action(res) - - @staticmethod def _unlink_camt053(self): """ Placeholder for camt053 specific actions before removing the EBICS data file and its related bank statements. """ - @staticmethod def _process_pain002(self): """ Placeholder for processing pain.002 files. @@ -526,7 +607,6 @@ class EbicsFile(models.Model): add import logic based upon OCA 'account_payment_return_import' """ - @staticmethod def _unlink_pain002(self): """ Placeholder for pain.002 specific actions before removing the diff --git a/account_ebics/models/ebics_file_format.py b/account_ebics/models/ebics_file_format.py index 48d3d47..a4f4081 100644 --- a/account_ebics/models/ebics_file_format.py +++ b/account_ebics/models/ebics_file_format.py @@ -1,4 +1,4 @@ -# Copyright 2009-2022 Noviat. +# Copyright 2009-2023 Noviat. # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). from odoo import api, fields, models diff --git a/account_ebics/models/ebics_userid.py b/account_ebics/models/ebics_userid.py index 10cb14c..6a863a9 100644 --- a/account_ebics/models/ebics_userid.py +++ b/account_ebics/models/ebics_userid.py @@ -1,4 +1,4 @@ -# Copyright 2009-2022 Noviat. +# Copyright 2009-2023 Noviat. # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). import base64 diff --git a/account_ebics/wizards/ebics_change_passphrase.py b/account_ebics/wizards/ebics_change_passphrase.py index 3f3c3ee..77faf6b 100644 --- a/account_ebics/wizards/ebics_change_passphrase.py +++ b/account_ebics/wizards/ebics_change_passphrase.py @@ -1,4 +1,4 @@ -# Copyright 2009-2022 Noviat. +# Copyright 2009-2023 Noviat. # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). import logging diff --git a/account_ebics/wizards/ebics_xfer.py b/account_ebics/wizards/ebics_xfer.py index b7db435..12eb03c 100644 --- a/account_ebics/wizards/ebics_xfer.py +++ b/account_ebics/wizards/ebics_xfer.py @@ -1,4 +1,4 @@ -# Copyright 2009-2022 Noviat. +# Copyright 2009-2023 Noviat. # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). """ diff --git a/account_ebics_batch/__manifest__.py b/account_ebics_batch/__manifest__.py index db12130..4f71eab 100644 --- a/account_ebics_batch/__manifest__.py +++ b/account_ebics_batch/__manifest__.py @@ -1,4 +1,4 @@ -# Copyright 2009-2022 Noviat. +# Copyright 2009-2023 Noviat. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). { diff --git a/account_ebics_batch/data/ir_cron_data.xml b/account_ebics_batch/data/ir_cron_data.xml index d33d59d..2726b75 100644 --- a/account_ebics_batch/data/ir_cron_data.xml +++ b/account_ebics_batch/data/ir_cron_data.xml @@ -10,7 +10,7 @@ 1 days -1 - + diff --git a/account_ebics_batch/models/ebics_batch_log.py b/account_ebics_batch/models/ebics_batch_log.py index f81af12..74d8b25 100644 --- a/account_ebics_batch/models/ebics_batch_log.py +++ b/account_ebics_batch/models/ebics_batch_log.py @@ -1,4 +1,4 @@ -# Copyright 2009-2022 Noviat. +# Copyright 2009-2023 Noviat. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from sys import exc_info diff --git a/account_ebics_batch_payment/__manifest__.py b/account_ebics_batch_payment/__manifest__.py index 747b538..f1dca19 100644 --- a/account_ebics_batch_payment/__manifest__.py +++ b/account_ebics_batch_payment/__manifest__.py @@ -1,4 +1,4 @@ -# Copyright 2009-2022 Noviat. +# Copyright 2009-2023 Noviat. # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). { diff --git a/account_ebics_batch_payment/models/account_batch_payment.py b/account_ebics_batch_payment/models/account_batch_payment.py index 62e51db..c313252 100644 --- a/account_ebics_batch_payment/models/account_batch_payment.py +++ b/account_ebics_batch_payment/models/account_batch_payment.py @@ -1,4 +1,4 @@ -# Copyright 2009-2022 Noviat. +# Copyright 2009-2023 Noviat. # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). from odoo import _, models diff --git a/account_ebics_oca_statement_import/__manifest__.py b/account_ebics_oca_statement_import/__manifest__.py index 24a56e4..3c4cdc2 100644 --- a/account_ebics_oca_statement_import/__manifest__.py +++ b/account_ebics_oca_statement_import/__manifest__.py @@ -1,4 +1,4 @@ -# Copyright 2020-2022 Noviat. +# Copyright 2020-2023 Noviat. # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). { @@ -13,6 +13,8 @@ "account_ebics", "account_statement_import", ], - "installable": True, + # installable False unit OCA statement import becomes + # available for 16.0 + "installable": False, "auto_install": True, } diff --git a/account_ebics_oe/__manifest__.py b/account_ebics_oe/__manifest__.py index a56f856..b442879 100644 --- a/account_ebics_oe/__manifest__.py +++ b/account_ebics_oe/__manifest__.py @@ -1,4 +1,4 @@ -# Copyright 2020-2022 Noviat. +# Copyright 2020-2023 Noviat. # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). { diff --git a/account_ebics_oe_statement_import/README.rst b/account_ebics_oe_statement_import/README.rst deleted file mode 100644 index d588ff2..0000000 --- a/account_ebics_oe_statement_import/README.rst +++ /dev/null @@ -1,22 +0,0 @@ -.. image:: https://img.shields.io/badge/license-LGPL--3-blue.png - :target: https://www.gnu.org/licenses/lgpl - :alt: License: LGPL-3 - -====================================================================== -Deploy account_ebics module with Odoo Enterprise Bank Statement Import -====================================================================== - -This module makes it possible to use Odoo Enterprise account_bank_statement_import -in combination with 'account_ebics'. - -This module will be installed automatically when following modules are activated -on your odoo database : - -- account_ebics_oe -- account_bank_statement_import - - -TODO ----- - -Adapt module for Odoo 16.0 bank statement import diff --git a/account_ebics_oe_statement_import/__init__.py b/account_ebics_oe_statement_import/__init__.py deleted file mode 100644 index 56429fc..0000000 --- a/account_ebics_oe_statement_import/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# from . import wizards diff --git a/account_ebics_oe_statement_import/__manifest__.py b/account_ebics_oe_statement_import/__manifest__.py deleted file mode 100644 index 5d201ed..0000000 --- a/account_ebics_oe_statement_import/__manifest__.py +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright 2020-2023 Noviat. -# License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). - -{ - "name": "account_ebics with Odoo Enterprise Bank Statement Import", - "summary": "Use Odoo Enterprise Bank Statement Import with account_ebics", - "version": "16.0.1.0.0", - "author": "Noviat", - "website": "https://www.noviat.com", - "category": "Hidden", - "license": "LGPL-3", - "depends": [ - "account_ebics_oe", - "account_bank_statement_import", - ], - "installable": True, - "auto_install": True, -} diff --git a/account_ebics_oe_statement_import/static/description/icon.png b/account_ebics_oe_statement_import/static/description/icon.png deleted file mode 100644 index 889d1294392923a091fba44560342126a11c1aac..0000000000000000000000000000000000000000 GIT binary patch literal 0 KcmV+b0RR6000031 literal 17137 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 diff --git a/account_ebics_oe_statement_import/wizards/__init__.py b/account_ebics_oe_statement_import/wizards/__init__.py deleted file mode 100644 index 7dafcd1..0000000 --- a/account_ebics_oe_statement_import/wizards/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import account_bank_statement_import diff --git a/account_ebics_oe_statement_import/wizards/account_bank_statement_import.py b/account_ebics_oe_statement_import/wizards/account_bank_statement_import.py deleted file mode 100644 index 03324a6..0000000 --- a/account_ebics_oe_statement_import/wizards/account_bank_statement_import.py +++ /dev/null @@ -1,60 +0,0 @@ -# Copyright 2009-2020 Noviat. -# License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). - -import logging - -from odoo import _, models - -_logger = logging.getLogger(__name__) - - -class AccountBankStatementImport(models.TransientModel): - _inherit = "account.bank.statement.import" - - def _check_parsed_data(self, stmts_vals, account_number): - """Basic and structural verifications""" - if self.env.context.get("active_model") == "ebics.file": - message = False - if len(stmts_vals) == 0: - message = _("This file doesn't contain any statement.") - if not message: - no_st_line = True - for vals in stmts_vals: - if vals["transactions"] and len(vals["transactions"]) > 0: - no_st_line = False - break - if no_st_line: - message = _("This file doesn't contain any transaction.") - if message: - log_msg = ( - _("Error detected while processing and EBICS File") - + ":\n" - + message - ) - _logger.warn(log_msg) - return - super()._check_parsed_data(stmts_vals, account_number) - - def _create_bank_statements(self, stmts_vals): - """ - Return error message to ebics.file when handling empty camt. - - Remarks/TODO: - We could add more info to the message (e.g. date, balance, ...) - and write this to the ebics.file, note field. - We could also create empty bank statement (in state done) to clearly - show days without transactions via the bank statement list view. - """ - if self.env.context.get("active_model") == "ebics.file": - transactions = False - for st_vals in stmts_vals: - if st_vals.get("transactions"): - transactions = True - break - if not transactions: - message = _("This file doesn't contain any transaction.") - st_line_ids = [] - notifications = {"type": "warning", "message": message, "details": ""} - return st_line_ids, [notifications] - - return super()._create_bank_statements(stmts_vals) diff --git a/account_ebics_payment_order/__manifest__.py b/account_ebics_payment_order/__manifest__.py index eeab9e2..b5be3f9 100644 --- a/account_ebics_payment_order/__manifest__.py +++ b/account_ebics_payment_order/__manifest__.py @@ -1,4 +1,4 @@ -# Copyright 2009-2022 Noviat. +# Copyright 2009-2023 Noviat. # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). { @@ -12,5 +12,7 @@ "data": [ "views/account_payment_order_views.xml", ], - "installable": True, + # installable False unit OCA payment order becomes + # available for 16.0 + "installable": False, } diff --git a/account_ebics_payment_order/models/account_payment_order.py b/account_ebics_payment_order/models/account_payment_order.py index 42045c2..ecd9c78 100644 --- a/account_ebics_payment_order/models/account_payment_order.py +++ b/account_ebics_payment_order/models/account_payment_order.py @@ -1,4 +1,4 @@ -# Copyright 2009-2022 Noviat. +# Copyright 2009-2023 Noviat. # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). from odoo import _, models diff --git a/setup/account_ebics_oca_statement_import/odoo/addons/account_ebics_oca_statement_import b/setup/account_ebics_oca_statement_import/odoo/addons/account_ebics_oca_statement_import deleted file mode 120000 index 766f43e..0000000 --- a/setup/account_ebics_oca_statement_import/odoo/addons/account_ebics_oca_statement_import +++ /dev/null @@ -1 +0,0 @@ -../../../../account_ebics_oca_statement_import \ No newline at end of file diff --git a/setup/account_ebics_oca_statement_import/setup.py b/setup/account_ebics_oca_statement_import/setup.py deleted file mode 100644 index 28c57bb..0000000 --- a/setup/account_ebics_oca_statement_import/setup.py +++ /dev/null @@ -1,6 +0,0 @@ -import setuptools - -setuptools.setup( - setup_requires=['setuptools-odoo'], - odoo_addon=True, -) diff --git a/setup/account_ebics_oe_statement_import/odoo/addons/account_ebics_oe_statement_import b/setup/account_ebics_oe_statement_import/odoo/addons/account_ebics_oe_statement_import deleted file mode 120000 index 75aca1f..0000000 --- a/setup/account_ebics_oe_statement_import/odoo/addons/account_ebics_oe_statement_import +++ /dev/null @@ -1 +0,0 @@ -../../../../account_ebics_oe_statement_import \ No newline at end of file diff --git a/setup/account_ebics_oe_statement_import/setup.py b/setup/account_ebics_oe_statement_import/setup.py deleted file mode 100644 index 28c57bb..0000000 --- a/setup/account_ebics_oe_statement_import/setup.py +++ /dev/null @@ -1,6 +0,0 @@ -import setuptools - -setuptools.setup( - setup_requires=['setuptools-odoo'], - odoo_addon=True, -) diff --git a/setup/account_ebics_payment_order/odoo/addons/account_ebics_payment_order b/setup/account_ebics_payment_order/odoo/addons/account_ebics_payment_order deleted file mode 120000 index 5ff2d5c..0000000 --- a/setup/account_ebics_payment_order/odoo/addons/account_ebics_payment_order +++ /dev/null @@ -1 +0,0 @@ -../../../../account_ebics_payment_order \ No newline at end of file diff --git a/setup/account_ebics_payment_order/setup.py b/setup/account_ebics_payment_order/setup.py deleted file mode 100644 index 28c57bb..0000000 --- a/setup/account_ebics_payment_order/setup.py +++ /dev/null @@ -1,6 +0,0 @@ -import setuptools - -setuptools.setup( - setup_requires=['setuptools-odoo'], - odoo_addon=True, -)