diff --git a/addons/payment/wizards/payment_link_wizard.py b/addons/payment/wizards/payment_link_wizard.py index 433ae57cfde..c9fa3b46eda 100644 --- a/addons/payment/wizards/payment_link_wizard.py +++ b/addons/payment/wizards/payment_link_wizard.py @@ -1,5 +1,5 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. - +from lxml import etree from werkzeug import urls from odoo import _, api, fields, models @@ -31,6 +31,41 @@ class PaymentLinkWizard(models.TransientModel): }) return res + @api.model + def fields_view_get(self, *args, **kwargs): + """ Overrides orm fields_view_get + + Using a Many2One field, when a user opens this wizard and tries to select a preferred + payment acquirer, he will get an AccessError telling that he is not allowed to access + 'payment.acquirer' records. This error is thrown because the Many2One field is filled + by the name_get() function and users don't have clearance to read 'payment.acquirer' records. + + This override allows replacing the Many2One with a selection field, that is prefilled in the + backend with the name of available acquirers. Therefore, Users will be able to select their + preferred acquirer. + + :return: composition of the requested view (including inherited views and extensions) + :rtype: dict + """ + res = super().fields_view_get(*args, **kwargs) + if res['type'] == 'form': + doc = etree.XML(res['arch']) + + # Replace acquirer_id with payment_acquirer_selection in the view + acq = doc.xpath("//field[@name='acquirer_id']")[0] + acq.attrib['name'] = 'payment_acquirer_selection' + acq.attrib['widget'] = 'selection' + acq.attrib['string'] = 'Force Payment Acquirer' + del acq.attrib['options'] + del acq.attrib['placeholder'] + + # Replace acquirer_id with payment_acquirer_selection in the fields list + xarch, xfields = self.env['ir.ui.view'].postprocess_and_fields(doc, model=self._name) + + res['arch'] = xarch + res['fields'] = xfields + return res + res_model = fields.Char('Related Document Model', required=True) res_id = fields.Integer('Related Document ID', required=True) amount = fields.Monetary(currency_field='currency_id', required=True) @@ -58,6 +93,14 @@ class PaymentLinkWizard(models.TransientModel): string="Has Multiple Acquirers", compute='_compute_has_multiple_acquirers', ) + payment_acquirer_selection = fields.Selection( + string="Payment acquirer selected", + selection='_selection_payment_acquirer_selection', + default='all', + compute='_compute_payment_acquirer_selection', + inverse='_inverse_payment_acquirer_selection', + required=True, + ) @api.onchange('amount', 'description') def _onchange_amount(self): @@ -66,7 +109,7 @@ class PaymentLinkWizard(models.TransientModel): if self.amount <= 0: raise ValidationError(_("The value of the payment amount must be positive.")) - @api.depends('amount', 'description', 'partner_id', 'currency_id', 'acquirer_id') + @api.depends('amount', 'description', 'partner_id', 'currency_id', 'payment_acquirer_selection') def _compute_values(self): for payment_link in self: payment_link.access_token = payment_utils.generate_access_token( @@ -84,12 +127,57 @@ class PaymentLinkWizard(models.TransientModel): @api.depends('company_id', 'partner_id', 'currency_id') def _compute_available_acquirer_ids(self): for link in self: - link.available_acquirer_ids = link.env['payment.acquirer']._get_compatible_acquirers( + link.available_acquirer_ids = link._get_payment_acquirer_available( + res_model=link.res_model, + res_id=link.res_id, company_id=link.company_id.id, partner_id=link.partner_id.id, - currency_id=link.currency_id.id + currency_id=link.currency_id.id, ) + @api.depends('acquirer_id') + def _compute_payment_acquirer_selection(self): + for link in self: + link.payment_acquirer_selection = link.acquirer_id.id if link.acquirer_id else 'all' + + def _inverse_payment_acquirer_selection(self): + for link in self: + link.acquirer_id = link.payment_acquirer_selection if link.payment_acquirer_selection != 'all' else False + + def _selection_payment_acquirer_selection(self): + """ Specify available acquirers in the selection field. + :return: The selection list of available acquirers. + :rtype: list[tuple] + """ + defaults = self.default_get(['res_model', 'res_id']) + selection = [('all', "All")] + res_model, res_id = defaults['res_model'], defaults['res_id'] + if res_id and res_model in ['account.move', "sale.order"]: + # At module install, the selection method is called + # but the document context isn't specified. + related_document = self.env[res_model].browse(res_id) + company_id = related_document.company_id + partner_id = related_document.partner_id + currency_id = related_document.currency_id + selection.extend( + self._get_payment_acquirer_available( + res_model=res_model, + res_id=res_id, + company_id=company_id.id, + partner_id=partner_id.id, + currency_id=currency_id.id, + ).name_get() + ) + return selection + + def _get_payment_acquirer_available(self, **kwargs): + """ Select and return the acquirers matching the criteria. + + :return: The compatible acquirers + :rtype: recordset of `payment.acquirer` + """ + return self.env['payment.acquirer'].sudo()._get_compatible_acquirers(**kwargs) + @api.depends('available_acquirer_ids') def _compute_has_multiple_acquirers(self): for link in self: @@ -106,5 +194,5 @@ class PaymentLinkWizard(models.TransientModel): f'&partner_id={payment_link.partner_id.id}' \ f'&company_id={payment_link.company_id.id}' \ f'&invoice_id={payment_link.res_id}' \ - f'{"&acquirer_id=" + str(payment_link.acquirer_id.id) if payment_link.acquirer_id else "" }' \ + f'{"&acquirer_id=" + str(payment_link.payment_acquirer_selection) if payment_link.payment_acquirer_selection != "all" else "" }' \ f'&access_token={payment_link.access_token}' diff --git a/addons/sale/wizard/sale_payment_link.py b/addons/sale/wizard/sale_payment_link.py index dc136b26b9b..0f85dbc6d5e 100644 --- a/addons/sale/wizard/sale_payment_link.py +++ b/addons/sale/wizard/sale_payment_link.py @@ -24,16 +24,17 @@ class PaymentLinkWizard(models.TransientModel): }) return res - @api.depends('company_id', 'partner_id', 'currency_id') - def _compute_available_acquirer_ids(self): - sale_links = self.filtered(lambda link: link.res_model == 'sale.order') - super(PaymentLinkWizard, self-sale_links)._compute_available_acquirer_ids() - for link in sale_links: - link.available_acquirer_ids = link.env['payment.acquirer']._get_compatible_acquirers( - company_id=link.company_id.id, - partner_id=link.partner_id.id, - currency_id=link.currency_id.id, - sale_order_id=link.res_id) + def _get_payment_acquirer_available(self, res_model, res_id, **kwargs): + """ Select and return the acquirers matching the criteria. + + :param str res_model: active model + :param int res_id: id of 'active_model' record + :return: The compatible acquirers + :rtype: recordset of `payment.acquirer` + """ + if res_model == 'sale.order': + kwargs['sale_order_id'] = res_id + return super()._get_payment_acquirer_available(**kwargs) def _generate_link(self): """ Override of payment to add the sale_order_id in the link. """ @@ -46,7 +47,7 @@ class PaymentLinkWizard(models.TransientModel): f'?reference={urls.url_quote(payment_link.description)}' \ f'&amount={payment_link.amount}' \ f'&sale_order_id={payment_link.res_id}' \ - f'{"&acquirer_id=" + str(payment_link.acquirer_id.id) if payment_link.acquirer_id else "" }' \ + f'{"&acquirer_id=" + str(payment_link.payment_acquirer_selection) if payment_link.payment_acquirer_selection != "all" else "" }' \ f'&access_token={payment_link.access_token}' # Order-related fields are retrieved in the controller else: