[ADD] account_netting: AR/AP netting

AR/AP netting
=============

This module allows to compensate the balance of a receivable account with the
balance of a payable account for the same partner, creating a journal item
that reflects this operation.

**WARNING**: This operation can be forbidden in your country by the accounting
regulations, so you should check current laws before using it. For example, in
Spain, this is not allowed at first instance, unless you document well the
operation from both parties.

Usage
=====

From any account journal entries view:

* Accounting/Journal Entries/Journal Items
* Accounting/Periodic Processing/Reconciliation/Manual Reconciliation

select all the lines that corresponds to both AR/AP operations from the same
partner. Click on "More > Compensate". If the items don't correspond to the
same partner or they aren't AR/AP accounts, you will get an error.

On contrary, a dialog box will be presented with the result of the operation
and a selection of the journal to register the operation. When you click on the
"Compensate" button, a journal entry is created with the corresponding
counterparts of the AR/AP operations.
This commit is contained in:
Pedro M. Baeza
2015-08-07 18:05:03 +02:00
committed by Fekete Mihai
parent 1c714c95b3
commit 0a572de67c
10 changed files with 473 additions and 0 deletions

View File

@@ -0,0 +1,76 @@
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
=============
AR/AP netting
=============
This module allows to compensate the balance of a receivable account with the
balance of a payable account for the same partner, creating a journal item
that reflects this operation.
**WARNING**: This operation can be forbidden in your country by the accounting
regulations, so you should check current laws before using it. For example, in
Spain, this is not allowed at first instance, unless you document well the
operation from both parties.
Usage
=====
From any account journal entries view:
* Accounting/Journal Entries/Journal Items
* Accounting/Periodic Processing/Reconciliation/Manual Reconciliation
select all the lines that corresponds to both AR/AP operations from the same
partner. Click on "More > Compensate". If the items don't correspond to the
same partner or they aren't AR/AP accounts, you will get an error.
On contrary, a dialog box will be presented with the result of the operation
and a selection of the journal to register the operation. When you click on the
"Compensate" button, a journal entry is created with the corresponding
counterparts of the AR/AP operations.
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/92/8.0
Known issues / Roadmap
======================
* We can add the possibility to pay the netting result amount directly from
the wizard.
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/account-financial-tools/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
`here <https://github.com/OCA/account-financial-tools/issues/new?body=module:%20account_netting%0Aversion:%208.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Credits
=======
Contributors
------------
* Pedro M. Baeza <pedro.baeza@serviciosbaeza.com>
Maintainer
----------
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
This module is maintained by the OCA.
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
To contribute to this module, please visit http://odoo-community.org.

View File

@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# (c) 2015 Pedro M. Baeza
# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html
from . import wizard

View File

@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# (c) 2015 Pedro M. Baeza
# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html
{
'name': 'Account netting',
'version': '8.0.1.0.0',
'summary': "Compensate AR/AP accounts from the same partner",
'category': 'Accounting & Finance',
'author': 'Serv. Tecnol. Avanzados - Pedro M. Baeza, '
'Odoo Community Association (OCA)',
'website': 'https://github.com/OCA/account-financial-tools',
'depends': [
'account',
],
'data': [
'wizard/account_move_make_netting_view.xml',
],
"installable": True,
}

133
account_netting/i18n/es.po Normal file
View File

@@ -0,0 +1,133 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * account_netting
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 8.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-08-07 15:54+0000\n"
"PO-Revision-Date: 2015-08-07 15:54+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: account_netting
#: code:addons/account_netting/wizard/account_move_make_netting.py:56
#, python-format
msgid "AR/AP netting"
msgstr "Compensación a cobrar/a pagar"
#. module: account_netting
#: code:addons/account_netting/wizard/account_move_make_netting.py:30
#, python-format
msgid "All entries must have a receivable or payable account"
msgstr "Todos los apuntes deben tener una cuenta a pagar o a cobrar"
#. module: account_netting
#: code:addons/account_netting/wizard/account_move_make_netting.py:33
#, python-format
msgid "All entries mustn't been reconciled"
msgstr "Ningún apunte debe estar conciliado"
#. module: account_netting
#: code:addons/account_netting/wizard/account_move_make_netting.py:39
#, python-format
msgid "All entries should have a partner and the partner must be the same for all."
msgstr "Todos los apuntes deben tener una empresa y la empresa debe ser la misma para todos."
#. module: account_netting
#: field:account.move.make.netting,balance:0
msgid "Balance"
msgstr "Saldo"
#. module: account_netting
#: field:account.move.make.netting,balance_type:0
msgid "Balance type"
msgstr "Tipo de saldo"
#. module: account_netting
#: view:account.move.make.netting:account_netting.view_account_move_make_netting_form
msgid "Cancel"
msgstr "Cancelar"
#. module: account_netting
#: view:account.move.make.netting:account_netting.view_account_move_make_netting_form
#: model:ir.actions.act_window,name:account_netting.act_account_move_make_netting
msgid "Compensate"
msgstr "Compensar"
#. module: account_netting
#: view:account.move.make.netting:account_netting.view_account_move_make_netting_form
msgid "Compensate entries"
msgstr "Compensar apuntes"
#. module: account_netting
#: field:account.move.make.netting,create_uid:0
msgid "Created by"
msgstr "Creado por"
#. module: account_netting
#: field:account.move.make.netting,create_date:0
msgid "Created on"
msgstr "Creado en"
#. module: account_netting
#: field:account.move.make.netting,id:0
msgid "ID"
msgstr "ID"
#. module: account_netting
#: field:account.move.make.netting,journal:0
msgid "Journal"
msgstr "Diario"
#. module: account_netting
#: field:account.move.make.netting,write_uid:0
msgid "Last Updated by"
msgstr "Última actualización por"
#. module: account_netting
#: field:account.move.make.netting,write_date:0
msgid "Last Updated on"
msgstr "Última actualización en"
#. module: account_netting
#: field:account.move.make.netting,move_lines:0
msgid "Move lines"
msgstr "Apuntes"
#. module: account_netting
#: view:account.move.make.netting:account_netting.view_account_move_make_netting_form
msgid "Select the journal where storing the journal entries"
msgstr "Seleccione el diario en el que se guardarán los apuntes"
#. module: account_netting
#: view:account.move.make.netting:account_netting.view_account_move_make_netting_form
msgid "This operation will generate account entries that are counterpart of the receivable/payable accounts selected, and reconcile each other, letting this balance in the partner:"
msgstr "Esta operación generará apuntes que serán la contrapartida de las cuentas a cobrar/a pagar seleccionadas, y las reconciliará entre ellas, dejando este saldo en la empresa:"
#. module: account_netting
#: selection:account.move.make.netting,balance_type:0
msgid "To pay"
msgstr "A pagar"
#. module: account_netting
#: selection:account.move.make.netting,balance_type:0
msgid "To receive"
msgstr "A cobrar"
#. module: account_netting
#: code:addons/account_netting/wizard/account_move_make_netting.py:24
#, python-format
msgid "You should compensate at least 2 journal entries."
msgstr "Debe compensar al menos 2 apuntes"
#. module: account_netting
#: view:account.move.make.netting:account_netting.view_account_move_make_netting_form
msgid "or"
msgstr "o"

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# (c) 2015 Pedro M. Baeza
# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html
from . import test_account_netting

View File

@@ -0,0 +1,77 @@
# -*- coding: utf-8 -*-
# (c) 2015 Pedro M. Baeza
# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html
import openerp.tests.common as common
from openerp import workflow
class TestAccountNetting(common.TransactionCase):
def setUp(self):
super(TestAccountNetting, self).setUp()
self.partner = self.env['res.partner'].create(
{'supplier': True,
'customer': True,
'name': "Supplier/Customer"
})
self.invoice_model = self.env['account.invoice']
self.account_receivable = self.env.ref('account.a_recv')
self.customer_invoice = self.invoice_model.create(
{'journal_id': self.env.ref('account.sales_journal').id,
'type': 'out_invoice',
'partner_id': self.partner.id,
'account_id': self.account_receivable.id,
'invoice_line': [(0, 0, {'name': 'Test',
'price_unit': 100.0})],
})
workflow.trg_validate(
self.uid, 'account.invoice', self.customer_invoice.id,
'invoice_open', self.cr)
customer_move = self.customer_invoice.move_id
self.move_line_1 = customer_move.line_id.filtered(
lambda x: x.account_id == self.account_receivable)
self.account_payable = self.env.ref('account.a_pay')
self.supplier_invoice = self.invoice_model.create(
{'journal_id': self.env.ref('account.expenses_journal').id,
'type': 'in_invoice',
'partner_id': self.partner.id,
'account_id': self.account_payable.id,
'invoice_line': [(0, 0, {'name': 'Test',
'price_unit': 1200.0})],
})
workflow.trg_validate(
self.uid, 'account.invoice', self.supplier_invoice.id,
'invoice_open', self.cr)
supplier_move = self.supplier_invoice.move_id
self.move_line_2 = supplier_move.line_id.filtered(
lambda x: x.account_id == self.account_payable)
def test_compensation(self):
obj = self.env['account.move.make.netting'].with_context(
active_ids=[self.move_line_1.id, self.move_line_2.id])
wizard = obj.create(
{'move_lines': [(6, 0, [self.move_line_1.id,
self.move_line_2.id])],
'journal': self.env.ref('account.miscellaneous_journal').id})
res = wizard.button_compensate()
move = self.env['account.move'].browse(res['res_id'])
self.assertEqual(
len(move.line_id), 2,
'AR/AP netting move has an incorrect line number')
move_line_receivable = move.line_id.filtered(
lambda x: x.account_id == self.account_receivable)
self.assertEqual(
move_line_receivable.credit, 100,
'Incorrect credit amount for receivable move line')
self.assertTrue(
move_line_receivable.reconcile_id,
'Receivable move line should be totally reconciled')
move_line_payable = move.line_id.filtered(
lambda x: x.account_id == self.account_payable)
self.assertEqual(
move_line_payable.debit, 100,
'Incorrect debit amount for payable move line')
self.assertTrue(
move_line_payable.reconcile_partial_id,
'Receivable move line should be partially reconciled')

View File

@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# (c) 2015 Pedro M. Baeza
# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html
from . import account_move_make_netting

View File

@@ -0,0 +1,115 @@
# -*- coding: utf-8 -*-
# (c) 2015 Pedro M. Baeza
# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html
from openerp import models, fields, api, exceptions, _
class AccountMoveMakeNetting(models.TransientModel):
_name = "account.move.make.netting"
journal = fields.Many2one(
comodel_name="account.journal", required=True,
domain="[('type', '=', 'general')]")
move_lines = fields.Many2many(comodel_name="account.move.line")
balance = fields.Float(readonly=True)
balance_type = fields.Selection(
selection=[('pay', 'To pay'), ('receive', 'To receive')],
readonly=True)
@api.model
def default_get(self, fields):
if len(self.env.context.get('active_ids', [])) < 2:
raise exceptions.ValidationError(
_("You should compensate at least 2 journal entries."))
move_lines = self.env['account.move.line'].browse(
self.env.context['active_ids'])
if (any(x not in ('payable', 'receivable') for
x in move_lines.mapped('account_id.type'))):
raise exceptions.ValidationError(
_("All entries must have a receivable or payable account"))
if any(move_lines.mapped('reconcile_id')):
raise exceptions.ValidationError(
_("All entries mustn't been reconciled"))
partner_id = None
for move in move_lines:
if (not move.partner_id or (
move.partner_id != partner_id and partner_id is not None)):
raise exceptions.ValidationError(
_("All entries should have a partner and the partner must "
"be the same for all."))
partner_id = move.partner_id
res = super(AccountMoveMakeNetting, self).default_get(fields)
res['move_lines'] = [(6, 0, move_lines.ids)]
balance = (sum(move_lines.mapped('debit')) -
sum(move_lines.mapped('credit')))
res['balance'] = abs(balance)
res['balance_type'] = 'pay' if balance < 0 else 'receive'
return res
@api.multi
def button_compensate(self):
self.ensure_one()
# Create account move
move = self.env['account.move'].create(
{
'ref': _('AR/AP netting'),
'journal_id': self.journal.id,
})
# Group amounts by account
account_groups = self.move_lines.read_group(
[('id', 'in', self.move_lines.ids)],
['account_id', 'debit', 'credit'], ['account_id'])
debtors = []
creditors = []
total_debtors = 0
total_creditors = 0
for account_group in account_groups:
balance = account_group['debit'] - account_group['credit']
group_vals = {
'account_id': account_group['account_id'][0],
'balance': abs(balance),
}
if balance > 0:
debtors.append(group_vals)
total_debtors += balance
else:
creditors.append(group_vals)
total_creditors += abs(balance)
# Create move lines
move_line_model = self.env['account.move.line']
netting_amount = min(total_creditors, total_debtors)
field_map = {1: 'debit', 0: 'credit'}
for i, group in enumerate([debtors, creditors]):
available_amount = netting_amount
for account_group in group:
if account_group['balance'] > available_amount:
amount = available_amount
else:
amount = account_group['balance']
move_line_vals = {
field_map[i]: amount,
'move_id': move.id,
'partner_id': self.move_lines[0].partner_id.id,
'date': move.date,
'period_id': move.period_id.id,
'journal_id': move.journal_id.id,
'name': move.ref,
'account_id': account_group['account_id'],
}
move_line_model.create(move_line_vals)
available_amount -= account_group['balance']
if available_amount <= 0:
break
# Make reconciliation
for move_line in move.line_id:
to_reconcile = move_line + self.move_lines.filtered(
lambda x: x.account_id == move_line.account_id)
to_reconcile.reconcile_partial()
# Open created move
action = self.env.ref('account.action_move_journal_line').read()[0]
action['view_mode'] = 'form'
del action['views']
del action['view_id']
action['res_id'] = move.id
return action

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<act_window name="Compensate"
res_model="account.move.make.netting"
src_model="account.move.line"
view_mode="form"
target="new"
key2="client_action_multi"
multi="True"
id="act_account_move_make_netting"/>
<record id="view_account_move_make_netting_form" model="ir.ui.view">
<field name="name">Compensate entries</field>
<field name="model">account.move.make.netting</field>
<field name="arch" type="xml">
<form string="Compensate entries">
<label string="This operation will generate account entries that are counterpart of the receivable/payable accounts selected, and reconcile each other, letting this balance in the partner:"/>
<group>
<field name="balance"/>
<field name="balance_type"/>
</group>
<group string="Select the journal where storing the journal entries">
<field name="journal"/>
</group>
<footer>
<button name="button_compensate" string="Compensate" type="object" class="oe_highlight"/>
or
<button string="Cancel" class="oe_link" special="cancel" />
</footer>
</form>
</field>
</record>
</data>
</openerp>