~sebastien.beau/openobject-addons/on-change-support-extra-addons

« back to all changes in this revision

Viewing changes to c2c_budget/c2c_budget_item.py

  • Committer: sebastien beau
  • Date: 2012-03-07 13:50:54 UTC
  • mfrom: (5560.1.82 extra-addons)
  • Revision ID: sebastien.beau@akretion.com.br-20120307135054-9fe1ww18do3gdjf8
[MERGE] merge with extra-trunk 5642

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- coding: utf-8 -*- 
2
 
##############################################################################
3
 
#
4
 
# Copyright (c) Camptocamp SA - http://www.camptocamp.com
5
 
# Author: Arnaud WÃŒst ported by Nicolas Bessi
6
 
#
7
 
#    This file is part of the c2c_budget module
8
 
#
9
 
# WARNING: This program as such is intended to be used by professional
10
 
# programmers who take the whole responsability of assessing all potential
11
 
# consequences resulting from its eventual inadequacies and bugs
12
 
# End users who are looking for a ready-to-use solution with commercial
13
 
# garantees and support are strongly adviced to contract a Free Software
14
 
# Service Company
15
 
#
16
 
# This program is Free Software; you can redistribute it and/or
17
 
# modify it under the terms of the GNU General Public License
18
 
# as published by the Free Software Foundation; either version 2
19
 
# of the License, or (at your option) any later version.
20
 
#
21
 
# This program is distributed in the hope that it will be useful,
22
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24
 
# GNU General Public License for more details.
25
 
#
26
 
# You should have received a copy of the GNU General Public License
27
 
# along with this program; if not, write to the Free Software
28
 
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
29
 
from osv import fields, osv
30
 
from copy import copy
31
 
import time
32
 
from c2c_reporting_tools.c2c_helper import *             
33
 
        
34
 
        
35
 
 
36
 
class c2c_budget_item(osv.osv):
37
 
    """ camptocamp budget item. This is a link between 
38
 
    budgets and financial accounts. """
39
 
 
40
 
    
41
 
    _name = "c2c_budget.item"  
42
 
    _description = "Budget items"
43
 
    _columns = {
44
 
        'code' : fields.char('Code', size=50, required=True),
45
 
        'name' : fields.char('Name', size=200,  required=True),
46
 
        'active' : fields.boolean('Active'),
47
 
        'parent_id' : fields.many2one('c2c_budget.item', 'Parent Item'),
48
 
        'children_ids' : fields.one2many(
49
 
                                            'c2c_budget.item', 
50
 
                                            'parent_id', 
51
 
                                            'Children Items'
52
 
                                        ),
53
 
        'account' : fields.many2many(
54
 
                                        'account.account', 
55
 
                                        'c2c_budget_item_account_rel', 
56
 
                                        'budget_item_id', 
57
 
                                        'account_id', 
58
 
                                        'Financial Account'
59
 
                                    ),
60
 
        'note' : fields.text('Notes'),
61
 
        'calculation' : fields.text('Calculation'),
62
 
        'type' : fields.selection(
63
 
                                    [
64
 
                                        ('view', 'View'),
65
 
                                        ('normal', 'Normal')
66
 
                                    ], 
67
 
                                    'Type',
68
 
                                     required=True
69
 
                                ),
70
 
        'sequence' : fields.integer('Sequence'),
71
 
        'style' : fields.selection(
72
 
                                        [
73
 
                                            ('normal', 'Normal'), 
74
 
                                            ('bold', 'Bold'), (
75
 
                                            'invisible', 'Invisible')
76
 
                                        ], 
77
 
                                        'Style', 
78
 
                                        required=True
79
 
                                    ),
80
 
            }
81
 
 
82
 
    _defaults = {
83
 
            'active'      : lambda *a: True, 
84
 
            'type'        : lambda *a: 'normal',
85
 
            'style'       : lambda *a: 'normal',
86
 
                 }
87
 
 
88
 
    _order = 'sequence,name'    
89
 
    
90
 
    def get_real_values_from_analytic_accounts(self, cr, uid, item_id, periods,\
91
 
        lines, company_id, currency_id, change_date, context=None):
92
 
        """return the sum of analytic move lines for 
93
 
        this item (and all subitems)"""
94
 
        if context is None: context = {}
95
 
 
96
 
        # filter the budget lines to work on
97
 
        budget_line_obj = self.pool.get('c2c_budget.line')         
98
 
        budget_lines = budget_line_obj.filter_by_items(
99
 
                                                            cr, 
100
 
                                                            uid, 
101
 
                                                            lines, 
102
 
                                                            [item_id], 
103
 
                                                            context
104
 
                                                        )
105
 
        
106
 
        # get the list of Analytic accounts related to those lines
107
 
        aa_ids = budget_line_obj.get_analytic_accounts(
108
 
                                                            cr, 
109
 
                                                            uid, 
110
 
                                                            budget_lines, 
111
 
                                                            company_id, 
112
 
                                                            context
113
 
                                                        )
114
 
        
115
 
        #get accounts (and subaccounts) related to the given item (and subitems)
116
 
        general_accounts_ids = self.get_accounts(
117
 
                                                    cr, 
118
 
                                                    uid, 
119
 
                                                    [item_id], 
120
 
                                                    company_id, 
121
 
                                                    context
122
 
                                                )
123
 
        
124
 
        #get dates limits 
125
 
        start_date = None
126
 
        end_date = None
127
 
        for p in periods:
128
 
            if start_date is None or start_date > p.date_start:
129
 
                start_date = p.date_start
130
 
            if end_date is None or end_date < p.date_stop:
131
 
                end_date = p.date_stop
132
 
                
133
 
                
134
 
        #we have all informations to look for Analytic Accounts' lines
135
 
        aa_lines_obj = self.pool.get('account.analytic.line')
136
 
        aa_lines_ids = aa_lines_obj.search(
137
 
                        cr, 
138
 
                        uid, 
139
 
                        [
140
 
                            ('date', '>=', start_date),
141
 
                            ('date', '<=', end_date),
142
 
                            ('general_account_id', 'in', general_accounts_ids),
143
 
                            ('account_id', 'in', aa_ids)], 
144
 
                            context=context
145
 
                        )
146
 
        aa_lines = aa_lines_obj.browse(cr, uid, aa_lines_ids, context=context)
147
 
        
148
 
        #now we have the lines, let's add them
149
 
        result = 0
150
 
        for l in aa_lines:
151
 
            result += c2c_helper.exchange_currency(
152
 
                                                    cr, 
153
 
                                l.amount, 
154
 
                                l.general_account_id.company_id.currency_id.id,
155
 
                                 currency_id, 
156
 
                                 c2c_helper.parse_date(change_date)
157
 
                                )
158
 
        
159
 
        return result
160
 
        
161
 
        
162
 
    
163
 
    
164
 
    def get_real_values(self, cr, uid, item, periods, company_id, \
165
 
        currency_id, change_date, context=None):
166
 
        """return the sum of the account move lines for this item """
167
 
        if context is None: context = {}
168
 
        result = 0
169
 
        
170
 
        # get the list of accounts and subaccounts linked to this item
171
 
        accounts = self.get_accounts(cr, uid,  [item.id], company_id, context)
172
 
        
173
 
        #get all move_lines linked to this item
174
 
        move_line_obj = self.pool.get('account.move.line')
175
 
        move_line_ids = move_line_obj.search(
176
 
                                cr, 
177
 
                                uid, 
178
 
                                [
179
 
                                    ('period_id', 'in', [p.id for p in periods]),
180
 
                                     ('account_id', 'in', accounts)
181
 
                                ]
182
 
                            )
183
 
        move_lines = move_line_obj.browse(cr, uid, move_line_ids, context=context)
184
 
        
185
 
        #sum all lines
186
 
        for l in move_lines:
187
 
            
188
 
            #multi company!
189
 
            if 'company_id' in move_line_obj._columns:
190
 
                line_currency_id = l.company_id.currency_id.id
191
 
            #get company's currency from account
192
 
            else: 
193
 
                line_currency_id = l.account_id.company_id.currency_id.id
194
 
                
195
 
            #debit ?
196
 
            if l.debit != 0:
197
 
                amount = l.debit
198
 
                sign = -1
199
 
                
200
 
            #credit ? 
201
 
            else: 
202
 
                amount = l.credit
203
 
                sign = 1
204
 
 
205
 
            result += sign * c2c_helper.exchange_currency(
206
 
                                            cr, 
207
 
                                            amount, 
208
 
                                            line_currency_id, 
209
 
                                            currency_id, 
210
 
                                            c2c_helper.parse_date(change_date)
211
 
                                        )
212
 
        
213
 
        return result
214
 
        
215
 
            
216
 
    
217
 
    def get_sub_items(self, cr, item_ids):
218
 
        """ return a flat list of ids of all items under 
219
 
        items in the tree structure """        
220
 
        parents_ids = item_ids
221
 
        
222
 
        items_ids = copy(parents_ids) 
223
 
        
224
 
        loop_counter = 0
225
 
        
226
 
        #for each "level" of parent
227
 
        while len(parents_ids) > 0:
228
 
            #get all the sub items of this level
229
 
            query = """SELECT id 
230
 
                       FROM c2c_budget_item 
231
 
                       WHERE parent_id IN ( %s ) 
232
 
                       AND active """ % ','.join(map(str,parents_ids))
233
 
            cr.execute(query)
234
 
            children_ids = map(lambda x: x[0], cr.fetchall())
235
 
            items_ids += children_ids
236
 
            
237
 
            #continue with next level            
238
 
            parents_ids = copy(children_ids)
239
 
 
240
 
            #count the loops to avoid infinit loops
241
 
            loop_counter += 1
242
 
            if (loop_counter > 100):
243
 
                raise osv.except_osv(
244
 
                'Recursion Error', 
245
 
                """It seems the item structure is recursive.
246
 
                Please check and correct it before to run this action again"""
247
 
                )
248
 
            
249
 
        return c2c_helper.unique(items_ids)
250
 
    
251
 
    
252
 
    
253
 
    def get_accounts(self, cr, uid,  item_ids, company_id, context=None):
254
 
        """return a list of accounts ids and their sub accounts 
255
 
        linked to items (item_ids) and their subitems """
256
 
        if context is None: context = {}
257
 
 
258
 
        sub_items_ids = self.get_sub_items(cr, item_ids)
259
 
        
260
 
        sub_items = self.browse(cr, uid, sub_items_ids)
261
 
        #gather all account linked to all subitems
262
 
        ids = []
263
 
        for i in sub_items:
264
 
            ids+= map (lambda x:x.id, i.account)
265
 
        
266
 
        #get the list of sub accounts of gathered accounts
267
 
        account_flat_list = self.pool.get('account.account').\
268
 
            get_children_flat_list(
269
 
                                    cr, 
270
 
                                    uid, 
271
 
                                    ids, 
272
 
                                    company_id, 
273
 
                                    context
274
 
                                )
275
 
        
276
 
        #here is the list of all accounts and subaccounts linked to items and subitems
277
 
        return account_flat_list
278
 
    
279
 
    
280
 
    
281
 
    def compute_view_items(self, items, items_values):
282
 
        """ compute items (type "view") values that are based on calculations 
283
 
            return the items_values param where computed values 
284
 
            are added (or replaced) 
285
 
            items is a list of items objects
286
 
            items_values is a dictionnary item_id => item_value
287
 
        """
288
 
        
289
 
        #prepare the dictionnary of values for formula remplacement.
290
 
        # put in it normal items' values and view items' values that do not have formula
291
 
        value_dict = {}
292
 
        for i in items:
293
 
            if (i.type == 'normal' or (i.type== 'view' and ((not i.calculation)\
294
 
             or i.calculation.strip() == "")) ) \
295
 
             and i.code and i.code.strip() != '':
296
 
                value_dict[""+i.code] = items_values[i.id]
297
 
        #this loop allow to use view items' results in formulas. 
298
 
        #count the number of errors that append. Loop until 
299
 
        #the number remain constant (=error in formulas) 
300
 
        #or reach 0 (=nothing more to compute)        
301
 
        previousErrorCounter = 0
302
 
        while True:
303
 
            errorCounter = 0
304
 
        
305
 
            #look throught the items if there is formula to compute?
306
 
            for i in items: 
307
 
                #if this item is a view, we must maybe 
308
 
                #replace its value by a calculation (if not already done)
309
 
                if i.type == 'view' \
310
 
                and i.calculation \
311
 
                and i.calculation.strip() != "" \
312
 
                and i.code \
313
 
                and (i.code+"" not in value_dict):
314
 
                    
315
 
                    formula_ok = True
316
 
                    exec_env = {'result': 0}
317
 
                    #replace keys by values in formula
318
 
                    try:
319
 
                        formula = i.calculation % value_dict
320
 
                    except Exception, e:
321
 
                        formula_ok = False
322
 
                        errorCounter += 1
323
 
                    #try to run the formula
324
 
                    if formula_ok:
325
 
                        result = None
326
 
                        try: 
327
 
                            exec formula in exec_env
328
 
                        except Exception, e:
329
 
                            formula_ok = False
330
 
                            errorCounter += 1
331
 
                    #retrive formula result
332
 
                    if formula_ok:
333
 
                        items_values[i.id] = exec_env['result']
334
 
                        value_dict[""+i.code] = exec_env['result']
335
 
                    else: 
336
 
                        items_values[i.id] = 'error'
337
 
            
338
 
            #the number of errors in this loop equal to the previous loop. 
339
 
            #No chance to get better... let's exit the "while true" loop
340
 
            if errorCounter == previousErrorCounter:
341
 
                break
342
 
            else: 
343
 
                previousErrorCounter = errorCounter
344
 
                
345
 
        return items_values
346
 
    
347
 
    
348
 
    
349
 
    def get_sorted_list(self, cr, uid, root_id):
350
 
        """return a list of items sorted by sequence to be used in reports 
351
 
           Data are returned in a list 
352
 
           (value=dictionnary(keys='id','code',
353
 
           'name','level', sequence, type, style))
354
 
        """
355
 
        
356
 
        def bySequence(first, second):
357
 
            """callback function to sort """
358
 
            if first['sequence'] > second['sequence']:
359
 
                return 1
360
 
            elif first['sequence'] < second['sequence']:
361
 
                return -1
362
 
            return 0
363
 
                
364
 
                
365
 
        flat_tree = self.get_flat_tree(cr, uid, root_id)
366
 
        flat_tree.sort(bySequence)
367
 
        
368
 
        item_ids = map(lambda x:x['id'], flat_tree)
369
 
        
370
 
        return self.browse(cr, uid, item_ids)
371
 
        
372
 
        
373
 
 
374
 
    def  get_flat_tree(self, cr, uid, root_id, level=0):
375
 
        """ return informations about a buget items tree strcuture. 
376
 
Data are returned in a list 
377
 
(value=dictionnary(keys='id','code','name','level', sequence, type, style))
378
 
Data are sorted as in the pre-order walk
379
 
algorithm in order to allow to display easily the tree in rapports 
380
 
example: 
381
 
    root    
382
 
     |_node 1
383
 
        |_sub node 1
384
 
     |_node 2
385
 
     |_ ...
386
 
 
387
 
Do not specify the level param when you call this method, 
388
 
it is used for recursive calls
389
 
"""
390
 
            
391
 
        result = []
392
 
 
393
 
        
394
 
        #this method is recursive so for the first call, 
395
 
        #we must init result with the root node
396
 
        if (level == 0):
397
 
            query = """SELECT id, code, name, sequence, type, style, %s as level
398
 
                       FROM c2c_budget_item 
399
 
                       WHERE id = %s """ % (level, str(root_id))
400
 
                    
401
 
            cr.execute(query)            
402
 
            result.append(cr.dictfetchall()[0])
403
 
            level += 1
404
 
 
405
 
 
406
 
        #get children's data
407
 
        query = """SELECT id, code, name, sequence, type, style, %s as level
408
 
                   FROM c2c_budget_item 
409
 
                   WHERE parent_id = %s
410
 
                   AND active 
411
 
                   ORDER BY sequence """ % (level, str(root_id))
412
 
        cr.execute(query)
413
 
        query_result = cr.dictfetchall()
414
 
        
415
 
        for child in query_result:
416
 
            result.append(child)
417
 
            #recursive call to append the children right after the item
418
 
            result += self.get_flat_tree(cr, uid, child['id'], level+1)
419
 
            
420
 
        #check to avoid inifit loop
421
 
        if (level > 100):
422
 
            raise osv.except_osv(
423
 
                                    'Recursion Error', 
424
 
                                    'It seems the budget items structure \
425
 
                                    is recursive (or too deep). \
426
 
                                    Please check and correct it\
427
 
                                    before to run this action again'
428
 
                                )
429
 
                    
430
 
        return result 
431
 
        
432
 
    
433
 
    
434
 
    
435
 
    def _check_recursion(self, cr, uid, ids, context=None, parent=None):
436
 
        """ use in _constraints[]: return false 
437
 
        if there is a recursion in the budget items structure """
438
 
 
439
 
        #use the parent check_recursion function defined in orm.py
440
 
        return super(c2c_budget_item,self)._check_recursion(
441
 
                                                            cr,
442
 
                                                            uid,
443
 
                                                            ids,
444
 
                                                            parent=parent or 'parent_id',
445
 
                                                            context=context
446
 
                                                        )
447
 
    
448
 
    
449
 
    
450
 
    _constraints = [
451
 
            (   _check_recursion, 
452
 
                'Error ! You can not create recursive\
453
 
                 budget items structure.', 
454
 
                ['parent_id']
455
 
            )
456
 
        ]
457
 
    
458
 
    
459
 
    
460
 
    def name_search(self, cr, user, name, args=None,\
461
 
        operator='ilike', context=None, limit=80):
462
 
        """search not only for a matching names but also 
463
 
        for a matching codes """
464
 
        
465
 
        if not args:
466
 
            args=[]
467
 
        if not context:
468
 
            context={}
469
 
        ids = self.search(
470
 
                            cr, 
471
 
                            user, 
472
 
                            [('code',operator,name)]+ args, 
473
 
                            limit=limit, 
474
 
                            context=context
475
 
                        )
476
 
        ids += self.search(
477
 
                            cr, 
478
 
                            user, 
479
 
                            [('name',operator,name)]+ args, 
480
 
                            limit=limit, 
481
 
                            context=context
482
 
                          )
483
 
        return self.name_get(
484
 
                                cr, 
485
 
                                user, 
486
 
                                ids,
487
 
                                 context
488
 
                            )        
489
 
    
490
 
    
491
 
    
492
 
    def search(self, cr, user, args, offset=0, \
493
 
        limit=None, order=None, context=None, count=False):
494
 
        """ special search. If we search an item from the budget 
495
 
        version form (in the budget lines) 
496
 
        then the choice is reduce to periods
497
 
        that overlap the budget dates"""
498
 
        
499
 
        result = [] 
500
 
           
501
 
        parent_result = super(c2c_budget_item, self).search(cr, user, args, offset, limit, order, context, count)    
502
 
        #special search
503
 
        if context != None and 'budget_id' in context \
504
 
            and context['budget_id'] != False:
505
 
            
506
 
            budget = self.pool.get('c2c_budget').browse(
507
 
                                                        cr, 
508
 
                                                        user, 
509
 
                                                        context['budget_id'], 
510
 
                                                        context
511
 
                                                    )
512
 
                        
513
 
            allowed_items = self.get_sub_items(
514
 
                                                cr, 
515
 
                                                [budget.budget_item_id.id]
516
 
                                                )
517
 
            for i in parent_result:
518
 
                if i in allowed_items: 
519
 
                    result.append(i)                   
520
 
        #normal search
521
 
        else: 
522
 
            result = parent_result
523
 
        return result
524
 
    
525
 
    
526
 
    
527
 
    def unlink(self, cr, uid, ids, context={}):
528
 
        """ delete subitems and catch the ugly error in case
529
 
         of the item is in use in another object """
530
 
        
531
 
        #build a list of all sub items 
532
 
        sub_ids = []
533
 
        if type(ids) == int:
534
 
            ids = [ids]
535
 
        sub_ids = self.get_sub_items(cr, ids)
536
 
                        
537
 
        #delete item and all subitems
538
 
        try: 
539
 
            return super(c2c_budget_item, self).unlink(
540
 
                                                        cr, 
541
 
                                                        uid, 
542
 
                                                        sub_ids, 
543
 
                                                        context
544
 
                                                    )
545
 
        except: 
546
 
            raise osv.except_osv(
547
 
                                'Unable to delete the item', 
548
 
                                'At least one of the items you are trying to\
549
 
                                 delete or one of their subitems is still \
550
 
                                 referenced by another element. \
551
 
                                 Check there is no budget lines that link to \
552
 
                                 these items and there is no budget that use \
553
 
                                 these items as budget structure root.')
554
 
            
555
 
 
556
 
            
557
 
 
558
 
 
559
 
    
560
 
c2c_budget_item()