From b963b1891e9381d3fb20147e86b14838aa81783e Mon Sep 17 00:00:00 2001 From: Luc De Meyer Date: Sat, 5 Dec 2020 18:56:12 +0100 Subject: [PATCH] [13.0]account_ebics 1.3.0 - generic order type --- account_ebics/data/ebics_file_format.xml | 201 +++++++++++------- .../migrations/13.0.1.3/post-migration.py | 41 ++++ .../migrations/13.0.1.3/pre-migration.py | 70 ++++++ account_ebics/models/ebics_file.py | 27 ++- account_ebics/models/ebics_file_format.py | 34 ++- .../models/fintech_ebics_register.py | 4 +- .../views/ebics_file_format_views.xml | 22 +- account_ebics/wizards/ebics_xfer.py | 133 +++++++----- 8 files changed, 376 insertions(+), 156 deletions(-) create mode 100644 account_ebics/migrations/13.0.1.3/post-migration.py create mode 100644 account_ebics/migrations/13.0.1.3/pre-migration.py diff --git a/account_ebics/data/ebics_file_format.xml b/account_ebics/data/ebics_file_format.xml index a910b5f..7aa2601 100644 --- a/account_ebics/data/ebics_file_format.xml +++ b/account_ebics/data/ebics_file_format.xml @@ -2,104 +2,151 @@ - - - camt.053.001.02.stm - down - C53 - Bank Statement in Format camt.053 - c53.xml - + - - - camt.052.001.02.stm + + camt.052 down C52 - Bank Statement in Format camt.052 + camt.052 + bank to customer account report in format camt.052 c52.xml - - - pain.001.001.03.sct - up - CCT - Payment Order in Format pain.001.001.03 - xml + + camt.052 + down + Z52 + camt.052 + bank to customer account report in format camt.052 + c52.xml + + + + camt.053 + down + C53 + camt.053 + Bank to customer statement report in format camt.053 + c53.xml - - pain.008.001.02.sdd - up - CDD - Sepa Core Direct Debit Order in Format pain.008.001.02 - xml + + camt.053 + down + Z53 + camt.053 + Bank to customer statement report in format camt.053 + c53.xml + + + + camt.054 + down + C54 + camt.054 + Bank to customer debit credit notification in format camt.054 + c52.xml - - pain.008.001.02.sbb - up - CDB - Sepa Direct Debit (B2B) Order in Format pain.008.001.02 - xml + + camt.054 + down + Z54 + camt.054 + Bank to customer debit credit notification in format camt.054 + c52.xml - - - pain.001 - up - XE2 - Payment Order in Format pain.001.001.03 - xml - - - - - pain.008 - up - XE3 - Direct Debit Order in Format pain.008.001.02 - xml - - - - + camt.xxx.cfonb120.stm down FDL - Bank Statement in Format cfonb120 + cfonb120 + Bank to customer statement report in format cfonb120 cfonb120.dat - - + + pain.002 + down + CDZ + Payment status report for direct debit in format pain.002 + psr.xml + + + + pain.002 + down + Z01 + Payment status report for direct debit in format pain.002 + psr.xml + + + + + + pain.001.001.03 + up + CCT + Payment Order in format pain.001.001.03 + cct.xml + + + + pain.001.001.03 + up + XE2 + Payment Order in format pain.001.001.03 + cct.xml + + + + pain.008.001.02.sdd + up + CDD + Sepa Core Direct Debit Order in format pain.008.001.02 + sdd.xml + + + + pain.008.001.02.sdd + up + CDD + Sepa Core Direct Debit Order in format pain.008.001.02 + sdd.xml + + + + pain.008.001.02.sdd + up + XE3 + Sepa Core Direct Debit Order in format pain.008.001.02 + sdd.xml + + + + pain.008.001.02.sbb + up + CDB + Sepa Direct Debit (B2B) Order in format pain.008.001.02 + sbb.xml + + + + pain.008.001.02.sbb + up + XE4 + Sepa Direct Debit (B2B) Order in format pain.008.001.02 + sbb.xml + + + pain.001.001.02.sct up FUL - Payment Order in Format pain.001.001.02 - xml + Payment Order in format pain.001.001.02 + cct.xml diff --git a/account_ebics/migrations/13.0.1.3/post-migration.py b/account_ebics/migrations/13.0.1.3/post-migration.py new file mode 100644 index 0000000..a082120 --- /dev/null +++ b/account_ebics/migrations/13.0.1.3/post-migration.py @@ -0,0 +1,41 @@ +# Copyright 2009-2020 Noviat. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +_FILE_FORMATS = [ + {'xml_id_name': 'ebics_ff_C52', + 'download_process_method': 'camt.052', + }, + {'xml_id_name': 'ebics_ff_C53', + 'download_process_method': 'camt.053', + }, + {'xml_id_name': 'ebics_ff_FDL_camt_xxx_cfonb120_stm', + 'download_process_method': 'cfonb120', + }, + +] + + +def migrate(cr, version): + for ff in _FILE_FORMATS: + _update_file_format(cr, ff) + + +def _update_file_format(cr, ff): + cr.execute( + """ + SELECT res_id FROM ir_model_data + WHERE module='account_ebics' AND name='{}' + """.format(ff['xml_id_name']) + ) + res = cr.fetchone() + if res: + cr.execute( + """ + UPDATE ebics_file_format + SET download_process_method='{download_process_method}' + WHERE id={ff_id}; + """.format( + download_process_method=ff['download_process_method'], + ff_id=res[0] + ) + ) diff --git a/account_ebics/migrations/13.0.1.3/pre-migration.py b/account_ebics/migrations/13.0.1.3/pre-migration.py new file mode 100644 index 0000000..7799b26 --- /dev/null +++ b/account_ebics/migrations/13.0.1.3/pre-migration.py @@ -0,0 +1,70 @@ +# Copyright 2009-2020 Noviat. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +_FILE_FORMATS = [ + {'old_xml_id_name': 'ebics_ff_camt_052_001_02_stm', + 'new_xml_id_name': 'ebics_ff_C52', + 'new_name': 'camt.052', + }, + {'old_xml_id_name': 'ebics_ff_camt_053_001_02_stm', + 'new_xml_id_name': 'ebics_ff_C53', + 'new_name': 'camt.053', + }, + {'old_xml_id_name': 'ebics_ff_camt_xxx_cfonb120_stm', + 'new_xml_id_name': 'ebics_ff_FDL_camt_xxx_cfonb120_stm', + }, + {'old_xml_id_name': 'ebics_ff_pain_001_001_03_sct', + 'new_xml_id_name': 'ebics_ff_CCT', + }, + {'old_xml_id_name': 'ebics_ff_pain_001', + 'new_xml_id_name': 'ebics_ff_XE2', + 'new_name': 'pain.001.001.03', + }, + {'old_xml_id_name': 'ebics_ff_pain_008_001_02_sdd', + 'new_xml_id_name': 'ebics_ff_CDD', + }, + {'old_xml_id_name': 'ebics_ff_pain_008', + 'new_xml_id_name': 'ebics_ff_XE3', + }, + {'old_xml_id_name': 'ebics_ff_pain_008_001_02_sbb', + 'new_xml_id_name': 'ebics_ff_CDB', + }, + {'old_xml_id_name': 'ebics_ff_pain_001_001_02_sct', + 'new_xml_id_name': 'ebics_ff_FUL_pain_001_001_02_sct', + }, +] + + +def migrate(cr, version): + if not version: + return + + for ff in _FILE_FORMATS: + _update_file_format(cr, ff) + + +def _update_file_format(cr, ff): + cr.execute( + """ + SELECT id, res_id FROM ir_model_data + WHERE module='account_ebics' AND name='{}' + """.format(ff['old_xml_id_name']) + ) + res = cr.fetchone() + if res: + query = """ + UPDATE ir_model_data + SET name='{new_xml_id_name}' + WHERE id={xml_id}; + """.format( + new_xml_id_name=ff["new_xml_id_name"], xml_id=res[0] + ) + if ff.get('new_name'): + query += """ + UPDATE ebics_file_format + SET name='{new_name}' + WHERE id={ff_id}; + """.format( + new_name=ff["new_name"], ff_id=res[1] + ) + cr.execute(query) diff --git a/account_ebics/models/ebics_file.py b/account_ebics/models/ebics_file.py index 90d7de6..36f2042 100644 --- a/account_ebics/models/ebics_file.py +++ b/account_ebics/models/ebics_file.py @@ -70,7 +70,7 @@ class EbicsFile(models.Model): raise UserError(_( "You can only remove EBICS files in state 'Draft'.")) # execute format specific actions - ff = ebics_file.format_id.name + ff = ebics_file.format_id.download_process_method if ff in ff_methods: if ff_methods[ff].get('unlink'): ff_methods[ff]['unlink'](ebics_file) @@ -92,7 +92,7 @@ class EbicsFile(models.Model): self = self.with_context(ctx) self.note_process = '' ff_methods = self._file_format_methods() - ff = self.format_id.name + ff = self.format_id.download_process_method if ff in ff_methods: if ff_methods[ff].get('process'): res = ff_methods[ff]['process'](self) @@ -120,15 +120,18 @@ class EbicsFile(models.Model): for extra file formats. """ res = { - 'camt.xxx.cfonb120.stm': + 'cfonb120': {'process': self._process_cfonb120, 'unlink': self._unlink_cfonb120}, - 'camt.052.001.02.stm': + 'camt.052': {'process': self._process_camt052, 'unlink': self._unlink_camt052}, - 'camt.053.001.02.stm': + 'camt.053': {'process': self._process_camt053, 'unlink': self._unlink_camt053}, + 'camt.054': + {'process': self._process_camt054, + 'unlink': self._unlink_camt054}, } return res @@ -294,6 +297,20 @@ class EbicsFile(models.Model): """ pass + @staticmethod + def _process_camt054(self): + import_module = 'account_bank_statement_import_camt_oca' + 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. + """ + pass + @staticmethod def _process_camt053(self): import_module = 'account_bank_statement_import_camt%' diff --git a/account_ebics/models/ebics_file_format.py b/account_ebics/models/ebics_file_format.py index e11f9e3..5864644 100644 --- a/account_ebics/models/ebics_file_format.py +++ b/account_ebics/models/ebics_file_format.py @@ -1,29 +1,41 @@ # Copyright 2009-2020 Noviat. # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). -from odoo import fields, models +from odoo import api, fields, models class EbicsFileFormat(models.Model): _name = 'ebics.file.format' _description = 'EBICS File Formats' - _order = 'type,name' + _order = 'type,name,order_type' name = fields.Char( string='Request Type', required=True, - help="E.g. camt.053.001.02.stm, camt.xxx.cfonb120.stm, " - "pain.001.001.03.sct (check your EBICS contract).\n") + help="E.g. camt.xxx.cfonb120.stm, pain.001.001.03.sct.\n" + "Specify camt.052, camt.053, camt.054 for camt " + "Order Types such as C53, Z53, C54, Z54.\n" + "This name has to match the 'Request Type' in your " + "EBICS contract for Order Type 'FDL' or 'FUL'.\n") type = fields.Selection( selection=[('down', 'Download'), ('up', 'Upload')], required=True) order_type = fields.Char( string='Order Type', + required=True, help="E.g. C53 (check your EBICS contract).\n" "For most banks in France you should use the " "format neutral Order Types 'FUL' for upload " "and 'FDL' for download.") + download_process_method = fields.Selection( + selection='_selection_download_process_method', + help="Enable processing within Odoo of the downloaded file " + "via the 'Process' button." + "E.g. specify camt.053 to import a camt.053 file and create " + "a bank statement.") + # TODO: + # move signature_class parameter so that it can be set per EBICS config signature_class = fields.Selection( selection=[('E', 'Single signature'), ('T', 'Transport signature')], @@ -32,9 +44,19 @@ class EbicsFileFormat(models.Model): "ERP system when using class 'E' to prevent unauthorised " "users to make supplier payments." "\nLeave this field empty to use the default " - "defined for your bank connection.") + "defined for your EBICS UserID.") description = fields.Char() suffix = fields.Char( required=True, help="Specify the filename suffix for this File Format." - "\nE.g. camt.053.xml") + "\nE.g. c53.xml") + + @api.model + def _selection_download_process_method(self): + methods = self.env['ebics.file']._file_format_methods().keys() + return [(x, x) for x in methods] + + @api.onchange('type') + def _onchange_type(self): + if self.type == 'up': + self.download_process_method = False diff --git a/account_ebics/models/fintech_ebics_register.py b/account_ebics/models/fintech_ebics_register.py index 5a97aaa..f908637 100644 --- a/account_ebics/models/fintech_ebics_register.py +++ b/account_ebics/models/fintech_ebics_register.py @@ -32,10 +32,10 @@ try: keycode=fintech_register_keycode, users=fintech_register_users) except RuntimeError as e: - if e.message == "'register' can be called only once": + if str(e) == "'register' can be called only once": pass else: - _logger.error(e.message) + _logger.error(str(e)) fintech.register() except Exception: msg = "fintech.register error" diff --git a/account_ebics/views/ebics_file_format_views.xml b/account_ebics/views/ebics_file_format_views.xml index 7e901ac..79ed9e2 100644 --- a/account_ebics/views/ebics_file_format_views.xml +++ b/account_ebics/views/ebics_file_format_views.xml @@ -20,13 +20,21 @@ ebics.file.format
- - - - - - - + + + + + + + + + + + + + diff --git a/account_ebics/wizards/ebics_xfer.py b/account_ebics/wizards/ebics_xfer.py index 0f97f08..f31df5f 100644 --- a/account_ebics/wizards/ebics_xfer.py +++ b/account_ebics/wizards/ebics_xfer.py @@ -64,8 +64,10 @@ class EbicsXfer(models.TransientModel): format_id = fields.Many2one( comodel_name='ebics.file.format', string='EBICS File Format', - help="Select EBICS File Format to upload/download.") + help="Select EBICS File Format to upload/download." + "\nLeave blank to download all available files.") order_type = fields.Char( + related='format_id.order_type', string='Order Type', help="For most banks in France you should use the " "format neutral Order Types 'FUL' for upload " @@ -160,56 +162,73 @@ class EbicsXfer(models.TransientModel): self.note = '' client = self._setup_client() if client: + download_formats = ( + self.format_id + or self.ebics_config_id.ebics_file_format_ids.filtered( + lambda r: r.type == 'down' + ) + ) ebics_files = self.env['ebics.file'] - success = False - order_type = self.order_type or 'FDL' date_from = self.date_from and self.date_from.isoformat() or None date_to = self.date_to and self.date_to.isoformat() or None - try: - if order_type == 'FDL': - data = client.FDL(self.format_id.name, date_from, date_to) + for df in download_formats: + try: + success = False + if df.order_type == 'FDL': + data = client.FDL(df.name, date_from, date_to) + else: + params = None + if date_from and date_to: + params = {'DateRange': { + 'Start': date_from, + 'End': date_to, + }} + data = client.download(df.order_type, params=params) + ebics_files += self._handle_download_data(data, df) + success = True + except EbicsFunctionalError: + e = exc_info() + self.note += '\n' + self.note += _( + "EBICS Functional Error during download of File Format %s (%s):" + ) % (df.name, df.order_type) + self.note += '\n' + self.note += '%s (code: %s)' % (e[1].message, e[1].code) + except EbicsTechnicalError: + e = exc_info() + self.note += '\n' + self.note += _( + "EBICS Technical Error during download of File Format %s (%s):" + ) % (df.name, df.order_type) + self.note += '\n' + self.note += '%s (code: %s)' % (e[1].message, e[1].code) + except EbicsVerificationError: + self.note += '\n' + self.note += _( + "EBICS Verification Error during download of " + "File Format %s (%s):" + ) % (df.name, df.order_type) + self.note += '\n' + self.note += _("The EBICS response could not be verified.") + except UserError as e: + self.note += '\n' + self.note += _( + "Warning during download of File Format %s (%s):" + ) % (df.name, df.order_type) + self.note += '\n' + self.note += e.name + except Exception: + self.note += '\n' + self.note += _( + "Unknown Error during download of File Format %s (%s):" + ) % (df.name, df.order_type) + tb = ''.join(format_exception(*exc_info())) + self.note += '\n%s' % tb else: - params = None - if date_from and date_to: - params = {'DateRange': { - 'Start': date_from, - 'End': date_to, - }} - data = client.download(order_type, params=params) - ebics_files += self._handle_download_data(data, self.format_id) - success = True - except EbicsFunctionalError: - e = exc_info() - self.note += '\n' - self.note += _("EBICS Functional Error:") - self.note += '\n' - self.note += '%s (code: %s)' % (e[1].message, e[1].code) - except EbicsTechnicalError: - e = exc_info() - self.note += '\n' - self.note += _("EBICS Technical Error:") - self.note += '\n' - self.note += '%s (code: %s)' % (e[1].message, e[1].code) - except EbicsVerificationError: - self.note += '\n' - self.note += _("EBICS Verification Error:") - self.note += '\n' - self.note += _("The EBICS response could not be verified.") - except UserError as e: - self.note += '\n' - self.note += _("Warning:") - self.note += '\n' - self.note += e.name - except Exception: - self.note += '\n' - self.note += _("Unknown Error") - tb = ''.join(format_exception(*exc_info())) - self.note += '\n%s' % tb - else: - # mark received data so that it is not included in further - # downloads - trans_id = client.last_trans_id - client.confirm_download(trans_id=trans_id, success=success) + # mark received data so that it is not included in further + # downloads + trans_id = client.last_trans_id + client.confirm_download(trans_id=trans_id, success=success) ctx['ebics_file_ids'] = ebics_files._ids @@ -258,9 +277,7 @@ class EbicsXfer(models.TransientModel): ef_format = self.format_id OrderID = False try: - order_type = self.order_type or 'FUL' - method = hasattr(client, order_type) \ - and getattr(client, order_type) + order_type = self.order_type if order_type == 'FUL': kwargs = {} bank = self.ebics_config_id.journal_ids[0].bank_id @@ -269,9 +286,7 @@ class EbicsXfer(models.TransientModel): kwargs['country'] = cc if self.test_mode: kwargs['TEST'] = 'TRUE' - OrderID = method(ef_format.name, upload_data, **kwargs) - elif order_type in ['CCT', 'CDD', 'CDB']: - OrderID = method(upload_data) + OrderID = client.FUL(ef_format.name, upload_data, **kwargs) else: OrderID = client.upload(order_type, upload_data) if OrderID: @@ -509,17 +524,17 @@ class EbicsXfer(models.TransientModel): else: o_list[-i] = chr(ord(c) + 1) break - next = ''.join(o_list) - if next == 'ZZZZ': - next = 'A000' - self.ebics_config_id.order_number = next + next_nr = ''.join(o_list) + if next_nr == 'ZZZZ': + next_nr = 'A000' + self.ebics_config_id.order_number = next_nr def _insert_line_terminator(self, data_in, line_len): data_in = data_in.replace(b'\n', b'').replace(b'\r', b'') data_out = b'' - max = len(data_in) + max_len = len(data_in) i = 0 - while i + line_len <= max: + while i + line_len <= max_len: data_out += data_in[i:i + line_len] + b'\n' i += line_len return data_out