Skip to content

Commit

Permalink
[ADD] add new module account_payment_multi to create payment links fo…
Browse files Browse the repository at this point in the history
…r multiples invoices
  • Loading branch information
maq-adhoc committed Apr 28, 2023
1 parent 0571160 commit c2ca0ca
Show file tree
Hide file tree
Showing 11 changed files with 381 additions and 0 deletions.
2 changes: 2 additions & 0 deletions account_payment_multi/__init__.py
@@ -0,0 +1,2 @@
from . import controllers
from . import wizards
28 changes: 28 additions & 0 deletions account_payment_multi/__manifest__.py
@@ -0,0 +1,28 @@
{
'name': "Account payment multi",
'description': """
Allows payment multiple invoices with a single payment link
""",
'author': 'ADHOC SA',
'website': "https://www.adhoc.com.ar",
'category': 'Technical',
'version': "15.0.1.0.0",
'depends': ['account_payment'],
'license': 'LGPL-3',
'images': [
],
'installable': True,
'assets': {
'web.assets_frontend': [
'account_payment_multi/static/src/js/payment_form.js',
'account_payment_multi/static/src/js/payment_multi.js',
],
},
'data': [
'views/payment_templates.xml',
'views/account_portal_templates.xml',
'wizards/payment_link_wizard_views.xml',
],
'demo': [
],
}
1 change: 1 addition & 0 deletions account_payment_multi/controllers/__init__.py
@@ -0,0 +1 @@
from . import portal
101 changes: 101 additions & 0 deletions account_payment_multi/controllers/portal.py
@@ -0,0 +1,101 @@
##############################################################################
# For copyright and license notices, see __manifest__.py file in module root
# directory
##############################################################################

from odoo import _, http
from odoo.exceptions import ValidationError
from odoo.fields import Command
from odoo.http import request

from odoo.addons.portal.controllers import portal
from odoo.tools import float_compare


class PaymentPortal(portal.CustomerPortal):

@http.route('/payment/invoice_multi_link', type='json', auth='user')
def invoice_multi_transaction(self, invoice_ids, amount, **kwargs):
invoices_sudo = request.env['account.move'].sudo()
for invoice in invoice_ids:
# Check the invoice id
invoices_sudo += self._document_check_access('account.move', invoice['id'], invoice['token'])
payment_link = request.env['payment.link.wizard'].sudo().with_context(active_id=invoices_sudo[0].id, active_ids=invoices_sudo.ids, active_model='account.move').create({})

if float_compare(payment_link.amount_max, amount, precision_rounding=payment_link.currency_id.rounding or 0.01) == -1:
raise ValidationError(_("Incorrect amount"))
return payment_link.link

@http.route()
def payment_pay(
self, reference=None, amount=None, currency_id=None, partner_id=None, company_id=None,
acquirer_id=None, access_token=None, invoice_id=None, **kwargs
):

amount = self._cast_as_float(amount)
if 'invoice_ids' in kwargs:
invoice_ids = [int(x) for x in kwargs['invoice_ids'].split(',') if x.isdigit()]
invoices_sudo = request.env['account.move'].sudo().browse(invoice_ids).exists()
if not invoices_sudo:
raise ValidationError(_("The provided parameters are invalid."))
if len(invoices_sudo.mapped('partner_id')) > 1:
raise ValidationError(_("Only pay invoices from the same customer."))
if len(invoices_sudo.mapped('currency_id')) > 1:
raise ValidationError(_("Only pay invoices from the same currency."))
if len(invoices_sudo.mapped('company_id')) > 1:
raise ValidationError(_("Only pay invoices from the same company."))
first_invoice_sudo = invoices_sudo[0]
# Check the access token against the invoice values. Done after fetching the invoice
# as we need the invoice fields to check the access token.
# if not payment_utils.check_access_token(
# access_token, first_invoice_sudo.partner_id.id, amount, first_invoice_sudo.currency_id.id
# ):
# raise ValidationError(_("The provided parameters are invalid."))
currency_id = first_invoice_sudo.currency_id.id
partner_id = first_invoice_sudo.partner_id.id
company_id = first_invoice_sudo.company_id.id

kwargs.update({
'invoice_ids': invoice_ids,
})
return super().payment_pay(
reference=reference, amount=amount, currency_id=currency_id, partner_id=partner_id, company_id=company_id,
acquirer_id=acquirer_id, access_token=access_token, invoice_id=invoice_id, **kwargs

)

def _get_custom_rendering_context_values(self, invoice_ids=None, **kwargs):
""" Override of `payment` to add the invoice id in the custom rendering context values.
:param int invoice_id: The invoice for which a payment id made, as an `account.move` id.
:param dict kwargs: Optional data. This parameter is not used here.
:return: The extended rendering context values.
:rtype: dict
"""
rendering_context_values = super()._get_custom_rendering_context_values(**kwargs)
if invoice_ids:
rendering_context_values['invoice_ids'] = invoice_ids

# Interrupt the payment flow if the invoice has been canceled.
# invoice_sudo = request.env['account.move'].sudo().browse(invoice_ids)
#if invoice_sudo.state == 'cancel':
# rendering_context_values['amount'] = 0.0
return rendering_context_values

def _create_transaction(self, *args, invoice_ids=None, custom_create_values=None, **kwargs):
""" Override of `payment` to add the invoice id in the custom create values.
:param int invoice_id: The invoice for which a payment id made, as an `account.move` id.
:param dict custom_create_values: Additional create values overwriting the default ones.
:param dict kwargs: Optional data. This parameter is not used here.
:return: The result of the parent method.
:rtype: recordset of `payment.transaction`
"""
##import pdb; pdb.set_trace()
if invoice_ids:
if custom_create_values is None:
custom_create_values = {}
custom_create_values['invoice_ids'] = [Command.set(invoice_ids)]
return super()._create_transaction(
*args, custom_create_values=custom_create_values, **kwargs
)
36 changes: 36 additions & 0 deletions account_payment_multi/static/src/js/payment_form.js
@@ -0,0 +1,36 @@
odoo.define('account_payment_multi.payment_form', require => {
'use strict';

const checkoutForm = require('payment.checkout_form');
const manageForm = require('payment.manage_form');

const PaymentMixin = {

//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------

/**
* Add `invoice_id` to the transaction route params if it is provided.
*
* @override method from payment.payment_form_mixin
* @private
* @param {string} code - The provider code of the selected payment option.
* @param {number} paymentOptionId - The id of the selected payment option.
* @param {string} flow - The online payment flow of the selected payment option.
* @return {object} The extended transaction route params.
*/
_prepareTransactionRouteParams: function (code, paymentOptionId, flow) {
const transactionRouteParams = this._super(...arguments);
return {
...transactionRouteParams,
'invoice_ids': this.txContext.invoiceIds ? this.txContext.invoiceIds : null,
};
},

};

checkoutForm.include(PaymentMixin);
manageForm.include(PaymentMixin);

});
71 changes: 71 additions & 0 deletions account_payment_multi/static/src/js/payment_multi.js
@@ -0,0 +1,71 @@
odoo.define('account_payment.multi', function (require) {
'use strict';

const core = require('web.core');
const publicWidget = require('web.public.widget');
const Dialog = require('web.Dialog');
const _t = core._t;

publicWidget.registry.AccountPaymentWidget = publicWidget.Widget.extend({
selector: '.payment_multi_table',
events: {
'change .checkbox_amount_residual': '_onChangeCheckboxAmountResidual',
'click .oe_multi_pay_now': '_onPayNowBtnClick',
},
init: function () {
this._super.apply(this, arguments);
//this._computeAmount();
},
_onChangeCheckboxAmountResidual: function(event) {
this._computeAmount()
},
_computeAmount: function(){
var items = this.el.getElementsByClassName('checkbox_amount_residual');
let total = 0;
let currency = false;
let old_currency = false;

for (let i = 0; i < items.length; i++) {
if (items[i].checked){
old_currency = currency;
currency = items[i].dataset.currencyName;
if (old_currency && currency != old_currency){
return new Dialog(null, {
title: _t("Error in selection"),
size: 'medium',
$content: _t(`<p>selected invoices must be in the same currency</p>`),
buttons: [{text: _t("Ok"), close: true}]
}).open();

}
total = total + parseFloat(items[i].dataset.amountResidual);
}
}
this.el.querySelectorAll('.oe_amount').forEach((selector) =>selector.innerHTML=currency + ' ' + total.toFixed(2));
if (total){
this.el.querySelectorAll('.multi_payment_selector').forEach((selector) =>selector.classList.remove('invisible'));
} else {
this.el.querySelectorAll('.multi_payment_selector').forEach((selector) =>selector.classList.add('invisible'));
}
},
_onPayNowBtnClick: function(event){
var items = this.el.getElementsByClassName('checkbox_amount_residual');
let total = 0;
let invoices = [];
for (let i = 0; i < items.length; i++) {
if (items[i].checked){
total = total + parseFloat(items[i].dataset.amountResidual);
invoices.push({id : parseInt(items[i].dataset.invoiceId), token: items[i].dataset.accessToken})
}
}
let params = {invoice_ids: invoices, amount: total}
return this._rpc({
route: "/payment/invoice_multi_link",
params: params,
}).then(async data => {
window.location = data;
});

}
});
});
54 changes: 54 additions & 0 deletions account_payment_multi/views/account_portal_templates.xml
@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>

<template id="portal_my_invoices_payment" name="Payment on My Invoices" inherit_id="account.portal_my_invoices">
<xpath expr="//t[@t-call='portal.portal_table']/thead/tr/th[1]" position="before">
<th></th>
</xpath>
<xpath expr="//t[@t-foreach='invoices']/tr/td[1]" position="before">
<td class="text-center">
<input
id="pay_amount_residual"
t-att-data-invoice-id = "invoice.id"
t-att-data-currency-name = "invoice.currency_id.name"
t-att-data-access-token = "invoice.access_token"
t-att-data-amount-residual = "invoice.amount_residual"
type="checkbox"
t-att-class="'checkbox_amount_residual'"
t-if="invoice.state == 'posted' and invoice.payment_state in ('not_paid', 'partial') and invoice.amount_total and invoice.move_type == 'out_invoice'"/>
</td>
</xpath>
<xpath expr="//t[@t-foreach='invoices']" position="before">
<tr>
<td class="text-center"><i class="fa fa-arrow-down"></i></td>
<td><strong class="oe_zeroAmount">Select invoices to pay</strong></td>
<td colspan="10">
<span class="multi_payment_selector invisible">
<a href="#" title="Pay Now" aria-label="Pay now" class="btn btn-sm btn-primary oe_multi_pay_now" role="button">
<i class="fa fa-arrow-circle-right"/><span class='d-none d-md-inline'> Pay <span class="oe_amount"></span> Now</span>
</a>
</span>
</td>
</tr>
</xpath>

<xpath expr="//t[@t-foreach='invoices']" position="after">
<tr>
<td class="text-center"></td>
<td></td>
<td colspan="10">
<span class="multi_payment_selector invisible">
<a href="#" title="Pay Now" aria-label="Pay now" class="btn btn-sm btn-primary oe_multi_pay_now" role="button">
<i class="fa fa-arrow-circle-right"/><span class='d-none d-md-inline'> Pay <span class="oe_amount"></span> Now</span>
</a>
</span>
</td>
</tr>
</xpath>
<t t-call="portal.portal_table" position="before">
<t t-set="classes" t-value="'payment_multi_table'"/>
</t>

</template>

</odoo>
14 changes: 14 additions & 0 deletions account_payment_multi/views/payment_templates.xml
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<template id="payment_checkout_inherit" inherit_id="payment.checkout">
<xpath expr="//form[@name='o_payment_checkout']" position="attributes">
<attribute name="t-att-data-invoice-ids">invoice_ids</attribute>
</xpath>
</template>

<template id="payment_manage_inherit" inherit_id="payment.manage">
<xpath expr="//form[@name='o_payment_manage']" position="attributes">
<attribute name="t-att-data-invoice-ids">invoice_ids</attribute>
</xpath>
</template>
</odoo>
1 change: 1 addition & 0 deletions account_payment_multi/wizards/__init__.py
@@ -0,0 +1 @@
from . import payment_link_wizard
60 changes: 60 additions & 0 deletions account_payment_multi/wizards/payment_link_wizard.py
@@ -0,0 +1,60 @@
##############################################################################
# For copyright and license notices, see __manifest__.py file in module root
# directory
##############################################################################

from odoo import models, fields, api
from werkzeug import urls


class PaymentLinkWizard(models.TransientModel):
_inherit = 'payment.link.wizard'

@api.model
def default_get(self, fields):
res = super().default_get(fields)
res_ids = self._context.get('active_ids')
res_model = self._context.get('active_model')
if len(res_ids) > 1 and res_model == 'account.move':
amount = sum(self.env[res_model].browse(res_ids).mapped('amount_residual'))
res.update({'res_ids': ','.join(map(str,res_ids)), 'amount': amount, 'amount_max':amount,})
return res

res_ids = fields.Char("Related Document IDS")

def _generate_link(self):
for payment_link in self:
if payment_link.res_ids:
related_document = self.env[payment_link.res_model].browse(payment_link.res_id)
base_url = related_document.get_base_url() # Don't generate links for the wrong website
payment_link.link = f'{base_url}/payment/pay' \
f'?reference={urls.url_quote(payment_link.description)}' \
f'&amount={payment_link.amount}' \
f'&currency_id={payment_link.currency_id.id}' \
f'&partner_id={payment_link.partner_id.id}' \
f'&company_id={payment_link.company_id.id}' \
f'&invoice_ids={payment_link.res_ids}' \
f'{"&acquirer_id=" + str(payment_link.payment_acquirer_selection) if payment_link.payment_acquirer_selection != "all" else "" }' \
f'&access_token={payment_link.access_token}'
else:
return super()._generate_link()

def _get_additional_link_values(self):
""" Override of `payment` to add `invoice_id` to the payment link values.
The other values related to the invoice are directly read from the invoice.
Note: self.ensure_one()
:return: The additional payment link values.
:rtype: dict
"""
res = super()._get_additional_link_values()
if self.res_model != 'account.move':
return res
if self.res_ids:
res_ids = [int(x) for x in self.res_ids.split(',') if x.isdigit()]
return {
'invoice_ids': res_ids,
}
return res
13 changes: 13 additions & 0 deletions account_payment_multi/wizards/payment_link_wizard_views.xml
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>

<record id="action_invoice_multi_generate_link" model="ir.actions.act_window">
<field name="name">Generate a Payment Link</field>
<field name="res_model">payment.link.wizard</field>
<field name="view_mode">form</field>
<field name="view_id" ref="payment.payment_link_wizard_view_form"/>
<field name="target">new</field>
<field name="binding_model_id" ref="account.model_account_move"/>
<field name="binding_view_types">list</field>
</record>
</odoo>

0 comments on commit c2ca0ca

Please sign in to comment.