# -*- coding: utf-8 -*- ############################################################################## # # Copyright 2012 Camptocamp SA (Guewen Baconnier) # Copyright (C) 2010 Sébastien Beau # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # ############################################################################## import time from openerp.osv.orm import Model, AbstractModel from openerp.osv import fields from openerp.tools.translate import _ from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT class easy_reconcile_options(AbstractModel): """Options of a reconciliation profile, columns shared by the configuration of methods and by the reconciliation wizards. This allows decoupling of the methods with the wizards and allows to launch the wizards alone """ _name = 'easy.reconcile.options' def _get_rec_base_date(self, cr, uid, context=None): return [('end_period_last_credit', 'End of period of most recent credit'), ('newest', 'Most recent move line'), ('actual', 'Today'), ('end_period', 'End of period of most recent move line'), ('newest_credit', 'Date of most recent credit'), ('newest_debit', 'Date of most recent debit')] _columns = { 'write_off': fields.float('Write off allowed'), 'account_lost_id': fields.many2one('account.account', 'Account Lost'), 'account_profit_id': fields.many2one('account.account', 'Account Profit'), 'journal_id': fields.many2one('account.journal', 'Journal'), 'date_base_on': fields.selection(_get_rec_base_date, required=True, string='Date of reconcilation'), 'filter': fields.char('Filter', size=128), } _defaults = { 'write_off': 0., 'date_base_on': 'end_period_last_credit', } class account_easy_reconcile_method(Model): _name = 'account.easy.reconcile.method' _description = 'reconcile method for account_easy_reconcile' _inherit = 'easy.reconcile.options' _auto = True # restore property set to False by AbstractModel _order = 'sequence' def _get_all_rec_method(self, cr, uid, context=None): return [ ('easy.reconcile.simple.name', 'Simple. Amount and Name'), ('easy.reconcile.simple.partner', 'Simple. Amount and Partner'), ('easy.reconcile.simple.reference', 'Simple. Amount and Reference'), ] def _get_rec_method(self, cr, uid, context=None): return self._get_all_rec_method(cr, uid, context=None) _columns = { 'name': fields.selection(_get_rec_method, 'Type', size=128, required=True), 'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the reconcile method"), 'task_id': fields.many2one('account.easy.reconcile', 'Task', required=True, ondelete='cascade'), } _defaults = { 'sequence': 1, } 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): _name = 'account.easy.reconcile' _description = 'account easy reconcile' def _get_total_unrec(self, cr, uid, ids, name, arg, context=None): obj_move_line = self.pool.get('account.move.line') res = {} for task in self.browse(cr, uid, ids, context=context): res[task.id] = len(obj_move_line.search( cr, uid, [('account_id', '=', task.account.id), ('reconcile_id', '=', False), ('reconcile_partial_id', '=', False)], context=context)) return res def _get_partial_rec(self, cr, uid, ids, name, arg, context=None): obj_move_line = self.pool.get('account.move.line') res = {} for task in self.browse(cr, uid, ids, context=context): res[task.id] = len(obj_move_line.search( cr, uid, [('account_id', '=', task.account.id), ('reconcile_id', '=', False), ('reconcile_partial_id', '!=', False)], context=context)) return res def _last_history(self, cr, uid, ids, name, args, context=None): result = {} for history in self.browse(cr, uid, ids, context=context): result[history.id] = False if history.history_ids: # history is sorted by date desc result[history.id] = history.history_ids[0].id return result _columns = { 'name': fields.char('Name', size=64, required=True), 'account': fields.many2one('account.account', 'Account', required=True), 'reconcile_method': fields.one2many('account.easy.reconcile.method', 'task_id', 'Method'), 'unreconciled_count': fields.function(_get_total_unrec, type='integer', string='Unreconciled Entries'), 'reconciled_partial_count': fields.function(_get_partial_rec, type='integer', string='Partially Reconciled Entries'), 'history_ids': fields.one2many( 'easy.reconcile.history', 'easy_reconcile_id', string='History'), 'last_history': fields.function( _last_history, string='Last History', type='many2one', relation='easy.reconcile.history', readonly=True), } def _prepare_run_transient(self, cr, uid, rec_method, context=None): return {'account_id': rec_method.task_id.account.id, 'write_off': rec_method.write_off, 'account_lost_id': rec_method.account_lost_id and \ rec_method.account_lost_id.id, 'account_profit_id': rec_method.account_profit_id and \ rec_method.account_profit_id.id, 'journal_id': rec_method.journal_id and rec_method.journal_id.id, 'date_base_on': rec_method.date_base_on, 'filter': rec_method.filter} def run_reconcile(self, cr, uid, ids, context=None): def find_reconcile_ids(fieldname, move_line_ids): if not move_line_ids: return [] sql = ("SELECT DISTINCT " + fieldname + " FROM account_move_line " " WHERE id in %s " " AND " + fieldname + " IS NOT NULL") cr.execute(sql, (tuple(move_line_ids),)) res = cr.fetchall() return [row[0] for row in res] if context is None: context = {} for rec in self.browse(cr, uid, ids, context=context): all_ml_rec_ids = [] all_ml_partial_ids = [] for method in rec.reconcile_method: rec_model = self.pool.get(method.name) auto_rec_id = rec_model.create( cr, uid, self._prepare_run_transient(cr, uid, method, context=context), context=context) ml_rec_ids, ml_partial_ids = rec_model.automatic_reconcile( cr, uid, auto_rec_id, context=context) all_ml_rec_ids += ml_rec_ids all_ml_partial_ids += ml_partial_ids reconcile_ids = find_reconcile_ids( 'reconcile_id', all_ml_rec_ids) partial_ids = find_reconcile_ids( 'reconcile_partial_id', all_ml_partial_ids) self.pool.get('easy.reconcile.history').create( cr, uid, {'easy_reconcile_id': rec.id, 'date': fields.datetime.now(), 'reconcile_ids': [(4, rid) for rid in reconcile_ids], 'reconcile_partial_ids': [(4, rid) for rid in partial_ids]}, context=context) return True def _no_history(self, cr, uid, rec, context=None): """ Raise an `osv.except_osv` error, supposed to be called when there is no history on the reconciliation task. """ raise osv.except_osv( _('Error'), _('There is no history of reconciled ' 'entries on the task: %s.') % rec.name) def last_history_reconcile(self, cr, uid, rec_id, context=None): """ Get the last history record for this reconciliation profile and return the action which opens move lines reconciled """ if isinstance(rec_id, (tuple, list)): assert len(rec_id) == 1, \ "Only 1 id expected" rec_id = rec_id[0] rec = self.browse(cr, uid, rec_id, context=context) if not rec.last_history: self._no_history(cr, uid, rec, context=context) return rec.last_history.open_reconcile() def last_history_partial(self, cr, uid, rec_id, context=None): """ Get the last history record for this reconciliation profile and return the action which opens move lines reconciled """ if isinstance(rec_id, (tuple, list)): assert len(rec_id) == 1, \ "Only 1 id expected" rec_id = rec_id[0] rec = self.browse(cr, uid, rec_id, context=context) if not rec.last_history: self._no_history(cr, uid, rec, context=context) return rec.last_history.open_partial()