~technofluid-team/openobject-addons/technofluid_multiple_installations

« back to all changes in this revision

Viewing changes to account/wizard/wizard_automatic_reconcile.py

  • Committer: pinky
  • Date: 2006-12-07 13:41:40 UTC
  • Revision ID: pinky-dedd7f8a42bd4557112a0513082691b8590ad6cc
New trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- encoding: utf-8 -*-
 
2
##############################################################################
 
3
#
 
4
# Copyright (c) 2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
 
5
#
 
6
# WARNING: This program as such is intended to be used by professional
 
7
# programmers who take the whole responsability of assessing all potential
 
8
# consequences resulting from its eventual inadequacies and bugs
 
9
# End users who are looking for a ready-to-use solution with commercial
 
10
# garantees and support are strongly adviced to contract a Free Software
 
11
# Service Company
 
12
#
 
13
# This program is Free Software; you can redistribute it and/or
 
14
# modify it under the terms of the GNU General Public License
 
15
# as published by the Free Software Foundation; either version 2
 
16
# of the License, or (at your option) any later version.
 
17
#
 
18
# This program is distributed in the hope that it will be useful,
 
19
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
20
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
21
# GNU General Public License for more details.
 
22
#
 
23
# You should have received a copy of the GNU General Public License
 
24
# along with this program; if not, write to the Free Software
 
25
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 
26
#
 
27
##############################################################################
 
28
 
 
29
import wizard
 
30
import netsvc
 
31
import pooler
 
32
import time
 
33
 
 
34
#TODO:
 
35
 
 
36
# a rajouter comme questions ds le wizard:
 
37
# - account_id (et mettre wizard ds le menu et pas sur client_action_multi)
 
38
# - journal
 
39
# - compte d'ajustement
 
40
# - montant max (0,03)
 
41
        # <field name="max_amount"/>
 
42
# - libelle write-off
 
43
# - devise principale ou secondaire
 
44
        # devise secondaire = amount_currency
 
45
        # si devise: pas prendre ceux avec montant_devise = 0
 
46
 
 
47
# a demander à fabien:
 
48
# - checkbox (comme ds sage) "lettrage rapide des comptes soldés"?
 
49
 
 
50
# pr creer ecriture: creer move.line avec period et journal dans le contexte
 
51
# faire methode sur period: get_current_period
 
52
 
 
53
_reconcile_form = '''<?xml version="1.0"?>
 
54
<form string="Reconciliation">
 
55
        <separator string="Options" colspan="4"/>
 
56
        <field name="account_ids" colspan="3" domain="[('reconcile','=',1)]"/>
 
57
        <field name="date1"/>
 
58
        <field name="date2"/>
 
59
        <field name="power"/>
 
60
        <separator string="Write-Off Move" colspan="4"/>
 
61
        <field name="max_amount"/>
 
62
        <field name="writeoff_acc_id"/>
 
63
        <field name="journal_id"/>
 
64
        <field name="period_id"/>
 
65
</form>'''
 
66
 
 
67
_reconcile_fields = {
 
68
        'account_ids': {
 
69
                'string': 'Account to reconcile',
 
70
                'type': 'many2many',
 
71
                'relation': 'account.account',
 
72
                'domain': [('reconcile','=',1)],
 
73
                'required': True
 
74
        },
 
75
        'writeoff_acc_id': {
 
76
                'string': 'Account',
 
77
                'type': 'many2one',
 
78
                'relation': 'account.account',
 
79
                'required': True
 
80
        },
 
81
        'journal_id': {
 
82
                'string': 'Journal',
 
83
                'type': 'many2one',
 
84
                'relation': 'account.journal',
 
85
                'required': True
 
86
        },
 
87
        'period_id': {
 
88
                'string': 'Period',
 
89
                'type': 'many2one',
 
90
                'relation': 'account.period',
 
91
                'required': True
 
92
        },
 
93
        'max_amount': {
 
94
                'string': 'Maximum write-off amount',
 
95
                'type': 'float',
 
96
        },
 
97
        #'currency': {
 
98
        #       'string': 'Reconcile in',
 
99
        #       'type': 'selection',
 
100
        #       'selection': [('current','current currency'), ('secondary','secondary currency')],
 
101
        #       'required': True
 
102
        #},
 
103
        'power': {
 
104
                'string': 'Power',
 
105
                'type': 'selection',
 
106
                'selection': [(p,str(p)) for p in range(2, 10)],
 
107
                'required': True
 
108
        },
 
109
        'date1': {
 
110
                'string': 'Start of period',
 
111
                'type': 'date',
 
112
                'required': True,
 
113
                'default': lambda *a: time.strftime('%Y-01-01')
 
114
        },
 
115
        'date2': {
 
116
                'string': 'End of period',
 
117
                'type': 'date',
 
118
                'required': True,
 
119
                'default': lambda *a: time.strftime('%Y-%m-%d')
 
120
        },
 
121
}
 
122
 
 
123
_result_form = '''<?xml version="1.0"?>
 
124
<form string="Reconciliation result">
 
125
        <field name="reconciled"/>
 
126
        <newline/>
 
127
        <field name="unreconciled"/>
 
128
</form>'''
 
129
 
 
130
_result_fields = {
 
131
        'reconciled': {
 
132
                'string': 'Reconciled transactions',
 
133
                'type': 'integer',
 
134
                'readonly': True
 
135
        },
 
136
        'unreconciled': {
 
137
                'string': 'Not reconciled transactions',
 
138
                'type': 'integer',
 
139
                'readonly': True
 
140
        },
 
141
}
 
142
 
 
143
#TODO: cleanup and comment this code... For now, it is awfulllll
 
144
# (way too complex, and really slow)...
 
145
def do_reconcile(cr, uid, credits, debits, max_amount, power, writeoff_acc_id, period_id, journal_id, context={}):
 
146
        # for one value of a credit, check all debits, and combination of them
 
147
        # depending on the power. It starts with a power of one and goes up
 
148
        # to the max power allowed
 
149
        def check2(value, move_list, power):
 
150
                def check(value, move_list, power):
 
151
                        for i in range(len(move_list)):
 
152
                                move = move_list[i]
 
153
                                if power == 1:
 
154
                                        if abs(value - move[1]) <= max_amount + 0.00001:
 
155
                                                return [move[0]]
 
156
                                else:
 
157
                                        del move_list[i]
 
158
                                        res = check(value - move[1], move_list, power-1)
 
159
                                        move_list[i:i] = [move]
 
160
                                        if res:
 
161
                                                res.append(move[0])
 
162
                                                return res
 
163
                        return False
 
164
 
 
165
                for p in range(1, power+1):
 
166
                        res = check(value, move_list, p)
 
167
                        if res:
 
168
                                return res
 
169
                return False
 
170
 
 
171
        # for a list of credit and debit and a given power, check if there 
 
172
        # are matching tuples of credit and debits, check all debits, and combination of them
 
173
        # depending on the power. It starts with a power of one and goes up
 
174
        # to the max power allowed
 
175
        def check4(list1, list2, power):
 
176
                def check3(value, list1, list2, list1power, power):
 
177
                        for i in range(len(list1)):
 
178
                                move = list1[i]
 
179
                                if list1power == 1:
 
180
                                        res = check2(value + move[1], list2, power - 1)
 
181
                                        if res:
 
182
                                                return ([move[0]], res)
 
183
                                else:
 
184
                                        del list1[i]
 
185
                                        res = check3(value + move[1], list1, list2, list1power-1, power-1)
 
186
                                        list1[i:i] = [move]
 
187
                                        if res:
 
188
                                                x, y = res
 
189
                                                x.append(move[0])
 
190
                                                return (x, y)
 
191
                        return False
 
192
 
 
193
                for p in range(1, power):
 
194
                        res = check3(0, list1, list2, p, power)
 
195
                        if res:
 
196
                                return res
 
197
                return False
 
198
                        
 
199
        def check5(list1, list2, max_power):
 
200
                for p in range(2, max_power+1):
 
201
                        res = check4(list1, list2, p)
 
202
                        if res:
 
203
                                return res
 
204
 
 
205
        ok = True
 
206
        reconciled = 0
 
207
        move_line_obj = pooler.get_pool(cr.dbname).get('account.move.line')
 
208
        while credits and debits and ok:
 
209
                res = check5(credits, debits, power)
 
210
                if res:
 
211
                        move_line_obj.reconcile(cr, uid, res[0] + res[1], 'auto', writeoff_acc_id, period_id, journal_id, context)
 
212
                        reconciled += len(res[0]) + len(res[1])
 
213
                        credits = [(id, credit) for (id, credit) in credits if id not in res[0]]
 
214
                        debits = [(id, debit) for (id, debit) in debits if id not in res[1]]
 
215
                else:
 
216
                        ok = False
 
217
        return (reconciled, len(credits)+len(debits))
 
218
                                
 
219
def _reconcile(self, cr, uid, data, context):
 
220
        service = netsvc.LocalService("object_proxy")
 
221
        move_line_obj = pooler.get_pool(cr.dbname).get('account.move.line')
 
222
        form = data['form']
 
223
        max_amount = form.get('max_amount', 0.0)
 
224
        power = form['power']
 
225
        reconciled = unreconciled = 0
 
226
        if not form['account_ids']:
 
227
                return {'reconciled':0, 'unreconciled':[0]}
 
228
        for account_id in form['account_ids'][0][2]:
 
229
        
 
230
                # reconcile automatically all transactions from partners whose balance is 0
 
231
                cr.execute(
 
232
                        "SELECT partner_id " \
 
233
                        "FROM account_move_line " \
 
234
                        "WHERE account_id=%d " \
 
235
                        "AND reconcile_id IS NULL " \
 
236
                        "AND state <> 'draft' " \
 
237
                        "GROUP BY partner_id " \
 
238
                        "HAVING ABS(SUM(debit-credit)) < %f AND count(*)>0",
 
239
                        (account_id, max_amount))
 
240
                partner_ids = [id for (id,) in cr.fetchall()]
 
241
 
 
242
                for partner_id in partner_ids:
 
243
                        cr.execute(
 
244
                                "SELECT id " \
 
245
                                "FROM account_move_line " \
 
246
                                "WHERE account_id=%d " \
 
247
                                "AND partner_id=%d " \
 
248
                                "AND state <> 'draft' " \
 
249
                                "AND reconcile_id IS NULL",
 
250
                                (account_id, partner_id))
 
251
                        line_ids = [id for (id,) in cr.fetchall()]
 
252
                        
 
253
                        if len(line_ids):
 
254
                                move_line_obj.reconcile(cr, uid, line_ids, 'auto', form['writeoff_acc_id'], form['period_id'], form['journal_id'], context)
 
255
                                reconciled += len(line_ids)
 
256
                
 
257
                # get the list of partners who have more than one unreconciled transaction
 
258
                cr.execute(
 
259
                        "SELECT partner_id " \
 
260
                        "FROM account_move_line " \
 
261
                        "WHERE account_id=%d " \
 
262
                        "AND reconcile_id IS NULL " \
 
263
                        "AND state <> 'draft' " \
 
264
                        "GROUP BY partner_id " \
 
265
                        "HAVING count(*)>1",
 
266
                        (account_id,))
 
267
                partner_ids = [id for (id,) in cr.fetchall()]
 
268
                #filter?
 
269
                for partner_id in partner_ids:
 
270
                        # get the list of unreconciled 'debit transactions' for this partner
 
271
                        cr.execute(
 
272
                                "SELECT id, debit " \
 
273
                                "FROM account_move_line " \
 
274
                                "WHERE account_id=%d " \
 
275
                                "AND partner_id=%d " \
 
276
                                "AND reconcile_id IS NULL " \
 
277
                                "AND state <> 'draft' " \
 
278
                                "AND debit > 0",
 
279
                                (account_id, partner_id))
 
280
                        debits = cr.fetchall()
 
281
                                
 
282
                        # get the list of unreconciled 'credit transactions' for this partner
 
283
                        cr.execute(
 
284
                                "SELECT id, credit " \
 
285
                                "FROM account_move_line " \
 
286
                                "WHERE account_id=%d " \
 
287
                                "AND partner_id=%d " \
 
288
                                "AND reconcile_id IS NULL " \
 
289
                                "AND state <> 'draft' " \
 
290
                                "AND credit > 0",
 
291
                                (account_id, partner_id))
 
292
                        credits = cr.fetchall()
 
293
                        
 
294
                        (rec, unrec) = do_reconcile(cr, uid, credits, debits, max_amount, power, form['writeoff_acc_id'], form['period_id'], form['journal_id'], context)
 
295
                        reconciled += rec
 
296
                        unreconciled += unrec
 
297
 
 
298
                # add the number of transactions for partners who have only one 
 
299
                # unreconciled transactions to the unreconciled count
 
300
                partner_filter = partner_ids and 'AND partner_id not in (%s)' % ','.join(map(str, filter(None, partner_ids))) or ''
 
301
                cr.execute(
 
302
                        "SELECT count(*) " \
 
303
                        "FROM account_move_line " \
 
304
                        "WHERE account_id=%d " \
 
305
                        "AND reconcile_id IS NULL " \
 
306
                        "AND state <> 'draft' " + partner_filter,
 
307
                        (account_id,))
 
308
                additional_unrec = cr.fetchone()[0]
 
309
        return {'reconciled':reconciled, 'unreconciled':unreconciled+additional_unrec}
 
310
 
 
311
class wiz_reconcile(wizard.interface):
 
312
        states = {
 
313
                'init': {
 
314
                        'actions': [],
 
315
                        'result': {'type':'form', 'arch':_reconcile_form, 'fields':_reconcile_fields, 'state':[('end','Cancel'),('reconcile','Reconcile')]}
 
316
                },
 
317
                'reconcile': {
 
318
                        'actions': [_reconcile],
 
319
                        'result': {'type':'form', 'arch':_result_form, 'fields':_result_fields, 'state':[('end','OK')]}
 
320
                }
 
321
        }
 
322
wiz_reconcile('account.automatic.reconcile')
 
323