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

« back to all changes in this revision

Viewing changes to msf_budget/msf_budget_line.py

  • Committer: chloups208
  • Date: 2012-11-21 11:15:15 UTC
  • mto: This revision was merged to the branch mainline in revision 1340.
  • Revision ID: chloups208@chloups208-laptop-20121121111515-myqv282h6xmgh053
utp-171 modification of fields po, po line, product, so, so line

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
##############################################################################
21
21
 
22
22
from osv import fields, osv
 
23
import datetime
 
24
from dateutil.relativedelta import relativedelta
23
25
 
24
 
# Overloading the one2many.get for budget lines to filter regarding context.
 
26
# Overloading the one2many.get for budget lines
 
27
# (used for filtering budget lines in the form view;
 
28
# dirty as f*ck, but hey, it works)
25
29
class one2many_budget_lines(fields.one2many):
26
 
 
 
30
    
27
31
    def get(self, cr, obj, ids, name, uid=None, offset=0, context=None, values=None):
28
 
        """
29
 
        Use 'granularity' value in context to filter budget lines.
30
 
        If granularity is 'view', then display only budget line that have line_type = view
31
 
        If 'expense', display budget lines that have line_type = view and normal
32
 
        If 'all' display budget lines that are view, normal and destination line_type
33
 
        Else, display view, normal and destination line_type ones.
34
 
 
35
 
        NB: This context also permit "Budget vs. Actual" report to work and display right lines regarding a given granularity.
36
 
        """
37
32
        if context is None:
38
33
            context = {}
39
34
        if values is None:
40
35
            values = {}
41
36
        res = {}
42
37
        display_type = {}
43
 
 
44
 
        domain = ['view', 'normal', 'destination']
45
 
        tuples = {
46
 
            'view': ['view'],
47
 
            'expense': ['view', 'normal'],
48
 
            'all': domain,
49
 
        }
50
 
        line_obj = obj.pool.get('msf.budget.line')
51
 
 
52
 
        if 'granularity' in context:
53
 
            display_type = context.get('granularity', False)
54
 
            if display_type and display_type in ['view', 'expense', 'all']:
55
 
                domain = tuples[display_type]
56
 
 
57
 
        for budget_id in ids:
58
 
            res[budget_id] = line_obj.search(cr, uid, [('budget_id', '=', budget_id), ('line_type', 'in', domain)])
59
 
 
 
38
        
 
39
        for budget in obj.read(cr, uid, ids, ['display_type']):
 
40
            res[budget['id']] = []
 
41
            display_type[budget['id']] = budget['display_type']
 
42
 
 
43
        budget_line_obj = obj.pool.get('msf.budget.line')
 
44
        budget_line_ids = budget_line_obj.search(cr, uid, [('budget_id', 'in', ids)])
 
45
        if budget_line_ids:
 
46
            for budget_line in  budget_line_obj.read(cr, uid, budget_line_ids, ['line_type', 'budget_id'], context=context):
 
47
                budget_id = budget_line['budget_id'][0]
 
48
                if display_type[budget_id] == 'all' \
 
49
                or (display_type[budget_id] == 'view' and budget_line['line_type'] == 'view') \
 
50
                or (display_type[budget_id] == 'expense' and budget_line['line_type'] != 'destination'):
 
51
                    res[budget_id].append(budget_line['id'])
60
52
        return res
61
53
 
62
54
class msf_budget_line(osv.osv):
63
55
    _name = "msf.budget.line"
64
 
    _order = "account_order, destination_id DESC, id"
 
56
    
 
57
    def _get_actual_amounts(self, cr, uid, ids, context=None):
 
58
        # Input: list of budget lines
 
59
        # Output: a dict of list {general_account_id: [jan_actual, feb_actual,...]}
 
60
        res = {}
 
61
        if context is None:
 
62
            context = {}
 
63
        # global values
 
64
        engagement_journal_ids = self.pool.get('account.analytic.journal').search(cr, uid, [('type', '=', 'engagement')], context=context)
 
65
        
 
66
        # we discard the ids, but retrieve the budget from it
 
67
        # Otherwise, view lines don't have values in "view lines only" display mode
 
68
        budget_line_ids = []
 
69
        if len(ids) > 0:
 
70
            budget = self.browse(cr, uid, ids[0], context=context).budget_id
 
71
            output_currency_id = budget.currency_id.id
 
72
                    
 
73
            cost_center_ids = self.pool.get('msf.budget.tools')._get_cost_center_ids(budget.cost_center_id)
 
74
                    
 
75
            # Create search domain (one search for all analytic lines)
 
76
            actual_domain = [('cost_center_id', 'in', cost_center_ids)]
 
77
            actual_domain.append(('date', '>=', budget.fiscalyear_id.date_start))
 
78
            actual_domain.append(('date', '<=', budget.fiscalyear_id.date_stop))
 
79
            # 3. commitments
 
80
            # if commitments are set to False in context, the engagement analytic journals are removed
 
81
            # from the domain
 
82
            if 'commitment' in context and not context['commitment'] and len(engagement_journal_ids) > 0:
 
83
                actual_domain.append(('journal_id', 'in', engagement_journal_ids))
 
84
            
 
85
            # Call budget_tools method
 
86
            res = self.pool.get('msf.budget.tools')._get_actual_amounts(cr, uid, output_currency_id, actual_domain, context=context)
 
87
        
 
88
        return res
 
89
        
 
90
    def _get_budget_amounts(self, cr, uid, ids, context=None):
 
91
        # Input: list of budget lines
 
92
        # Output: a dict of list {general_account_id: [jan_budget, feb_budget,...]}
 
93
        res = {}
 
94
        if context is None:
 
95
            context = {}
 
96
            
 
97
        if len(ids) > 0:
 
98
            budget = self.browse(cr, uid, ids[0], context=context).budget_id
 
99
            
 
100
            if budget.type == 'normal':
 
101
                # Budget values are stored in lines; just retrieve and add them
 
102
                for budget_line in self.browse(cr, uid, ids, context=context):
 
103
                    budget_line_destination_id = budget_line.destination_id and budget_line.destination_id.id or False
 
104
                    if budget_line.budget_values:
 
105
                        res[budget_line.account_id.id, budget_line_destination_id] = eval(budget_line.budget_values)
 
106
                    else:
 
107
                        res[budget_line.account_id.id, budget_line_destination_id] = [0] * 12
 
108
            else:
 
109
                # fill with 0s
 
110
                for budget_line in self.browse(cr, uid, ids, context=context):
 
111
                    budget_line_destination_id = budget_line.destination_id and budget_line.destination_id.id or False
 
112
                    res[budget_line.account_id.id, budget_line_destination_id] = [0] * 12
 
113
                # Not stored in lines; retrieve child budgets, get their budget values and add
 
114
                cost_center_list = self.pool.get('msf.budget.tools')._get_cost_center_ids(budget.cost_center_id)
 
115
                # For each cost center, get the latest non-draft budget
 
116
                for cost_center_id in cost_center_list:
 
117
                    cr.execute("SELECT id FROM msf_budget WHERE fiscalyear_id = %s \
 
118
                                                            AND cost_center_id = %s \
 
119
                                                            AND decision_moment_id = %s \
 
120
                                                            AND state != 'draft' \
 
121
                                                            AND type = 'normal' \
 
122
                                                            ORDER BY version DESC LIMIT 1",
 
123
                                                           (budget.fiscalyear_id.id,
 
124
                                                            cost_center_id,
 
125
                                                            budget.decision_moment_id.id))
 
126
                    if cr.rowcount:
 
127
                        # A budget was found; get its lines and their amounts
 
128
                        child_budget_id = cr.fetchall()[0][0]
 
129
                        child_line_ids = self.search(cr,
 
130
                                                     uid,
 
131
                                                     [('budget_id', '=', child_budget_id)],
 
132
                                                     context=context)
 
133
                        child_budget_amounts = self._get_budget_amounts(cr, uid, child_line_ids, context=context)
 
134
                        for child_line in self.browse(cr, uid, child_line_ids, context=context):
 
135
                            child_line_destination_id = child_line.destination_id and child_line.destination_id.id or False
 
136
                            if (child_line.account_id.id, child_line_destination_id) not in res:
 
137
                                res[child_line.account_id.id, child_line_destination_id] = child_budget_amounts[child_line.account_id.id, child_line_destination_id]
 
138
                            else:
 
139
                                res[child_line.account_id.id, child_line_destination_id] = [sum(pair) for pair in 
 
140
                                                                                                             zip(child_budget_amounts[child_line.account_id.id, child_line_destination_id],
 
141
                                                                                                             res[child_line.account_id.id, child_line_destination_id])]
 
142
 
 
143
        return res
 
144
    
 
145
    def _compute_total_amounts(self, cr, uid, budget_amount_list, actual_amount_list, context=None):
 
146
        # period_id
 
147
        if context is None:
 
148
            context = {}
 
149
        budget_amount = 0
 
150
        actual_amount = 0
 
151
        month_stop = 0
 
152
        if 'period_id' in context:
 
153
            period = self.pool.get('account.period').browse(cr, uid, context['period_id'], context=context)
 
154
            month_stop = datetime.datetime.strptime(period.date_stop, '%Y-%m-%d').month
 
155
        else:
 
156
            month_stop = 12
 
157
        # actual amount
 
158
        if actual_amount_list:
 
159
            for i in range(month_stop):
 
160
                actual_amount += actual_amount_list[i]
 
161
        # budget amount
 
162
        if budget_amount_list:
 
163
            for i in range(month_stop):
 
164
                budget_amount += budget_amount_list[i]
 
165
                
 
166
        return {'actual_amount': actual_amount,
 
167
                'budget_amount': budget_amount}
 
168
    
 
169
    def _get_total_amounts(self, cr, uid, ids, field_names=None, arg=None, context=None):
 
170
        res = {}
 
171
        if context is None:
 
172
            context = {}
 
173
        
 
174
        actual_amounts = self._get_actual_amounts(cr, uid, ids, context)
 
175
        budget_amounts = self._get_budget_amounts(cr, uid, ids, context)
 
176
        
 
177
        # Browse each line
 
178
        for budget_line in self.browse(cr, uid, ids, context=context):
 
179
            budget_line_destination_id = budget_line.destination_id and budget_line.destination_id.id or False
 
180
            line_amounts = self._compute_total_amounts(cr,
 
181
                                                       uid,
 
182
                                                       budget_amounts[budget_line.account_id.id, budget_line_destination_id],
 
183
                                                       actual_amounts[budget_line.account_id.id, budget_line_destination_id],
 
184
                                                       context=context)
 
185
            actual_amount = line_amounts['actual_amount']
 
186
            budget_amount = line_amounts['budget_amount']
 
187
                    
 
188
            # We have budget amount and actual amount, compute the remaining ones
 
189
            percentage = 0.0
 
190
            if budget_amount != 0.0:
 
191
                percentage = round((actual_amount / budget_amount) * 100.0)
 
192
            res[budget_line.id] = {'budget_amount': budget_amount,
 
193
                                   'actual_amount': actual_amount,
 
194
                                   'balance': budget_amount - actual_amount,
 
195
                                   'percentage': percentage}
 
196
        
 
197
        return res
 
198
    
 
199
    def _get_monthly_amounts(self, cr, uid, ids, context=None):
 
200
        res = []
 
201
        if context is None:
 
202
            context = {}
 
203
            
 
204
        actual_amounts = self._get_actual_amounts(cr, uid, ids, context)
 
205
        budget_amounts = self._get_budget_amounts(cr, uid, ids, context)
 
206
        
 
207
        # if period id, only retrieve a subset
 
208
        month_stop = 0
 
209
        if 'period_id' in context:
 
210
            period = self.pool.get('account.period').browse(cr, uid, context['period_id'], context=context)
 
211
            month_stop = datetime.datetime.strptime(period.date_stop, '%Y-%m-%d').month
 
212
        else:
 
213
            month_stop = 12
 
214
                
 
215
        # Browse each line
 
216
        for budget_line in self.browse(cr, uid, ids, context=context):
 
217
            budget_line_destination_id = budget_line.destination_id and budget_line.destination_id.id or False
 
218
            if budget_line.line_type == 'view' \
 
219
                or ('granularity' in context and context['granularity'] == 'all') \
 
220
                or ('granularity' in context and context['granularity'] == 'expense' and budget_line.line_type != 'destination'):
 
221
                line_actual_amounts = actual_amounts[budget_line.account_id.id, budget_line_destination_id]
 
222
                line_budget_amounts = budget_amounts[budget_line.account_id.id, budget_line_destination_id]
 
223
                
 
224
                line_name = budget_line.account_id.code
 
225
                if budget_line.destination_id:
 
226
                    line_name += " " + budget_line.destination_id.code
 
227
                line_name += " " + budget_line.account_id.name
 
228
                line_values = [line_name]
 
229
                if 'breakdown' in context and context['breakdown'] == 'month':
 
230
                    # Need to add breakdown values
 
231
                    for i in range(month_stop):
 
232
                        
 
233
                        line_values.append(line_budget_amounts[i])
 
234
                        line_values.append(line_actual_amounts[i])
 
235
                
 
236
                total_amounts = self._compute_total_amounts(cr,
 
237
                                                           uid,
 
238
                                                           line_budget_amounts,
 
239
                                                           line_actual_amounts,
 
240
                                                           context=context)
 
241
                line_values.append(total_amounts['budget_amount'])
 
242
                line_values.append(total_amounts['actual_amount'])
 
243
                # add to result
 
244
                res.append(line_values)
 
245
            
 
246
        return res
 
247
    
65
248
    def _get_name(self, cr, uid, ids, field_names=None, arg=None, context=None):
66
249
        result = self.browse(cr, uid, ids, context=context)
67
250
        res = {}
68
251
        for rs in result:
69
252
            account = rs.account_id
70
 
            name = account.code
 
253
            name = account.code 
71
254
            if rs.destination_id:
72
255
                name += " "
73
256
                name += rs.destination_id.code
75
258
            name += account.name
76
259
            res[rs.id] = name
77
260
        return res
78
 
 
79
 
    def _get_month_names(self, number=12):
80
 
        """
81
 
        Return a list of all month field to be used from the first one to the given number (included).
82
 
        """
83
 
        res = []
84
 
        # Do not permit to give a number superior to 12!
85
 
        if number > 12:
86
 
            number = 12
87
 
        for x in xrange(1, number+1, 1):
88
 
            res.append('month' + str(x))
89
 
        return res
90
 
 
91
 
    def _get_domain(self, line_type, account_id, cost_center_ids, destination_id, date_start, date_stop):
92
 
        """
93
 
        Create a domain regarding budget line elements (to be used in a search()).
94
 
        Return a list.
95
 
        """
96
 
        if isinstance(cost_center_ids, (int, long)):
97
 
            cost_center_ids = [cost_center_ids]
98
 
        res = [
99
 
            ('cost_center_id', 'in', cost_center_ids),
100
 
            ('date', '>=', date_start),
101
 
            ('date', '<=', date_stop),
102
 
        ]
103
 
        if line_type == 'destination':
104
 
            res.append(('destination_id', '=', destination_id))
105
 
        if line_type in ['destination', 'normal']:
106
 
            res.append(('general_account_id', '=', account_id)),
107
 
        else:
108
 
            res.append(('general_account_id', 'child_of', account_id))
109
 
        return res
110
 
 
111
 
    def _get_sql_domain(self, cr, uid, request, params, line_type, account_id, destination_id):
112
 
        """
113
 
        Create a SQL domain regarding budget line elements (to be used in a SQL request).
114
 
        Return a 2 params:
115
 
          - SQL request
116
 
          - SQL params (list)
117
 
        """
118
 
        if not request:
119
 
            request = ""
120
 
        if not params:
121
 
            params = []
122
 
        if line_type == 'destination':
123
 
            request += """ AND destination_id = %s """
124
 
            params.append(destination_id)
125
 
        if line_type in ['destination', 'normal']:
126
 
            request += """ AND general_account_id = %s """
127
 
            params.append(account_id)
128
 
        else:
129
 
            request += """ AND general_account_id IN %s """
130
 
            account_ids = self.pool.get('account.account').search(cr, uid, [('parent_id', 'child_of', account_id)])
131
 
            params.append(tuple(account_ids))
132
 
        return request, params
133
 
 
134
 
    def _get_account_order(self, cr, uid, ids, field_names=None, arg=None, context=None):
135
 
        ret = {}
136
 
        account_obj = self.pool.get('account.account')
137
 
        if isinstance(ids, (int, long)):
138
 
            ids = [ids]
139
 
        seen = {}
140
 
        for line in self.read(cr, uid, ids, ['account_id'], context=context):
141
 
            account_id = line['account_id'] and line['account_id'][0]
142
 
            if account_id:
143
 
                if account_id not in seen:
144
 
                    acc = account_obj.read(cr, uid, account_id, ['parent_left'])
145
 
                    seen[account_id] = acc['parent_left']
146
 
                ret[line['id']] = seen[account_id]
147
 
            else:
148
 
                ret[line['id']] = 0
149
 
        return ret
150
 
 
151
 
    def _get_amounts(self, cr, uid, ids, field_names=None, arg=None, context=None):
152
 
        """
153
 
        Those field can be asked for:
154
 
          - actual_amount
155
 
          - comm_amount
156
 
          - balance
157
 
          - percentage
158
 
        With some depends:
159
 
          - percentage needs actual_amount, comm_amount, balance and budget_amount
160
 
          - balance needs actual_amount, comm_amount and budget_amount
161
 
 
162
 
        NB:
163
 
          - if 'period_id' in context, we change date_stop for SQL request to the date_stop of the given period to reduce computation
164
 
          - if 'currency_table_id' in context, we compute actual amounts (and commitment ones) currency by currency
165
 
        """
166
 
        # Some checks
167
 
        if context is None:
168
 
            context = {}
169
 
        if isinstance(ids, (int, long)):
170
 
            ids = [ids]
171
 
        # Prepare some values
172
 
        res = {}
173
 
        budget_ok = False
174
 
        actual_ok = False
175
 
        commitment_ok = False
176
 
        percentage_ok = False
177
 
        balance_ok = False
178
 
        budget_amounts = {}
179
 
        actual_amounts = {}
180
 
        comm_amounts = {}
181
 
        cur_obj = self.pool.get('res.currency')
182
 
        # If period_id in context, use another date_stop element.
183
 
        date_period_stop = False
184
 
        month_number = 12
185
 
        if 'period_id' in context:
186
 
            period = self.pool.get('account.period').read(cr, uid, context.get('period_id', False), ['date_stop', 'number'], context=context)
187
 
            if period and period.get('date_stop', False):
188
 
                date_period_stop = period.get('date_stop')
189
 
            if period and period.get('number', False):
190
 
                month_number = period.get('number')
191
 
        # Check if we need to use another currency_table_id
192
 
        other_currencies = False
193
 
        date_context = {}
194
 
        company_currency_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
195
 
        if context.get('currency_table_id', False):
196
 
            other_currencies = True
197
 
            date_context.update({'currency_table_id': context.get('currency_table_id')})
198
 
        # Check in which case we are regarding field names. Compute actual and commitment when we need balance and/or percentage.
199
 
        if 'budget_amount' in field_names:
200
 
            budget_ok = True
201
 
        if 'actual_amount' in field_names:
202
 
            actual_ok = True
203
 
        if 'comm_amount' in field_names:
204
 
            actual_ok = True
205
 
            commitment_ok = True
206
 
        if 'percentage' in field_names:
207
 
            budget_ok = True
208
 
            actual_ok = True
209
 
            percentage_ok = True
210
 
        if 'balance' in field_names:
211
 
            budget_ok = True
212
 
            actual_ok = True
213
 
            balance_ok = True
214
 
        # In some cases (reports) we don't want to display commitment values. But we have to include them into "balance" and percentage computation.
215
 
        if 'commitment' in context:
216
 
            commitment_ok = context.get('commitment', False)
217
 
        # Compute actual and/or commitments
218
 
        if actual_ok or commitment_ok or percentage_ok or balance_ok:
219
 
            # COMPUTE ACTUAL/COMMITMENT
220
 
            ana_obj = self.pool.get('account.analytic.line')
221
 
            ana_account_obj = self.pool.get('account.analytic.account')
222
 
            cur_obj = self.pool.get('res.currency')
223
 
            # Create default values
224
 
            for index in ids:
225
 
                if actual_ok:
226
 
                    actual_amounts.setdefault(index, 0.0)
227
 
                if commitment_ok:
228
 
                    comm_amounts.setdefault(index, 0.0)
229
 
            # Now, only use 'destination' line to do process and complete parent one at the same time
230
 
            sql = """
231
 
                SELECT l.id, l.line_type, l.account_id, l.destination_id, b.cost_center_id, f.date_start, f.date_stop
232
 
                FROM msf_budget_line AS l, msf_budget AS b, account_fiscalyear AS f
233
 
                WHERE l.budget_id = b.id
234
 
                AND b.fiscalyear_id = f.id
235
 
                AND l.id IN %s
236
 
                ORDER BY l.line_type, l.id"""
237
 
            cr.execute(sql, (tuple(ids),))
238
 
            # Prepare SQL2 request that contains sum of amount of given analytic lines (in functional currency)
239
 
            sql2 = """
240
 
                SELECT SUM(amount)
241
 
                FROM account_analytic_line
242
 
                WHERE id in %s"""
243
 
            # Prepare SQL3 request in case we have other currencies to compute
244
 
            sql3 = """
245
 
                SELECT l.currency_id, SUM(l.amount_currency)
246
 
                FROM account_analytic_line AS l, account_analytic_journal AS j
247
 
                WHERE l.journal_id = j.id
248
 
                AND l.cost_center_id IN %s
249
 
                AND l.date >= %s
250
 
                AND l.date <= %s"""
251
 
            sql3_end = """ GROUP BY l.currency_id"""
252
 
            # Process destination lines
253
 
            for line in cr.fetchall():
254
 
                # fetch some values
255
 
                line_id, line_type, account_id, destination_id, cost_center_id, date_start, date_stop = line
256
 
                cost_center_ids = ana_account_obj.search(cr, uid, [('parent_id', 'child_of', cost_center_id)])
257
 
                if date_period_stop:
258
 
                    date_stop = date_period_stop
259
 
                criteria = self._get_domain(line_type, account_id, cost_center_ids, destination_id, date_start, date_stop)
260
 
                # TWO METHODS to display actual/commitments
261
 
                # (1) Either we use functional amounts (no currency_table)
262
 
                # (2) Or we use a currency table to change amounts to functional amounts at fiscalyear date_stop
263
 
                if not other_currencies:
264
 
                    # (1) Use functional amounts: NO conversion
265
 
                    # fill in ACTUAL AMOUNTS
266
 
                    if actual_ok:
267
 
                        actual_criteria = list(criteria) + [('journal_id.type', '!=', 'engagement')]
268
 
                        ana_ids = ana_obj.search(cr, uid, actual_criteria)
269
 
                        if ana_ids:
270
 
                            cr.execute(sql2, (tuple(ana_ids),))
271
 
                            mnt_result = cr.fetchall()
272
 
                            if mnt_result:
273
 
                                    actual_amounts[line_id] += mnt_result[0][0] * -1
274
 
                    # fill in COMMITMENT AMOUNTS
275
 
                    if commitment_ok:
276
 
                        commitment_criteria = list(criteria) + [('journal_id.type', '=', 'engagement')]
277
 
                        ana_ids = ana_obj.search(cr, uid, commitment_criteria)
278
 
                        if ana_ids:
279
 
                            cr.execute(sql2, (tuple(ana_ids),))
280
 
                            mnt_result = cr.fetchall()
281
 
                            if mnt_result:
282
 
                                comm_amounts[line_id] += mnt_result[0][0] * -1
283
 
                else:
284
 
                    # (2) OTHER CURRENCIES to compute
285
 
                    # Note that to not compute each analytic lines we use the sum of each currency and convert it to the functional currency using the given currency_table_id in the context
286
 
                    tmp_sql_params = [tuple(cost_center_ids), date_start, date_stop]
287
 
                    tmp_sql, sql_params = self._get_sql_domain(cr, uid, sql3, tmp_sql_params, line_type, account_id, destination_id)
288
 
                    # Use fiscalyear end date as date on which we do conversion
289
 
                    date_context.update({'date': date_stop})
290
 
 
291
 
                    def get_amounts_and_compute_total(local_request, local_params, local_end_request):
292
 
                        """
293
 
                        Use request.
294
 
                        Finish it with local_end_request.
295
 
                        Execute it.
296
 
                        Fetch amounts.
297
 
                        Compute them by currency.
298
 
                        Return total result
299
 
                        """
300
 
                        total = 0.0
301
 
                        if local_end_request:
302
 
                            local_request += local_end_request
303
 
                        cr.execute(local_request, tuple(local_params))
304
 
                        if cr.rowcount:
305
 
                            analytic_amounts = cr.fetchall()
306
 
                            # Browse each currency amount and convert it to the functional currency (company one)
307
 
                            for currency_id, amount in analytic_amounts:
308
 
                                tmp_amount = cur_obj.compute(cr, uid, currency_id, company_currency_id, amount, round=False, context=date_context)
309
 
                                total += (tmp_amount * -1) # As analytic amounts are negative, we should use the opposite to make budget with positive values
310
 
                        return total
311
 
 
312
 
                    if actual_ok:
313
 
                        actual_sql = tmp_sql + """ AND j.type != 'engagement' """
314
 
                        actual_amounts[line_id] += get_amounts_and_compute_total(actual_sql, sql_params, sql3_end)
315
 
                    if commitment_ok:
316
 
                        commitment_sql = tmp_sql + """ AND j.type = 'engagement' """
317
 
                        comm_amounts[line_id] += get_amounts_and_compute_total(commitment_sql, sql_params, sql3_end)
318
 
        # Budget line amounts
319
 
        if budget_ok:
320
 
            month_names = self._get_month_names(month_number)
321
 
            sql = """
322
 
            SELECT id, COALESCE(""" + '+'.join(month_names) + """, 0.0)
323
 
            FROM msf_budget_line
324
 
            WHERE id IN %s;
325
 
            """
326
 
            cr.execute(sql, (tuple(ids),))
327
 
            tmp_res = cr.fetchall()
328
 
            if tmp_res:
329
 
                budget_amounts = dict(tmp_res)
330
 
        # Prepare result
331
 
        for line_id in ids:
332
 
            actual_amount = line_id in actual_amounts and actual_amounts[line_id] or 0.0
333
 
            comm_amount = line_id in comm_amounts and comm_amounts[line_id] or 0.0
334
 
            res[line_id] = {'actual_amount': actual_amount, 'comm_amount': comm_amount, 'balance': 0.0, 'percentage': 0.0, 'budget_amount': 0.0,}
335
 
            if budget_ok:
336
 
                budget_amount = line_id in budget_amounts and budget_amounts[line_id] or 0.0
337
 
                res[line_id].update({'budget_amount': budget_amount,})
338
 
            if balance_ok:
339
 
                balance = budget_amount - actual_amount
340
 
                if commitment_ok:
341
 
                    balance -= comm_amount
342
 
                res[line_id].update({'balance': balance,})
343
 
            if percentage_ok:
344
 
                if budget_amount != 0.0:
345
 
                    base = actual_amount
346
 
                    if commitment_ok:
347
 
                        base += comm_amount
348
 
                    percentage = round(base / budget_amount * 100.0)
349
 
                    res[line_id].update({'percentage': percentage,})
350
 
        return res
351
 
 
352
 
    def _get_total(self, cr, uid, ids, field_names=None, arg=None, context=None):
353
 
        """
354
 
        Give the sum of all month for the given budget lines.
355
 
        If period_id in context, just display months from the first one to the given period month (included)
356
 
        """
357
 
        # Some checks
358
 
        if isinstance(ids,(int, long)):
359
 
            ids = [ids]
360
 
        month_number = 12
361
 
        if 'period_id' in context:
362
 
            period = self.pool.get('account.period').read(cr, uid, context.get('period_id', False), ['number'])
363
 
            if period and period.get('number', False):
364
 
                month_number = period.get('number')
365
 
        month_names = self._get_month_names(month_number)
366
 
        # Prepare some values
367
 
        res = {}
368
 
        sql = """
369
 
            SELECT id, COALESCE(""" + '+'.join(month_names) + """, 0.0)
370
 
            FROM msf_budget_line
371
 
            WHERE id IN %s"""
372
 
        cr.execute(sql, (tuple(ids),))
373
 
        tmp_res = cr.fetchall()
374
 
        if tmp_res:
375
 
            res = dict(tmp_res)
376
 
        return res
377
 
 
 
261
    
378
262
    _columns = {
379
263
        'budget_id': fields.many2one('msf.budget', 'Budget', ondelete='cascade'),
380
264
        'account_id': fields.many2one('account.account', 'Account', required=True, domain=[('type', '!=', 'view')]),
381
265
        'destination_id': fields.many2one('account.analytic.account', 'Destination', domain=[('category', '=', 'DEST')]),
382
 
        'name': fields.function(_get_name, method=True, store=False, string="Name", type="char", readonly="True", size=512),
383
 
        'month1': fields.float("Month 01"),
384
 
        'month2': fields.float("Month 02"),
385
 
        'month3': fields.float("Month 03"),
386
 
        'month4': fields.float("Month 04"),
387
 
        'month5': fields.float("Month 05"),
388
 
        'month6': fields.float("Month 06"),
389
 
        'month7': fields.float("Month 07"),
390
 
        'month8': fields.float("Month 08"),
391
 
        'month9': fields.float("Month 09"),
392
 
        'month10': fields.float("Month 10"),
393
 
        'month11': fields.float("Month 11"),
394
 
        'month12': fields.float("Month 12"),
395
 
        'total': fields.function(_get_total, method=True, store=False, string="Total", type="float", readonly=True, help="Get all month total amount"),
396
 
        'budget_amount': fields.function(_get_amounts, method=True, store=False, string="Budget amount", type="float", readonly=True, multi="budget_amounts"),
397
 
        'actual_amount': fields.function(_get_amounts, method=True, store=False, string="Actual amount", type="float", readonly=True, multi="budget_amounts"),
398
 
        'comm_amount': fields.function(_get_amounts, method=True, store=False, string="Commitments amount", type="float", readonly=True, multi="budget_amounts"),
399
 
        'balance': fields.function(_get_amounts, method=True, store=False, string="Balance", type="float", readonly=True, multi="budget_amounts"),
400
 
        'percentage': fields.function(_get_amounts, method=True, store=False, string="Percentage", type="float", readonly=True, multi="budget_amounts"),
 
266
        'name': fields.function(_get_name, method=True, store=False, string="Name", type="char", readonly="True"),
 
267
        'budget_values': fields.char('Budget Values (list of float to evaluate)', size=256),
 
268
        'budget_amount': fields.function(_get_total_amounts, method=True, store=False, string="Budget amount", type="float", readonly="True", multi="all"),
 
269
        'actual_amount': fields.function(_get_total_amounts, method=True, store=False, string="Actual amount", type="float", readonly="True", multi="all"),
 
270
        'balance': fields.function(_get_total_amounts, method=True, store=False, string="Balance", type="float", readonly="True", multi="all"),
 
271
        'percentage': fields.function(_get_total_amounts, method=True, store=False, string="Percentage", type="float", readonly="True", multi="all"),
401
272
        'parent_id': fields.many2one('msf.budget.line', 'Parent Line'),
402
273
        'child_ids': fields.one2many('msf.budget.line', 'parent_id', 'Child Lines'),
403
274
        'line_type': fields.selection([('view','View'),
404
275
                                       ('normal','Normal'),
405
276
                                       ('destination', 'Destination')], 'Line type', required=True),
406
 
        'account_code': fields.related('account_id', 'code', type='char', string='Account code', size=64, store=True),
407
 
        'account_order': fields.function(_get_account_order, type='integer', string='order', method=True, store=True),
408
277
    }
409
 
 
410
 
 
 
278
    
411
279
    _defaults = {
412
 
        'line_type': lambda *a: 'normal',
413
 
        'month1': lambda *a: 0.0,
414
 
        'month2': lambda *a: 0.0,
415
 
        'month3': lambda *a: 0.0,
416
 
        'month4': lambda *a: 0.0,
417
 
        'month5': lambda *a: 0.0,
418
 
        'month6': lambda *a: 0.0,
419
 
        'month7': lambda *a: 0.0,
420
 
        'month8': lambda *a: 0.0,
421
 
        'month9': lambda *a: 0.0,
422
 
        'month10': lambda *a: 0.0,
423
 
        'month11': lambda *a: 0.0,
424
 
        'month12': lambda *a: 0.0,
 
280
        'line_type': 'normal',
425
281
    }
426
 
 
 
282
    
 
283
    def get_parent_line(self, cr, uid, vals, context=None):
 
284
        # Method to check if the used account has a parent,
 
285
        # and retrieve or create the corresponding parent line.
 
286
        # It also adds budget values to parent lines
 
287
        parent_account_id = False
 
288
        parent_line_ids = []
 
289
        if 'account_id' in vals and 'budget_id' in vals:
 
290
            if 'destination_id' in vals:
 
291
                # Special case: the line has a destination, so the parent is a line
 
292
                # with the same account and no destination
 
293
                parent_account_id = vals['account_id']
 
294
                parent_line_ids = self.search(cr, uid, [('account_id', '=', vals['account_id']),
 
295
                                                        ('budget_id', '=', vals['budget_id']),
 
296
                                                        ('line_type', '=', 'normal')], context=context)
 
297
            else:
 
298
                # search for budget line
 
299
                account = self.pool.get('account.account').browse(cr, uid, vals['account_id'], context=context)
 
300
                chart_of_account_ids = self.pool.get('account.account').search(cr, uid, [('code', '=', 'MSF')], context=context)
 
301
                if account.parent_id and account.parent_id.id in chart_of_account_ids:
 
302
                    # no need to create the parent
 
303
                    return
 
304
                else:
 
305
                    parent_account_id = account.parent_id.id
 
306
                    parent_line_ids = self.search(cr, uid, [('account_id', '=', parent_account_id),
 
307
                                                            ('budget_id', '=', vals['budget_id'])], context=context)
 
308
            if len(parent_line_ids) > 0:
 
309
                # Parent line exists
 
310
                if 'budget_values' in vals:
 
311
                    # we add the budget values to the parent one
 
312
                    parent_line = self.browse(cr, uid, parent_line_ids[0], context=context)
 
313
                    parent_budget_values = [sum(pair) for pair in zip(eval(parent_line.budget_values),
 
314
                                                                      eval(vals['budget_values']))]
 
315
                    # write parent
 
316
                    super(msf_budget_line, self).write(cr,
 
317
                                                       uid,
 
318
                                                       parent_line_ids,
 
319
                                                       {'budget_values': str(parent_budget_values)},
 
320
                                                       context=context)
 
321
                    # use method on parent with original budget values
 
322
                    self.get_parent_line(cr,
 
323
                                         uid,
 
324
                                         {'account_id': parent_line.account_id.id,
 
325
                                          'budget_id': parent_line.budget_id.id,
 
326
                                          'budget_values': vals['budget_values']},
 
327
                                         context=context)
 
328
                # add parent id to vals
 
329
                vals.update({'parent_id': parent_line_ids[0]})
 
330
            else:
 
331
                # Create parent line and add it to vals, except if it's the main parent
 
332
                parent_vals = {'budget_id': vals['budget_id'],
 
333
                               'account_id': parent_account_id}
 
334
                if 'line_type' in vals and vals['line_type'] == 'destination':
 
335
                    parent_vals['line_type'] = 'normal'
 
336
                else:
 
337
                    parent_vals['line_type'] = 'view'
 
338
                # default parent budget values: the one from the (currently) only child
 
339
                if 'budget_values' in vals:
 
340
                    parent_vals.update({'budget_values': vals['budget_values']})
 
341
                parent_budget_line_id = self.create(cr, uid, parent_vals, context=context)
 
342
                vals.update({'parent_id': parent_budget_line_id})
 
343
        return
 
344
            
 
345
    
 
346
    def create(self, cr, uid, vals, context=None):
 
347
        self.get_parent_line(cr, uid, vals, context=context)
 
348
        return super(msf_budget_line, self).create(cr, uid, vals, context=context)
 
349
    
 
350
    def write(self, cr, uid, ids, vals, context=None):
 
351
        self.get_parent_line(cr, uid, vals, context=context)
 
352
        return super(msf_budget_line, self).write(cr, uid, ids, vals, context=context)
 
353
    
427
354
msf_budget_line()
428
355
 
429
356
class msf_budget(osv.osv):
430
357
    _name = "msf.budget"
431
358
    _inherit = "msf.budget"
432
 
 
 
359
    
433
360
    _columns = {
434
361
        'budget_line_ids': one2many_budget_lines('msf.budget.line', 'budget_id', 'Budget Lines'),
435
362
    }
436
 
 
 
363
    
437
364
msf_budget()
438
365
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: