[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:
committed by
oco-odoo
parent
7710b82c26
commit
cbf462f9b9
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user