From 9fe7de21bd3242bc8cf15715818546c9bf8bf4d9 Mon Sep 17 00:00:00 2001
From: "@" <@>
Date: Wed, 13 Jun 2012 16:28:32 +0200
Subject: [PATCH] [IMP] account_easy_reconcile: modularized reconciliation
methods
(lp:account-extra-addons rev 26.1.9)
---
account_easy_reconcile/easy_reconcile.py | 279 +++++++++++++---------
account_easy_reconcile/easy_reconcile.xml | 2 +-
2 files changed, 169 insertions(+), 112 deletions(-)
diff --git a/account_easy_reconcile/easy_reconcile.py b/account_easy_reconcile/easy_reconcile.py
index 99311307..eae444c8 100644
--- a/account_easy_reconcile/easy_reconcile.py
+++ b/account_easy_reconcile/easy_reconcile.py
@@ -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'
diff --git a/account_easy_reconcile/easy_reconcile.xml b/account_easy_reconcile/easy_reconcile.xml
index f3371679..58738641 100644
--- a/account_easy_reconcile/easy_reconcile.xml
+++ b/account_easy_reconcile/easy_reconcile.xml
@@ -19,7 +19,7 @@
-
+