[ADD] account_full_reconcile: module that reintroduce the concept of full reconciliation, to have a named reference on journal items
This commit is contained in:
@@ -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)
|
||||
""")
|
||||
@@ -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',
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import account_full_reconcile
|
||||
@@ -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')
|
||||
@@ -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
|
||||
|
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<!-- Full Reconcile -->
|
||||
<record id="view_full_reconcile_form" model="ir.ui.view">
|
||||
<field name="name">account.full.reconcile.form</field>
|
||||
<field name="model">account.full.reconcile</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Matching">
|
||||
<group>
|
||||
<div class="oe_title" colspan="4">
|
||||
<h1><field name="name" readonly="1"/></h1>
|
||||
</div>
|
||||
<separator string="Matched Journal Items" colspan="4"/>
|
||||
<field name="reconciled_line_ids" readonly="1" colspan="4" nolabel="1"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_move_line_form" model="ir.ui.view">
|
||||
<field name="name">account.move.line.form.full.reconcile</field>
|
||||
<field name="model">account.move.line</field>
|
||||
<field name="inherit_id" ref="account.view_move_line_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<data>
|
||||
<xpath expr="//label[@for='reconciled']" position="replace">
|
||||
<label for="full_reconcile_id"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='reconciled']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='reconciled']" position="after">
|
||||
<field name="full_reconcile_id"/>
|
||||
</xpath>
|
||||
<xpath expr="//button[@name='open_reconcile_view']" position="replace">
|
||||
<button name="open_reconcile_view"
|
||||
class="oe_link"
|
||||
type="object"
|
||||
string="-> View partially reconciled entries"
|
||||
attrs="{'invisible': ['|', ('reconciled', '=', True), '&', ('matched_debit_ids', '=', []),('matched_credit_ids', '=', [])]}">
|
||||
</button>
|
||||
</xpath>
|
||||
</data>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
Reference in New Issue
Block a user