diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index 23051ea5f31..097dac5769f 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -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 diff --git a/addons/account/models/res_partner_bank.py b/addons/account/models/res_partner_bank.py index 701bc45d455..94e78f15b45 100644 --- a/addons/account/models/res_partner_bank.py +++ b/addons/account/models/res_partner_bank.py @@ -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. diff --git a/addons/account_qr_code_sepa/models/res_bank.py b/addons/account_qr_code_sepa/models/res_bank.py index 29ed6473708..638a83b7d00 100644 --- a/addons/account_qr_code_sepa/models/res_bank.py +++ b/addons/account_qr_code_sepa/models/res_bank.py @@ -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': diff --git a/addons/l10n_ch/models/account_invoice.py b/addons/l10n_ch/models/account_invoice.py index a7f94ea205e..88c7774fb88 100644 --- a/addons/l10n_ch/models/account_invoice.py +++ b/addons/l10n_ch/models/account_invoice.py @@ -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): diff --git a/addons/l10n_ch/models/res_bank.py b/addons/l10n_ch/models/res_bank.py index a06542d2d81..a173b04b4fc 100644 --- a/addons/l10n_ch/models/res_bank.py +++ b/addons/l10n_ch/models/res_bank.py @@ -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): diff --git a/addons/l10n_ch/tests/test_swissqr.py b/addons/l10n_ch/tests/test_swissqr.py index d19a25332cc..3a57d36410b 100644 --- a/addons/l10n_ch/tests/test_swissqr.py +++ b/addons/l10n_ch/tests/test_swissqr.py @@ -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 diff --git a/addons/l10n_ch/wizard/qr_invoice_wizard.py b/addons/l10n_ch/wizard/qr_invoice_wizard.py index 4dfbf18e7b1..d89705703ed 100644 --- a/addons/l10n_ch/wizard/qr_invoice_wizard.py +++ b/addons/l10n_ch/wizard/qr_invoice_wizard.py @@ -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',