From d6ee39b32e23eb0554f29f520b690b87f640a835 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mar=C3=A7al=20Isern?= Date: Mon, 24 Feb 2020 20:35:52 +0100 Subject: [PATCH] [IMP] account_banking_sepa_direct_debit: black, isort --- account_banking_sepa_direct_debit/__init__.py | 1 - .../__manifest__.py | 45 +-- .../data/account_payment_method.xml | 6 +- .../data/mandate_expire_cron.xml | 10 +- .../data/pain.008.001.03.xsd | 2 +- .../data/report_paperformat.xml | 4 +- .../demo/sepa_direct_debit_demo.xml | 99 ++--- .../models/__init__.py | 1 - .../models/account_banking_mandate.py | 116 +++--- .../models/account_payment_line.py | 58 +-- .../models/account_payment_method.py | 28 +- .../models/account_payment_mode.py | 33 +- .../models/account_payment_order.py | 312 +++++++++------- .../models/bank_payment_line.py | 9 +- .../models/common.py | 13 +- .../models/res_company.py | 27 +- .../models/res_config.py | 7 +- .../post_install.py | 13 +- .../reports/sepa_direct_debit_mandate.xml | 25 +- .../static/description/icon.svg | 2 +- .../static/src/css/report.css | 22 +- .../tests/__init__.py | 1 - .../tests/test_mandate.py | 46 +-- .../tests/test_sdd.py | 351 ++++++++++-------- .../views/account_banking_mandate_view.xml | 124 ++++--- .../views/account_payment_mode.xml | 32 +- .../report_sepa_direct_debit_mandate.xml | 279 ++++++++++---- .../views/res_config.xml | 24 +- 28 files changed, 975 insertions(+), 715 deletions(-) diff --git a/account_banking_sepa_direct_debit/__init__.py b/account_banking_sepa_direct_debit/__init__.py index 24b65ee99..e2b93a535 100644 --- a/account_banking_sepa_direct_debit/__init__.py +++ b/account_banking_sepa_direct_debit/__init__.py @@ -1,3 +1,2 @@ - from . import models from .post_install import update_bank_journals diff --git a/account_banking_sepa_direct_debit/__manifest__.py b/account_banking_sepa_direct_debit/__manifest__.py index 8ebb276f5..a75def61a 100644 --- a/account_banking_sepa_direct_debit/__manifest__.py +++ b/account_banking_sepa_direct_debit/__manifest__.py @@ -4,30 +4,25 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). { - 'name': 'Account Banking SEPA Direct Debit', - 'summary': 'Create SEPA files for Direct Debit', - 'version': '12.0.1.2.0', - 'license': 'AGPL-3', - 'author': "Akretion, " - "Tecnativa, " - "Odoo Community Association (OCA)", - 'website': 'https://github.com/OCA/bank-payment', - 'category': 'Banking addons', - 'depends': [ - 'account_banking_pain_base', - 'account_banking_mandate', + "name": "Account Banking SEPA Direct Debit", + "summary": "Create SEPA files for Direct Debit", + "version": "12.0.1.2.0", + "license": "AGPL-3", + "author": "Akretion, " "Tecnativa, " "Odoo Community Association (OCA)", + "website": "https://github.com/OCA/bank-payment", + "category": "Banking addons", + "depends": ["account_banking_pain_base", "account_banking_mandate"], + "data": [ + "views/account_banking_mandate_view.xml", + "views/res_config.xml", + "views/account_payment_mode.xml", + "data/mandate_expire_cron.xml", + "data/account_payment_method.xml", + "data/report_paperformat.xml", + "reports/sepa_direct_debit_mandate.xml", + "views/report_sepa_direct_debit_mandate.xml", ], - 'data': [ - 'views/account_banking_mandate_view.xml', - 'views/res_config.xml', - 'views/account_payment_mode.xml', - 'data/mandate_expire_cron.xml', - 'data/account_payment_method.xml', - 'data/report_paperformat.xml', - 'reports/sepa_direct_debit_mandate.xml', - 'views/report_sepa_direct_debit_mandate.xml', - ], - 'demo': ['demo/sepa_direct_debit_demo.xml'], - 'post_init_hook': 'update_bank_journals', - 'installable': True, + "demo": ["demo/sepa_direct_debit_demo.xml"], + "post_init_hook": "update_bank_journals", + "installable": True, } diff --git a/account_banking_sepa_direct_debit/data/account_payment_method.xml b/account_banking_sepa_direct_debit/data/account_payment_method.xml index c81129d70..cf4a6256c 100644 --- a/account_banking_sepa_direct_debit/data/account_payment_method.xml +++ b/account_banking_sepa_direct_debit/data/account_payment_method.xml @@ -1,11 +1,11 @@ - + SEPA Direct Debit for customers sepa_direct_debit inbound - - + + pain.008.001.02 diff --git a/account_banking_sepa_direct_debit/data/mandate_expire_cron.xml b/account_banking_sepa_direct_debit/data/mandate_expire_cron.xml index 1f4496b61..70783ef80 100644 --- a/account_banking_sepa_direct_debit/data/mandate_expire_cron.xml +++ b/account_banking_sepa_direct_debit/data/mandate_expire_cron.xml @@ -1,20 +1,18 @@ - - + - Set SEPA Direct Debit Mandates to Expired - - + + code model._sdd_mandate_set_state_to_expired() 1 days -1 - + diff --git a/account_banking_sepa_direct_debit/data/pain.008.001.03.xsd b/account_banking_sepa_direct_debit/data/pain.008.001.03.xsd index 73d894379..48c02daae 100644 --- a/account_banking_sepa_direct_debit/data/pain.008.001.03.xsd +++ b/account_banking_sepa_direct_debit/data/pain.008.001.03.xsd @@ -922,4 +922,4 @@ - \ No newline at end of file + diff --git a/account_banking_sepa_direct_debit/data/report_paperformat.xml b/account_banking_sepa_direct_debit/data/report_paperformat.xml index f6ce4c94e..76c8dd351 100644 --- a/account_banking_sepa_direct_debit/data/report_paperformat.xml +++ b/account_banking_sepa_direct_debit/data/report_paperformat.xml @@ -1,6 +1,5 @@ - + - European A4 low margin for SEPA @@ -16,5 +15,4 @@ 0 80 - diff --git a/account_banking_sepa_direct_debit/demo/sepa_direct_debit_demo.xml b/account_banking_sepa_direct_debit/demo/sepa_direct_debit_demo.xml index 4c9f03885..d794e855a 100644 --- a/account_banking_sepa_direct_debit/demo/sepa_direct_debit_demo.xml +++ b/account_banking_sepa_direct_debit/demo/sepa_direct_debit_demo.xml @@ -1,49 +1,54 @@ - - + - - - - SEPA Direct Debit of customers - - variable - - - - - - FR78ZZZ424242 - - - - - - sepa - recurrent - first - - valid - - - - - - - - - - - sepa - recurrent - first - - valid - - - - - - - - + + + SEPA Direct Debit of customers + + variable + + + + + FR78ZZZ424242 + + + + + sepa + recurrent + first + + valid + + + + + + + + sepa + recurrent + first + + valid + + + + + diff --git a/account_banking_sepa_direct_debit/models/__init__.py b/account_banking_sepa_direct_debit/models/__init__.py index 390ed9216..5413d1ae3 100644 --- a/account_banking_sepa_direct_debit/models/__init__.py +++ b/account_banking_sepa_direct_debit/models/__init__.py @@ -1,4 +1,3 @@ - from . import res_company from . import res_config from . import account_banking_mandate diff --git a/account_banking_sepa_direct_debit/models/account_banking_mandate.py b/account_banking_sepa_direct_debit/models/account_banking_mandate.py index 15636cbaa..888396686 100644 --- a/account_banking_sepa_direct_debit/models/account_banking_mandate.py +++ b/account_banking_sepa_direct_debit/models/account_banking_mandate.py @@ -1,10 +1,12 @@ # Copyright 2013-2016 Akretion - Alexis de Lattre # Copyright 2014 Tecnativa - Pedro M. Baeza # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). -from odoo import models, fields, api, exceptions, _ -from datetime import datetime -from dateutil.relativedelta import relativedelta import logging +from datetime import datetime + +from dateutil.relativedelta import relativedelta + +from odoo import _, api, exceptions, fields, models NUMBER_OF_UNUSED_MONTHS_BEFORE_EXPIRY = 36 @@ -13,89 +15,97 @@ logger = logging.getLogger(__name__) class AccountBankingMandate(models.Model): """SEPA Direct Debit Mandate""" - _inherit = 'account.banking.mandate' - _rec_name = 'display_name' - format = fields.Selection( - selection_add=[('sepa', 'Sepa Mandate')], default='sepa') + _inherit = "account.banking.mandate" + _rec_name = "display_name" + + format = fields.Selection(selection_add=[("sepa", "Sepa Mandate")], default="sepa") type = fields.Selection( - selection_add=[ - ('recurrent', 'Recurrent'), - ('oneoff', 'One-Off') - ]) + selection_add=[("recurrent", "Recurrent"), ("oneoff", "One-Off")] + ) recurrent_sequence_type = fields.Selection( - [('first', 'First'), - ('recurring', 'Recurring'), - ('final', 'Final')], - string='Sequence Type for Next Debit', track_visibility='onchange', + [("first", "First"), ("recurring", "Recurring"), ("final", "Final")], + string="Sequence Type for Next Debit", + track_visibility="onchange", help="This field is only used for Recurrent mandates, not for " - "One-Off mandates.", default="first") - scheme = fields.Selection([ - ('CORE', 'Basic (CORE)'), - ('B2B', 'Enterprise (B2B)')], - string='Scheme', default="CORE", track_visibility='onchange') + "One-Off mandates.", + default="first", + ) + scheme = fields.Selection( + [("CORE", "Basic (CORE)"), ("B2B", "Enterprise (B2B)")], + string="Scheme", + default="CORE", + track_visibility="onchange", + ) unique_mandate_reference = fields.Char(size=35) # cf ISO 20022 - display_name = fields.Char(compute='_compute_display_name2', store=True) + display_name = fields.Char(compute="_compute_display_name2", store=True) @api.multi - @api.constrains('type', 'recurrent_sequence_type') + @api.constrains("type", "recurrent_sequence_type") def _check_recurring_type(self): for mandate in self: - if (mandate.type == 'recurrent' and - not mandate.recurrent_sequence_type): + if mandate.type == "recurrent" and not mandate.recurrent_sequence_type: raise exceptions.Warning( _("The recurrent mandate '%s' must have a sequence type.") - % mandate.unique_mandate_reference) + % mandate.unique_mandate_reference + ) @api.multi - @api.depends('unique_mandate_reference', 'recurrent_sequence_type') + @api.depends("unique_mandate_reference", "recurrent_sequence_type") def _compute_display_name2(self): for mandate in self: - if mandate.format == 'sepa': - mandate.display_name = '%s (%s)' % ( - mandate.unique_mandate_reference, - mandate.recurrent_sequence_type) + if mandate.format == "sepa": + mandate.display_name = "{} ({})".format( + mandate.unique_mandate_reference, mandate.recurrent_sequence_type, + ) else: mandate.display_name = mandate.unique_mandate_reference @api.multi - @api.onchange('partner_bank_id') + @api.onchange("partner_bank_id") def mandate_partner_bank_change(self): for mandate in self: super(AccountBankingMandate, self).mandate_partner_bank_change() res = {} if ( - mandate.state == 'valid' and - mandate.partner_bank_id and - mandate.type == 'recurrent' and - mandate.recurrent_sequence_type != 'first' + mandate.state == "valid" + and mandate.partner_bank_id + and mandate.type == "recurrent" + and mandate.recurrent_sequence_type != "first" ): - mandate.recurrent_sequence_type = 'first' - res['warning'] = { - 'title': _('Mandate update'), - 'message': _("As you changed the bank account attached " - "to this mandate, the 'Sequence Type' has " - "been set back to 'First'."), + mandate.recurrent_sequence_type = "first" + res["warning"] = { + "title": _("Mandate update"), + "message": _( + "As you changed the bank account attached " + "to this mandate, the 'Sequence Type' has " + "been set back to 'First'." + ), } return res @api.model def _sdd_mandate_set_state_to_expired(self): - logger.info('Searching for SDD Mandates that must be set to Expired') + logger.info("Searching for SDD Mandates that must be set to Expired") expire_limit_date = datetime.today() + relativedelta( - months=-NUMBER_OF_UNUSED_MONTHS_BEFORE_EXPIRY) - expire_limit_date_str = expire_limit_date.strftime('%Y-%m-%d') + months=-NUMBER_OF_UNUSED_MONTHS_BEFORE_EXPIRY + ) + expire_limit_date_str = expire_limit_date.strftime("%Y-%m-%d") expired_mandates = self.search( - ['|', - ('last_debit_date', '=', False), - ('last_debit_date', '<=', expire_limit_date_str), - ('state', '=', 'valid'), - ('signature_date', '<=', expire_limit_date_str)]) + [ + "|", + ("last_debit_date", "=", False), + ("last_debit_date", "<=", expire_limit_date_str), + ("state", "=", "valid"), + ("signature_date", "<=", expire_limit_date_str), + ] + ) if expired_mandates: - expired_mandates.write({'state': 'expired'}) + expired_mandates.write({"state": "expired"}) logger.info( - 'The following SDD Mandate IDs has been set to expired: %s' - % expired_mandates.ids) + "The following SDD Mandate IDs has been set to expired: %s" + % expired_mandates.ids + ) else: - logger.info('0 SDD Mandates had to be set to Expired') + logger.info("0 SDD Mandates had to be set to Expired") return True diff --git a/account_banking_sepa_direct_debit/models/account_payment_line.py b/account_banking_sepa_direct_debit/models/account_payment_line.py index 7b9b887ea..769d37cd6 100644 --- a/account_banking_sepa_direct_debit/models/account_payment_line.py +++ b/account_banking_sepa_direct_debit/models/account_payment_line.py @@ -1,18 +1,19 @@ # Copyright 2019 ACSONE SA/NV # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import api, models, _ +from odoo import _, api, models from odoo.exceptions import UserError class AccountPaymentLine(models.Model): - _inherit = 'account.payment.line' + _inherit = "account.payment.line" @api.multi def draft2open_payment_line_check(self): res = super(AccountPaymentLine, self).draft2open_payment_line_check() sepa_dd_lines = self.filtered( - lambda l: l.order_id.payment_method_id.code == 'sepa_direct_debit') + lambda l: l.order_id.payment_method_id.code == "sepa_direct_debit" + ) sepa_dd_lines._check_sepa_direct_debit_ready() return res @@ -25,24 +26,33 @@ class AccountPaymentLine(models.Model): """ for rec in self: if not rec.mandate_id: - raise UserError(_( - "Missing SEPA Direct Debit mandate on the line with " - "partner {partner_name} (reference {reference}).").format( - partner_name=rec.partner_id.name, reference=rec.name)) - if rec.mandate_id.state != 'valid': - raise UserError(_( - "The SEPA Direct Debit mandate with reference " - "{mandate_ref} for partner {partner_name} has " - "expired.").format( - mandate_ref=rec.mandate_id.unique_mandate_reference, - partner_name=rec.partner_id.name)) - if rec.mandate_id.type == 'oneoff' and \ - rec.mandate_id.last_debit_date: - raise UserError(_( - "The SEPA Direct Debit mandate with reference " - "{mandate_ref} for partner {partner_name} has type set " - "to 'One-Off' but has a last debit date set to " - "{last_debit_date}. Therefore, it cannot be used.").format( - mandate_ref=rec.mandate_id.unique_mandate_reference, - partner_name=rec.partner_id.name, - last_debit_date=rec.mandate_id.last_debit_date)) + raise UserError( + _( + "Missing SEPA Direct Debit mandate on the line with " + "partner {partner_name} (reference {reference})." + ).format(partner_name=rec.partner_id.name, reference=rec.name) + ) + if rec.mandate_id.state != "valid": + raise UserError( + _( + "The SEPA Direct Debit mandate with reference " + "{mandate_ref} for partner {partner_name} has " + "expired." + ).format( + mandate_ref=rec.mandate_id.unique_mandate_reference, + partner_name=rec.partner_id.name, + ) + ) + if rec.mandate_id.type == "oneoff" and rec.mandate_id.last_debit_date: + raise UserError( + _( + "The SEPA Direct Debit mandate with reference " + "{mandate_ref} for partner {partner_name} has type set " + "to 'One-Off' but has a last debit date set to " + "{last_debit_date}. Therefore, it cannot be used." + ).format( + mandate_ref=rec.mandate_id.unique_mandate_reference, + partner_name=rec.partner_id.name, + last_debit_date=rec.mandate_id.last_debit_date, + ) + ) diff --git a/account_banking_sepa_direct_debit/models/account_payment_method.py b/account_banking_sepa_direct_debit/models/account_payment_method.py index 91344929e..93fc3d5ea 100644 --- a/account_banking_sepa_direct_debit/models/account_payment_method.py +++ b/account_banking_sepa_direct_debit/models/account_payment_method.py @@ -1,26 +1,30 @@ # Copyright 2016 Akretion (Alexis de Lattre ) # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). -from odoo import models, fields, api +from odoo import api, fields, models class AccountPaymentMethod(models.Model): - _inherit = 'account.payment.method' + _inherit = "account.payment.method" - pain_version = fields.Selection(selection_add=[ - ('pain.008.001.02', 'pain.008.001.02 (recommended for direct debit)'), - ('pain.008.001.03', 'pain.008.001.03'), - ('pain.008.001.04', 'pain.008.001.04'), - ('pain.008.003.02', 'pain.008.003.02 (direct debit in Germany)'), - ]) + pain_version = fields.Selection( + selection_add=[ + ("pain.008.001.02", "pain.008.001.02 (recommended for direct debit)"), + ("pain.008.001.03", "pain.008.001.03"), + ("pain.008.001.04", "pain.008.001.04"), + ("pain.008.003.02", "pain.008.003.02 (direct debit in Germany)"), + ] + ) @api.multi def get_xsd_file_path(self): self.ensure_one() if self.pain_version in [ - 'pain.008.001.02', 'pain.008.001.03', 'pain.008.001.04', - 'pain.008.003.02']: - path = 'account_banking_sepa_direct_debit/data/%s.xsd'\ - % self.pain_version + "pain.008.001.02", + "pain.008.001.03", + "pain.008.001.04", + "pain.008.003.02", + ]: + path = "account_banking_sepa_direct_debit/data/%s.xsd" % self.pain_version return path return super(AccountPaymentMethod, self).get_xsd_file_path() diff --git a/account_banking_sepa_direct_debit/models/account_payment_mode.py b/account_banking_sepa_direct_debit/models/account_payment_mode.py index d294fe200..d8744df70 100644 --- a/account_banking_sepa_direct_debit/models/account_payment_mode.py +++ b/account_banking_sepa_direct_debit/models/account_payment_mode.py @@ -1,32 +1,37 @@ # Copyright 2016 Tecnativa - Antonio Espinosa # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). -from odoo import models, fields, api, _ -from .common import is_sepa_creditor_identifier_valid +from odoo import _, api, fields, models from odoo.exceptions import ValidationError +from .common import is_sepa_creditor_identifier_valid + class AccountPaymentMode(models.Model): - _inherit = 'account.payment.mode' + _inherit = "account.payment.mode" sepa_creditor_identifier = fields.Char( - string='SEPA Creditor Identifier', size=35, + string="SEPA Creditor Identifier", + size=35, help="Enter the Creditor Identifier that has been attributed to your " - "company to make SEPA Direct Debits. If not defined, " - "SEPA Creditor Identifier from company will be used.\n" - "This identifier is composed of :\n" - "- your country ISO code (2 letters)\n" - "- a 2-digits checkum\n" - "- a 3-letters business code\n" - "- a country-specific identifier") + "company to make SEPA Direct Debits. If not defined, " + "SEPA Creditor Identifier from company will be used.\n" + "This identifier is composed of :\n" + "- your country ISO code (2 letters)\n" + "- a 2-digits checkum\n" + "- a 3-letters business code\n" + "- a country-specific identifier", + ) @api.multi - @api.constrains('sepa_creditor_identifier') + @api.constrains("sepa_creditor_identifier") def _check_sepa_creditor_identifier(self): for payment_mode in self: if payment_mode.sepa_creditor_identifier: if not is_sepa_creditor_identifier_valid( - payment_mode.sepa_creditor_identifier): + payment_mode.sepa_creditor_identifier + ): raise ValidationError( _("The SEPA Creditor Identifier '%s' is invalid.") - % payment_mode.sepa_creditor_identifier) + % payment_mode.sepa_creditor_identifier + ) diff --git a/account_banking_sepa_direct_debit/models/account_payment_order.py b/account_banking_sepa_direct_debit/models/account_payment_order.py index 4e0d5ec17..7ef5eadba 100644 --- a/account_banking_sepa_direct_debit/models/account_payment_order.py +++ b/account_banking_sepa_direct_debit/models/account_payment_order.py @@ -2,64 +2,72 @@ # Copyright 2018 Tecnativa - Pedro M. Baeza # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from lxml import etree + from odoo import _, api, exceptions, fields, models from odoo.exceptions import UserError -from lxml import etree class AccountPaymentOrder(models.Model): - _inherit = 'account.payment.order' + _inherit = "account.payment.order" @api.multi def generate_payment_file(self): """Creates the SEPA Direct Debit file. That's the important code !""" self.ensure_one() - if self.payment_method_id.code != 'sepa_direct_debit': + if self.payment_method_id.code != "sepa_direct_debit": return super(AccountPaymentOrder, self).generate_payment_file() pain_flavor = self.payment_method_id.pain_version # We use pain_flavor.startswith('pain.008.001.xx') # to support country-specific extensions such as # pain.008.001.02.ch.01 (cf l10n_ch_sepa) - if pain_flavor.startswith('pain.008.001.02'): - bic_xml_tag = 'BIC' + if pain_flavor.startswith("pain.008.001.02"): + bic_xml_tag = "BIC" name_maxsize = 70 - root_xml_tag = 'CstmrDrctDbtInitn' - elif pain_flavor.startswith('pain.008.003.02'): - bic_xml_tag = 'BIC' + root_xml_tag = "CstmrDrctDbtInitn" + elif pain_flavor.startswith("pain.008.003.02"): + bic_xml_tag = "BIC" name_maxsize = 70 - root_xml_tag = 'CstmrDrctDbtInitn' - elif pain_flavor.startswith('pain.008.001.03'): - bic_xml_tag = 'BICFI' + root_xml_tag = "CstmrDrctDbtInitn" + elif pain_flavor.startswith("pain.008.001.03"): + bic_xml_tag = "BICFI" name_maxsize = 140 - root_xml_tag = 'CstmrDrctDbtInitn' - elif pain_flavor.startswith('pain.008.001.04'): - bic_xml_tag = 'BICFI' + root_xml_tag = "CstmrDrctDbtInitn" + elif pain_flavor.startswith("pain.008.001.04"): + bic_xml_tag = "BICFI" name_maxsize = 140 - root_xml_tag = 'CstmrDrctDbtInitn' + root_xml_tag = "CstmrDrctDbtInitn" else: raise UserError( - _("Payment Type Code '%s' is not supported. The only " - "Payment Type Code supported for SEPA Direct Debit are " - "'pain.008.001.02', 'pain.008.001.03' and " - "'pain.008.001.04'.") % pain_flavor) + _( + "Payment Type Code '%s' is not supported. The only " + "Payment Type Code supported for SEPA Direct Debit are " + "'pain.008.001.02', 'pain.008.001.03' and " + "'pain.008.001.04'." + ) + % pain_flavor + ) pay_method = self.payment_mode_id.payment_method_id xsd_file = pay_method.get_xsd_file_path() gen_args = { - 'bic_xml_tag': bic_xml_tag, - 'name_maxsize': name_maxsize, - 'convert_to_ascii': pay_method.convert_to_ascii, - 'payment_method': 'DD', - 'file_prefix': 'sdd_', - 'pain_flavor': pain_flavor, - 'pain_xsd_file': xsd_file, + "bic_xml_tag": bic_xml_tag, + "name_maxsize": name_maxsize, + "convert_to_ascii": pay_method.convert_to_ascii, + "payment_method": "DD", + "file_prefix": "sdd_", + "pain_flavor": pain_flavor, + "pain_xsd_file": xsd_file, } nsmap = self.generate_pain_nsmap() attrib = self.generate_pain_attrib() - xml_root = etree.Element('Document', nsmap=nsmap, attrib=attrib) + xml_root = etree.Element("Document", nsmap=nsmap, attrib=attrib) pain_root = etree.SubElement(xml_root, root_xml_tag) # A. Group header - group_header, nb_of_transactions_a, control_sum_a = \ - self.generate_group_header_block(pain_root, gen_args) + ( + group_header, + nb_of_transactions_a, + control_sum_a, + ) = self.generate_group_header_block(pain_root, gen_args) transactions_count_a = 0 amount_control_sum_a = 0.0 lines_per_group = {} @@ -70,22 +78,21 @@ class AccountPaymentOrder(models.Model): priority = line.priority categ_purpose = line.category_purpose scheme = line.mandate_id.scheme - if line.mandate_id.type == 'oneoff': - seq_type = 'OOFF' - elif line.mandate_id.type == 'recurrent': - seq_type_map = { - 'recurring': 'RCUR', - 'first': 'FRST', - 'final': 'FNAL', - } + if line.mandate_id.type == "oneoff": + seq_type = "OOFF" + elif line.mandate_id.type == "recurrent": + seq_type_map = {"recurring": "RCUR", "first": "FRST", "final": "FNAL"} seq_type_label = line.mandate_id.recurrent_sequence_type assert seq_type_label is not False seq_type = seq_type_map[seq_type_label] else: - raise exceptions.UserError(_( - "Invalid mandate type in '%s'. Valid ones are 'Recurrent' " - "or 'One-Off'" - ) % line.mandate_id.unique_mandate_reference) + raise exceptions.UserError( + _( + "Invalid mandate type in '%s'. Valid ones are 'Recurrent' " + "or 'One-Off'" + ) + % line.mandate_id.unique_mandate_reference + ) # The field line.date is the requested payment date # taking into account the 'date_preferred' setting # cf account_banking_payment_export/models/account_payment.py @@ -96,128 +103,169 @@ class AccountPaymentOrder(models.Model): else: lines_per_group[key] = [line] - for (requested_date, priority, categ_purpose, sequence_type, scheme),\ - lines in list(lines_per_group.items()): + for ( + (requested_date, priority, categ_purpose, sequence_type, scheme), + lines, + ) in list(lines_per_group.items()): requested_date = fields.Date.to_string(requested_date) # B. Payment info - payment_info, nb_of_transactions_b, control_sum_b = \ - self.generate_start_payment_info_block( - pain_root, - "self.name + '-' + " - "sequence_type + '-' + requested_date.replace('-', '') " - "+ '-' + priority + '-' + category_purpose", - priority, scheme, categ_purpose, - sequence_type, requested_date, { - 'self': self, - 'sequence_type': sequence_type, - 'priority': priority, - 'category_purpose': categ_purpose or 'NOcateg', - 'requested_date': requested_date, - }, gen_args) + ( + payment_info, + nb_of_transactions_b, + control_sum_b, + ) = self.generate_start_payment_info_block( + pain_root, + "self.name + '-' + " + "sequence_type + '-' + requested_date.replace('-', '') " + "+ '-' + priority + '-' + category_purpose", + priority, + scheme, + categ_purpose, + sequence_type, + requested_date, + { + "self": self, + "sequence_type": sequence_type, + "priority": priority, + "category_purpose": categ_purpose or "NOcateg", + "requested_date": requested_date, + }, + gen_args, + ) self.generate_party_block( - payment_info, 'Cdtr', 'B', - self.company_partner_bank_id, gen_args) - charge_bearer = etree.SubElement(payment_info, 'ChrgBr') + payment_info, "Cdtr", "B", self.company_partner_bank_id, gen_args + ) + charge_bearer = etree.SubElement(payment_info, "ChrgBr") if self.sepa: - charge_bearer_text = 'SLEV' + charge_bearer_text = "SLEV" else: charge_bearer_text = self.charge_bearer charge_bearer.text = charge_bearer_text creditor_scheme_identification = etree.SubElement( - payment_info, 'CdtrSchmeId') + payment_info, "CdtrSchmeId" + ) self.generate_creditor_scheme_identification( creditor_scheme_identification, - 'self.payment_mode_id.sepa_creditor_identifier or ' - 'self.company_id.sepa_creditor_identifier', - 'SEPA Creditor Identifier', {'self': self}, 'SEPA', gen_args) + "self.payment_mode_id.sepa_creditor_identifier or " + "self.company_id.sepa_creditor_identifier", + "SEPA Creditor Identifier", + {"self": self}, + "SEPA", + gen_args, + ) transactions_count_b = 0 amount_control_sum_b = 0.0 for line in lines: transactions_count_b += 1 # C. Direct Debit Transaction Info - dd_transaction_info = etree.SubElement( - payment_info, 'DrctDbtTxInf') - payment_identification = etree.SubElement( - dd_transaction_info, 'PmtId') + dd_transaction_info = etree.SubElement(payment_info, "DrctDbtTxInf") + payment_identification = etree.SubElement(dd_transaction_info, "PmtId") instruction_identification = etree.SubElement( - payment_identification, 'InstrId') + payment_identification, "InstrId" + ) instruction_identification.text = self._prepare_field( - 'Instruction Identification', 'line.name', - {'line': line}, 35, gen_args=gen_args) + "Instruction Identification", + "line.name", + {"line": line}, + 35, + gen_args=gen_args, + ) end2end_identification = etree.SubElement( - payment_identification, 'EndToEndId') + payment_identification, "EndToEndId" + ) end2end_identification.text = self._prepare_field( - 'End to End Identification', 'line.name', - {'line': line}, 35, gen_args=gen_args) + "End to End Identification", + "line.name", + {"line": line}, + 35, + gen_args=gen_args, + ) currency_name = self._prepare_field( - 'Currency Code', 'line.currency_id.name', - {'line': line}, 3, gen_args=gen_args) + "Currency Code", + "line.currency_id.name", + {"line": line}, + 3, + gen_args=gen_args, + ) instructed_amount = etree.SubElement( - dd_transaction_info, 'InstdAmt', Ccy=currency_name) - instructed_amount.text = '%.2f' % line.amount_currency + dd_transaction_info, "InstdAmt", Ccy=currency_name + ) + instructed_amount.text = "%.2f" % line.amount_currency amount_control_sum_a += line.amount_currency amount_control_sum_b += line.amount_currency - dd_transaction = etree.SubElement( - dd_transaction_info, 'DrctDbtTx') - mandate_related_info = etree.SubElement( - dd_transaction, 'MndtRltdInf') + dd_transaction = etree.SubElement(dd_transaction_info, "DrctDbtTx") + mandate_related_info = etree.SubElement(dd_transaction, "MndtRltdInf") mandate_identification = etree.SubElement( - mandate_related_info, 'MndtId') + mandate_related_info, "MndtId" + ) mandate_identification.text = self._prepare_field( - 'Unique Mandate Reference', - 'line.mandate_id.unique_mandate_reference', - {'line': line}, 35, gen_args=gen_args) + "Unique Mandate Reference", + "line.mandate_id.unique_mandate_reference", + {"line": line}, + 35, + gen_args=gen_args, + ) mandate_signature_date = etree.SubElement( - mandate_related_info, 'DtOfSgntr') + mandate_related_info, "DtOfSgntr" + ) mandate_signature_date.text = self._prepare_field( - 'Mandate Signature Date', - 'signature_date', + "Mandate Signature Date", + "signature_date", { - 'line': line, - 'signature_date': fields.Date.to_string( - line.mandate_id.signature_date, + "line": line, + "signature_date": fields.Date.to_string( + line.mandate_id.signature_date ), - }, 10, gen_args=gen_args) - if sequence_type == 'FRST' and line.mandate_id.last_debit_date: + }, + 10, + gen_args=gen_args, + ) + if sequence_type == "FRST" and line.mandate_id.last_debit_date: amendment_indicator = etree.SubElement( - mandate_related_info, 'AmdmntInd') - amendment_indicator.text = 'true' + mandate_related_info, "AmdmntInd" + ) + amendment_indicator.text = "true" amendment_info_details = etree.SubElement( - mandate_related_info, 'AmdmntInfDtls') + mandate_related_info, "AmdmntInfDtls" + ) ori_debtor_account = etree.SubElement( - amendment_info_details, 'OrgnlDbtrAcct') - ori_debtor_account_id = etree.SubElement( - ori_debtor_account, 'Id') + amendment_info_details, "OrgnlDbtrAcct" + ) + ori_debtor_account_id = etree.SubElement(ori_debtor_account, "Id") ori_debtor_agent_other = etree.SubElement( - ori_debtor_account_id, 'Othr') + ori_debtor_account_id, "Othr" + ) ori_debtor_agent_other_id = etree.SubElement( - ori_debtor_agent_other, 'Id') - ori_debtor_agent_other_id.text = 'SMNDA' + ori_debtor_agent_other, "Id" + ) + ori_debtor_agent_other_id.text = "SMNDA" # Until 20/11/2016, SMNDA meant # "Same Mandate New Debtor Agent" # After 20/11/2016, SMNDA means # "Same Mandate New Debtor Account" self.generate_party_block( - dd_transaction_info, 'Dbtr', 'C', - line.partner_bank_id, gen_args, line) + dd_transaction_info, + "Dbtr", + "C", + line.partner_bank_id, + gen_args, + line, + ) if line.purpose: - purpose = etree.SubElement( - dd_transaction_info, 'Purp') - etree.SubElement(purpose, 'Cd').text = line.purpose + purpose = etree.SubElement(dd_transaction_info, "Purp") + etree.SubElement(purpose, "Cd").text = line.purpose - self.generate_remittance_info_block( - dd_transaction_info, line, gen_args) + self.generate_remittance_info_block(dd_transaction_info, line, gen_args) nb_of_transactions_b.text = str(transactions_count_b) - control_sum_b.text = '%.2f' % amount_control_sum_b + control_sum_b.text = "%.2f" % amount_control_sum_b nb_of_transactions_a.text = str(transactions_count_a) - control_sum_a.text = '%.2f' % amount_control_sum_a + control_sum_a.text = "%.2f" % amount_control_sum_a - return self.finalize_sepa_file_creation( - xml_root, gen_args) + return self.finalize_sepa_file_creation(xml_root, gen_args) @api.multi def generated2uploaded(self): @@ -230,7 +278,7 @@ class AccountPaymentOrder(models.Model): # is generated BEFORE, which will allow the split # of the account move per sequence_type res = super(AccountPaymentOrder, self).generated2uploaded() - abmo = self.env['account.banking.mandate'] + abmo = self.env["account.banking.mandate"] for order in self: to_expire_mandates = abmo.browse([]) first_mandates = abmo.browse([]) @@ -239,25 +287,25 @@ class AccountPaymentOrder(models.Model): if bline.mandate_id in all_mandates: continue all_mandates += bline.mandate_id - if bline.mandate_id.type == 'oneoff': + if bline.mandate_id.type == "oneoff": to_expire_mandates += bline.mandate_id - elif bline.mandate_id.type == 'recurrent': + elif bline.mandate_id.type == "recurrent": seq_type = bline.mandate_id.recurrent_sequence_type - if seq_type == 'final': + if seq_type == "final": to_expire_mandates += bline.mandate_id - elif seq_type == 'first': + elif seq_type == "first": first_mandates += bline.mandate_id - all_mandates.write( - {'last_debit_date': order.date_generated}) - to_expire_mandates.write({'state': 'expired'}) - first_mandates.write({ - 'recurrent_sequence_type': 'recurring', - }) + all_mandates.write({"last_debit_date": order.date_generated}) + to_expire_mandates.write({"state": "expired"}) + first_mandates.write({"recurrent_sequence_type": "recurring"}) for first_mandate in first_mandates: - first_mandate.message_post(body=_( - "Automatically switched from First to " - "Recurring when the debit order " - "%s has been marked as uploaded.") - % (order.id, order.name)) + first_mandate.message_post( + body=_( + "Automatically switched from First to " + "Recurring when the debit order " + "%s has been marked as uploaded." + ) + % (order.id, order.name) + ) return res diff --git a/account_banking_sepa_direct_debit/models/bank_payment_line.py b/account_banking_sepa_direct_debit/models/bank_payment_line.py index ed51f6126..fb7646f4c 100644 --- a/account_banking_sepa_direct_debit/models/bank_payment_line.py +++ b/account_banking_sepa_direct_debit/models/bank_payment_line.py @@ -1,11 +1,11 @@ # Copyright 2015-2016 Akretion - Alexis de Lattre # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). -from odoo import models, api +from odoo import api, models class BankPaymentLine(models.Model): - _inherit = 'bank.payment.line' + _inherit = "bank.payment.line" @api.multi def move_line_offsetting_account_hashcode(self): @@ -16,7 +16,6 @@ class BankPaymentLine(models.Model): So we split the transfer move lines by mandate type, so easier reconciliation of the bank statement. """ - hashcode = super(BankPaymentLine, self).\ - move_line_offsetting_account_hashcode() - hashcode += '-' + str(self.mandate_id.recurrent_sequence_type) + hashcode = super(BankPaymentLine, self).move_line_offsetting_account_hashcode() + hashcode += "-" + str(self.mandate_id.recurrent_sequence_type) return hashcode diff --git a/account_banking_sepa_direct_debit/models/common.py b/account_banking_sepa_direct_debit/models/common.py index 5395e30de..fc371a768 100644 --- a/account_banking_sepa_direct_debit/models/common.py +++ b/account_banking_sepa_direct_debit/models/common.py @@ -19,20 +19,17 @@ def is_sepa_creditor_identifier_valid(sepa_creditor_identifier): try: sci = str(sepa_creditor_identifier).lower() except Exception: - logger.warning( - "SEPA Creditor ID should contain only ASCII characters.") + logger.warning("SEPA Creditor ID should contain only ASCII characters.") return False if len(sci) < 9: return False - before_replacement = sci[7:] + sci[0:2] + '00' - logger.debug( - "SEPA ID check before_replacement = %s" % before_replacement) - after_replacement = '' + before_replacement = sci[7:] + sci[0:2] + "00" + logger.debug("SEPA ID check before_replacement = %s" % before_replacement) + after_replacement = "" for char in before_replacement: if char.isalpha(): after_replacement += str(ord(char) - 87) else: after_replacement += char - logger.debug( - "SEPA ID check after_replacement = %s" % after_replacement) + logger.debug("SEPA ID check after_replacement = %s" % after_replacement) return int(sci[2:4]) == (98 - (int(after_replacement) % 97)) diff --git a/account_banking_sepa_direct_debit/models/res_company.py b/account_banking_sepa_direct_debit/models/res_company.py index f708e4006..346d66df2 100644 --- a/account_banking_sepa_direct_debit/models/res_company.py +++ b/account_banking_sepa_direct_debit/models/res_company.py @@ -3,29 +3,34 @@ # Copyright 2016 Tecnativa - Antonio Espinosa # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). -from odoo import models, fields, api, _ -from .common import is_sepa_creditor_identifier_valid +from odoo import _, api, fields, models from odoo.exceptions import ValidationError +from .common import is_sepa_creditor_identifier_valid + class ResCompany(models.Model): - _inherit = 'res.company' + _inherit = "res.company" sepa_creditor_identifier = fields.Char( - string='SEPA Creditor Identifier', size=35, + string="SEPA Creditor Identifier", + size=35, help="Enter the Creditor Identifier that has been attributed to your " - "company to make SEPA Direct Debits. This identifier is composed " - "of :\n- your country ISO code (2 letters)\n- a 2-digits " - "checkum\n- a 3-letters business code\n- a country-specific " - "identifier") + "company to make SEPA Direct Debits. This identifier is composed " + "of :\n- your country ISO code (2 letters)\n- a 2-digits " + "checkum\n- a 3-letters business code\n- a country-specific " + "identifier", + ) @api.multi - @api.constrains('sepa_creditor_identifier') + @api.constrains("sepa_creditor_identifier") def _check_sepa_creditor_identifier(self): for company in self: if company.sepa_creditor_identifier: if not is_sepa_creditor_identifier_valid( - company.sepa_creditor_identifier): + company.sepa_creditor_identifier + ): raise ValidationError( _("The SEPA Creditor Identifier '%s' is invalid.") - % company.sepa_creditor_identifier) + % company.sepa_creditor_identifier + ) diff --git a/account_banking_sepa_direct_debit/models/res_config.py b/account_banking_sepa_direct_debit/models/res_config.py index 47222bc9b..d1e45e14c 100644 --- a/account_banking_sepa_direct_debit/models/res_config.py +++ b/account_banking_sepa_direct_debit/models/res_config.py @@ -1,13 +1,12 @@ # Copyright 2016 Akretion - Alexis de Lattre # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). -from odoo import models, fields +from odoo import fields, models class ResConfigSettings(models.TransientModel): - _inherit = 'res.config.settings' + _inherit = "res.config.settings" sepa_creditor_identifier = fields.Char( - related='company_id.sepa_creditor_identifier', - readonly=False, + related="company_id.sepa_creditor_identifier", readonly=False ) diff --git a/account_banking_sepa_direct_debit/post_install.py b/account_banking_sepa_direct_debit/post_install.py index 7fc58e37b..c6de61867 100644 --- a/account_banking_sepa_direct_debit/post_install.py +++ b/account_banking_sepa_direct_debit/post_install.py @@ -1,18 +1,15 @@ # Copyright 2016 Akretion (Alexis de Lattre ) # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from odoo import api, SUPERUSER_ID +from odoo import SUPERUSER_ID, api def update_bank_journals(cr, registry): with api.Environment.manage(): env = api.Environment(cr, SUPERUSER_ID, {}) - ajo = env['account.journal'] - journals = ajo.search([('type', '=', 'bank')]) - sdd = env.ref( - 'account_banking_sepa_direct_debit.sepa_direct_debit') + ajo = env["account.journal"] + journals = ajo.search([("type", "=", "bank")]) + sdd = env.ref("account_banking_sepa_direct_debit.sepa_direct_debit") if sdd: - journals.write({ - 'inbound_payment_method_ids': [(4, sdd.id)], - }) + journals.write({"inbound_payment_method_ids": [(4, sdd.id)]}) return diff --git a/account_banking_sepa_direct_debit/reports/sepa_direct_debit_mandate.xml b/account_banking_sepa_direct_debit/reports/sepa_direct_debit_mandate.xml index acc8bb6a7..f8fd87a00 100644 --- a/account_banking_sepa_direct_debit/reports/sepa_direct_debit_mandate.xml +++ b/account_banking_sepa_direct_debit/reports/sepa_direct_debit_mandate.xml @@ -1,12 +1,17 @@ - + - -