2010-04-26 12:41:33 +00:00
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
2010-09-08 05:10:21 +00:00
2010-04-26 12:41:33 +00:00
import time
2012-12-06 14:56:32 +00:00
from openerp . osv import fields , osv
from openerp . tools . translate import _
2010-04-26 12:41:33 +00:00
class account_automatic_reconcile ( osv . osv_memory ) :
_name = ' account.automatic.reconcile '
_description = ' Automatic Reconcile '
_columns = {
2010-09-23 13:56:37 +00:00
' account_ids ' : fields . many2many ( ' account.account ' , ' reconcile_account_rel ' , ' reconcile_id ' , ' account_id ' , ' Accounts to Reconcile ' , domain = [ ( ' reconcile ' , ' = ' , 1 ) ] , ) ,
2010-06-14 11:46:11 +00:00
' writeoff_acc_id ' : fields . many2one ( ' account.account ' , ' Account ' ) ,
' journal_id ' : fields . many2one ( ' account.journal ' , ' Journal ' ) ,
' period_id ' : fields . many2one ( ' account.period ' , ' Period ' ) ,
2010-04-26 12:41:33 +00:00
' max_amount ' : fields . float ( ' Maximum write-off amount ' ) ,
2011-12-31 07:14:16 +00:00
' power ' : fields . selection ( [ ( p , str ( p ) ) for p in range ( 2 , 5 ) ] , ' Power ' , required = True , help = ' Number of partial amounts that can be combined to find a balance point can be chosen as the power of the automatic reconciliation ' ) ,
2010-04-26 12:41:33 +00:00
' reconciled ' : fields . integer ( ' Reconciled transactions ' , readonly = True ) ,
' unreconciled ' : fields . integer ( ' Not reconciled transactions ' , readonly = True ) ,
2010-06-14 11:46:11 +00:00
' allow_write_off ' : fields . boolean ( ' Allow write off ' )
2010-04-28 09:55:32 +00:00
}
2010-04-26 12:41:33 +00:00
2010-11-19 13:48:01 +00:00
def _get_reconciled ( self , cr , uid , context = None ) :
if context is None :
context = { }
2010-04-28 09:55:32 +00:00
return context . get ( ' reconciled ' , 0 )
2010-04-26 12:41:33 +00:00
2010-11-19 13:48:01 +00:00
def _get_unreconciled ( self , cr , uid , context = None ) :
if context is None :
context = { }
2010-04-28 09:55:32 +00:00
return context . get ( ' unreconciled ' , 0 )
2010-04-26 12:41:33 +00:00
_defaults = {
' reconciled ' : _get_reconciled ,
' unreconciled ' : _get_unreconciled ,
2010-09-08 05:10:21 +00:00
' power ' : 2
2010-04-28 09:55:32 +00:00
}
2010-04-26 12:41:33 +00:00
#TODO: cleanup and comment this code... For now, it is awfulllll
# (way too complex, and really slow)...
def do_reconcile ( self , cr , uid , credits , debits , max_amount , power , writeoff_acc_id , period_id , journal_id , context = None ) :
# for one value of a credit, check all debits, and combination of them
# depending on the power. It starts with a power of one and goes up
# to the max power allowed
move_line_obj = self . pool . get ( ' account.move.line ' )
if context is None :
context = { }
def check2 ( value , move_list , power ) :
def check ( value , move_list , power ) :
for i in range ( len ( move_list ) ) :
move = move_list [ i ]
if power == 1 :
if abs ( value - move [ 1 ] ) < = max_amount + 0.00001 :
return [ move [ 0 ] ]
else :
del move_list [ i ]
res = check ( value - move [ 1 ] , move_list , power - 1 )
move_list [ i : i ] = [ move ]
if res :
res . append ( move [ 0 ] )
return res
return False
for p in range ( 1 , power + 1 ) :
res = check ( value , move_list , p )
if res :
return res
return False
# for a list of credit and debit and a given power, check if there
# are matching tuples of credit and debits, check all debits, and combination of them
# depending on the power. It starts with a power of one and goes up
# to the max power allowed
def check4 ( list1 , list2 , power ) :
def check3 ( value , list1 , list2 , list1power , power ) :
for i in range ( len ( list1 ) ) :
move = list1 [ i ]
if list1power == 1 :
res = check2 ( value + move [ 1 ] , list2 , power - 1 )
if res :
return ( [ move [ 0 ] ] , res )
else :
del list1 [ i ]
res = check3 ( value + move [ 1 ] , list1 , list2 , list1power - 1 , power - 1 )
list1 [ i : i ] = [ move ]
if res :
x , y = res
x . append ( move [ 0 ] )
return ( x , y )
return False
for p in range ( 1 , power ) :
res = check3 ( 0 , list1 , list2 , p , power )
if res :
return res
return False
def check5 ( list1 , list2 , max_power ) :
for p in range ( 2 , max_power + 1 ) :
res = check4 ( list1 , list2 , p )
if res :
return res
ok = True
reconciled = 0
while credits and debits and ok :
res = check5 ( credits , debits , power )
if res :
move_line_obj . reconcile ( cr , uid , res [ 0 ] + res [ 1 ] , ' auto ' , writeoff_acc_id , period_id , journal_id , context )
reconciled + = len ( res [ 0 ] ) + len ( res [ 1 ] )
credits = [ ( id , credit ) for ( id , credit ) in credits if id not in res [ 0 ] ]
debits = [ ( id , debit ) for ( id , debit ) in debits if id not in res [ 1 ] ]
else :
ok = False
return ( reconciled , len ( credits ) + len ( debits ) )
def reconcile ( self , cr , uid , ids , context = None ) :
move_line_obj = self . pool . get ( ' account.move.line ' )
obj_model = self . pool . get ( ' ir.model.data ' )
if context is None :
context = { }
2011-02-15 09:20:57 +00:00
form = self . browse ( cr , uid , ids , context = context ) [ 0 ]
max_amount = form . max_amount or 0.0
power = form . power
allow_write_off = form . allow_write_off
2010-04-26 12:41:33 +00:00
reconciled = unreconciled = 0
2011-02-15 09:20:57 +00:00
if not form . account_ids :
2012-08-07 11:06:16 +00:00
raise osv . except_osv ( _ ( ' User Error! ' ) , _ ( ' You must select accounts to reconcile. ' ) )
2011-02-15 09:20:57 +00:00
for account_id in form . account_ids :
params = ( account_id . id , )
2010-09-08 07:46:18 +00:00
if not allow_write_off :
2010-10-07 06:21:29 +00:00
query = """ SELECT partner_id FROM account_move_line WHERE account_id= %s AND reconcile_id IS NULL
AND state < > ' draft ' GROUP BY partner_id
2010-09-23 14:52:39 +00:00
HAVING ABS ( SUM ( debit - credit ) ) = 0.0 AND count ( * ) > 0 """
2010-06-14 11:46:11 +00:00
else :
2010-10-07 06:21:29 +00:00
query = """ SELECT partner_id FROM account_move_line WHERE account_id= %s AND reconcile_id IS NULL
AND state < > ' draft ' GROUP BY partner_id
2010-09-23 14:02:21 +00:00
HAVING ABS ( SUM ( debit - credit ) ) < % s AND count ( * ) > 0 """
2010-09-23 15:10:50 +00:00
params + = ( max_amount , )
2010-04-26 12:41:33 +00:00
# reconcile automatically all transactions from partners whose balance is 0
2010-09-23 14:02:21 +00:00
cr . execute ( query , params )
2010-04-26 12:41:33 +00:00
partner_ids = [ id for ( id , ) in cr . fetchall ( ) ]
for partner_id in partner_ids :
cr . execute (
" SELECT id " \
" FROM account_move_line " \
" WHERE account_id= %s " \
" AND partner_id= %s " \
" AND state <> ' draft ' " \
" AND reconcile_id IS NULL " ,
2011-02-15 09:20:57 +00:00
( account_id . id , partner_id ) )
2010-04-26 12:41:33 +00:00
line_ids = [ id for ( id , ) in cr . fetchall ( ) ]
2010-10-11 06:03:40 +00:00
if line_ids :
2010-04-26 12:41:33 +00:00
reconciled + = len ( line_ids )
2010-09-08 07:46:18 +00:00
if allow_write_off :
2011-02-15 09:20:57 +00:00
move_line_obj . reconcile ( cr , uid , line_ids , ' auto ' , form . writeoff_acc_id . id , form . period_id . id , form . journal_id . id , context )
2010-09-08 07:46:18 +00:00
else :
2010-11-19 13:48:01 +00:00
move_line_obj . reconcile_partial ( cr , uid , line_ids , ' manual ' , context = context )
2010-09-23 14:52:39 +00:00
# get the list of partners who have more than one unreconciled transaction
cr . execute (
" SELECT partner_id " \
" FROM account_move_line " \
" WHERE account_id= %s " \
" AND reconcile_id IS NULL " \
" AND state <> ' draft ' " \
" AND partner_id IS NOT NULL " \
" GROUP BY partner_id " \
" HAVING count(*)>1 " ,
2011-02-15 09:20:57 +00:00
( account_id . id , ) )
2010-09-23 14:52:39 +00:00
partner_ids = [ id for ( id , ) in cr . fetchall ( ) ]
#filter?
for partner_id in partner_ids :
# get the list of unreconciled 'debit transactions' for this partner
cr . execute (
" SELECT id, debit " \
" FROM account_move_line " \
" WHERE account_id= %s " \
" AND partner_id= %s " \
" AND reconcile_id IS NULL " \
" AND state <> ' draft ' " \
2010-10-06 13:31:59 +00:00
" AND debit > 0 " \
" ORDER BY date_maturity " ,
2011-02-15 09:20:57 +00:00
( account_id . id , partner_id ) )
2010-09-23 14:52:39 +00:00
debits = cr . fetchall ( )
# get the list of unreconciled 'credit transactions' for this partner
cr . execute (
" SELECT id, credit " \
" FROM account_move_line " \
" WHERE account_id= %s " \
" AND partner_id= %s " \
" AND reconcile_id IS NULL " \
" AND state <> ' draft ' " \
2010-10-06 13:31:59 +00:00
" AND credit > 0 " \
" ORDER BY date_maturity " ,
2011-02-15 09:20:57 +00:00
( account_id . id , partner_id ) )
2010-09-23 14:52:39 +00:00
credits = cr . fetchall ( )
2011-02-15 09:20:57 +00:00
( rec , unrec ) = self . do_reconcile ( cr , uid , credits , debits , max_amount , power , form . writeoff_acc_id . id , form . period_id . id , form . journal_id . id , context )
2010-09-23 14:52:39 +00:00
reconciled + = rec
unreconciled + = unrec
# add the number of transactions for partners who have only one
# unreconciled transactions to the unreconciled count
partner_filter = partner_ids and ' AND partner_id not in ( %s ) ' % ' , ' . join ( map ( str , filter ( None , partner_ids ) ) ) or ' '
cr . execute (
" SELECT count(*) " \
" FROM account_move_line " \
" WHERE account_id= %s " \
" AND reconcile_id IS NULL " \
" AND state <> ' draft ' " + partner_filter ,
2011-02-15 09:20:57 +00:00
( account_id . id , ) )
2010-09-23 14:52:39 +00:00
additional_unrec = cr . fetchone ( ) [ 0 ]
unreconciled = unreconciled + additional_unrec
2010-06-14 11:46:11 +00:00
context . update ( { ' reconciled ' : reconciled , ' unreconciled ' : unreconciled } )
2010-04-26 12:41:33 +00:00
model_data_ids = obj_model . search ( cr , uid , [ ( ' model ' , ' = ' , ' ir.ui.view ' ) , ( ' name ' , ' = ' , ' account_automatic_reconcile_view1 ' ) ] )
resource_id = obj_model . read ( cr , uid , model_data_ids , fields = [ ' res_id ' ] ) [ 0 ] [ ' res_id ' ]
return {
' view_type ' : ' form ' ,
' view_mode ' : ' form ' ,
' res_model ' : ' account.automatic.reconcile ' ,
' views ' : [ ( resource_id , ' form ' ) ] ,
' type ' : ' ir.actions.act_window ' ,
' target ' : ' new ' ,
2010-04-29 06:09:43 +00:00
' context ' : context ,
2010-04-26 12:41:33 +00:00
}
2010-04-29 06:35:55 +00:00
2011-02-01 21:08:39 +00:00
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: