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

closes #370

X-original-commit: 157c914
Signed-off-by: augusto-weiss <awe@adhoc.com.ar>
  • Loading branch information
maq-adhoc authored and augusto-weiss committed Jan 10, 2024
1 parent 8c7c3c3 commit 19d5e54
Show file tree
Hide file tree
Showing 12 changed files with 461 additions and 0 deletions.
71 changes: 71 additions & 0 deletions account_payment_multi/RADME.rst
@@ -0,0 +1,71 @@
.. |company| replace:: ADHOC SA

.. |company_logo| image:: https://raw.githubusercontent.com/ingadhoc/maintainer-tools/master/resources/adhoc-logo.png
:alt: ADHOC SA
:target: https://www.adhoc.com.ar

.. |icon| image:: https://raw.githubusercontent.com/ingadhoc/maintainer-tools/master/resources/adhoc-icon.png

.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png
:target: https://www.gnu.org/licenses/agpl
:alt: License: AGPL-3

==============
account_payment_multi
==============

This module extends the online invoice payment functionality by allowing you to generate a singlep ayment links to pay multiple invoices.
It is available both from the backend and from the customer portal.

Installation
============

To install this module, you need to:

#. Do this ...

Configuration
=============

To configure this module, you need to:

#. Go to ...

Usage
=====

To use this module, you need to:

#. Go to ...

.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: http://runbot.adhoc.com.ar/

Bug Tracker
===========

Bugs are tracked on `GitHub Issues
<https://github.com/ingadhoc/account_payment/issues>`_. In case of trouble, please
check there if your issue has already been reported. If you spotted it first,
help us smashing it by providing a detailed and welcomed feedback.

Credits
=======

Images
------

* |company| |icon|

Contributors
------------

Maintainer
----------

|company_logo|

This module is maintained by the |company|.

To contribute to this module, please visit https://www.adhoc.com.ar.
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.get('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('commercial_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.commercial_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);

});
74 changes: 74 additions & 0 deletions account_payment_multi/static/src/js/payment_multi.js
@@ -0,0 +1,74 @@
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_group = false;
let group = false;
console.log(group);
for (let i = 0; i < items.length; i++) {
if (items[i].checked){
old_group = group;
group = items[i].dataset.invoiceGroup;
currency = items[i].dataset.currencyName;
if (old_group && group != old_group){
items[i].checked = false;
return new Dialog(null, {
title: _t("Error in selection"),
size: 'medium',
$content: _t(`<p>selected invoices must be in the same currency, partner and company</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;
});

}
});
});
55 changes: 55 additions & 0 deletions account_payment_multi/views/account_portal_templates.xml
@@ -0,0 +1,55 @@
<?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-group = "'%s-%s-%s' % (invoice.currency_id.id, invoice.commercial_partner_id.id , invoice.company_id.id)"
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

0 comments on commit 19d5e54

Please sign in to comment.