mirror of
https://github.com/OCA/account-financial-tools.git
synced 2025-02-02 12:47:26 +02:00
[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:
committed by
Fekete Mihai
parent
1c714c95b3
commit
0a572de67c
76
account_netting/README.rst
Normal file
76
account_netting/README.rst
Normal 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.
|
||||
5
account_netting/__init__.py
Normal file
5
account_netting/__init__.py
Normal 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
|
||||
20
account_netting/__openerp__.py
Normal file
20
account_netting/__openerp__.py
Normal 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
133
account_netting/i18n/es.po
Normal 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"
|
||||
|
||||
BIN
account_netting/static/description/icon.png
Normal file
BIN
account_netting/static/description/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
5
account_netting/tests/__init__.py
Normal file
5
account_netting/tests/__init__.py
Normal 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
|
||||
77
account_netting/tests/test_account_netting.py
Normal file
77
account_netting/tests/test_account_netting.py
Normal 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')
|
||||
5
account_netting/wizard/__init__.py
Normal file
5
account_netting/wizard/__init__.py
Normal 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
|
||||
115
account_netting/wizard/account_move_make_netting.py
Normal file
115
account_netting/wizard/account_move_make_netting.py
Normal 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
|
||||
37
account_netting/wizard/account_move_make_netting_view.xml
Normal file
37
account_netting/wizard/account_move_make_netting_view.xml
Normal 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>
|
||||
Reference in New Issue
Block a user