mirror of
https://github.com/OCA/account-reconcile.git
synced 2025-01-20 12:27:39 +02:00
[IMP] account_easy_reconcile: modularized reconciliation methods
(lp:account-extra-addons rev 26.1.9)
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
#########################################################################
|
||||
# #
|
||||
# Copyright (C) 2010 Sébastien Beau #
|
||||
# Copyright (C) 2012 Camptocamp SA (authored by Guewen Baconnier) #
|
||||
# #
|
||||
#This program is free software: you can redistribute it and/or modify #
|
||||
#it under the terms of the GNU General Public License as published by #
|
||||
@@ -19,25 +20,29 @@
|
||||
|
||||
import time
|
||||
import string
|
||||
from osv import fields, osv
|
||||
from tools.translate import _
|
||||
from tools import DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from operator import itemgetter, attrgetter
|
||||
from openerp.osv.orm import Model, TransientModel, AbstractModel
|
||||
from openerp.osv import fields
|
||||
from openerp.tools.translate import _
|
||||
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
||||
|
||||
|
||||
class account_easy_reconcile_method(osv.osv):
|
||||
class account_easy_reconcile_method(Model):
|
||||
|
||||
_name = 'account.easy.reconcile.method'
|
||||
_description = 'reconcile method for account_easy_reconcile'
|
||||
|
||||
def onchange_name(self, cr, uid, id, name, write_off, context=None):
|
||||
if name in ['action_rec_auto_name', 'action_rec_auto_partner']:
|
||||
if name in ['easy.reconcile.simple.name',
|
||||
'easy.reconcile.simple.partner']:
|
||||
if write_off>0:
|
||||
return {'value': {'require_write_off': True, 'require_account_id': True, 'require_journal_id': True}}
|
||||
return {'value': {'require_write_off': True}}
|
||||
return {}
|
||||
|
||||
def onchange_write_off(self, cr, uid, id, name, write_off, context=None):
|
||||
if name in ['action_rec_auto_name', 'action_rec_auto_partner']:
|
||||
if name in ['easy.reconcile.simple.name',
|
||||
'easy.reconcile.simple.partner']:
|
||||
if write_off>0:
|
||||
return {'value': {'require_account_id': True, 'require_journal_id': True}}
|
||||
else:
|
||||
@@ -46,8 +51,8 @@ class account_easy_reconcile_method(osv.osv):
|
||||
|
||||
def _get_all_rec_method(self, cr, uid, context=None):
|
||||
return [
|
||||
('action_rec_auto_name', 'Simple method based on amount and name'),
|
||||
('action_rec_auto_partner', 'Simple method based on amount and partner'),
|
||||
('easy.reconcile.simple.name', 'Simple method based on amount and name'),
|
||||
('easy.reconcile.simple.partner', 'Simple method based on amount and partner'),
|
||||
]
|
||||
|
||||
def _get_rec_method(self, cr, uid, context=None):
|
||||
@@ -72,6 +77,7 @@ class account_easy_reconcile_method(osv.osv):
|
||||
('newest_debit', 'Date of most recent debit')],
|
||||
string='Date of reconcilation'),
|
||||
'filter': fields.char('Filter', size=128),
|
||||
'task_id': fields.many2one('account.easy.reconcile', 'Task', required=True, ondelete='cascade'),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
@@ -80,10 +86,22 @@ class account_easy_reconcile_method(osv.osv):
|
||||
|
||||
_order = 'sequence'
|
||||
|
||||
account_easy_reconcile_method()
|
||||
def init(self, cr):
|
||||
""" Migration stuff, name is not anymore methods names
|
||||
but models name"""
|
||||
cr.execute("""
|
||||
UPDATE account_easy_reconcile_method
|
||||
SET name = 'easy.reconcile.simple.partner'
|
||||
WHERE name = 'action_rec_auto_partner'
|
||||
""")
|
||||
cr.execute("""
|
||||
UPDATE account_easy_reconcile_method
|
||||
SET name = 'easy.reconcile.simple.name'
|
||||
WHERE name = 'action_rec_auto_name'
|
||||
""")
|
||||
|
||||
class account_easy_reconcile(Model):
|
||||
|
||||
class account_easy_reconcile(osv.osv):
|
||||
_name = 'account.easy.reconcile'
|
||||
_description = 'account easy reconcile'
|
||||
|
||||
@@ -103,6 +121,98 @@ class account_easy_reconcile(osv.osv):
|
||||
'unreconcile_entry_number': fields.function(_get_unrec_number, method=True, type='integer', string='Unreconcile Entries'),
|
||||
}
|
||||
|
||||
def run_reconcile(self, cr, uid, ids, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
for rec_id in ids:
|
||||
rec = self.browse(cr, uid, rec_id, context=context)
|
||||
total_rec = 0
|
||||
details = ''
|
||||
count = 0
|
||||
|
||||
for method in rec.reconcile_method:
|
||||
count += 1
|
||||
ctx = dict(
|
||||
context,
|
||||
date_base_on=method.date_base_on,
|
||||
filter=eval(method.filter or '[]'),
|
||||
write_off=(method.write_off > 0 and method.write_off) or 0,
|
||||
account_lost_id=method.account_lost_id.id,
|
||||
account_profit_id=method.account_profit_id.id,
|
||||
journal_id=method.journal_id.id)
|
||||
|
||||
rec_model = self.pool.get(method.name)
|
||||
auto_rec_id = rec_model.create(
|
||||
cr, uid, {'easy_reconcile_id': rec_id}, context=ctx)
|
||||
res = rec_model.automatic_reconcile(cr, uid, auto_rec_id, context=ctx)
|
||||
|
||||
details += _(' method %d : %d lines |') % (count, res)
|
||||
log = self.read(cr, uid, rec_id, ['rec_log'], context=context)['rec_log']
|
||||
log_lines = log and log.splitlines() or []
|
||||
log_lines[0:0] = [_('%s : %d lines have been reconciled (%s)') %
|
||||
(time.strftime(DEFAULT_SERVER_DATETIME_FORMAT), total_rec, details[0:-2])]
|
||||
log = "\n".join(log_lines)
|
||||
self.write(cr, uid, rec_id, {'rec_log': log}, context=context)
|
||||
return True
|
||||
|
||||
|
||||
class easy_reconcile_base(AbstractModel):
|
||||
"""Abstract Model for reconciliation methods"""
|
||||
|
||||
_name = 'easy.reconcile.base'
|
||||
|
||||
_columns = {
|
||||
'easy_reconcile_id': fields.many2one('account.easy.reconcile', string='Easy Reconcile')
|
||||
}
|
||||
|
||||
def automatic_reconcile(self, cr, uid, ids, context=None):
|
||||
"""Must be inherited to implement the reconciliation"""
|
||||
raise NotImplementedError
|
||||
|
||||
def _base_columns(self, rec):
|
||||
"""Mandatory columns for move lines queries
|
||||
An extra column aliased as `key` should be defined
|
||||
in each query."""
|
||||
aml_cols = (
|
||||
'id',
|
||||
'debit',
|
||||
'credit',
|
||||
'date',
|
||||
'period_id',
|
||||
'ref',
|
||||
'name',
|
||||
'partner_id',
|
||||
'account_id',
|
||||
'move_id')
|
||||
return ["account_move_line.%s" % col for col in aml_cols]
|
||||
|
||||
def _select(self, rec, *args, **kwargs):
|
||||
return "SELECT %s" % ', '.join(self._base_columns(rec))
|
||||
|
||||
def _from(self, rec, *args, **kwargs):
|
||||
return "FROM account_move_line"
|
||||
|
||||
def _where(self, rec, *args, **kwargs):
|
||||
where = ("WHERE account_move_line.account_id = %s "
|
||||
"AND account_move_line.reconcile_id IS NULL ")
|
||||
# it would be great to use dict for params
|
||||
# but as we use _where_calc in _get_filter
|
||||
# which returns a list, we have to
|
||||
# accomodate with that
|
||||
params = [rec.easy_reconcile_id.account.id]
|
||||
return where, params
|
||||
|
||||
def _get_filter(self, cr, uid, rec, context):
|
||||
ml_obj = self.pool.get('account.move.line')
|
||||
where = ''
|
||||
params = []
|
||||
if context.get('filter'):
|
||||
dummy, where, params = ml_obj._where_calc(
|
||||
cr, uid, context['filter'], context=context).get_sql()
|
||||
if where:
|
||||
where = " AND %s" % where
|
||||
return where, params
|
||||
|
||||
def _below_writeoff_limit(self, cr, uid, lines,
|
||||
writeoff_limit, context=None):
|
||||
precision = self.pool.get('decimal.precision').precision_get(
|
||||
@@ -195,16 +305,29 @@ class account_easy_reconcile(osv.osv):
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class easy_reconcile_simple(AbstractModel):
|
||||
|
||||
_name = 'easy.reconcile.simple'
|
||||
_inherit = 'easy.reconcile.base'
|
||||
|
||||
# has to be subclassed
|
||||
# field name used as key for matching the move lines
|
||||
_key_field = None
|
||||
|
||||
def rec_auto_lines_simple(self, cr, uid, lines, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
if self._key_field is None:
|
||||
raise ValueError("_key_field has to be defined")
|
||||
|
||||
count = 0
|
||||
res = 0
|
||||
while (count < len(lines)):
|
||||
for i in range(count+1, len(lines)):
|
||||
writeoff_account_id = False
|
||||
if lines[count]['key'] != lines[i]['key']:
|
||||
if lines[count][self._key_field] != lines[i][self._key_field]:
|
||||
break
|
||||
|
||||
check = False
|
||||
@@ -227,119 +350,53 @@ class account_easy_reconcile(osv.osv):
|
||||
count += 1
|
||||
return res
|
||||
|
||||
def _get_filter(self, cr, uid, account_id, context):
|
||||
ml_obj = self.pool.get('account.move.line')
|
||||
where = ''
|
||||
params = []
|
||||
if context.get('filter'):
|
||||
dummy, where, params = ml_obj._where_calc(
|
||||
cr, uid, context['filter'], context=context).get_sql()
|
||||
if where:
|
||||
where = " AND %s" % where
|
||||
return where, params
|
||||
def _simple_order(self, rec, *args, **kwargs):
|
||||
return "ORDER BY account_move_line.%s" % self._key_field
|
||||
|
||||
def _base_columns(self, easy_rec):
|
||||
"""Mandatory columns for move lines queries
|
||||
An extra column aliased as `key` should be defined
|
||||
in each query."""
|
||||
aml_cols = (
|
||||
'id',
|
||||
'debit',
|
||||
'credit',
|
||||
'date',
|
||||
'period_id',
|
||||
'ref',
|
||||
'name',
|
||||
'partner_id',
|
||||
'account_id',
|
||||
'move_id')
|
||||
return ["account_move_line.%s" % col for col in aml_cols]
|
||||
|
||||
def _base_select(self, easy_rec, *args, **kwargs):
|
||||
return "SELECT %s" % ', '.join(self._base_columns(easy_rec))
|
||||
|
||||
def _base_from(self, easy_rec, *args, **kwargs):
|
||||
return "FROM account_move_line"
|
||||
|
||||
def _base_where(self, easy_rec, *args, **kwargs):
|
||||
where = ("WHERE account_move_line.account_id = %s "
|
||||
"AND account_move_line.reconcile_id IS NULL ")
|
||||
# it would be great to use dict for params
|
||||
# but as we use _where_calc in _get_filter
|
||||
# which returns a list, we have to
|
||||
# accomodate with that
|
||||
params = [easy_rec.account.id]
|
||||
return where, params
|
||||
|
||||
def _simple_order(self, easy_rec, key, *args, **kwargs):
|
||||
return "ORDER BY account_move_line.%s" % key
|
||||
|
||||
def _action_rec_simple(self, cr, uid, easy_rec, key, context=None):
|
||||
def _action_rec_simple(self, cr, uid, rec, context=None):
|
||||
"""Match only 2 move lines, do not allow partial reconcile"""
|
||||
select = self._base_select(easy_rec)
|
||||
select += ", account_move_line.%s as key " % key
|
||||
where, params = self._base_where(easy_rec)
|
||||
where += " AND account_move_line.%s IS NOT NULL " % key
|
||||
select = self._select(rec)
|
||||
select += ", account_move_line.%s " % self._key_field
|
||||
where, params = self._where(rec)
|
||||
where += " AND account_move_line.%s IS NOT NULL " % self._key_field
|
||||
|
||||
where2, params2 = self._get_filter(cr, uid, easy_rec, context=context)
|
||||
where2, params2 = self._get_filter(cr, uid, rec, context=context)
|
||||
query = ' '.join((
|
||||
select,
|
||||
self._base_from(easy_rec),
|
||||
self._from(rec),
|
||||
where, where2,
|
||||
self._simple_order(easy_rec, key)))
|
||||
self._simple_order(rec)))
|
||||
|
||||
cr.execute(query, params + params2)
|
||||
lines = cr.dictfetchall()
|
||||
return self.rec_auto_lines_simple(cr, uid, lines, context)
|
||||
|
||||
def _action_rec_auto_name(self, cr, uid, easy_rec, context):
|
||||
return self._action_rec_simple(
|
||||
cr, uid, easy_rec, 'name', context=context)
|
||||
|
||||
def _action_rec_auto_partner(self, cr, uid, easy_rec, context):
|
||||
return self._action_rec_simple(
|
||||
cr, uid, easy_rec, 'partner_id', context=context)
|
||||
|
||||
def action_rec_auto(self, cr, uid, ids, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
for rec_id in ids:
|
||||
rec = self.browse(cr, uid, rec_id, context=context)
|
||||
total_rec = 0
|
||||
details = ''
|
||||
count = 0
|
||||
for method in rec.reconcile_method:
|
||||
count += 1
|
||||
ctx = dict(
|
||||
context,
|
||||
date_base_on=method.date_base_on,
|
||||
filter=eval(method.filter or '[]'),
|
||||
write_off=(method.write_off > 0 and method.write_off) or 0,
|
||||
account_lost_id=method.account_lost_id.id,
|
||||
account_profit_id=method.account_profit_id.id,
|
||||
journal_id=method.journal_id.id)
|
||||
|
||||
py_meth = getattr(self, "_%s" % method.name)
|
||||
res = py_meth(cr, uid, rec, context=ctx)
|
||||
details += _(' method %d : %d lines |') % (count, res)
|
||||
log = self.read(cr, uid, rec_id, ['rec_log'], context=context)['rec_log']
|
||||
log_lines = log and log.splitlines() or []
|
||||
log_lines[0:0] = [_('%s : %d lines have been reconciled (%s)') %
|
||||
(time.strftime(DEFAULT_SERVER_DATETIME_FORMAT), total_rec, details[0:-2])]
|
||||
log = "\n".join(log_lines)
|
||||
self.write(cr, uid, rec_id, {'rec_log': log}, context=context)
|
||||
return True
|
||||
|
||||
account_easy_reconcile()
|
||||
def automatic_reconcile(self, cr, uid, ids, context=None):
|
||||
if isinstance(ids, (int, long)):
|
||||
ids = [ids]
|
||||
assert len(ids) == 1, "Has to be called on one id"
|
||||
rec = self.browse(cr, uid, ids[0], context=context)
|
||||
return self._action_rec_simple(cr, uid, rec, context=context)
|
||||
|
||||
|
||||
class account_easy_reconcile_method(osv.osv):
|
||||
class easy_reconcile_simple_name(TransientModel):
|
||||
|
||||
_inherit = 'account.easy.reconcile.method'
|
||||
_name = 'easy.reconcile.simple.name'
|
||||
_inherit = 'easy.reconcile.simple'
|
||||
_auto = True # False when inherited from AbstractModel
|
||||
|
||||
_columns = {
|
||||
'task_id': fields.many2one('account.easy.reconcile', 'Task', required=True, ondelete='cascade'),
|
||||
}
|
||||
# has to be subclassed
|
||||
# field name used as key for matching the move lines
|
||||
_key_field = 'name'
|
||||
|
||||
account_easy_reconcile_method()
|
||||
|
||||
class easy_reconcile_simple_partner(TransientModel):
|
||||
|
||||
_name = 'easy.reconcile.simple.partner'
|
||||
_inherit = 'easy.reconcile.simple'
|
||||
_auto = True # False when inherited from AbstractModel
|
||||
|
||||
# has to be subclassed
|
||||
# field name used as key for matching the move lines
|
||||
_key_field = 'partner'
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<field name="scheduler"/>
|
||||
<separator colspan="4" string="Reconcile Method" />
|
||||
<field name="reconcile_method" colspan = "4" nolabel="1"/>
|
||||
<button icon="gtk-ok" name="action_rec_auto" colspan = "4" string="Start Auto Reconcilation" type="object"/>
|
||||
<button icon="gtk-ok" name="run_reconcile" colspan = "4" string="Start Auto Reconcilation" type="object"/>
|
||||
<separator colspan="4" string="Log" />
|
||||
<field name="rec_log" colspan = "4" nolabel="1"/>
|
||||
</form>
|
||||
|
||||
Reference in New Issue
Block a user