diff --git a/account_netting/README.rst b/account_netting/README.rst new file mode 100644 index 000000000..73cf5f783 --- /dev/null +++ b/account_netting/README.rst @@ -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 `_. +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 `_. + + +Credits +======= + +Contributors +------------ + +* Pedro M. Baeza + +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. diff --git a/account_netting/__init__.py b/account_netting/__init__.py new file mode 100644 index 000000000..9b0f517f5 --- /dev/null +++ b/account_netting/__init__.py @@ -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 diff --git a/account_netting/__openerp__.py b/account_netting/__openerp__.py new file mode 100644 index 000000000..7f4bafe60 --- /dev/null +++ b/account_netting/__openerp__.py @@ -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, +} diff --git a/account_netting/i18n/es.po b/account_netting/i18n/es.po new file mode 100644 index 000000000..9c6e19e27 --- /dev/null +++ b/account_netting/i18n/es.po @@ -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" + diff --git a/account_netting/static/description/icon.png b/account_netting/static/description/icon.png new file mode 100644 index 000000000..3a0328b51 Binary files /dev/null and b/account_netting/static/description/icon.png differ diff --git a/account_netting/tests/__init__.py b/account_netting/tests/__init__.py new file mode 100644 index 000000000..89ab9a71c --- /dev/null +++ b/account_netting/tests/__init__.py @@ -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 diff --git a/account_netting/tests/test_account_netting.py b/account_netting/tests/test_account_netting.py new file mode 100644 index 000000000..c5a6a5ce4 --- /dev/null +++ b/account_netting/tests/test_account_netting.py @@ -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') diff --git a/account_netting/wizard/__init__.py b/account_netting/wizard/__init__.py new file mode 100644 index 000000000..45a789c27 --- /dev/null +++ b/account_netting/wizard/__init__.py @@ -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 diff --git a/account_netting/wizard/account_move_make_netting.py b/account_netting/wizard/account_move_make_netting.py new file mode 100644 index 000000000..fbdc7dc80 --- /dev/null +++ b/account_netting/wizard/account_move_make_netting.py @@ -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 diff --git a/account_netting/wizard/account_move_make_netting_view.xml b/account_netting/wizard/account_move_make_netting_view.xml new file mode 100644 index 000000000..7a90badb1 --- /dev/null +++ b/account_netting/wizard/account_move_make_netting_view.xml @@ -0,0 +1,37 @@ + + + + + + + Compensate entries + account.move.make.netting + +
+
+
+ + +
+