~unifield-team/unifield-wm/us-671-homere

« back to all changes in this revision

Viewing changes to msf_budget/msf_budget.py

[UF-43] fix added noupdate to demo data

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# encoding: utf-8
2
 
##############################################################################
3
 
#
4
 
#    OpenERP, Open Source Management Solution
5
 
#    Copyright (C) 2011 MSF, TeMPO consulting
6
 
#
7
 
#    This program is free software: you can redistribute it and/or modify
8
 
#    it under the terms of the GNU General Public License as published by
9
 
#    the Free Software Foundation, either version 3 of the License, or
10
 
#    (at your option) any later version.
11
 
#
12
 
#    This program is distributed in the hope that it will be useful,
13
 
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 
#    GNU General Public License for more details.
16
 
#
17
 
#    You should have received a copy of the GNU General Public License
18
 
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
 
#
20
 
##############################################################################
21
 
 
22
 
from osv import fields, osv
23
 
from tools.translate import _
24
 
 
25
 
import datetime
26
 
 
27
 
class msf_budget(osv.osv):
28
 
    _name = "msf.budget"
29
 
    _description = 'MSF Budget'
30
 
    _trace = True
31
 
 
32
 
    def _get_total_budget_amounts(self, cr, uid, ids, field_names=None, arg=None, context=None):
33
 
        res = {}
34
 
        sql = """
35
 
        SELECT expense.budget_id, COALESCE(expense.total, 0.0) - COALESCE(income.total, 0.0) AS diff
36
 
        FROM (
37
 
            SELECT budget_id, SUM(COALESCE(month1 + month2 + month3 + month4 + month5 + month6 + month7 + month8 + month9 + month10 + month11 + month12, 0.0)) AS total
38
 
            FROM msf_budget_line AS l, account_account AS a, account_account_type AS t
39
 
            WHERE budget_id IN %s
40
 
            AND l.account_id = a.id
41
 
            AND a.user_type = t.id
42
 
            AND t.code = 'expense'
43
 
            AND a.type != 'view'
44
 
            AND l.line_type = 'destination'
45
 
            GROUP BY budget_id
46
 
        ) AS expense
47
 
        LEFT JOIN (
48
 
            SELECT budget_id, SUM(COALESCE(month1 + month2 + month3 + month4 + month5 + month6 + month7 + month8 + month9 + month10 + month11 + month12, 0.0)) AS total
49
 
            FROM msf_budget_line AS l, account_account AS a, account_account_type AS t
50
 
            WHERE budget_id IN %s
51
 
            AND l.account_id = a.id
52
 
            AND a.user_type = t.id
53
 
            AND t.code = 'income'
54
 
            AND a.type != 'view'
55
 
            AND l.line_type = 'destination'
56
 
            GROUP BY budget_id
57
 
        ) AS income ON expense.budget_id = income.budget_id"""
58
 
        cr.execute(sql, (tuple(ids),tuple(ids),))
59
 
        tmp_res = cr.fetchall()
60
 
        if not tmp_res:
61
 
            return res
62
 
        for b_id in ids:
63
 
            res.setdefault(b_id, 0.0)
64
 
        res.update(dict(tmp_res))
65
 
        return res
66
 
 
67
 
    def _get_instance_type(self, cr, uid, ids, field_names=None, arg=None, context=None):
68
 
        """
69
 
        Retrieve instance type regarding cost center id and check on instances which one have this cost center as "top cost center for budget"
70
 
        """
71
 
        if not context:
72
 
            context = {}
73
 
        res = {}
74
 
        for budget in self.browse(cr, uid, ids):
75
 
            res[budget.id] = 'project'
76
 
            if budget.cost_center_id:
77
 
                target_ids = self.pool.get('account.target.costcenter').search(cr, uid, [('cost_center_id', '=', budget.cost_center_id.id), ('is_top_cost_center', '=', True), ('instance_id.level', '=', 'coordo')])
78
 
                if target_ids:
79
 
                    res[budget.id] = 'coordo'
80
 
            if not budget.cost_center_id.parent_id:
81
 
                res[budget.id] = 'section'
82
 
        return res
83
 
 
84
 
    def _search_instance_type(self, cr, uid, obj, name, args, context=None):
85
 
        """
86
 
        Search all budget that have a cost coster used in a top_cost_center for an instance for the given type
87
 
        """
88
 
        res = []
89
 
        if not context:
90
 
            context = {}
91
 
        if not args:
92
 
            return res
93
 
        if args[0] and args[0][2]:
94
 
            target_ids = self.pool.get('account.target.costcenter').search(cr, uid, [('is_top_cost_center', '=', True), ('instance_id.level', '=', 'coordo')])
95
 
            coordo_ids = [x and x.cost_center_id and x.cost_center_id.id for x in self.pool.get('account.target.costcenter').browse(cr, uid, target_ids)]
96
 
            hq_ids = self.pool.get('account.analytic.account').search(cr, uid, [('parent_id', '=', False)])
97
 
            if isinstance(hq_ids, (int, long)):
98
 
                hq_ids = [hq_ids]
99
 
            if args[0][2] == 'section':
100
 
                return [('cost_center_id', 'in', hq_ids)]
101
 
            elif args[0][2] == 'coordo':
102
 
                return [('cost_center_id', 'in', coordo_ids)]
103
 
            elif args[0][2] == 'project':
104
 
                return [('cost_center_id', 'not in', hq_ids), ('cost_center_id', 'not in', coordo_ids)]
105
 
        return res
106
 
 
107
 
    _columns = {
108
 
        'name': fields.char('Name', size=64, required=True),
109
 
        'code': fields.char('Code', size=64, required=True),
110
 
        'fiscalyear_id': fields.many2one('account.fiscalyear', 'Fiscal Year', required=True),
111
 
        'state': fields.selection([('draft','Draft'),('valid','Validated'),('done','Done')], 'State', select=True, required=True),
112
 
        'cost_center_id': fields.many2one('account.analytic.account', 'Cost Center', domain=[('category', '=', 'OC'), ('type', '=', 'normal')], required=True),
113
 
        'decision_moment_id': fields.many2one('msf.budget.decision.moment', 'Decision Moment', required=True),
114
 
        'decision_moment_order': fields.related('decision_moment_id', 'order', string="Decision Moment Order", readonly=True, store=True, type="integer"),
115
 
        'version': fields.integer('Version'),
116
 
        'currency_id': fields.many2one('res.currency', 'Currency', required=True),
117
 
        'type': fields.selection([('normal', 'Normal'), ('view', 'View')], string="Budget type"),
118
 
        'total_budget_amount': fields.function(_get_total_budget_amounts, method=True, store=False, string="Total Budget Amount", type="float", readonly=True),
119
 
        'instance_type': fields.function(_get_instance_type, fnct_search=_search_instance_type, method=True, store=False, string='Instance type', type='selection', selection=[('section', 'HQ'), ('coordo', 'Coordo'), ('project', 'Project')], readonly=True),
120
 
    }
121
 
 
122
 
    _defaults = {
123
 
        'currency_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.currency_id.id,
124
 
        'state': 'draft',
125
 
        'type': 'normal',
126
 
    }
127
 
 
128
 
    _order = 'decision_moment_order desc, version, code'
129
 
 
130
 
    def _check_parent(self, cr, uid, vals, context=None):
131
 
        """
132
 
        Check budget's parent to see if it exist.
133
 
        Create it if we're on another instance that top cost center one.
134
 
        Note: context can contains a list of budget lines. This permit to avoid problem of budget line template time consuming.
135
 
        We hope the copy() will take less time than the creation of an entire budget template.
136
 
        """
137
 
        # Some checks
138
 
        if context is None:
139
 
            context = {}
140
 
        # Prepare some values
141
 
        top_cost_center = self.pool.get('res.users').browse(cr, uid, uid).company_id.instance_id.top_cost_center_id
142
 
        ana_obj = self.pool.get('account.analytic.account')
143
 
        fy_obj = self.pool.get('account.fiscalyear')
144
 
        tool_obj = self.pool.get('msf.budget.tools')
145
 
        # Fetch cost center info (id and parent)
146
 
        cc_id = vals.get('cost_center_id', False)
147
 
        cc = ana_obj.read(cr, uid, cc_id, ['parent_id'], context=context)
148
 
        parent_id = cc.get('parent_id', False) and cc.get('parent_id')[0] or False
149
 
        # Fetch fiscalyear info
150
 
        fy_id = vals.get('fiscalyear_id', False)
151
 
        fy = fy_obj.read(cr, uid, fy_id, ['code'])
152
 
        # Fetch decision moment id
153
 
        decision_moment_id = vals.get('decision_moment_id', False)
154
 
 
155
 
        # Check that no parent cost center exists for the given values
156
 
        if cc_id and cc_id != top_cost_center.id and parent_id:
157
 
            parent_cost_center = ana_obj.read(cr, uid, parent_id, ['code', 'name'], context=context)
158
 
            have_parent_budget = self.search(cr, uid, [('fiscalyear_id', '=', fy_id), ('cost_center_id', '=', parent_id), ('decision_moment_id', '=', decision_moment_id)], count=1, context=context)
159
 
            if have_parent_budget == 0:
160
 
                # Create budget's parent
161
 
                budget_vals = {
162
 
                    'name': "Budget " + fy.get('code', '')[4:6] + " - " + parent_cost_center.get('name', ''),
163
 
                    'code': "BU" + fy.get('code')[4:6] + " - " + parent_cost_center.get('code', ''),
164
 
                    'fiscalyear_id': fy_id,
165
 
                    'cost_center_id': parent_id,
166
 
                    'decision_moment_id': decision_moment_id,
167
 
                    'type': 'view'
168
 
                }
169
 
                parent_budget_id = self.create(cr, uid, budget_vals, context=context)
170
 
                # Create budget's line.
171
 
                tool_obj.create_budget_lines(cr, uid, parent_budget_id, context=context)
172
 
                # Validate this parent
173
 
                self.write(cr, uid, [parent_budget_id], {'state': 'valid'}, context=context)
174
 
        return True
175
 
 
176
 
    def create(self, cr, uid, vals, context=None):
177
 
        """
178
 
        Create a budget then check its parent.
179
 
        """
180
 
        res = super(msf_budget, self).create(cr, uid, vals, context=context)
181
 
        # Check parent budget
182
 
        self._check_parent(cr, uid, vals, context=context)
183
 
        return res
184
 
 
185
 
    def write(self, cr, uid, ids, vals, context=None):
186
 
        """
187
 
        Goal is to update parent budget regarding these criteria:
188
 
          - context is synchronization
189
 
          - state is in vals
190
 
          - state is different from draft (validated or done)
191
 
        """
192
 
 
193
 
        if not ids:
194
 
            return True
195
 
        if context is None:
196
 
            context = {}
197
 
        res = super(msf_budget, self).write(cr, uid, ids, vals, context=context)
198
 
        if context.get('sync_update_execution', False) and vals.get('state', False) and vals.get('state') != 'draft':
199
 
            # Update parent budget
200
 
            self.update_parent_budgets(cr, uid, ids, context=context)
201
 
 
202
 
        budget = self.browse(cr, uid, ids, context=context)[0]
203
 
        if budget.type == 'normal' and vals.get('state') == 'done':  # do not process for view accounts
204
 
            ala_obj = self.pool.get('account.analytic.account')
205
 
            # get parent cc
206
 
            cc_parent_ids = ala_obj._get_parent_of(cr, uid, budget.cost_center_id.id, context=context)
207
 
            # exclude the cc of the current budget line
208
 
            parent_cc_ids = [x for x in cc_parent_ids if x != budget.cost_center_id.id]
209
 
            # find all ccs which have the same parent
210
 
            all_cc_ids = ala_obj.search(cr, uid, [('parent_id','in',parent_cc_ids)], context=context)
211
 
            # remove parent ccs from the list
212
 
            peer_cc_ids = [x for x in all_cc_ids if x not in parent_cc_ids]
213
 
            # find peer budget lines based on cc
214
 
            peer_budget_ids = self.search(cr, uid, [('cost_center_id','in',peer_cc_ids),('decision_moment_id','=',budget.decision_moment_id.id),('fiscalyear_id','=',budget.fiscalyear_id.id),'!',('id','=',budget.id)],context=context)
215
 
            peer_budgets = self.browse(cr, uid, peer_budget_ids, context=context)
216
 
 
217
 
            all_done = True
218
 
            for peer in peer_budgets:
219
 
                if peer.state != 'done':
220
 
                    all_done = False
221
 
            if all_done == True:
222
 
                parent_ids = self.search(cr, uid, [('cost_center_id', 'in', parent_cc_ids),('decision_moment_id','=',budget.decision_moment_id.id),('fiscalyear_id','=',budget.fiscalyear_id.id),'!',('state','=','done')],context=context)
223
 
                self.write(cr, uid, parent_ids, {'state': 'done'},context=context)
224
 
        return res
225
 
 
226
 
    def update(self, cr, uid, ids, context=None):
227
 
        """
228
 
        Update given budget. But only update view one.
229
 
        """
230
 
        # Some checks
231
 
        if context is None:
232
 
            context = {}
233
 
        if isinstance(ids, (int, long)):
234
 
            ids = [ids]
235
 
        # Prepare some values
236
 
        ana_obj = self.pool.get('account.analytic.account')
237
 
        line_obj = self.pool.get('msf.budget.line')
238
 
        sql = """
239
 
            SELECT
240
 
                SUM(COALESCE(month1, 0)),
241
 
                SUM(COALESCE(month2, 0)),
242
 
                SUM(COALESCE(month3, 0)),
243
 
                SUM(COALESCE(month4, 0)),
244
 
                SUM(COALESCE(month5, 0)),
245
 
                SUM(COALESCE(month6, 0)),
246
 
                SUM(COALESCE(month7, 0)),
247
 
                SUM(COALESCE(month8, 0)),
248
 
                SUM(COALESCE(month9, 0)),
249
 
                SUM(COALESCE(month10, 0)),
250
 
                SUM(COALESCE(month11, 0)),
251
 
                SUM(COALESCE(month12, 0))
252
 
            FROM msf_budget_line
253
 
            WHERE id IN %s"""
254
 
        # Filter budget to only update those that are view one
255
 
        to_update = self.search(cr, uid, [('id', 'in', ids), ('type', '=', 'view')])
256
 
        # Then update budget, one by one, line by line...
257
 
        for budget in self.browse(cr, uid, to_update, context=context):
258
 
            cost_center_id = budget.cost_center_id and budget.cost_center_id.id or False
259
 
            if not cost_center_id:
260
 
                raise osv.except_osv(_('Error'), _('Problem while reading Cost Center for the given budget: %s') % (budget.get('name', ''),))
261
 
            child_cc_ids = ana_obj.search(cr, uid, [('parent_id', 'child_of', cost_center_id)])
262
 
            budget_ids = []
263
 
            # For each CC, search the last budget
264
 
            for cc_id in child_cc_ids:
265
 
                cc_args = [
266
 
                    ('cost_center_id', '=', cc_id),
267
 
                    ('type', '!=', 'view'),
268
 
                    ('state', '!=', 'draft'),
269
 
                    ('decision_moment_id', '=', budget.decision_moment_id.id)
270
 
                ]
271
 
                corresponding_budget_ids = self.search(cr, uid, cc_args, limit=1, order='version DESC')
272
 
                if corresponding_budget_ids:
273
 
                    budget_ids.append(corresponding_budget_ids)
274
 
            # Browse each budget line to update it
275
 
            for budget_line in budget.budget_line_ids:
276
 
                line_vals = {
277
 
                    'month1': 0.0,
278
 
                    'month2': 0.0,
279
 
                    'month3': 0.0,
280
 
                    'month4': 0.0,
281
 
                    'month5': 0.0,
282
 
                    'month6': 0.0,
283
 
                    'month7': 0.0,
284
 
                    'month8': 0.0,
285
 
                    'month9': 0.0,
286
 
                    'month10': 0.0,
287
 
                    'month11': 0.0,
288
 
                    'month12': 0.0
289
 
                }
290
 
                # search all linked budget lines
291
 
                args = [('budget_id', 'in', budget_ids), ('account_id', '=', budget_line.account_id.id), ('line_type', '=', budget_line.line_type)]
292
 
                if budget_line.destination_id:
293
 
                    args.append(('destination_id', '=', budget_line.destination_id.id))
294
 
                child_line_ids = line_obj.search(cr, uid, args, context=context)
295
 
                if child_line_ids:
296
 
                    cr.execute(sql, (tuple(child_line_ids),))
297
 
                    if cr.rowcount:
298
 
                        tmp_res = cr.fetchall()
299
 
                        res = tmp_res and tmp_res[0]
300
 
                        if res:
301
 
                            for x in xrange(1, 13, 1):
302
 
                                try:
303
 
                                    line_vals.update({'month'+str(x): res[x - 1]})
304
 
                                except IndexError:
305
 
                                    continue
306
 
                line_obj.write(cr, uid, [budget_line.id], line_vals)
307
 
        return True
308
 
 
309
 
    def update_parent_budgets(self, cr, uid, ids, context=None):
310
 
        """
311
 
        Search all parent budget and update them.
312
 
        """
313
 
        # Some checks
314
 
        if context is None:
315
 
            context = {}
316
 
        if isinstance(ids, (int, long)):
317
 
            ids = [ids]
318
 
        # We only need to update parent budgets.
319
 
        # So we search all parent cost center (but only them, so we don't care about cost center that are linked to given budgets)
320
 
        # Then we use these parent cost center to find budget to update (only budget lines)
321
 
        budgets = self.read(cr, uid, ids, ['cost_center_id'])
322
 
        cost_center_ids = [x.get('cost_center_id', False) and x.get('cost_center_id')[0] or 0 for x in budgets]
323
 
        cc_parent_ids = self.pool.get('account.analytic.account')._get_parent_of(cr, uid, cost_center_ids, context=context)
324
 
        parent_ids = [x for x in cc_parent_ids if x not in cost_center_ids]
325
 
        to_update = self.search(cr, uid, [('cost_center_id', 'in', parent_ids)])
326
 
        # Update budgets
327
 
        self.update(cr, uid, to_update, context=context)
328
 
        return True
329
 
 
330
 
    def button_display_type(self, cr, uid, ids, context=None, *args, **kwargs):
331
 
        """
332
 
        Just reset the budget view to give the context to the one2many_budget_lines object
333
 
        """
334
 
        if context is None:
335
 
            context = {}
336
 
        if isinstance(ids, (int, long)):
337
 
            ids = [ids]
338
 
        # do not erase the previous context!
339
 
        context.update({
340
 
            'active_id': ids[0],
341
 
            'active_ids': ids,
342
 
        })
343
 
        return {
344
 
            'name': _('Budgets'),
345
 
            'type': 'ir.actions.act_window',
346
 
            'res_model': 'msf.budget',
347
 
            'target': 'crush',
348
 
            'view_mode': 'form,tree',
349
 
            'view_type': 'form',
350
 
            'res_id': ids[0],
351
 
            'context': context,
352
 
        }
353
 
 
354
 
    def budget_summary_open_window(self, cr, uid, ids, context=None):
355
 
        budget_id = False
356
 
        if not ids:
357
 
            fiscalyear_id = self.pool.get('account.fiscalyear').find(cr, uid, datetime.date.today(), True, context=context)
358
 
            prop_instance = self.pool.get('res.users').browse(cr, uid, uid).company_id.instance_id
359
 
            if prop_instance.top_cost_center_id:
360
 
                cr.execute("SELECT id FROM msf_budget WHERE fiscalyear_id = %s \
361
 
                            AND cost_center_id = %s \
362
 
                            AND state != 'draft' \
363
 
                            ORDER BY decision_moment_order DESC, version DESC LIMIT 1",
364
 
                            (fiscalyear_id,
365
 
                             prop_instance.top_cost_center_id.id))
366
 
                if cr.rowcount:
367
 
                    # A budget was found
368
 
                    budget_id = cr.fetchall()[0][0]
369
 
        else:
370
 
            if isinstance(ids, (int, long)):
371
 
                ids = [ids]
372
 
            budget_id = ids[0]
373
 
 
374
 
        if budget_id:
375
 
            parent_line_id = self.pool.get('msf.budget.summary').create(cr,
376
 
                uid, {'budget_id': budget_id}, context=context)
377
 
            if parent_line_id:
378
 
                context.update({'display_fp': True})
379
 
                return {
380
 
                       'type': 'ir.actions.act_window',
381
 
                       'res_model': 'msf.budget.summary',
382
 
                       'view_type': 'tree',
383
 
                       'view_mode': 'tree',
384
 
                       'target': 'current',
385
 
                       'domain': [('id', '=', parent_line_id)],
386
 
                       'context': context
387
 
                }
388
 
        return {}
389
 
 
390
 
    def action_confirmed(self, cr, uid, ids, context=None):
391
 
        """
392
 
        At budget validation we should update all parent budgets.
393
 
        To do this, each parent need to take all its validated children budget at the last version.
394
 
        """
395
 
        # Some checks
396
 
        if context is None:
397
 
            context = {}
398
 
        if isinstance(ids, (int, long)):
399
 
            ids = [ids]
400
 
        # Only validate budget that are draft!
401
 
        to_validate = []
402
 
        for budget in self.read(cr, uid, ids, ['state']):
403
 
            if budget.get('state', '') and budget.get('state') == 'draft':
404
 
                to_validate.append(budget.get('id', 0))
405
 
        # Change budget statuses. Important in order to include given budgets in their parents!
406
 
        self.write(cr, uid, to_validate, {'state': 'valid'}, context=context)
407
 
        # Update parent budget
408
 
        self.update_parent_budgets(cr, uid, to_validate, context=context)
409
 
        return True
410
 
 
411
 
    def unlink(self, cr, uid, ids, context=None):
412
 
        '''
413
 
        UFTP-156: Make sure that the validated budget cannot be deleted
414
 
        '''
415
 
        for budget in self.browse(cr, uid, ids, context=context):
416
 
            if budget.state == 'valid':
417
 
                raise osv.except_osv(_('Error'), _('You cannot delete the validated budget!'))
418
 
 
419
 
        return super(msf_budget, self).unlink(cr, uid, budget.id, context=context)
420
 
 
421
 
msf_budget()
422
 
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: