diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index a9f35b6a604..a3fcaca19ee 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -3348,7 +3348,7 @@ class AccountMove(models.Model): return None unstruct_ref = self.ref if self.ref else self.name - rslt = self.partner_bank_id.build_qr_code_url(self.amount_residual, unstruct_ref, self.payment_reference, self.currency_id, self.partner_id, qr_code_method, silent_errors=False) + rslt = self.partner_bank_id.build_qr_code_base64(self.amount_residual, unstruct_ref, self.payment_reference, self.currency_id, self.partner_id, qr_code_method, silent_errors=False) # We only set qr_code_method after generating the url; otherwise, it # could be set even in case of a failure in the QR code generation diff --git a/addons/account/models/account_payment.py b/addons/account/models/account_payment.py index ef955af8e52..626c63950a9 100644 --- a/addons/account/models/account_payment.py +++ b/addons/account/models/account_payment.py @@ -498,7 +498,7 @@ class AccountPayment(models.Model): and pay.currency_id: if pay.partner_bank_id: - qr_code = pay.partner_bank_id.build_qr_code_url(pay.amount, pay.ref, pay.ref, pay.currency_id, pay.partner_id) + qr_code = pay.partner_bank_id.build_qr_code_base64(pay.amount, pay.ref, pay.ref, pay.currency_id, pay.partner_id) else: qr_code = None diff --git a/addons/account/models/res_bank.py b/addons/account/models/res_bank.py index 226e1c889ad..bb01198badd 100644 --- a/addons/account/models/res_bank.py +++ b/addons/account/models/res_bank.py @@ -1,13 +1,18 @@ # -*- coding: utf-8 -*- +import base64 from odoo import api, models, fields, _ from odoo.exceptions import UserError +from odoo.tools.image import image_data_uri + +import werkzeug +import werkzeug.exceptions class ResPartnerBank(models.Model): _inherit = 'res.partner.bank' - def build_qr_code_url(self, amount, free_communication, structured_communication, currency, debtor_partner, qr_method=None, silent_errors=True): - """ Returns the QR-code report URL to pay to this account with the given parameters, + def _build_qr_code_vals(self, amount, free_communication, structured_communication, currency, debtor_partner, qr_method=None, silent_errors=True): + """ Returns the QR-code vals needed to generate the QR-code report link to pay this account with the given parameters, or None if no QR-code could be generated. :param amount: The amount to be paid @@ -33,7 +38,14 @@ class ResPartnerBank(models.Model): error_message = self._check_for_qr_code_errors(candidate_method, amount, currency, debtor_partner, free_communication, structured_communication) if not error_message: - return self._get_qr_code_url(candidate_method, amount, currency, debtor_partner, free_communication, structured_communication) + return { + 'qr_method': candidate_method, + 'amount': amount, + 'currency': currency, + 'debtor_partner': debtor_partner, + 'free_communication': free_communication, + 'structured_communication': structured_communication, + } elif not silent_errors: error_header = _("The following error prevented '%s' QR-code to be generated though it was detected as eligible: ", candidate_name) @@ -41,6 +53,38 @@ class ResPartnerBank(models.Model): return None + def build_qr_code_url(self, amount, free_communication, structured_communication, currency, debtor_partner, qr_method=None, silent_errors=True): + vals = self._build_qr_code_vals(amount, free_communication, structured_communication, currency, debtor_partner, qr_method, silent_errors) + if vals: + return self._get_qr_code_url( + vals['qr_method'], + vals['amount'], + vals['currency'], + vals['debtor_partner'], + vals['free_communication'], + vals['structured_communication'], + ) + return None + + def build_qr_code_base64(self, amount, free_communication, structured_communication, currency, debtor_partner, qr_method=None, silent_errors=True): + vals = self._build_qr_code_vals(amount, free_communication, structured_communication, currency, debtor_partner, qr_method, silent_errors) + if vals: + return self._get_qr_code_base64( + vals['qr_method'], + vals['amount'], + vals['currency'], + vals['debtor_partner'], + vals['free_communication'], + vals['structured_communication'] + ) + return None + + def _get_qr_vals(self, qr_method, amount, currency, debtor_partner, free_communication, structured_communication): + return None + + def _get_qr_code_generation_params(self, qr_method, amount, currency, debtor_partner, free_communication, structured_communication): + return None + def _get_qr_code_url(self, qr_method, amount, currency, debtor_partner, free_communication, structured_communication): """ Hook for extension, to support the different QR generation methods. This function uses the provided qr_method to try generation a QR-code for @@ -54,6 +98,36 @@ class ResPartnerBank(models.Model): :param free_communication: Free communication to add to the payment when generating one with the QR-code :param structured_communication: Structured communication to add to the payment when generating one with the QR-code """ + # pylint: disable=E1137 + # (PyLint doesn't get that we are not assigning to None here) + params = self._get_qr_code_generation_params(qr_method, amount, currency, debtor_partner, free_communication, structured_communication) + if params: + params['type'] = params.pop('barcode_type') + return '/report/barcode/?' + werkzeug.urls.url_encode(params) + return None + + def _get_qr_code_base64(self, qr_method, amount, currency, debtor_partner, free_communication, structured_communication): + """ Hook for extension, to support the different QR generation methods. + This function uses the provided qr_method to try generation a QR-code for + the given data. It it succeeds, it returns QR code in base64 url; else None. + + :param qr_method: The QR generation method to be used to make the QR-code. + :param amount: The amount to be paid + :param currency: The currency in which amount is expressed + :param debtor_partner: The partner to which this QR-code is aimed (so the one who will have to pay) + :param free_communication: Free communication to add to the payment when generating one with the QR-code + :param structured_communication: Structured communication to add to the payment when generating one with the QR-code + """ + # pylint: disable=E1137 + # pylint: disable=E1134 + # (PyLint doesn't get that we are not assigning to None here) + params = self._get_qr_code_generation_params(qr_method, amount, currency, debtor_partner, free_communication, structured_communication) + if params: + try: + barcode = self.env['ir.actions.report'].barcode(**params) + except (ValueError, AttributeError): + raise werkzeug.exceptions.HTTPException(description='Cannot convert into barcode.') + return image_data_uri(base64.b64encode(barcode)) return None @api.model diff --git a/addons/account_qr_code_sepa/models/res_bank.py b/addons/account_qr_code_sepa/models/res_bank.py index 1a453d9e236..0927b429c1d 100644 --- a/addons/account_qr_code_sepa/models/res_bank.py +++ b/addons/account_qr_code_sepa/models/res_bank.py @@ -2,15 +2,12 @@ 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): + def _get_qr_vals(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 = [ @@ -27,10 +24,19 @@ class ResPartnerBank(models.Model): comment[:141], # Remittance Information (Unstructured) (can't be set if there is a structured one) '', # Beneficiary to Originator Information ] + return qr_code_vals + return super()._get_qr_vals(qr_method, amount, currency, debtor_partner, free_communication, structured_communication) - 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 _get_qr_code_generation_params(self, qr_method, amount, currency, debtor_partner, free_communication, structured_communication): + if qr_method == 'sct_qr': + return { + 'barcode_type': 'QR', + 'width': 128, + 'height': 128, + 'humanreadable': 1, + 'value': '\n'.join(self._get_qr_vals(qr_method, amount, currency, debtor_partner, free_communication, structured_communication)), + } + 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): if qr_method == 'sct_qr': diff --git a/addons/l10n_ch/models/res_bank.py b/addons/l10n_ch/models/res_bank.py index e393f576a09..8dd9a8de5ae 100644 --- a/addons/l10n_ch/models/res_bank.py +++ b/addons/l10n_ch/models/res_bank.py @@ -9,7 +9,6 @@ from odoo.addons.base_iban.models.res_partner_bank import normalize_iban, pretty from odoo.exceptions import ValidationError from odoo.tools.misc import mod10r -import werkzeug.urls ISR_SUBSCRIPTION_CODE = {'CHF': '01', 'EUR': '03'} CLEARING = "09000" @@ -236,14 +235,6 @@ class ResPartnerBank(models.Model): 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): - if qr_method == 'ch_qr': - qr_code_vals = self._l10n_ch_get_qr_vals(amount, currency, debtor_partner, free_communication, structured_communication) - - return '/report/barcode/?type=%s&value=%s&width=%s&height=%s&quiet=1&mask=ch_cross' % ('QR', werkzeug.urls.url_quote_plus('\n'.join(qr_code_vals)), 256, 256) - - return super()._get_qr_code_url(qr_method, amount, currency, debtor_partner, free_communication, structured_communication) - def _l10n_ch_get_qr_vals(self, amount, currency, debtor_partner, free_communication, structured_communication): comment = "" if free_communication: @@ -301,6 +292,23 @@ class ResPartnerBank(models.Model): 'EPD', # Mandatory trailer part ] + def _get_qr_vals(self, qr_method, amount, currency, debtor_partner, free_communication, structured_communication): + if qr_method == 'ch_qr': + return self._l10n_ch_get_qr_vals(amount, currency, debtor_partner, free_communication, structured_communication) + return super()._get_qr_vals(qr_method, amount, currency, debtor_partner, free_communication, structured_communication) + + def _get_qr_code_generation_params(self, qr_method, amount, currency, debtor_partner, free_communication, structured_communication): + if qr_method == 'ch_qr': + return { + 'barcode_type': 'QR', + 'width': 256, + 'height': 256, + 'quiet': 1, + 'mask': 'ch_cross', + 'value': '\n'.join(self._get_qr_vals(qr_method, amount, currency, debtor_partner, free_communication, structured_communication)), + } + return super()._get_qr_code_generation_params(qr_method, amount, currency, debtor_partner, free_communication, structured_communication) + def _get_partner_address_lines(self, partner): """ Returns a tuple of two elements containing the address lines to use for this partner. Line 1 contains the street and number, line 2 contains diff --git a/addons/l10n_ch/report/swissqr_report.py b/addons/l10n_ch/report/swissqr_report.py index 9c3efd91f1a..178777cb035 100644 --- a/addons/l10n_ch/report/swissqr_report.py +++ b/addons/l10n_ch/report/swissqr_report.py @@ -12,7 +12,7 @@ class ReportSwissQR(models.AbstractModel): qr_code_urls = {} for invoice in docs: - qr_code_urls[invoice.id] = invoice.partner_bank_id.build_qr_code_url(invoice.amount_residual, invoice.ref or invoice.name, invoice.payment_reference, invoice.currency_id, invoice.partner_id, qr_method='ch_qr', silent_errors=False) + qr_code_urls[invoice.id] = invoice.partner_bank_id.build_qr_code_base64(invoice.amount_residual, invoice.ref or invoice.name, invoice.payment_reference, invoice.currency_id, invoice.partner_id, qr_method='ch_qr', silent_errors=False) return { 'doc_ids': docids, diff --git a/addons/l10n_ch/report/swissqr_report.xml b/addons/l10n_ch/report/swissqr_report.xml index 1617a512d70..776c26fc8c6 100644 --- a/addons/l10n_ch/report/swissqr_report.xml +++ b/addons/l10n_ch/report/swissqr_report.xml @@ -16,8 +16,6 @@