summaryrefslogtreecommitdiff
path: root/addons/account_qr_code_sepa
diff options
context:
space:
mode:
authoroco-odoo <oco@odoo.com>2020-02-07 10:10:09 +0000
committeroco-odoo <oco@odoo.com>2020-03-30 11:28:41 +0000
commit0eef278268d1f4cce30c97bfef723f769b1a48e0 (patch)
treeae9df84a16740d8a41b079da599017a3f55f0a2d /addons/account_qr_code_sepa
parentc1f3596bd32efcbec40e66c29930ccdc29072f92 (diff)
[IMP] base, account, l10n_ch, website_sale, payment, web: make payment QR-code generation more generic
This new modelling makes it easier to add new QR-code formats, and allows using all of them in website_sale and account.payment's form view as well (so, Swiss QR codes are now available there, while they were restricted to only invoices in the past). All barcodes are now generated as reports, from a dedicated route. This was only partly the case before : Swiss QR added a cross on top of the QR-code directly in the template, it wasn't part of the image returned by the route; now it is. [ADD] base_qr_code_sepa: new module decoupling SEPA QR-codes generation from the base module Each new QR-code generation option should thus be done in a dedicated module (or added to a localization) in the future. [IMP] base_qr_code_sepa: update the generated QR codes to version 2 of the specification Version 1 is still supported, so no need to backport this. [IMP] l10n_ch: make Swiss QR-codes compatible with the new version of the specification (the old one is deprecated) This will be backported to 11.0 and 12.0, as these QR-codes will soon replace ISR. [IMP] account: make it possible to mark manual payments as sent with a button on the form view This way, when making them directly with a QR-code (or doing a more classical wire transfer), people can keep track of what they already have asked the bank to do, and what they still have to treat. closes odoo/odoo#44839 Related: odoo/enterprise#8262 Related: odoo/upgrade#992 Signed-off-by: Laurent Smet <smetl@users.noreply.github.com>
Diffstat (limited to 'addons/account_qr_code_sepa')
-rw-r--r--addons/account_qr_code_sepa/__init__.py4
-rw-r--r--addons/account_qr_code_sepa/__manifest__.py19
-rw-r--r--addons/account_qr_code_sepa/models/__init__.py3
-rw-r--r--addons/account_qr_code_sepa/models/res_bank.py59
-rw-r--r--addons/account_qr_code_sepa/tests/__init__.py3
-rw-r--r--addons/account_qr_code_sepa/tests/test_sepa_qr.py62
6 files changed, 150 insertions, 0 deletions
diff --git a/addons/account_qr_code_sepa/__init__.py b/addons/account_qr_code_sepa/__init__.py
new file mode 100644
index 00000000000..8d60a0f8187
--- /dev/null
+++ b/addons/account_qr_code_sepa/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+
+from . import models
+from . import tests \ No newline at end of file
diff --git a/addons/account_qr_code_sepa/__manifest__.py b/addons/account_qr_code_sepa/__manifest__.py
new file mode 100644
index 00000000000..0c3438485e4
--- /dev/null
+++ b/addons/account_qr_code_sepa/__manifest__.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+{
+ 'name': "base_qr_code_sepa",
+
+ 'description': """
+ This module adds support for SEPA Credit Transfer QR-code generation.
+ """,
+
+ 'category': 'Accounting/Payment',
+ 'version': '0.1',
+
+ # any module necessary for this one to work correctly
+ 'depends': ['account'],
+
+ 'data': [
+ ],
+
+ 'auto_install': True,
+} \ No newline at end of file
diff --git a/addons/account_qr_code_sepa/models/__init__.py b/addons/account_qr_code_sepa/models/__init__.py
new file mode 100644
index 00000000000..6570df4c3b8
--- /dev/null
+++ b/addons/account_qr_code_sepa/models/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+from . import res_bank \ No newline at end of file
diff --git a/addons/account_qr_code_sepa/models/res_bank.py b/addons/account_qr_code_sepa/models/res_bank.py
new file mode 100644
index 00000000000..afd47553cc0
--- /dev/null
+++ b/addons/account_qr_code_sepa/models/res_bank.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+
+from odoo import models, fields, api, _
+
+import werkzeug
+
+
+class ResPartnerBank(models.Model):
+ _inherit = 'res.partner.bank'
+
+ def _get_qr_code_url(self, qr_method, amount, currency, debtor_partner, free_communication, structured_communication):
+ if qr_method == 'sct_qr':
+
+ comment = (free_communication or '') if not structured_communication else ''
+
+ qr_code_vals = [
+ 'BCD', # Service Tag
+ '002', # Version
+ '1', # Character Set
+ 'SCT', # Identification Code
+ self.bank_bic or '', # BIC of the Beneficiary Bank
+ (self.acc_holder_name or self.partner_id.name)[:71], # Name of the Beneficiary
+ self.sanitized_acc_number, # Account Number of the Beneficiary
+ str(amount), # Amount of the Transfer in EUR
+ '', # Purpose of the Transfer
+ (structured_communication or '')[:36], # Remittance Information (Structured)
+ comment[:141], # Remittance Information (Unstructured) (can't be set if there is a structured one)
+ '', # Beneficiary to Originator Information
+ ]
+
+ return '/report/barcode/?' + werkzeug.urls.url_encode({'type': 'QR', 'value': '\n'.join(qr_code_vals), 'width': 128, 'height': 128, 'humanreadable': 1})
+
+ return super()._get_qr_code_url(qr_method, amount, currency, debtor_partner, free_communication, structured_communication)
+
+ def _eligible_for_qr_code(self, qr_method, debtor_partner, currency):
+ if qr_method == 'sct_qr':
+
+ # Some countries share the same IBAN country code
+ # (e.g. Åland Islands and Finland IBANs are 'FI', but Åland Islands' code is 'AX').
+ sepa_country_codes = self.env.ref('base.sepa_zone').country_ids.mapped('code')
+ non_iban_codes = {'AX', 'NC', 'YT', 'TF', 'BL', 'RE', 'MF', 'GP', 'PM', 'PF', 'GF', 'MQ', 'JE', 'GG', 'IM'}
+ sepa_iban_codes = {code for code in sepa_country_codes if code not in non_iban_codes}
+
+ return currency.name == 'EUR' and self.acc_type == 'iban' and self.sanitized_acc_number[:2] in sepa_iban_codes
+
+ return super()._eligible_for_qr_code(qr_method, debtor_partner, currency)
+
+ def _check_for_qr_code_errors(self, qr_method, amount, currency, debtor_partner, free_communication, structured_communication):
+ if qr_method == 'sct_qr':
+ if not self.acc_holder_name and not self.partner_id.name:
+ return _("The account receiving the payment must have an account horder name or partner name set.")
+
+ return super()._check_for_qr_code_errors(qr_method, amount, currency, debtor_partner, free_communication, structured_communication)
+
+ @api.model
+ def _get_available_qr_methods(self):
+ rslt = super()._get_available_qr_methods()
+ rslt.append(('sct_qr', _("SEPA Credit Transfer QR"), 20))
+ return rslt
diff --git a/addons/account_qr_code_sepa/tests/__init__.py b/addons/account_qr_code_sepa/tests/__init__.py
new file mode 100644
index 00000000000..b570597d7f4
--- /dev/null
+++ b/addons/account_qr_code_sepa/tests/__init__.py
@@ -0,0 +1,3 @@
+# *-* coding:utf-8 *-*
+
+from . import test_sepa_qr \ No newline at end of file
diff --git a/addons/account_qr_code_sepa/tests/test_sepa_qr.py b/addons/account_qr_code_sepa/tests/test_sepa_qr.py
new file mode 100644
index 00000000000..27575632bc3
--- /dev/null
+++ b/addons/account_qr_code_sepa/tests/test_sepa_qr.py
@@ -0,0 +1,62 @@
+# -*- coding:utf-8 -*-
+
+from odoo.exceptions import UserError
+from odoo.addons.account.tests.common import AccountTestInvoicingCommon
+
+class TestSEPAQRCode(AccountTestInvoicingCommon):
+ """ Tests the generation of Swiss QR-codes on invoices
+ """
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestSEPAQRCode, cls).setUpClass()
+
+ cls.company_data['company'].qr_code = True
+ cls.acc_sepa_iban = cls.env['res.partner.bank'].create({
+ 'acc_number': 'BE15001559627230',
+ 'partner_id': cls.company_data['company'].partner_id.id,
+ })
+
+ cls.acc_non_sepa_iban = cls.env['res.partner.bank'].create({
+ 'acc_number': 'SA4420000001234567891234',
+ 'partner_id': cls.company_data['company'].partner_id.id,
+ })
+
+ cls.sepa_qr_invoice = cls.env['account.move'].create({
+ 'move_type': 'out_invoice',
+ 'partner_id': cls.partner_a.id,
+ 'currency_id': cls.env.ref('base.EUR').id,
+ 'invoice_partner_bank_id': cls.acc_sepa_iban.id,
+ 'company_id': cls.company_data['company'].id,
+ 'invoice_line_ids': [
+ (0, 0, {'quantity': 1, 'price_unit': 100})
+ ],
+ })
+
+ def test_sepa_qr_code_generation(self):
+ """ Check different cases of SEPA QR-code generation, when qr_method is
+ specified beforehand.
+ """
+ self.sepa_qr_invoice.qr_code_method = 'sct_qr'
+
+ # Using a SEPA IBAN should work
+ self.sepa_qr_invoice.generate_qr_code()
+
+ # Using a non-SEPA IBAN shouldn't
+ self.sepa_qr_invoice.invoice_partner_bank_id = self.acc_non_sepa_iban
+ with self.assertRaises(UserError, msg="It shouldn't be possible to generate a SEPA QR-code for IBAN of countries outside SEPA zone."):
+ self.sepa_qr_invoice.generate_qr_code()
+
+ # Changing the currency should break it as well
+ self.sepa_qr_invoice.invoice_partner_bank_id = self.acc_sepa_iban
+ self.sepa_qr_invoice.currency_id = self.env.ref('base.USD').id
+ with self.assertRaises(UserError, msg="It shouldn't be possible to generate a SEPA QR-code for another currency as EUR."):
+ self.sepa_qr_invoice.generate_qr_code()
+
+ def test_sepa_qr_code_detection(self):
+ """ Checks SEPA QR-code auto-detection when no specific QR-method
+ is given to the invoice.
+ """
+ self.sepa_qr_invoice.generate_qr_code()
+ self.assertEqual(self.sepa_qr_invoice.qr_code_method, 'sct_qr', "SEPA QR-code generator should have been chosen for this invoice.")
+