summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYannick Vaucher <yannick.vaucher@camptocamp.com>2020-05-19 13:30:23 +0000
committerJosse Colpaert <jco@odoo.com>2020-07-14 14:49:15 +0000
commitda6c9027f4df6ca98420ffafcf32e37dc916d374 (patch)
tree33837a356ace117be18be49bc7e232e59d791115
parente53218964a5088cad0b2fa36390b62bd59e1fda6 (diff)
[IMP] l10n_ch: clearer validations for bank fields and vendor bills (ISR ref)
On res.partner.bank, we check that: - l10n_ch_postal contains a valid postal number - l10n_ch_isr_subscription_chf contains a valid ISR subscription number - l10n_ch_isr_subscription_eur contains a valid ISR subscription number ISR subscriptions numbers are postal numbers but starting with 01 or 03. Those codes are reserved to ISR issuance. When the bank account on a Vendor Bill is detected as an ISR Issuer, check the reference is actually an ISR. The 27 digits ISR Reference is error prone when typed by hand and an error at this stage would break the payment process later. This is required to avoid batch payment error with SEPA. We prefer using the pretty form xx-yyyyy-z of a postal account. The Swiss users will identify it more easily. We always want to auto fill the field l10n_ch_postal when possible from acc_number, which includes only 2 cases of filling acc_number: 1. a 9 position postal account number 2. an IBAN from PostFinance which includes clearing 09000 Original prs: Closes #51645, #51544, #51560 closes odoo/odoo#54455 X-original-commit: f8a3ec438e3b5fa56305db729a78304aec7a6716 Signed-off-by: Quentin De Paoli (qdp) <qdp@openerp.com> Signed-off-by: Josse Colpaert <jco@openerp.com>
-rw-r--r--addons/l10n_ch/i18n_extra/l10n_ch.pot54
-rw-r--r--addons/l10n_ch/models/account_invoice.py30
-rw-r--r--addons/l10n_ch/models/res_bank.py94
-rw-r--r--addons/l10n_ch/tests/__init__.py4
-rw-r--r--addons/l10n_ch/tests/test_l10n_ch_isr_print.py (renamed from addons/l10n_ch/tests/test_l10n_ch_isr.py)2
-rw-r--r--addons/l10n_ch/tests/test_onchange_l10n_ch_postal.py108
-rw-r--r--addons/l10n_ch/tests/test_swissqr.py8
-rw-r--r--addons/l10n_ch/tests/test_vendor_bill_isr.py119
-rw-r--r--addons/l10n_ch/views/account_invoice_view.xml6
9 files changed, 394 insertions, 31 deletions
diff --git a/addons/l10n_ch/i18n_extra/l10n_ch.pot b/addons/l10n_ch/i18n_extra/l10n_ch.pot
index 8cff14a83ca..441e37ee846 100644
--- a/addons/l10n_ch/i18n_extra/l10n_ch.pot
+++ b/addons/l10n_ch/i18n_extra/l10n_ch.pot
@@ -4,10 +4,10 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: Odoo Server 13.0\n"
+"Project-Id-Version: Odoo Server 13.0+e\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-05-28 09:52+0000\n"
-"PO-Revision-Date: 2020-06-17 10:00+0000\n"
+"POT-Creation-Date: 2020-07-09 16:03+0000\n"
+"PO-Revision-Date: 2020-07-09 16:03+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
@@ -336,17 +336,17 @@ msgstr ""
#. module: l10n_ch
#: model_terms:ir.ui.view,arch_db:l10n_ch.l10n_ch_swissqr_template
-msgid "<span class=\"title\">Additional information</span><br/>"
+msgid "<span class=\"title\">Account / Payable to</span><br/>"
msgstr ""
#. module: l10n_ch
#: model_terms:ir.ui.view,arch_db:l10n_ch.l10n_ch_swissqr_template
-msgid "<span class=\"title\">Amount</span><br/>"
+msgid "<span class=\"title\">Additional information</span><br/>"
msgstr ""
#. module: l10n_ch
#: model_terms:ir.ui.view,arch_db:l10n_ch.l10n_ch_swissqr_template
-msgid "<span class=\"title\">Account / Payable to</span><br/>"
+msgid "<span class=\"title\">Amount</span><br/>"
msgstr ""
#. module: l10n_ch
@@ -361,11 +361,6 @@ msgstr ""
#. module: l10n_ch
#: model_terms:ir.ui.view,arch_db:l10n_ch.l10n_ch_swissqr_template
-msgid "<span class=\"title\">Payable to</span><br/>"
-msgstr ""
-
-#. module: l10n_ch
-#: model_terms:ir.ui.view,arch_db:l10n_ch.l10n_ch_swissqr_template
msgid "<span class=\"title\">Reference</span><br/>"
msgstr ""
@@ -1027,7 +1022,6 @@ msgid "Immeubles d’exploitation"
msgstr ""
#. module: l10n_ch
-#: model:account.fiscal.position,name:l10n_ch.1_fiscal_position_template_import
#: model:account.fiscal.position.template,name:l10n_ch.fiscal_position_template_import
msgid "Import/Export"
msgstr ""
@@ -1081,6 +1075,11 @@ msgid "Journal Entries"
msgstr ""
#. module: l10n_ch
+#: model:ir.model.fields,field_description:l10n_ch.field_account_move__l10n_ch_isr_needs_fixing
+msgid "L10N Ch Isr Needs Fixing"
+msgstr ""
+
+#. module: l10n_ch
#: model:ir.model.fields,field_description:l10n_ch.field_account_move__l10n_ch_isr_number
msgid "L10N Ch Isr Number"
msgstr ""
@@ -1217,6 +1216,13 @@ msgid "Plan comptable 2015 (Suisse)"
msgstr ""
#. module: l10n_ch
+#: model_terms:ir.ui.view,arch_db:l10n_ch.isr_invoice_form
+msgid ""
+"Please fill in a correct ISR reference in the payment reference. The banks "
+"will refuse your payment file otherwise."
+msgstr ""
+
+#. module: l10n_ch
#: code:addons/l10n_ch/models/res_bank.py:0
#, python-format
msgid "Postal"
@@ -1419,7 +1425,6 @@ msgid "Subventions, taxes touristiques à 0%"
msgstr ""
#. module: l10n_ch
-#: model:account.fiscal.position,name:l10n_ch.1_fiscal_position_template_1
#: model:account.fiscal.position.template,name:l10n_ch.fiscal_position_template_1
msgid "Suisse national"
msgstr ""
@@ -1577,11 +1582,27 @@ msgid "TVA due à 7.7% (Incl. TN)"
msgstr ""
#. module: l10n_ch
+#: code:addons/l10n_ch/models/res_bank.py:0
+#, python-format
+msgid ""
+"The ISR subcription {} for {} number is not valid.\n"
+"It must starts with {} and we a valid postal number format. eg. {}"
+msgstr ""
+
+#. module: l10n_ch
#: model:ir.model.fields,help:l10n_ch.field_account_move__l10n_ch_currency_name
msgid "The name of this invoice's currency"
msgstr ""
#. module: l10n_ch
+#: code:addons/l10n_ch/models/res_bank.py:0
+#, python-format
+msgid ""
+"The postal number {} is not valid.\n"
+"It must be a valid postal number format. eg. 10-8060-7"
+msgstr ""
+
+#. module: l10n_ch
#: model:ir.model.fields,help:l10n_ch.field_account_move__l10n_ch_isr_number
msgid "The reference number associated with this invoice"
msgstr ""
@@ -1628,6 +1649,13 @@ msgid "Travaux en cours"
msgstr ""
#. module: l10n_ch
+#: model:ir.model.fields,help:l10n_ch.field_account_move__l10n_ch_isr_needs_fixing
+msgid ""
+"Used to show a warning banner when the vendor bill needs a correct ISR "
+"payment reference. "
+msgstr ""
+
+#. module: l10n_ch
#: model:account.account.template,name:l10n_ch.ch_coa_3940
msgid "Variation de la valeur des prestations non facturées"
msgstr ""
diff --git a/addons/l10n_ch/models/account_invoice.py b/addons/l10n_ch/models/account_invoice.py
index 3ed18cbad8a..1760ca4d2b6 100644
--- a/addons/l10n_ch/models/account_invoice.py
+++ b/addons/l10n_ch/models/account_invoice.py
@@ -27,6 +27,7 @@ class AccountMove(models.Model):
l10n_ch_isr_sent = fields.Boolean(default=False, help="Boolean value telling whether or not the ISR corresponding to this invoice has already been printed or sent by mail.")
l10n_ch_currency_name = fields.Char(related='currency_id.name', readonly=True, string="Currency Name", help="The name of this invoice's currency") #This field is used in the "invisible" condition field of the 'Print ISR' button.
+ l10n_ch_isr_needs_fixing = fields.Boolean(compute="_compute_l10n_ch_isr_needs_fixing", help="Used to show a warning banner when the vendor bill needs a correct ISR payment reference. ")
@api.depends('partner_bank_id.l10n_ch_isr_subscription_eur', 'partner_bank_id.l10n_ch_isr_subscription_chf')
def _compute_l10n_ch_isr_subscription(self):
@@ -154,6 +155,33 @@ class AccountMove(models.Model):
record.l10n_ch_isr_subscription and \
record.l10n_ch_currency_name in ['EUR', 'CHF']
+ @api.depends('move_type', 'partner_bank_id', 'payment_reference')
+ def _compute_l10n_ch_isr_needs_fixing(self):
+ for inv in self:
+ if inv.move_type == 'in_invoice' and inv.company_id.country_id.code == "CH":
+ partner_bank = inv.partner_bank_id
+ if partner_bank._is_isr_issuer() and not inv._has_isr_ref():
+ inv.l10n_ch_isr_needs_fixing = True
+ continue
+ inv.l10n_ch_isr_needs_fixing = False
+
+ def _has_isr_ref(self):
+ """Check if this invoice has a valid ISR reference (for Switzerland)
+ e.g.
+ 12371
+ 000000000000000000000012371
+ 210000000003139471430009017
+ 21 00000 00003 13947 14300 09017
+ """
+ self.ensure_one()
+ ref = self.payment_reference or self.ref
+ if not ref:
+ return False
+ ref = ref.replace(' ', '')
+ if re.match(r'^(\d{2,27})$', ref):
+ return ref == mod10r(ref[:-1])
+ return False
+
def split_total_amount(self):
""" Splits the total amount of this invoice in two parts, using the dot as
a separator, and taking two precision digits (always displayed).
@@ -173,7 +201,7 @@ class AccountMove(models.Model):
self.l10n_ch_isr_sent = True
return self.env.ref('l10n_ch.l10n_ch_isr_report').report_action(self)
else:
- raise ValidationError(_("""You cannot generate an ISR yet.\n
+ raise ValidationError(_("""You cannot generate an ISR yet.\n
For this, you need to :\n
- set a valid postal account number (or an IBAN referencing one) for your company\n
- define its bank\n
diff --git a/addons/l10n_ch/models/res_bank.py b/addons/l10n_ch/models/res_bank.py
index 5f6337541c0..63e704c2671 100644
--- a/addons/l10n_ch/models/res_bank.py
+++ b/addons/l10n_ch/models/res_bank.py
@@ -4,25 +4,39 @@
import re
from odoo import api, fields, models, _
+from odoo.exceptions import ValidationError
from odoo.tools.misc import mod10r
from odoo.exceptions import UserError
import werkzeug.urls
+ISR_SUBSCRIPTION_CODE = {'CHF': '01', 'EUR': '03'}
+CLEARING = "09000"
+_re_postal = re.compile('^[0-9]{2}-[0-9]{1,6}-[0-9]$')
+
+
def _is_l10n_ch_postal(account_ref):
- """ Returns True iff the string account_ref is a valid postal account number,
+ """ Returns True if the string account_ref is a valid postal account number,
i.e. it only contains ciphers and is last cipher is the result of a recursive
modulo 10 operation ran over the rest of it. Shorten form with - is also accepted.
"""
- if re.match('^[0-9]{2}-[0-9]{1,6}-[0-9]$', account_ref or ''):
+ if _re_postal.match(account_ref or ''):
ref_subparts = account_ref.split('-')
- account_ref = ref_subparts[0] + ref_subparts[1].rjust(6,'0') + ref_subparts[2]
+ account_ref = ref_subparts[0] + ref_subparts[1].rjust(6, '0') + ref_subparts[2]
if re.match('\d+$', account_ref or ''):
account_ref_without_check = account_ref[:-1]
return mod10r(account_ref_without_check) == account_ref
return False
+def _is_l10n_ch_isr_issuer(account_ref, currency_code):
+ """ Returns True if the string account_ref is a valid a valid ISR issuer
+ An ISR issuer is postal account number that starts by 01 (CHF) or 03 (EUR),
+ """
+ if (account_ref or '').startswith(ISR_SUBSCRIPTION_CODE[currency_code]):
+ return _is_l10n_ch_postal(account_ref)
+ return False
+
class ResPartnerBank(models.Model):
_inherit = 'res.partner.bank'
@@ -40,6 +54,38 @@ class ResPartnerBank(models.Model):
l10n_ch_isr_subscription_eur = fields.Char(string='EUR ISR Subscription Number', help='The subscription number provided by the bank or Postfinance to identify the bank, used to generate ISR in EUR. eg. 03-162-5')
l10n_ch_show_subscription = fields.Boolean(compute='_compute_l10n_ch_show_subscription', default=lambda self: self.env.company.country_id.code == 'CH')
+ def _is_isr_issuer(self):
+ return (_is_l10n_ch_isr_issuer(self.l10n_ch_postal, 'CHF')
+ or _is_l10n_ch_isr_issuer(self.l10n_ch_postal, 'EUR'))
+
+ @api.constrains("l10n_ch_postal", "partner_id")
+ def _check_postal_num(self):
+ """Validate postal number format"""
+ for rec in self:
+ if rec.l10n_ch_postal and not _is_l10n_ch_postal(self.l10n_ch_postal):
+ # l10n_ch_postal is used for the purpose of Client Number on your own accounts, so don't do the check there
+ if rec.partner_id and not rec.partner_id.ref_company_ids:
+ raise ValidationError(
+ _("The postal number {} is not valid.\n"
+ "It must be a valid postal number format. eg. 10-8060-7").format(rec.l10n_ch_postal))
+ return True
+
+ @api.constrains("l10n_ch_isr_subscription_chf", "l10n_ch_isr_subscription_eur")
+ def _check_subscription_num(self):
+ """Validate ISR subscription number format
+ Subscription number can only starts with 01 or 03
+ """
+ for rec in self:
+ for currency in ["CHF", "EUR"]:
+ subscrip = rec.l10n_ch_isr_subscription_chf if currency == "CHF" else rec.l10n_ch_isr_subscription_eur
+ if subscrip and not _is_l10n_ch_isr_issuer(subscrip, currency):
+ example = "01-162-8" if currency == "CHF" else "03-162-5"
+ raise ValidationError(
+ _("The ISR subcription {} for {} number is not valid.\n"
+ "It must starts with {} and we a valid postal number format. eg. {}"
+ ).format(subscrip, currency, ISR_SUBSCRIPTION_CODE[currency], example))
+ return True
+
@api.depends('partner_id', 'company_id')
def _compute_l10n_ch_show_subscription(self):
for bank in self:
@@ -89,19 +135,45 @@ class ResPartnerBank(models.Model):
record.l10n_ch_postal = record.acc_number.split(" ")[0]
else:
record.l10n_ch_postal = record.acc_number
- if record.partner_id:
- record.acc_number = record.acc_number + ' ' + record.partner_id.name
+ # In case of ISR issuer, this number is not
+ # unique and we fill acc_number with partner
+ # name to give proper information to the user
+ if record.partner_id and record.acc_number[:2] in ["01", "03"]:
+ record.acc_number = ("{} {}").format(record.acc_number, record.partner_id.name)
+
+ @api.model
+ def _is_postfinance_iban(self, iban):
+ """Postfinance IBAN have format
+ CHXX 0900 0XXX XXXX XXXX K
+ Where 09000 is the clearing number
+ """
+ return iban.startswith('CH') and iban[4:9] == CLEARING
+
+ @api.model
+ def _pretty_postal_num(self, number):
+ """format a postal account number or an ISR subscription number
+ as per specifications with '-' separators.
+ eg. 010001628 -> 01-162-8
+ """
+ if re.match('^[0-9]{2}-[0-9]{1,6}-[0-9]$', number or ''):
+ return number
+ currency_code = number[:2]
+ middle_part = number[2:-1]
+ trailing_cipher = number[-1]
+ middle_part = middle_part.lstrip("0")
+ return currency_code + '-' + middle_part + '-' + trailing_cipher
@api.model
def _retrieve_l10n_ch_postal(self, iban):
- """ Reads a swiss postal account number from a an IBAN and returns it as
+ """Reads a swiss postal account number from a an IBAN and returns it as
a string. Returns None if no valid postal account number was found, or
- the given iban was not from Switzerland.
+ the given iban was not from Swiss Postfinance.
+
+ CH09 0900 0000 1000 8060 7 -> 10-8060-7
"""
- if iban[:2] == 'CH':
- #the IBAN corresponds to a swiss account
- if _is_l10n_ch_postal(iban[-12:]):
- return iban[-12:]
+ if self._is_postfinance_iban(iban):
+ # the IBAN corresponds to a swiss account
+ return self._pretty_postal_num(iban[-9:])
return None
def _get_qr_code_url(self, qr_method, amount, currency, debtor_partner, free_communication, structured_communication):
diff --git a/addons/l10n_ch/tests/__init__.py b/addons/l10n_ch/tests/__init__.py
index 26725066f62..65de010232a 100644
--- a/addons/l10n_ch/tests/__init__.py
+++ b/addons/l10n_ch/tests/__init__.py
@@ -2,5 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_ch_qr_code
-from . import test_l10n_ch_isr
from . import test_swissqr
+from . import test_l10n_ch_isr_print
+from . import test_vendor_bill_isr
+from . import test_onchange_l10n_ch_postal
diff --git a/addons/l10n_ch/tests/test_l10n_ch_isr.py b/addons/l10n_ch/tests/test_l10n_ch_isr_print.py
index 196ad01346e..bd568636706 100644
--- a/addons/l10n_ch/tests/test_l10n_ch_isr.py
+++ b/addons/l10n_ch/tests/test_l10n_ch_isr_print.py
@@ -34,7 +34,7 @@ class ISRTest(AccountTestInvoicingCommon):
assertBankAccountValid('010391391', 'postal', expected_postal='010391391')
assertBankAccountValid('010391394', 'bank')
- assertBankAccountValid('CH6309000000250097798', 'iban', expected_postal='000250097798')
+ assertBankAccountValid('CH6309000000250097798', 'iban', expected_postal='25-9779-8')
assertBankAccountValid('GR1601101250000000012300695', 'iban', expected_postal=False)
def test_isr(self):
diff --git a/addons/l10n_ch/tests/test_onchange_l10n_ch_postal.py b/addons/l10n_ch/tests/test_onchange_l10n_ch_postal.py
new file mode 100644
index 00000000000..7763f51b0be
--- /dev/null
+++ b/addons/l10n_ch/tests/test_onchange_l10n_ch_postal.py
@@ -0,0 +1,108 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from odoo.tests import common
+from odoo.tests.common import Form, SavepointCase
+
+
+CH_ISR_ISSUER = '01-162-8'
+CH_IBAN = 'CH15 3881 5158 3845 3843 7'
+FR_IBAN = 'FR83 8723 4133 8709 9079 4002 530'
+CH_POST_IBAN = 'CH09 0900 0000 1000 8060 7'
+CH_POSTAL_ACC = '10-8060-7'
+
+
+class TestOnchangePostal(SavepointCase):
+
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True))
+ cls.partner = cls.env.ref('base.res_partner_12')
+ cls.ch_bank = cls.env['res.bank'].create({
+ 'name': 'Alternative Bank Schweiz AG',
+ 'bic': 'ALSWCH21XXX',
+ })
+ cls.post_bank = cls.env['res.bank'].search(
+ [('bic', '=', 'POFICHBEXXX')])
+ if not cls.post_bank:
+ cls.post_bank = cls.env['res.bank'].create({
+ 'name': 'PostFinance AG',
+ 'bic': 'POFICHBEXXX',
+ })
+
+ def new_partner_bank_form(self):
+ form = Form(
+ self.env['res.partner.bank'],
+ view="l10n_ch.isr_partner_bank_form",
+ )
+ form.partner_id = self.partner
+ return form
+
+ def test_onchange_acc_number_isr_issuer(self):
+ """The user entered ISR issuer number into acc_number
+
+ We detect and move it to l10n_ch_postal.
+ It must be moved as it is not unique.
+ """
+ bank_acc = self.new_partner_bank_form()
+ bank_acc.acc_number = CH_ISR_ISSUER
+ account = bank_acc.save()
+
+ self.assertEqual(
+ account.acc_number,
+ "{} {}".format(CH_ISR_ISSUER, self.partner.name)
+ )
+ self.assertEqual(account.l10n_ch_postal, CH_ISR_ISSUER)
+ self.assertEqual(account.acc_type, 'postal')
+
+ def test_onchange_acc_number_postal(self):
+ """The user entered postal number into acc_number
+
+ We detect and copy it to l10n_ch_postal.
+ """
+ bank_acc = self.new_partner_bank_form()
+ bank_acc.acc_number = CH_POSTAL_ACC
+ account = bank_acc.save()
+
+ self.assertEqual(account.acc_number, CH_POSTAL_ACC)
+ self.assertEqual(account.l10n_ch_postal, CH_POSTAL_ACC)
+ self.assertEqual(account.acc_type, 'postal')
+
+ def test_onchange_acc_number_iban_ch(self):
+ bank_acc = self.new_partner_bank_form()
+ bank_acc.acc_number = CH_IBAN
+ account = bank_acc.save()
+
+ self.assertEqual(account.acc_number, CH_IBAN)
+ self.assertFalse(account.l10n_ch_postal)
+ self.assertEqual(account.acc_type, 'iban')
+
+ def test_onchange_acc_number_iban_ch_postfinance(self):
+ """The user enter a postal IBAN, postal number can be deduced"""
+ bank_acc = self.new_partner_bank_form()
+ bank_acc.acc_number = CH_POST_IBAN
+ account = bank_acc.save()
+
+ self.assertEqual(account.acc_number, CH_POST_IBAN)
+ self.assertEqual(account.l10n_ch_postal, CH_POSTAL_ACC)
+ self.assertEqual(account.acc_type, 'iban')
+
+ def test_onchange_acc_number_iban_foreign(self):
+ """Check IBAN still works changed"""
+ bank_acc = self.new_partner_bank_form()
+ bank_acc.acc_number = FR_IBAN
+ account = bank_acc.save()
+
+ self.assertEqual(account.acc_number, FR_IBAN)
+ self.assertFalse(account.l10n_ch_postal)
+ self.assertEqual(account.acc_type, 'iban')
+
+ def test_onchange_acc_number_none(self):
+ """Check misc format still works"""
+ bank_acc = self.new_partner_bank_form()
+ bank_acc.acc_number = 'anything'
+ account = bank_acc.save()
+
+ self.assertEqual(account.acc_number, 'anything')
+ self.assertFalse(account.l10n_ch_postal)
+ self.assertEqual(account.acc_type, 'bank')
diff --git a/addons/l10n_ch/tests/test_swissqr.py b/addons/l10n_ch/tests/test_swissqr.py
index 1cc0092a7cd..8976f990097 100644
--- a/addons/l10n_ch/tests/test_swissqr.py
+++ b/addons/l10n_ch/tests/test_swissqr.py
@@ -100,12 +100,12 @@ class TestSwissQR(AccountTestInvoicingCommon):
)
if ref_type == 'QRR':
- self.assertTrue(invoice.invoice_payment_ref)
- struct_ref = invoice.invoice_payment_ref
+ self.assertTrue(invoice.payment_reference)
+ struct_ref = invoice.payment_reference
unstr_msg = invoice.ref or invoice.name or ''
else:
struct_ref = ''
- unstr_msg = invoice.invoice_payment_ref or invoice.ref or invoice.name or ''
+ unstr_msg = invoice.payment_reference or invoice.ref or invoice.name or ''
unstr_msg = (unstr_msg or invoice.number).replace('/', '%2F')
payload = (
@@ -148,7 +148,7 @@ class TestSwissQR(AccountTestInvoicingCommon):
None,
invoice.partner_id,
None,
- invoice.invoice_payment_ref,
+ invoice.payment_reference,
invoice.ref or invoice.name,
)
self.assertEqual(url, expected_url)
diff --git a/addons/l10n_ch/tests/test_vendor_bill_isr.py b/addons/l10n_ch/tests/test_vendor_bill_isr.py
new file mode 100644
index 00000000000..bc76a6cc83b
--- /dev/null
+++ b/addons/l10n_ch/tests/test_vendor_bill_isr.py
@@ -0,0 +1,119 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from odoo.tests import Form, common
+from odoo.exceptions import ValidationError
+
+
+CH_ISR_SUBSCRIPTION = "01-162-8"
+CH_POSTAL = "10-8060-7"
+CH_IBAN = "CH15 3881 5158 3845 3843 7"
+ISR_REFERENCE_GOOD = "16 00011 23456 78901 23456 78901"
+ISR_REFERENCE_ZEROS = "00 00000 00000 00001 23456 78903"
+ISR_REFERENCE_NO_ZEROS = "1 23456 78903"
+ISR_REFERENCE_BAD = "11 11111 11111 11111 11111 11111"
+
+
+class TestVendorBillISR(common.SavepointCase):
+ """Check we can encode Vendor bills with ISR references
+
+ The ISR is a structured reference with a checksum.
+ User are guided to ensure they don't encode wrong ISR references.
+ Only vendors with ISR issuer accounts send ISR references.
+
+ ISR references can be received at least till 2022.
+
+ """
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestVendorBillISR, cls).setUpClass()
+ cls.abs_bank = cls.env["res.bank"].create(
+ {"name": "Alternative Bank Schweiz", "bic": "ABSOCH22XXX"}
+ )
+ cls.supplier1 = cls.env["res.partner"].create({"name": "Supplier ISR"})
+ cls.supplier2 = cls.env["res.partner"].create({"name": "Supplier postal"})
+ cls.supplier3 = cls.env["res.partner"].create({"name": "Supplier IBAN"})
+
+ cls.bank_acc_isr = cls.env['res.partner.bank'].create({
+ "acc_number": "ISR 01-162-8 Supplier ISR",
+ "partner_id": cls.supplier1.id,
+ "l10n_ch_postal": CH_ISR_SUBSCRIPTION,
+ })
+ cls.bank_acc_postal = cls.env['res.partner.bank'].create({
+ "acc_number": CH_POSTAL,
+ "partner_id": cls.supplier2.id,
+ "l10n_ch_postal": CH_POSTAL,
+ })
+ cls.bank_acc_iban = cls.env['res.partner.bank'].create({
+ "acc_number": CH_IBAN,
+ "partner_id": cls.supplier2.id,
+ "l10n_ch_postal": False,
+ })
+
+ def test_isr_ref(self):
+ """Enter ISR reference with ISR subscription account number
+
+ The vendor bill can be saved.
+ """
+ self.env.company.country_id = self.env.ref('base.ch')
+ form = Form(self.env["account.move"].with_context(
+ default_move_type="in_invoice"), view="l10n_ch.isr_invoice_form")
+ form.partner_id = self.supplier1
+ form.partner_bank_id = self.bank_acc_isr
+
+ form.payment_reference = ISR_REFERENCE_GOOD
+ invoice = form.save()
+
+ self.assertFalse(invoice.l10n_ch_isr_needs_fixing)
+
+ def test_isr_ref_with_zeros(self):
+ """Enter ISR reference with ISR subscription account number
+
+ An ISR Reference can have lots of zeros on the left.
+
+ The vendor bill can be saved.
+ """
+ self.env.company.country_id = self.env.ref('base.ch')
+ form = Form(self.env["account.move"].with_context(
+ default_move_type="in_invoice"), view="l10n_ch.isr_invoice_form")
+ form.partner_id = self.supplier1
+ form.partner_bank_id = self.bank_acc_isr
+
+ form.payment_reference = ISR_REFERENCE_ZEROS
+ invoice = form.save()
+
+ self.assertFalse(invoice.l10n_ch_isr_needs_fixing)
+
+ def test_isr_ref_no_zeros(self):
+ """Enter ISR reference with ISR subscription account number
+
+ An ISR Reference full of zeros can be entered starting by the
+ first non zero digit.
+
+ The vendor bill can be saved.
+ """
+ self.env.company.country_id = self.env.ref('base.ch')
+ form = Form(self.env["account.move"].with_context(
+ default_move_type="in_invoice"), view="l10n_ch.isr_invoice_form")
+ form.partner_id = self.supplier1
+ form.partner_bank_id = self.bank_acc_isr
+
+ form.payment_reference = ISR_REFERENCE_NO_ZEROS
+ invoice = form.save()
+
+ self.assertFalse(invoice.l10n_ch_isr_needs_fixing)
+
+ def test_isr_wrong_ref(self):
+ """Mistype ISR reference with ISR subscription account number
+ Check it will show the warning
+ """
+ self.env.company.country_id = self.env.ref('base.ch')
+ form = Form(self.env["account.move"].with_context(
+ default_move_type="in_invoice"), view="l10n_ch.isr_invoice_form")
+ form.partner_id = self.supplier1
+ form.partner_bank_id = self.bank_acc_isr
+
+ form.payment_reference = ISR_REFERENCE_BAD
+ invoice = form.save()
+
+ self.assertTrue(invoice.l10n_ch_isr_needs_fixing) \ No newline at end of file
diff --git a/addons/l10n_ch/views/account_invoice_view.xml b/addons/l10n_ch/views/account_invoice_view.xml
index 046dc2b3544..1c5a59284a8 100644
--- a/addons/l10n_ch/views/account_invoice_view.xml
+++ b/addons/l10n_ch/views/account_invoice_view.xml
@@ -60,6 +60,12 @@
groups="base.group_user"
/>
</xpath>
+ <header position="after">
+ <field name="l10n_ch_isr_needs_fixing" invisible="1"/>
+ <div groups="account.group_account_invoice" class="alert alert-warning" role="alert" style="margin-bottom:0px;" attrs="{'invisible': [('l10n_ch_isr_needs_fixing', '=', False)]}">
+ Please fill in a correct ISR reference in the payment reference. The banks will refuse your payment file otherwise.
+ </div>
+ </header>
</field>
</record>