From b757a938141960d77136d2ff8122ca404685de2d Mon Sep 17 00:00:00 2001 From: "@" <@> Date: Tue, 12 Jun 2012 22:41:27 +0200 Subject: [PATCH] [REF] account_easy_reconcile: refactoring of easy_reconcile (lp:account-extra-addons rev 26.1.8) --- account_easy_reconcile/easy_reconcile.py | 238 +++++++++++++++++------ 1 file changed, 176 insertions(+), 62 deletions(-) diff --git a/account_easy_reconcile/easy_reconcile.py b/account_easy_reconcile/easy_reconcile.py index 16b3cae8..99311307 100644 --- a/account_easy_reconcile/easy_reconcile.py +++ b/account_easy_reconcile/easy_reconcile.py @@ -22,6 +22,7 @@ import string from osv import fields, osv from tools.translate import _ from tools import DEFAULT_SERVER_DATETIME_FORMAT +from operator import itemgetter, attrgetter class account_easy_reconcile_method(osv.osv): @@ -62,7 +63,14 @@ class account_easy_reconcile_method(osv.osv): 'require_write_off': fields.boolean('Require Write-off'), 'require_account_id': fields.boolean('Require Account'), 'require_journal_id': fields.boolean('Require Journal'), - 'date_base_on': fields.selection([('newest', 'the most recent'), ('actual', 'today'), ('credit_line', 'credit line date'), ('debit_line', 'debit line date')], 'Date Base On'), + 'date_base_on': fields.selection( + [('newest', 'Most recent move line'), + ('actual', 'Today'), + ('end_period_last_credit', 'End of period of most recent credit'), + ('end_period', 'End of period of most recent move line'), + ('newest_credit', 'Date of most recent credit'), + ('newest_debit', 'Date of most recent debit')], + string='Date of reconcilation'), 'filter': fields.char('Filter', size=128), } @@ -95,15 +103,104 @@ class account_easy_reconcile(osv.osv): 'unreconcile_entry_number': fields.function(_get_unrec_number, method=True, type='integer', string='Unreconcile Entries'), } + def _below_writeoff_limit(self, cr, uid, lines, + writeoff_limit, context=None): + precision = self.pool.get('decimal.precision').precision_get( + cr, uid, 'Account') + keys = ('debit', 'credit') + sums = reduce( + lambda line, memo: + dict((key, value + memo[key]) + for key, value + in line.iteritems() + if key in keys), lines) + + debit, credit = sums['debit'], sums['credit'] + writeoff_amount = round(debit - credit, precision) + return bool(writeoff_limit >= abs(writeoff_amount)), debit, credit + + def _get_rec_date(self, cr, uid, lines, based_on='end_period_last_credit', context=None): + period_obj = self.pool.get('account.period') + + def last_period(mlines): + period_ids = [ml['period_id'] for ml in mlines] + periods = period_obj.browse( + cr, uid, period_ids, context=context) + return max(periods, key=attrgetter('date_stop')) + + def last_date(mlines): + return max(mlines, key=itemgetter('date')) + + def credit(mlines): + return [l for l in mlines if l['credit'] > 0] + + def debit(mlines): + return [l for l in mlines if l['debit'] > 0] + + if based_on == 'end_period_last_credit': + return last_period(credit(lines)).date_stop + if based_on == 'end_period': + return last_period(lines).date_stop + elif based_on == 'newest': + return last_date(lines)['date'] + elif based_on == 'newest_credit': + return last_date(credit(lines))['date'] + elif based_on == 'newest_debit': + return last_date(debit(lines))['date'] + # reconcilation date will be today + # when date is None + return None + + def _reconcile_lines(self, cr, uid, lines, allow_partial=False, context=None): + if context is None: + context = {} + + ml_obj = self.pool.get('account.move.line') + writeoff = context.get('write_off', 0.) + + keys = ('debit', 'credit') + + line_ids = [l['id'] for l in lines] + below_writeoff, sum_debit, sum_credit = self._below_writeoff_limit( + cr, uid, lines, writeoff, context=context) + date = self._get_rec_date( + cr, uid, lines, context.get('date_base_on'), context=context) + + rec_ctx = dict(context, date_p=date) + if below_writeoff: + if sum_credit < sum_debit: + writeoff_account_id = context.get('account_profit_id', False) + else: + writeoff_account_id = context.get('account_lost_id', False) + + period_id = self.pool.get('account.period').find( + cr, uid, dt=date, context=context)[0] + + ml_obj.reconcile( + cr, uid, + line_ids, + type='auto', + writeoff_acc_id=writeoff_account_id, + writeoff_period_id=period_id, + writeoff_journal_id=context.get('journal_id'), + context=rec_ctx) + return True + elif allow_partial: + ml_obj.reconcile_partial( + cr, uid, + line_ids, + type='manual', + context=rec_ctx) + return True + + return False + def rec_auto_lines_simple(self, cr, uid, lines, context=None): - if not context: - context={} + if context is None: + context = {} count = 0 res = 0 - precision = self.pool.get('decimal.precision').precision_get( - cr, uid, 'Account') - max_diff = context.get('write_off', 0.) while (count < len(lines)): for i in range(count+1, len(lines)): writeoff_account_id = False @@ -122,69 +219,86 @@ class account_easy_reconcile(osv.osv): if not check: continue - diff = round(abs(credit_line['credit'] - debit_line['debit']), precision) - if diff <= max_diff: - if context.get('write_off', 0) > 0 and diff: - if credit_line['credit'] < debit_line['debit']: - writeoff_account_id = context.get('account_profit_id', False) - else: - writeoff_account_id = context.get('account_lost_id', False) - - context['comment'] = _('Write-Off %s') % credit_line['key'] - - if context.get('date_base_on') == 'credit_line': - date = credit_line['date'] - elif context.get('date_base_on') == 'debit_line': - date = debit_line['date'] - elif context.get('date_base_on') == 'newest': - date = (credit_line['date'] > debit_line['date']) and credit_line['date'] or debit_line['date'] - else: - date = None - - context['date_p'] = date - period_id = self.pool.get('account.period').find(cr, uid, dt=date, context=context)[0] - - self.pool.get('account.move.line').reconcile(cr, uid, [lines[count]['id'], lines[i]['id']], writeoff_acc_id=writeoff_account_id, writeoff_period_id=period_id, writeoff_journal_id=context.get('journal_id'), context=context) - del lines[i] + if self._reconcile_lines(cr, uid, [credit_line, debit_line], + allow_partial=False, context=context): res += 2 + del lines[i] break count += 1 return res - def get_params(self, cr, uid, account_id, context): + def _get_filter(self, cr, uid, account_id, context): + ml_obj = self.pool.get('account.move.line') + where = '' + params = [] if context.get('filter'): - (from_clause, where_clause, where_clause_params) = self.pool.get('account.move.line')._where_calc(cr, uid, context['filter'], context=context).get_sql() - if where_clause: - where_clause = " AND %s" % where_clause - where_clause_params = account_id, + tuple(where_clause_params) - else: - where_clause = '' - where_clause_params = account_id, - return where_clause, where_clause_params + 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 action_rec_auto_name(self, cr, uid, account_id, context): - (qu1, qu2) = self.get_params(cr, uid, account_id, context) - cr.execute(""" - SELECT name as key, credit, debit, id, date - FROM account_move_line - WHERE account_id=%s - AND reconcile_id IS NULL - AND name IS NOT NULL""" + qu1 + " ORDER BY name", - qu2) + 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): + """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 + + where2, params2 = self._get_filter(cr, uid, easy_rec, context=context) + query = ' '.join(( + select, + self._base_from(easy_rec), + where, where2, + self._simple_order(easy_rec, key))) + + cr.execute(query, params + params2) lines = cr.dictfetchall() return self.rec_auto_lines_simple(cr, uid, lines, context) - def action_rec_auto_partner(self, cr, uid, account_id, context): - (qu1, qu2) = self.get_params(cr, uid, account_id, context) - cr.execute(""" - SELECT partner_id as key, credit, debit, id, date - FROM account_move_line - WHERE account_id=%s - AND reconcile_id IS NULL - AND partner_id IS NOT NULL""" + qu1 + " ORDER BY partner_id", - qu2) - 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: @@ -205,11 +319,11 @@ class account_easy_reconcile(osv.osv): account_profit_id=method.account_profit_id.id, journal_id=method.journal_id.id) - py_meth = getattr(self, method.name) - res = py_meth(cr, uid, rec.account.id, context=context) + 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 = 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)