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 @@ -