diff --git a/l10n_be_iso20022_pain/README.rst b/l10n_be_iso20022_pain/README.rst new file mode 100644 index 00000000..36505beb --- /dev/null +++ b/l10n_be_iso20022_pain/README.rst @@ -0,0 +1,64 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +================================== +ISO 20022 PAIN Support for Belgium +================================== + +This module adds Belgium-specific support to OCA/bank-payment/account_payment_order. + +* support of the BBA structured communication type [1] + +Reference information can be found in +* https://www.febelfin.be/fr/paiements/directives-et-protocoles-standards-bancaires +* https://www.febelfin.be/nl/betaalverkeer/richtlijnen-en-protocollen-bankstandaarden +* [1] https://www.febelfin.be/sites/default/files/Payments/AOS-OGMVCS.pdf + +Installation +============ + +There is nothing specific to do to install this module, +except having the dependent modules available in your addon path. + +It is recommended to install l10n_be_invoice_bba, and you will +probably want to use account_banking_sepa_credit_transfer and/or +account_banking_sepa_direct_debit. + +Usage +===== + +This module adds a new 'Belgium BBA' communication types on payment lines. +When adding invoices to payment orders, invoices having this BBA communication type +automatically put the correct communication type on payment lines. Generated +PAIN files then use the correct communication type. + +Credits +======= + +Images +------ + +* Odoo Community Association: `Icon `_. + +Contributors +------------ + +* Stéphane Bidoul +* Thomas Binsfeld + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit https://odoo-community.org. + diff --git a/l10n_be_iso20022_pain/__init__.py b/l10n_be_iso20022_pain/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/l10n_be_iso20022_pain/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/l10n_be_iso20022_pain/__manifest__.py b/l10n_be_iso20022_pain/__manifest__.py new file mode 100644 index 00000000..63478216 --- /dev/null +++ b/l10n_be_iso20022_pain/__manifest__.py @@ -0,0 +1,17 @@ +# Copyright 2017 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + 'name': 'ISO 20022 PAIN Support for Belgium', + 'summary': """ + This module adds Belgium-specific support to account_payment_order.""", + 'version': '11.0.1.0.0', + 'license': 'AGPL-3', + 'author': 'ACSONE SA/NV,Noviat,Odoo Community Association (OCA)', + 'website': 'https://github.com/OCA/l10n-belgium', + 'depends': [ + 'account_payment_order', + 'l10n_be_invoice_bba', + ], + 'installable': True, +} diff --git a/l10n_be_iso20022_pain/i18n/fr.po b/l10n_be_iso20022_pain/i18n/fr.po new file mode 100644 index 00000000..995d01cc --- /dev/null +++ b/l10n_be_iso20022_pain/i18n/fr.po @@ -0,0 +1,41 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * l10n_be_iso20022_pain +# +# Translators: +# OCA Transbot , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-12-20 01:41+0000\n" +"PO-Revision-Date: 2017-12-20 01:41+0000\n" +"Last-Translator: OCA Transbot , 2017\n" +"Language-Team: French (https://www.transifex.com/oca/teams/23907/fr/)\n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#. module: l10n_be_iso20022_pain +#: code:addons/l10n_be_iso20022_pain/models/account_payment_line.py:16 +#, python-format +msgid "BBA Structured Communication" +msgstr "Communication Structurée" + +#. module: l10n_be_iso20022_pain +#: code:addons/l10n_be_iso20022_pain/models/account_payment_line.py:39 +#, python-format +msgid "Invalid BBA Structured Communication !" +msgstr "Communication Structurée invalide !" + +#. module: l10n_be_iso20022_pain +#: model:ir.model,name:l10n_be_iso20022_pain.model_account_move_line +msgid "Journal Item" +msgstr "Écriture comptable" + +#. module: l10n_be_iso20022_pain +#: model:ir.model,name:l10n_be_iso20022_pain.model_account_payment_line +msgid "Payment Lines" +msgstr "Lignes de paiement" diff --git a/l10n_be_iso20022_pain/i18n/l10n_be_iso20022_pain.pot b/l10n_be_iso20022_pain/i18n/l10n_be_iso20022_pain.pot new file mode 100644 index 00000000..bfc39b5c --- /dev/null +++ b/l10n_be_iso20022_pain/i18n/l10n_be_iso20022_pain.pot @@ -0,0 +1,37 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * l10n_be_iso20022_pain +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: l10n_be_iso20022_pain +#: code:addons/l10n_be_iso20022_pain/models/account_payment_line.py:16 +#, python-format +msgid "BBA Structured Communication" +msgstr "" + +#. module: l10n_be_iso20022_pain +#: code:addons/l10n_be_iso20022_pain/models/account_payment_line.py:39 +#, python-format +msgid "Invalid BBA Structured Communication !" +msgstr "" + +#. module: l10n_be_iso20022_pain +#: model:ir.model,name:l10n_be_iso20022_pain.model_account_move_line +msgid "Journal Item" +msgstr "" + +#. module: l10n_be_iso20022_pain +#: model:ir.model,name:l10n_be_iso20022_pain.model_account_payment_line +msgid "Payment Lines" +msgstr "" + diff --git a/l10n_be_iso20022_pain/models/__init__.py b/l10n_be_iso20022_pain/models/__init__.py new file mode 100644 index 00000000..5f742d96 --- /dev/null +++ b/l10n_be_iso20022_pain/models/__init__.py @@ -0,0 +1,3 @@ +from . import account_invoice +from . import account_payment_line +from . import account_move_line diff --git a/l10n_be_iso20022_pain/models/account_invoice.py b/l10n_be_iso20022_pain/models/account_invoice.py new file mode 100644 index 00000000..6c3d942b --- /dev/null +++ b/l10n_be_iso20022_pain/models/account_invoice.py @@ -0,0 +1,23 @@ +# Copyright 2009-2019 Noviat. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models, _ + + +class AccountInvoice(models.Model): + _inherit = 'account.invoice' + + reference_type = fields.Selection( + selection='_selection_reference_type') + + @api.model + def _selection_reference_type(self): + """ + This field is defined in the two 'depends' modules with inconsistency + on selection list. + TODO: make PR towards OCA account_payment_order to fix conflict + """ + return [ + ('none', _('Free Communication')), + ('bba', _('BBA Structured Communication')), + ('structured', _('Structured Reference'))] diff --git a/l10n_be_iso20022_pain/models/account_move_line.py b/l10n_be_iso20022_pain/models/account_move_line.py new file mode 100644 index 00000000..5713fecd --- /dev/null +++ b/l10n_be_iso20022_pain/models/account_move_line.py @@ -0,0 +1,18 @@ +# Copyright 2017 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, models + + +class AccountMoveLine(models.Model): + _inherit = 'account.move.line' + + @api.multi + def _prepare_payment_line_vals(self, payment_order): + self.ensure_one() + vals = super(AccountMoveLine, self).\ + _prepare_payment_line_vals(payment_order) + if 'communication' in vals and self.invoice_id.reference_type == 'bba': + vals['communication'] =\ + self.invoice_id.reference.replace('+', '').replace('/', '') + return vals diff --git a/l10n_be_iso20022_pain/models/account_payment_line.py b/l10n_be_iso20022_pain/models/account_payment_line.py new file mode 100644 index 00000000..795ecba9 --- /dev/null +++ b/l10n_be_iso20022_pain/models/account_payment_line.py @@ -0,0 +1,45 @@ +# Copyright 2017 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import re + +from odoo import api, fields, models, _ +from odoo.exceptions import ValidationError + + +class AccountPaymentLine(models.Model): + _inherit = 'account.payment.line' + + communication_type = fields.Selection( + selection_add=[ + ('bba', _('BBA Structured Communication')), + ], + ) + + @api.model + def check_bbacomm(self, val): + supported_chars = '0-9' + pattern = re.compile('[^' + supported_chars + ']') + if pattern.findall(val or ''): + return False + if len(val) == 12: + base = int(val[:10]) + mod = base % 97 or 97 + if mod == int(val[-2:]): + return True + return False + + @api.multi + @api.constrains('communication', 'communication_type') + def _check_communication(self): + for rec in self: + if rec.communication_type == 'bba'and \ + not self.check_bbacomm(rec.communication): + raise ValidationError(_( + "Invalid BBA Structured Communication !")) + + def invoice_reference_type2communication_type(self): + res = super(AccountPaymentLine, self)\ + .invoice_reference_type2communication_type() + res['bba'] = 'bba' + return res diff --git a/l10n_be_iso20022_pain/static/description/icon.png b/l10n_be_iso20022_pain/static/description/icon.png new file mode 100644 index 00000000..3a0328b5 Binary files /dev/null and b/l10n_be_iso20022_pain/static/description/icon.png differ diff --git a/l10n_be_iso20022_pain/tests/__init__.py b/l10n_be_iso20022_pain/tests/__init__.py new file mode 100644 index 00000000..fb4e7047 --- /dev/null +++ b/l10n_be_iso20022_pain/tests/__init__.py @@ -0,0 +1 @@ +from . import test_l10n_be_iso20022_pain diff --git a/l10n_be_iso20022_pain/tests/test_l10n_be_iso20022_pain.py b/l10n_be_iso20022_pain/tests/test_l10n_be_iso20022_pain.py new file mode 100644 index 00000000..ccb53941 --- /dev/null +++ b/l10n_be_iso20022_pain/tests/test_l10n_be_iso20022_pain.py @@ -0,0 +1,142 @@ +# Copyright 2017 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.tests.common import SavepointCase +from odoo.exceptions import ValidationError + + +class TestL10nBeIso20022Pain(SavepointCase): + + @classmethod + def setUpClass(cls): + super(TestL10nBeIso20022Pain, cls).setUpClass() + + # MODELS + cls.invoice_model = cls.env['account.invoice'] + cls.account_model = cls.env['account.account'] + cls.payment_line_model = cls.env['account.payment.line'] + cls.payment_mode_model = cls.env['account.payment.mode'] + cls.journal_model = cls.env['account.journal'] + + # INSTANCES + cls.payable_account = cls.account_model.search([ + ('user_type_id', + '=', + cls.env.ref('account.data_account_type_payable').id) + ], limit=1) + cls.expense_account = cls.account_model.search([ + ('user_type_id', + '=', + cls.env.ref('account.data_account_type_expenses').id) + ], limit=1) + cls.purchase_journal = cls.journal_model.search([ + ('type', '=', 'purchase') + ], limit=1) + cls.bank_journal = cls.journal_model.search([ + ('type', '=', 'bank') + ], limit=1) + # Instance: Payment Mode + cls.payment_mode = cls.payment_mode_model.create({ + 'name': 'Test payment mode', + 'payment_method_id': cls.env.ref( + 'account.account_payment_method_manual_out').id, + 'fixed_journal_id': cls.bank_journal.id, + 'bank_account_link': 'fixed', + }) + # Instance: Invoice + cls.invoice = cls.invoice_model.create({ + 'partner_id': cls.env.ref('base.res_partner_2').id, + 'journal_id': cls.purchase_journal.id, + 'account_id': cls.payable_account.id, + 'type': 'in_invoice', + 'invoice_line_ids': [(0, 0, { + 'name': 'Test invoice line', + 'account_id': cls.expense_account.id, + 'quantity': 2.000, + 'price_unit': 2.99, + })], + 'reference_type': 'bba', + 'reference': '+++868/0542/73023+++', + 'payment_mode_id': cls.payment_mode.id + }) + + def _prepare_payment_line_creation_dict(self): + return { + 'currency_id': self.env.ref('base.EUR').id, + 'partner_id': self.env.ref('base.res_partner_2').id, + 'communication_type': 'bba', + 'amount_currency': 123.321, + } + + def test_create_account_payment_line_01(self): + """ + Data: + - A draft valid draft invoice with BBA communication + Test case: + - Open the invoice + - Prepare payment + Expected result: + - The created payment line takes the BBA communication + """ + self.invoice.action_invoice_open() + self.invoice.create_account_payment_line() + pl = self.payment_line_model.search( + [('move_line_id.invoice_id', '=', self.invoice.id)], limit=1) + self.assertEqual(pl.communication, '868054273023') + + def test_create_account_payment_line_02(self): + """ + Data: + - No invoice, no payment line, nothing + Test case: + - Create a new payment line with invalid BBA communication + Expected result: + - ValidationError + """ + vals = self._prepare_payment_line_creation_dict() + vals['communication'] = '868054273024' + with self.assertRaises(ValidationError): + self.payment_line_model.create(vals) + + def test_create_account_payment_line_03(self): + """ + Data: + - No invoice, no payment line, nothing + Test case: + - Create a new payment line with invalid BBA communication + (too short) + Expected result: + - ValidationError + """ + vals = self._prepare_payment_line_creation_dict() + vals['communication'] = '8680542730241' + with self.assertRaises(ValidationError): + self.payment_line_model.create(vals) + + def test_create_account_payment_line_04(self): + """ + Data: + - No invoice, no payment line, nothing + Test case: + - Create a new payment line with invalid BBA communication + (too long) + Expected result: + - ValidationError + """ + vals = self._prepare_payment_line_creation_dict() + vals['communication'] = '86805427302' + with self.assertRaises(ValidationError): + self.payment_line_model.create(vals) + + def test_create_account_payment_line_05(self): + """ + Data: + - No invoice, no payment line, nothing + Test case: + - Create a new payment line with valid BBA communication + Expected result: + - The payment line is created with the valid communication + """ + vals = self._prepare_payment_line_creation_dict() + vals['communication'] = '868054273023' + self.payment_line_model.create(vals)