[IMP] account: make QR error message more explicit

When trying to add a QR code to an invoice, if some conditions weren't met, the user would more often than not get a message simply stating :
"The chosen QR code is not eligible with this invoice".
This was confusing to the user, since the reason for a QR code to not be eligible are multiple : invalid IBAN, unavailable in X country, wrong currency...
Modified the _eligible_for_qr_code function to _get_error_messages_for_qr() so it returns an error message if the qr code is not eligible, and None otherwise.

task-3069753

closes odoo/odoo#116482

Signed-off-by: Olivier Colson (oco) <oco@odoo.com>
This commit is contained in:
Camille Spiritus
2023-03-31 11:58:47 +02:00
committed by oco-odoo
parent 7710b82c26
commit cbf462f9b9
7 changed files with 47 additions and 43 deletions
+5 -3
View File
@@ -3964,12 +3964,14 @@ class AccountMove(models.Model):
qr_code_method = self.qr_code_method
if qr_code_method:
# If the user set a qr code generator manually, we check that we can use it
if not self.partner_bank_id._eligible_for_qr_code(self.qr_code_method, self.partner_id, self.currency_id):
raise UserError(_("The chosen QR-code type is not eligible for this invoice."))
error_msg = self.partner_bank_id._get_error_messages_for_qr(self.qr_code_method, self.partner_id, self.currency_id)
if error_msg:
raise UserError(error_msg)
else:
# Else we find one that's eligible and assign it to the invoice
for candidate_method, _candidate_name in self.env['res.partner.bank'].get_available_qr_methods_in_sequence():
if self.partner_bank_id._eligible_for_qr_code(candidate_method, self.partner_id, self.currency_id, raises_error=False):
error_msg = self.partner_bank_id._get_error_messages_for_qr(candidate_method, self.partner_id, self.currency_id)
if not error_msg:
qr_code_method = candidate_method
break
+7 -5
View File
@@ -112,14 +112,14 @@ class ResPartnerBank(models.Model):
return None
self.ensure_one()
if not currency:
raise UserError(_("Currency must always be provided in order to generate a QR-code"))
available_qr_methods = self.get_available_qr_methods_in_sequence()
candidate_methods = qr_method and [(qr_method, dict(available_qr_methods)[qr_method])] or available_qr_methods
for candidate_method, candidate_name in candidate_methods:
if self._eligible_for_qr_code(candidate_method, debtor_partner, currency, not silent_errors):
error_msg = self._get_error_messages_for_qr(candidate_method, debtor_partner, currency)
if not error_msg:
error_message = self._check_for_qr_code_errors(candidate_method, amount, currency, debtor_partner, free_communication, structured_communication)
if not error_message:
@@ -216,18 +216,20 @@ class ResPartnerBank(models.Model):
all_available.sort(key=lambda x: x[2])
return [(code, name) for (code, name, sequence) in all_available]
def _eligible_for_qr_code(self, qr_method, debtor_partner, currency, raises_error=True):
def _get_error_messages_for_qr(self, qr_method, debtor_partner, currency):
""" Tells whether or not the criteria to apply QR-generation
method qr_method are met for a payment on this account, in the
given currency, by debtor_partner. This does not impeach generation errors,
it only checks that this type of QR-code *should be* possible to generate.
If not, returns an adequate error message to be displayed to the user if need be.
Consistency of the required field needs then to be checked by _check_for_qr_code_errors().
:returns: None if the qr method is eligible, or the error message
"""
return False
return None
def _check_for_qr_code_errors(self, qr_method, amount, currency, debtor_partner, free_communication, structured_communication):
""" Checks the data before generating a QR-code for the specified qr_method
(this method must have been checked for eligbility by _eligible_for_qr_code() first).
(this method must have been checked for eligbility by _get_error_messages_for_qr() first).
Returns None if no error was found, or a string describing the first error encountered
so that it can be reported to the user.
+12 -6
View File
@@ -38,18 +38,24 @@ class ResPartnerBank(models.Model):
}
return super()._get_qr_code_generation_params(qr_method, amount, currency, debtor_partner, free_communication, structured_communication)
def _eligible_for_qr_code(self, qr_method, debtor_partner, currency, raises_error=True):
def _get_error_messages_for_qr(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)
error_messages = []
if currency.name != 'EUR':
error_messages.append(_("Can't generate a SEPA QR Code with the %s currency." % currency.name))
if self.acc_type != 'iban':
error_messages.append(_("Can't generate a SEPA QR code if the account type isn't IBAN."))
if not (self.sanitized_acc_number and self.sanitized_acc_number[:2] in sepa_iban_codes):
error_messages.append(_("Can't generate a SEPA QR code with a non SEPA iban."))
if len(error_messages) > 0:
return '\r\n'.join(error_messages)
return None
return super()._get_error_messages_for_qr(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':
+2 -2
View File
@@ -40,8 +40,8 @@ class AccountMove(models.Model):
@api.depends('partner_id', 'currency_id')
def _compute_l10n_ch_qr_is_valid(self):
for move in self:
move.l10n_ch_is_qr_valid = move.move_type == 'out_invoice' \
and move.partner_bank_id._eligible_for_qr_code('ch_qr', move.partner_id, move.currency_id, raises_error=False)
error_messages = move.partner_bank_id._get_error_messages_for_qr('ch_qr', move.partner_id, move.currency_id)
move.l10n_ch_is_qr_valid = move.move_type == 'out_invoice' and not error_messages
@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):
+12 -12
View File
@@ -346,11 +346,9 @@ class ResPartnerBank(models.Model):
and int(''.join(str(int(x, 36)) for x in clean(reference[4:] + reference[:4], ' -.,/:').upper().strip())) % 97 == 1
# see https://github.com/arthurdejong/python-stdnum/blob/master/stdnum/iso11649.py
def _eligible_for_qr_code(self, qr_method, debtor_partner, currency, raises_error=True):
if qr_method == 'sct_qr' and debtor_partner.country_id.code == 'CH' and self.journal_id.country_code == 'CH':
return False
if qr_method == 'ch_qr':
error_messages = [_("The QR code could not be generated for the following reason(s):")]
def _get_error_messages_for_qr(self, qr_method, debtor_partner, currency):
def _get_error_for_ch_qr():
error_messages = [_("The Swiss QR code could not be generated for the following reason(s):")]
if self.acc_type != 'iban':
error_messages.append(_("The account type isn't QR-IBAN or IBAN."))
if self.partner_id.country_id.code != 'CH':
@@ -358,13 +356,15 @@ class ResPartnerBank(models.Model):
if not debtor_partner or debtor_partner.country_id.code not in ('CH', 'LI'):
error_messages.append(_("The debtor partner's address isn't located in Switzerland."))
if currency.id not in (self.env.ref('base.EUR').id, self.env.ref('base.CHF').id):
error_messages.append(_("The currency isn't EUR nor CHF. \r\n"))
if len(error_messages) != 1:
if raises_error:
raise UserError(' '.join(error_messages))
return False
return True
return super()._eligible_for_qr_code(qr_method, debtor_partner, currency, raises_error)
error_messages.append(_("The currency isn't EUR nor CHF."))
return '\r\n'.join(error_messages) if len(error_messages) > 1 else None
if qr_method == 'sct_qr' and not _get_error_for_ch_qr():
return _("When both parties are located in Switzerland, "
"you should use the Swiss QR code process.")
if qr_method == 'ch_qr':
return _get_error_for_ch_qr()
return super()._get_error_messages_for_qr(qr_method, debtor_partner, currency)
def _check_for_qr_code_errors(self, qr_method, amount, currency, debtor_partner, free_communication, structured_communication):
def _partner_fields_set(partner):
+6 -9
View File
@@ -93,16 +93,16 @@ class TestSwissQR(AccountTestInvoicingCommon):
def swissqr_not_generated(self, invoice):
""" Prints the given invoice and tests that no Swiss QR generation is triggered. """
self.assertFalse(
invoice.partner_bank_id._eligible_for_qr_code('ch_qr', invoice.partner_id, invoice.currency_id),
self.assertTrue(
invoice.partner_bank_id._get_error_messages_for_qr('ch_qr', invoice.partner_id, invoice.currency_id),
'No Swiss QR should be generated for this invoice',
)
def swissqr_generated(self, invoice, ref_type='NON'):
""" Ensure correct params for Swiss QR generation. """
self.assertTrue(
invoice.partner_bank_id._eligible_for_qr_code('ch_qr', invoice.partner_id, invoice.currency_id), 'A Swiss QR can be generated'
self.assertFalse(
invoice.partner_bank_id._get_error_messages_for_qr('ch_qr', invoice.partner_id, invoice.currency_id), 'A Swiss QR can be generated'
)
if ref_type == 'QRR':
@@ -162,11 +162,8 @@ class TestSwissQR(AccountTestInvoicingCommon):
self.assertEqual(params, expected_params)
def test_swissQR_missing_bank(self):
# Let us test the generation of a SwissQR for an invoice, first by showing an
# QR is included in the invoice is only generated when Odoo has all the data it needs.
with self.assertRaises(UserError), self.cr.savepoint():
self.invoice1.action_post()
self.swissqr_not_generated(self.invoice1)
self.invoice1.action_post()
self.swissqr_not_generated(self.invoice1)
def test_swissQR_iban(self):
# Now we add an account for payment to our invoice
+3 -6
View File
@@ -75,12 +75,9 @@ class QrInvoiceWizard(models.TransientModel):
# Log a message inside the chatter explaining why the invoice is faulty.
for inv in faulty_invoices:
try:
# The error potentially raised in the following function helps create the wizard's message.
inv.partner_bank_id._eligible_for_qr_code('ch_qr', inv.partner_id, inv.currency_id, raises_error=True)
except UserError as e:
inv.message_post(body=e.name, message_type="comment")
error_msg = inv.partner_bank_id._get_error_messages_for_qr('ch_qr', inv.partner_id, inv.currency_id)
if error_msg:
inv.message_post(body=error_msg, message_type="comment")
action_vals = {
'name': _("Invalid Invoices"),
'type': 'ir.actions.act_window',