diff --git a/account_reconcile_restrict_partner_mismatch/README.rst b/account_reconcile_restrict_partner_mismatch/README.rst new file mode 100644 index 00000000..e69de29b diff --git a/account_reconcile_restrict_partner_mismatch/__init__.py b/account_reconcile_restrict_partner_mismatch/__init__.py new file mode 100755 index 00000000..bf588bc8 --- /dev/null +++ b/account_reconcile_restrict_partner_mismatch/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import report diff --git a/account_reconcile_restrict_partner_mismatch/__manifest__.py b/account_reconcile_restrict_partner_mismatch/__manifest__.py new file mode 100644 index 00000000..a9e05f06 --- /dev/null +++ b/account_reconcile_restrict_partner_mismatch/__manifest__.py @@ -0,0 +1,19 @@ +# Copyright 2019 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +{ + 'name': 'Reconcile restrict partner mismatch', + 'summary': 'Restrict reconciliation on receivable ' + 'and payable accounts to the same partner', + 'version': '12.0.1.0.0', + 'depends': ['account'], + 'author': 'Camptocamp, Odoo Community Association (OCA)', + 'website': 'http://www.github.com/OCA/account-reconcile', + 'category': 'Finance', + 'license': 'AGPL-3', + 'data': [ + 'report/account_move_lines_report.xml', + 'security/ir.model.access.csv', + ], + 'installable': True, +} diff --git a/account_reconcile_restrict_partner_mismatch/models/__init__.py b/account_reconcile_restrict_partner_mismatch/models/__init__.py new file mode 100755 index 00000000..8795b3be --- /dev/null +++ b/account_reconcile_restrict_partner_mismatch/models/__init__.py @@ -0,0 +1 @@ +from . import account_move_line diff --git a/account_reconcile_restrict_partner_mismatch/models/account_move_line.py b/account_reconcile_restrict_partner_mismatch/models/account_move_line.py new file mode 100644 index 00000000..8ab5f13e --- /dev/null +++ b/account_reconcile_restrict_partner_mismatch/models/account_move_line.py @@ -0,0 +1,29 @@ +# Copyright 2019 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import api, models, _ +from odoo.exceptions import UserError +from odoo.tools import config + + +class AccountMoveLine(models.Model): + _inherit = "account.move.line" + + @api.multi + def reconcile(self, writeoff_acc_id=False, writeoff_journal_id=False): + if (config['test_enable'] + and not self.env.context.get('test_partner_mismatch')): + return super().reconcile(writeoff_acc_id, writeoff_journal_id) + + # to be consistent with parent method + if not self: + return True + partners = set() + for line in self: + if line.account_id.internal_type in ('receivable', 'payable'): + partners.add(line.partner_id.id) + if len(partners) > 1: + raise UserError(_('The partner has to be the same on all' + ' lines for receivable and payable accounts!')) + return super().reconcile( + writeoff_acc_id, writeoff_journal_id) diff --git a/account_reconcile_restrict_partner_mismatch/readme/DESCRIPTION.rst b/account_reconcile_restrict_partner_mismatch/readme/DESCRIPTION.rst new file mode 100644 index 00000000..a546b3fa --- /dev/null +++ b/account_reconcile_restrict_partner_mismatch/readme/DESCRIPTION.rst @@ -0,0 +1,10 @@ +This module restricts reconciliation between journal items when: + + - both items have different partners + - one item is with partner and the other without it + +This rule applies only for journal items using receivable and payable account type. + +As at the moment of installation some journal items could have been reconciled +using different partners, you can detect them in menu Accounting > Adviser > +Reconciled items with partner mismatch. diff --git a/account_reconcile_restrict_partner_mismatch/report/__init__.py b/account_reconcile_restrict_partner_mismatch/report/__init__.py new file mode 100644 index 00000000..f32b29ee --- /dev/null +++ b/account_reconcile_restrict_partner_mismatch/report/__init__.py @@ -0,0 +1 @@ +from . import report_reconciled_lines diff --git a/account_reconcile_restrict_partner_mismatch/report/account_move_lines_report.xml b/account_reconcile_restrict_partner_mismatch/report/account_move_lines_report.xml new file mode 100644 index 00000000..14b7276f --- /dev/null +++ b/account_reconcile_restrict_partner_mismatch/report/account_move_lines_report.xml @@ -0,0 +1,61 @@ + + + + Reconciled items with partner mismatch + account.reconcile.partner.mismatch.report + + + + + + + + + + + + + + + + + + + account.reconcile.partner.mismatch.report.form + account.reconcile.partner.mismatch.report + +
+ + + + + + + + + + + + + + + + + + +
+
+
+ + + Reconciled items with partner mismatch + account.reconcile.partner.mismatch.report + tree,form + + + +
diff --git a/account_reconcile_restrict_partner_mismatch/report/report_reconciled_lines.py b/account_reconcile_restrict_partner_mismatch/report/report_reconciled_lines.py new file mode 100644 index 00000000..ac2cfc57 --- /dev/null +++ b/account_reconcile_restrict_partner_mismatch/report/report_reconciled_lines.py @@ -0,0 +1,65 @@ +# Copyright 2019 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + + +from odoo import api, fields, models, tools + + +class AccountReconcilePartnerMismatchReport(models.Model): + _name = 'account.reconcile.partner.mismatch.report' + _auto = False + + partial_reconcile_id = fields.Many2one( + 'account.partial.reconcile', + string="Partial Reconcile" + ) + full_reconcile_id = fields.Many2one('account.full.reconcile') + account_id = fields.Many2one( + 'account.account', + string="Account" + ) + account_type_id = fields.Many2one( + 'account.account.type', + string="Account type", + ) + debit_move_id = fields.Many2one('account.move.line', string="Debit move") + debit_amount = fields.Float("Debit amount") + debit_partner_id = fields.Many2one('res.partner', string="Debit partner") + credit_move_id = fields.Many2one('account.move.line', string="Credit move") + credit_amount = fields.Float("Credit amount") + credit_partner_id = fields.Many2one('res.partner', string="Credit partner") + + @api.model_cr + def init(self): + """Select lines which violate defined rules""" + tools.drop_view_if_exists(self.env.cr, self._table) + self._cr.execute( + """CREATE OR REPLACE VIEW %s AS ( + SELECT pr.id id + , pr.id partial_reconcile_id + , pr.full_reconcile_id + , pr.debit_move_id + , daml.debit debit_amount + , aat.id account_type_id + , daml.partner_id debit_partner_id + , daml.account_id account_id + , pr.credit_move_id + , caml.credit credit_amount + , caml.partner_id credit_partner_id + FROM account_partial_reconcile pr + LEFT JOIN account_move_line daml + ON daml.id = pr.debit_move_id + LEFT JOIN account_move_line caml + ON caml.id = pr.credit_move_id + LEFT JOIN account_account_type aat + ON daml.user_type_id = aat.id + WHERE aat.type in ('receivable', 'payable') + AND (daml.partner_id <> caml.partner_id + OR (daml.partner_id IS NULL + AND caml.partner_id IS NOT NULL) + OR (caml.partner_id IS NULL + AND daml.partner_id IS NOT NULL)) + ) + """ + % self._table + ) diff --git a/account_reconcile_restrict_partner_mismatch/security/ir.model.access.csv b/account_reconcile_restrict_partner_mismatch/security/ir.model.access.csv new file mode 100644 index 00000000..8dea174a --- /dev/null +++ b/account_reconcile_restrict_partner_mismatch/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_account_reconcile_partner_mismatch_report,access_account_reconcile_partner_mismatch_report,model_account_reconcile_partner_mismatch_report,account.group_account_user,1,0,0,0 diff --git a/account_reconcile_restrict_partner_mismatch/static/description/icon.png b/account_reconcile_restrict_partner_mismatch/static/description/icon.png new file mode 100644 index 00000000..3a0328b5 Binary files /dev/null and b/account_reconcile_restrict_partner_mismatch/static/description/icon.png differ diff --git a/account_reconcile_restrict_partner_mismatch/static/description/index.html b/account_reconcile_restrict_partner_mismatch/static/description/index.html new file mode 100644 index 00000000..876a1f6c --- /dev/null +++ b/account_reconcile_restrict_partner_mismatch/static/description/index.html @@ -0,0 +1,419 @@ + + + + + + +Account Set Reconcilable + + + +
+

Account Set Reconcilable

+ + +

Beta License: AGPL-3 OCA/account-reconcile Translate me on Weblate Try me on Runbot

+

Allows to set as reconcilable a non reconcilable account that already have journal items.

+

Table of contents

+
+ +
+
+

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.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Eficent
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

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.

+

This module is part of the OCA/account-reconcile project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/account_reconcile_restrict_partner_mismatch/tests/__init__.py b/account_reconcile_restrict_partner_mismatch/tests/__init__.py new file mode 100644 index 00000000..5eaab019 --- /dev/null +++ b/account_reconcile_restrict_partner_mismatch/tests/__init__.py @@ -0,0 +1 @@ +from . import test_reconciliation diff --git a/account_reconcile_restrict_partner_mismatch/tests/test_reconciliation.py b/account_reconcile_restrict_partner_mismatch/tests/test_reconciliation.py new file mode 100644 index 00000000..a21c6af9 --- /dev/null +++ b/account_reconcile_restrict_partner_mismatch/tests/test_reconciliation.py @@ -0,0 +1,104 @@ +# Copyright 2019 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo.addons.account.tests.account_test_classes import AccountingTestCase +from odoo.exceptions import UserError + + +class TestReconciliation(AccountingTestCase): + + def setUp(self): + super().setUp() + self.env = self.env(context=dict( + self.env.context, tracking_disable=True, + test_partner_mismatch=True) + ) + + self.partner = self.env.ref("base.res_partner_2") + self.partner_id = self.partner.id + rec_type = self.env['account.account'].search([ + ('user_type_id', '=', + self.env.ref('account.data_account_type_receivable').id) + ], limit=1) + pay_type = self.env['account.account'].search([ + ('user_type_id', '=', + self.env.ref('account.data_account_type_payable').id) + ], limit=1) + self.account_rcv = (self.partner.property_account_receivable_id + or rec_type) + self.account_rsa = self.partner.property_account_payable_id or pay_type + + self.bank_journal = self.env['account.journal']. \ + create({'name': 'Bank', 'type': 'bank', 'code': 'BNK67'}) + self.aml = self.init_moves() + self.wizard = self.env['account.move.line.reconcile.writeoff']. \ + with_context(active_ids=[x.id for x in self.aml]).create({ + 'journal_id': self.bank_journal.id, + 'writeoff_acc_id': self.account_rsa.id + }) + + def create_move(self, name, amount): + debit_line_vals = { + 'name': name, + 'debit': amount > 0 and amount or 0.0, + 'credit': amount < 0 and -amount or 0.0, + 'account_id': self.account_rcv.id, + } + credit_line_vals = debit_line_vals.copy() + credit_line_vals['debit'] = debit_line_vals['credit'] + credit_line_vals['credit'] = debit_line_vals['debit'] + credit_line_vals['account_id'] = self.account_rsa.id + vals = { + 'journal_id': self.bank_journal.id, + 'line_ids': [(0, 0, debit_line_vals), (0, 0, credit_line_vals)] + } + return self.env['account.move'].create(vals).id + + def init_moves(self): + move_list_vals = [ + ('1', -1.83), + ('2', 728.35), + ('3', -4.46), + ('4', 0.32), + ('5', 14.72), + ('6', -737.10), + ] + move_ids = [] + for name, amount in move_list_vals: + move_ids.append(self.create_move(name, amount)) + aml_recs = self.env['account.move.line'].search([ + ('move_id', 'in', move_ids), + ('account_id', '=', self.account_rcv.id) + ]) + return aml_recs + + def test_reconcile_no_partner(self): + self.wizard.trans_rec_reconcile() + self.assertTrue(all(self.aml.mapped('reconciled'))) + + def test_reconcile_partner_mismatch(self): + self.aml[0].partner_id = self.partner.id + with self.assertRaises(UserError): + self.wizard.trans_rec_reconcile() + # all lines with same partner allowed + self.aml.write({'partner_id': self.partner.id}) + self.wizard.trans_rec_reconcile() + self.assertTrue(all(self.aml.mapped('reconciled'))) + + def test_reconcile_accounts_excluded(self): + self.aml[0].partner_id = self.partner.id + with self.assertRaises(UserError): + self.wizard.trans_rec_reconcile() + # reconciliation forbiden only for certain types of accounts + account = self.env['account.account'].search([ + ('user_type_id.type', '=', 'other') + ], limit=1) + account.reconcile = True + self.aml[0].account_id = account.id + with self.assertRaises(UserError): + self.wizard.trans_rec_reconcile() + # reconciliation for different partners allowed + # for not forbidden types + self.aml.write({'account_id': account.id}) + self.wizard.trans_rec_reconcile() + self.assertTrue(all(self.aml.mapped('reconciled')))