diff --git a/addons/account_full_reconcile/__init__.py b/addons/account_full_reconcile/__init__.py new file mode 100644 index 00000000000..9c0ea901d41 --- /dev/null +++ b/addons/account_full_reconcile/__init__.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import models + +from openerp import SUPERUSER_ID +from collections import OrderedDict +from openerp.tools import float_compare + +def _migrate_full_reconcile(cr, registry): + #avoid doing anything if the table has already something in it (already migrated) + cr.execute("""SELECT count(id) FROM account_full_reconcile""") + res = cr.fetchone()[0] + if res: + return + #check column reconcile_id exists on account.move.line + cr.execute(""" + SELECT 1 FROM information_schema.columns + WHERE table_name = 'account_move_line' + AND column_name = 'reconcile_id' + """) + if not cr.fetchone(): + #if no, it's a fresh v9 database and we cannot migrate anything. Rebuild the whole table based on partial + all_partial_rec_ids = registry['account.partial.reconcile'].search(cr, SUPERUSER_ID, []) + already_processed = {} + for partial in registry['account.partial.reconcile'].browse(cr, SUPERUSER_ID, all_partial_rec_ids): + partial_rec_set = OrderedDict.fromkeys([partial]) + aml_set = set() + total_debit = 0 + total_credit = 0 + for partial_rec in partial_rec_set: + if partial_rec in already_processed: + continue + for aml in [partial_rec.debit_move_id, partial_rec.credit_move_id]: + if aml not in aml_set: + total_debit += aml.debit + total_credit += aml.credit + aml_set |= set([aml]) + for x in aml.matched_debit_ids | aml.matched_credit_ids: + partial_rec_set[x] = None + partial_rec_ids = [] + for x in partial_rec_set.keys(): + partial_rec_ids.append(x.id) + already_processed[x] = None + aml_ids = [x.id for x in aml_set] + if aml_ids and partial_rec_ids: + #then, if the total debit and credit are equal, the reconciliation is full + digits_rounding_precision = aml.company_id.currency_id.rounding + if float_compare(total_debit, total_credit, precision_rounding=digits_rounding_precision) == 0: + #in that case, mark the reference on the partial reconciliations and the entries + registry['account.full.reconcile'].create(cr, SUPERUSER_ID, { + 'partial_reconcile_ids': [(6, 0, partial_rec_ids)], + 'reconciled_line_ids': [(6, 0, aml_ids)] + }, context={'check_move_validity': False}) + return + + #otherwise, use the table account_move_reconcile if it exists (not dropped by migration) + cr.execute("SELECT 1 FROM information_schema.tables WHERE table_name = 'account_move_reconcile'") + if cr.fetchone(): + #account_move_reconcile exists + #copy old table + cr.execute(""" + INSERT INTO account_full_reconcile (id, name, create_date) + SELECT id, name, create_date + FROM account_move_reconcile + WHERE id IN ( + SELECT DISTINCT reconcile_id FROM account_move_line WHERE reconcile_id IS NOT NULL) + """) + else: + #account_move_reconcile was dropped during migration, rebuild that table + cr.execute(""" + INSERT INTO account_full_reconcile (id, name) + SELECT DISTINCT reconcile_id, reconcile_ref FROM account_move_line WHERE reconcile_id IS NOT NULL + """) + #update the index of account_full_reconcile + cr.execute("SELECT setval('account_full_reconcile_id_seq', (SELECT MAX(id) FROM account_full_reconcile))") + + #copy values on account.move.line + cr.execute("UPDATE account_move_line SET full_reconcile_id = reconcile_id WHERE reconcile_id IS NOT NULL") + #copy the full_reconcile_id existing of account.move.line to their account.partial.reconcile + cr.execute(""" + WITH tmp_table AS ( + SELECT rec.id AS partial_id, aml.reconcile_id AS full_id + FROM account_move_line aml + RIGHT JOIN account_partial_reconcile rec + ON aml.id = rec.debit_move_id + WHERE aml.reconcile_id IS NOT NULL + UNION + SELECT rec.id AS partial_id, aml.reconcile_id AS full_id + FROM account_move_line aml + RIGHT JOIN account_partial_reconcile rec + ON aml.id = rec.credit_move_id + WHERE aml.reconcile_id IS NOT NULL) + UPDATE account_partial_reconcile p + SET full_reconcile_id = (SELECT full_id FROM tmp_table tmp WHERE p.id = tmp.partial_id) + WHERE p.id IN (SELECT partial_id FROM tmp_table) + """) diff --git a/addons/account_full_reconcile/__openerp__.py b/addons/account_full_reconcile/__openerp__.py new file mode 100644 index 00000000000..f2c54d31d9e --- /dev/null +++ b/addons/account_full_reconcile/__openerp__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +{ + 'name': 'Full Reconciliation Concept', + 'version': '1.1', + 'category': 'Accounting & Finance', + 'description': """ +Add the concept of full reconciliation back into the accounting. + """, + 'website': 'https://www.odoo.com/page/accounting', + 'depends': ['account'], + 'data': [ + 'security/ir.model.access.csv', + 'views/account_full_reconcile_view.xml', + ], + 'demo': [], + 'installable': True, + 'auto_install': True, + 'post_init_hook': '_migrate_full_reconcile', +} diff --git a/addons/account_full_reconcile/models/__init__.py b/addons/account_full_reconcile/models/__init__.py new file mode 100644 index 00000000000..03e58f9db59 --- /dev/null +++ b/addons/account_full_reconcile/models/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import account_full_reconcile diff --git a/addons/account_full_reconcile/models/account_full_reconcile.py b/addons/account_full_reconcile/models/account_full_reconcile.py new file mode 100644 index 00000000000..4b666f88371 --- /dev/null +++ b/addons/account_full_reconcile/models/account_full_reconcile.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from collections import OrderedDict +from openerp.tools import float_compare +from openerp import api, fields, models + + +class AccountMoveLine(models.Model): + _inherit = "account.move.line" + full_reconcile_id = fields.Many2one('account.full.reconcile', string="Matching Number") + + +class AccountPartialReconcile(models.Model): + _inherit = "account.partial.reconcile" + + full_reconcile_id = fields.Many2one('account.full.reconcile', string="Full Reconcile") + + @api.model + def create(self, vals): + res = super(AccountPartialReconcile, self).create(vals) + #check if the reconcilation is full + #first, gather all journal items involved in the reconciliation just created + partial_rec_set = OrderedDict.fromkeys([x for x in res]) + aml_set = self.env['account.move.line'] + total_debit = 0 + total_credit = 0 + for partial_rec in partial_rec_set: + for aml in [partial_rec.debit_move_id, partial_rec.credit_move_id]: + if aml not in aml_set: + total_debit += aml.debit + total_credit += aml.credit + aml_set |= aml + for x in aml.matched_debit_ids | aml.matched_credit_ids: + partial_rec_set[x] = None + partial_rec_ids = [x.id for x in partial_rec_set.keys()] + aml_ids = [x.id for x in aml_set] + #then, if the total debit and credit are equal, the reconciliation is full + digits_rounding_precision = aml_set[0].company_id.currency_id.rounding + if float_compare(total_debit, total_credit, precision_rounding=digits_rounding_precision) == 0: + #in that case, mark the reference on the partial reconciliations and the entries + self.env['account.full.reconcile'].with_context(check_move_validity=False).create({ + 'partial_reconcile_ids': [(6, 0, partial_rec_ids)], + 'reconciled_line_ids': [(6, 0, aml_ids)]}) + return res + + @api.multi + def unlink(self): + """When removing a partial reconciliation, also unlink its full reconciliation if it exists""" + full_rec = self.env['account.full.reconcile'] + for rec in self: + if rec.full_reconcile_id: + full_rec |= rec.full_reconcile_id + full_rec.unlink() + return super(AccountPartialReconcile, self).unlink() + + +class AccountFullReconcile(models.Model): + _name = "account.full.reconcile" + _description = "Full Reconcile" + + name = fields.Char(string='Number', required=True, copy=False, default=lambda self: self.env['ir.sequence'].next_by_code('account.reconcile')) + partial_reconcile_ids = fields.One2many('account.partial.reconcile', 'full_reconcile_id', string='Reconciliation Parts') + reconciled_line_ids = fields.One2many('account.move.line', 'full_reconcile_id', string='Matched Journal Items') diff --git a/addons/account_full_reconcile/security/ir.model.access.csv b/addons/account_full_reconcile/security/ir.model.access.csv new file mode 100644 index 00000000000..08cd0c1b3a9 --- /dev/null +++ b/addons/account_full_reconcile/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_account_full_reconcile_group_invoice,account.full.reconcile.group.invoice,model_account_full_reconcile,account.group_account_invoice,1,1,1,0 +access_account_full_reconcile,account.full.reconcile,model_account_full_reconcile,account.group_account_user,1,1,1,1 diff --git a/addons/account_full_reconcile/views/account_full_reconcile_view.xml b/addons/account_full_reconcile/views/account_full_reconcile_view.xml new file mode 100644 index 00000000000..db67f4729b3 --- /dev/null +++ b/addons/account_full_reconcile/views/account_full_reconcile_view.xml @@ -0,0 +1,50 @@ + + + + + + + account.full.reconcile.form + account.full.reconcile + +
+ +
+

+
+ + +
+
+
+
+ + + account.move.line.form.full.reconcile + account.move.line + + + + + + + 1 + + + + + + + + + + +
+
+