~unifield-team/unifield-wm/us-826

850.3.1 by Matthieu Dietrich
UF-1087: [IMP] factorize actual calculation for local expense report
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
1994.2.80 by Olivier DOSSMANN
REF-36 [IMP] Clean code for msf_budget module with Eclispe
22
from osv import osv
850.3.1 by Matthieu Dietrich
UF-1087: [IMP] factorize actual calculation for local expense report
23
import datetime
24
25
class msf_budget_tools(osv.osv):
26
    _name = "msf.budget.tools"
1994.2.74 by Olivier DOSSMANN
REF-32 [IMP] Budget import - change method to read lines and import them in the system
27
28
    def get_expense_accounts(self, cr, uid, context=None):
29
        """
30
        Get all "is_analytic_addicted" accounts except if context notify to only use expense ones.
31
        By using this method you also retrieve ALL parents EXCEPT the first one: MSF account.
32
        """
1874.4.14 by Olivier DOSSMANN
UTP-944 [IMP] Adapt budget reports - Local expenses
33
        # Checks
34
        if context is None:
35
            context = {}
36
        # Prepare some values
850.3.1 by Matthieu Dietrich
UF-1087: [IMP] factorize actual calculation for local expense report
37
        res = []
38
        account_obj = self.pool.get('account.account')
39
        # get the last parent
1994.2.74 by Olivier DOSSMANN
REF-32 [IMP] Budget import - change method to read lines and import them in the system
40
        top_ids = account_obj.search(cr, uid, [('code', '=', 'MSF')], context=context)
1874.4.14 by Olivier DOSSMANN
UTP-944 [IMP] Adapt budget reports - Local expenses
41
        # get normal analytic-a-holic accounts. UTP-944: only expenses ones if "only_expenses" in context. Do not include Extra-accounting accounts and incomes one.
42
        domain = [('type', '!=', 'view'), ('user_type_report_type', '!=', 'none')]
43
        if context.get('only_expenses', False) and context.get('only_expenses') is True:
44
            domain += [('user_type_code', '=', 'expense'), ('user_type_report_type', '=', 'expense')]
45
        else:
46
            domain += [('is_analytic_addicted', '=', True)]
1994.2.74 by Olivier DOSSMANN
REF-32 [IMP] Budget import - change method to read lines and import them in the system
47
        account_ids = account_obj.search(cr, uid, domain, context=context)
48
        if account_ids:
49
            parent_ids = account_obj._get_parent_of(cr, uid, account_ids, context=context)
50
            if parent_ids:
51
                res = [x for x in parent_ids if x not in top_ids]
52
        return res
53
54
    def get_budget_line_template(self, cr, uid, context=None):
55
        """
56
        Create a template that contains all budget line main values for a new budget.
57
        """
58
        # Some checks
59
        if context is None:
60
            context = {}
61
        # Prepare some values
62
        res = []
63
        # Search all income/expense accounts (except if context contains "only_expenses" set to True)
64
        account_ids = self.get_expense_accounts(cr, uid, context=context)
65
        if not account_ids:
66
            return []
67
        # We use a SQL request to keep an order of accounts regarding their parents so that parents could be created before their childs.
68
        sql = """
69
            SELECT id, CASE WHEN type != 'view' THEN 'normal' ELSE 'view' END AS account_type, parent_id
70
            FROM account_account
71
            WHERE id IN %s
72
            ORDER BY parent_id ASC"""
73
        cr.execute(sql, (tuple(account_ids),))
74
        if not cr.rowcount:
75
            raise osv.except_osv(_('Error'), _('Unable to find needed info.'))
76
        tmp_res = cr.fetchall()
77
        # We take destination ids for a given account (and we make a dictionnary to be quickly used)
78
        accounts = self.pool.get('account.account').read(cr, uid, account_ids, ['destination_ids'], context=context)
79
        destinations = {}
80
        for account in accounts:
81
            destinations[account.get('id')] = account.get('destination_ids')
82
        # We then create the final result with all needed elements
83
        for line_id, line_type, parent_id in tmp_res:
84
            line_vals = {
85
                'id': line_id,
86
                'type': line_type,
87
                'parent_id': parent_id,
88
                'destination_ids': []
89
            }
90
            if line_id in destinations:
91
                line_vals.update({'destination_ids': destinations[line_id]})
92
            res.append(line_vals)
93
        return res
94
95
    def create_budget_lines(self, cr, uid, budget_id, sequence=False, context=None):
96
        """
97
        Create budget lines for a given budget.
98
        If no budget: do nothing.
99
        If no sequence: only create budget lines without any specific amounts.
100
101
        Creation synthesis:
102
        1/ get the initial template
103
        2/ for each line create its budget line
104
        3/a) if sequence: fetch budget values and fill in destination lines, normal lines and parents
105
        3/b) if NO sequence, just create destination lines, normal lines and parents.
106
        """
107
        # Some checks
108
        if context is None:
109
            context = {}
110
        if not budget_id:
111
            return False
112
        # Prepare some values
113
        a_obj = self.pool.get('account.account')
114
        chart_of_account_ids = a_obj.search(cr, uid, [('code', '=', 'MSF')], context=context)
115
        budget_line_obj = self.pool.get('msf.budget.line')
116
        imported_obj = self.pool.get('imported.msf.budget.line')
117
        sql = """
118
            SELECT SUM(COALESCE(month1, 0.0)), SUM(COALESCE(month2, 0.0)), SUM(COALESCE(month3, 0.0)), SUM(COALESCE(month4, 0.0)), SUM(COALESCE(month5, 0.0)), SUM(COALESCE(month6, 0.0)), SUM(COALESCE(month7, 0.0)), SUM(COALESCE(month8, 0.0)), SUM(COALESCE(month9, 0.0)), SUM(COALESCE(month10, 0.0)), SUM(COALESCE(month11, 0.0)), SUM(COALESCE(month12, 0.0))
119
            FROM msf_budget_line
120
            WHERE id IN %s"""
121
        # Get budget line template from budget tools
122
        template = self.get_budget_line_template(cr, uid, context=context)
123
        # Browse each budget line and create needed values
1994.2.76 by Olivier DOSSMANN
REF-32 [IMP] Budget import - way to compute parent's budget values.
124
        to_proceed = []
1994.2.74 by Olivier DOSSMANN
REF-32 [IMP] Budget import - change method to read lines and import them in the system
125
        mapping_accounts = {}
126
        for budget_line in template:
127
            # Create budget line
128
            line_id = budget_line.get('id')
129
            line_type = budget_line.get('type')
130
            parent_id = budget_line.get('parent_id', False)
131
            # Do not use top parent account (those in chart_of_account_ids)
132
            if parent_id in chart_of_account_ids:
133
                parent_id = False
134
            budget_line_vals = {
135
                'budget_id': budget_id,
136
                'account_id': line_id,
137
                'line_type': line_type,
138
                'month1': 0.0,
139
                'month2': 0.0,
140
                'month3': 0.0,
141
                'month4': 0.0,
142
                'month5': 0.0,
143
                'month6': 0.0,
144
                'month7': 0.0,
145
                'month8': 0.0,
146
                'month9': 0.0,
147
                'month10': 0.0,
148
                'month11': 0.0,
149
                'month12': 0.0,
150
            }
151
            if parent_id:
152
                if parent_id not in mapping_accounts:
153
                    raise osv.except_osv(_('Error'), _('You did not create budget line in the right order. A parent does not exist!'))
154
                budget_line_vals.update({'parent_id': mapping_accounts[parent_id]})
155
            # Create line
156
            budget_line_id = budget_line_obj.create(cr, uid, budget_line_vals, context=context)
1994.2.76 by Olivier DOSSMANN
REF-32 [IMP] Budget import - way to compute parent's budget values.
157
            to_proceed.append(budget_line_id)
1994.2.74 by Olivier DOSSMANN
REF-32 [IMP] Budget import - change method to read lines and import them in the system
158
            mapping_accounts[line_id] = budget_line_id
159
            if line_type == 'normal':
160
                # Update vals with the new line type
161
                budget_line_vals.update({
162
                    'line_type': 'destination',
163
                })
164
                # Browse each destination to create its line
165
                for destination_id in budget_line.get('destination_ids', []):
166
                    budget_line_vals.update({
167
                        'destination_id': destination_id,
168
                        'month1': 0.0,
169
                        'month2': 0.0,
170
                        'month3': 0.0,
171
                        'month4': 0.0,
172
                        'month5': 0.0,
173
                        'month6': 0.0,
174
                        'month7': 0.0,
175
                        'month8': 0.0,
176
                        'month9': 0.0,
177
                        'month10': 0.0,
178
                        'month11': 0.0,
179
                        'month12': 0.0,
180
                        'parent_id': budget_line_id
181
                    })
182
                    # Fetch values if sequence is given (which permit to find some lines)
183
                    if sequence:
184
                        # Search if the CSV file have this kind of tuple account/destination and fetch values
185
                        csv_line_ids = imported_obj.search(cr, uid, [('account_id', '=', line_id), ('destination_id', '=', destination_id)], context=context)
186
                        # If yes, complete budget values from month1 to month12
187
                        if csv_line_ids:
188
                            csv_line = imported_obj.read(cr, uid, csv_line_ids[0], ['month1', 'month2', 'month3', 'month4', 'month5', 'month6', 'month7', 'month8', 'month9', 'month10', 'month11', 'month12'])
189
                            budget_line_vals.update({
190
                                'month1': csv_line.get('month1', 0.0),
191
                                'month2': csv_line.get('month2', 0.0),
192
                                'month3': csv_line.get('month3', 0.0),
193
                                'month4': csv_line.get('month4', 0.0),
194
                                'month5': csv_line.get('month5', 0.0),
195
                                'month6': csv_line.get('month6', 0.0),
196
                                'month7': csv_line.get('month7', 0.0),
197
                                'month8': csv_line.get('month8', 0.0),
198
                                'month9': csv_line.get('month9', 0.0),
199
                                'month10': csv_line.get('month10', 0.0),
200
                                'month11': csv_line.get('month11', 0.0),
201
                                'month12': csv_line.get('month12', 0.0),
202
                            })
203
                    # Create destination line
1994.2.80 by Olivier DOSSMANN
REF-36 [IMP] Clean code for msf_budget module with Eclispe
204
                    budget_line_obj.create(cr, uid, budget_line_vals, context=context)
1994.2.76 by Olivier DOSSMANN
REF-32 [IMP] Budget import - way to compute parent's budget values.
205
        # Fill in parent lines (only if sequence is given which means that we have probably some values in destination lines)
206
        if sequence:
207
            vals_headers = ['month1', 'month2', 'month3', 'month4', 'month5', 'month6', 'month6', 'month7', 'month8', 'month9', 'month10', 'month11', 'month12']
208
            for budget_line_id in to_proceed:
209
                # Search child_ids
210
                child_ids = budget_line_obj.search(cr, uid, [('parent_id', 'child_of', budget_line_id)])
211
                # Do the sum of them
212
                cr.execute(sql, (tuple(child_ids),))
213
                tmp_res = cr.fetchall()
214
                # If result, write on the given budget line the result
215
                if tmp_res:
216
                    budget_line_vals = dict(zip(vals_headers, [x[0] for x in tmp_res]))
217
                    budget_line_obj.write(cr, uid, budget_line_id, budget_line_vals, context=context)
1994.2.74 by Olivier DOSSMANN
REF-32 [IMP] Budget import - change method to read lines and import them in the system
218
        return True
219
220
    def _create_expense_account_line_amounts(self, cr, uid, account_ids, actual_amounts, context=None):
221
        # Some checks
222
        if context is None:
223
            context = {}
224
        if isinstance(account_ids, (int, long)):
225
            account_ids = [account_ids]
226
        a_obj = self.pool.get('account.account')
227
        # Browse accounts
228
        for account_id in account_ids:
229
            if (account_id, False) not in actual_amounts:
230
                account = a_obj.browse(cr, uid, account_id, context=context)
231
                result = [0] * 12
232
                if account.type == 'view':
233
                    # children are accounts
234
                    for child_account in account.child_id:
235
                        if (child_account.id, False) not in actual_amounts:
236
                            self._create_expense_account_line_amounts(cr, uid, child_account.id, actual_amounts, context=context)
237
                        result = [sum(pair) for pair in zip(result, actual_amounts[child_account.id, False])]
238
                else:
239
                    # children are account, destination tuples (already in actual_amounts)
240
                    # get all tuples starting with (account_id)
1994.2.80 by Olivier DOSSMANN
REF-36 [IMP] Clean code for msf_budget module with Eclispe
241
                    for account_destination in [tuple_acc_dest for tuple_acc_dest in actual_amounts.keys() if tuple_acc_dest[0] == account_id and tuple_acc_dest[1] is not False]:
1994.2.74 by Olivier DOSSMANN
REF-32 [IMP] Budget import - change method to read lines and import them in the system
242
                        result = [sum(pair) for pair in zip(result, actual_amounts[account_destination])]
243
                actual_amounts[account_id, False] = result
850.3.1 by Matthieu Dietrich
UF-1087: [IMP] factorize actual calculation for local expense report
244
        return
1994.2.60 by Olivier DOSSMANN
REF-25 [IMP] Simplify search of cost center childs in budget tools
245
1994.2.64 by Olivier DOSSMANN
REF-25 [FIX] Problem with a method that do not use right cr/uid.
246
    def _get_cost_center_ids(self, cr, uid, browse_cost_center):
247
        return self.pool.get('account.analytic.account').search(cr, uid, [('parent_id', 'child_of', browse_cost_center.id)])
1994.2.60 by Olivier DOSSMANN
REF-25 [IMP] Simplify search of cost center childs in budget tools
248
827.11.3 by Matthieu Dietrich
UF-1101: [IMP] added analytic destination to budgets
249
    def _create_account_destination_domain(self, account_destination_list):
250
        if len(account_destination_list) == 0:
251
            return ['&',
252
                    ('general_account_id', 'in', []),
253
                    ('destination_id', 'in', [])]
254
        elif len(account_destination_list) == 1:
255
            return ['&',
256
                    ('general_account_id', '=', account_destination_list[0][0]),
257
                    ('destination_id', '=', account_destination_list[0][1])]
258
        else:
259
            return ['|'] + self._create_account_destination_domain([account_destination_list[0]]) + self._create_account_destination_domain(account_destination_list[1:])
1994.2.80 by Olivier DOSSMANN
REF-36 [IMP] Clean code for msf_budget module with Eclispe
260
850.3.1 by Matthieu Dietrich
UF-1087: [IMP] factorize actual calculation for local expense report
261
    def _get_actual_amounts(self, cr, uid, output_currency_id, domain=[], context=None):
262
        # Input: domain for the selection of analytic lines (cost center, date, etc...)
827.11.3 by Matthieu Dietrich
UF-1101: [IMP] added analytic destination to budgets
263
        # Output: a dict of list {(general_account_id, destination_id): [jan_actual, feb_actual,...]}
850.3.1 by Matthieu Dietrich
UF-1087: [IMP] factorize actual calculation for local expense report
264
        res = {}
265
        if context is None:
266
            context = {}
827.11.3 by Matthieu Dietrich
UF-1101: [IMP] added analytic destination to budgets
267
        destination_obj = self.pool.get('account.destination.link')
268
        # list to store every existing destination link in the system
1994.2.74 by Olivier DOSSMANN
REF-32 [IMP] Budget import - change method to read lines and import them in the system
269
        account_ids = self.get_expense_accounts(cr, uid, context=context)
1994.2.80 by Olivier DOSSMANN
REF-36 [IMP] Clean code for msf_budget module with Eclispe
270
1994.2.74 by Olivier DOSSMANN
REF-32 [IMP] Budget import - change method to read lines and import them in the system
271
        destination_link_ids = destination_obj.search(cr, uid, [('account_id', 'in',  account_ids)], context=context)
827.11.3 by Matthieu Dietrich
UF-1101: [IMP] added analytic destination to budgets
272
        account_destination_ids = [(dest.account_id.id, dest.destination_id.id)
273
                                   for dest
274
                                   in destination_obj.browse(cr, uid, destination_link_ids, context=context)]
1994.2.80 by Olivier DOSSMANN
REF-36 [IMP] Clean code for msf_budget module with Eclispe
275
850.3.1 by Matthieu Dietrich
UF-1087: [IMP] factorize actual calculation for local expense report
276
        # Fill all general accounts
827.11.3 by Matthieu Dietrich
UF-1101: [IMP] added analytic destination to budgets
277
        for account_id, destination_id in account_destination_ids:
278
            res[account_id, destination_id] = [0] * 12
1994.2.80 by Olivier DOSSMANN
REF-36 [IMP] Clean code for msf_budget module with Eclispe
279
850.3.1 by Matthieu Dietrich
UF-1087: [IMP] factorize actual calculation for local expense report
280
        # fill search domain (one search for all analytic lines)
827.11.3 by Matthieu Dietrich
UF-1101: [IMP] added analytic destination to budgets
281
        domain += self._create_account_destination_domain(account_destination_ids)
1994.2.80 by Olivier DOSSMANN
REF-36 [IMP] Clean code for msf_budget module with Eclispe
282
850.3.1 by Matthieu Dietrich
UF-1087: [IMP] factorize actual calculation for local expense report
283
        # Analytic domain is now done; lines are retrieved and added
284
        analytic_line_obj = self.pool.get('account.analytic.line')
285
        analytic_lines = analytic_line_obj.search(cr, uid, domain, context=context)
286
        # use currency_table_id
287
        currency_table = None
288
        if 'currency_table_id' in context:
289
            currency_table = context['currency_table_id']
1994.2.80 by Olivier DOSSMANN
REF-36 [IMP] Clean code for msf_budget module with Eclispe
290
850.3.1 by Matthieu Dietrich
UF-1087: [IMP] factorize actual calculation for local expense report
291
        # parse each line and add it to the right array
292
        for analytic_line in analytic_line_obj.browse(cr, uid, analytic_lines, context=context):
293
            date_context = {'date': analytic_line.source_date or analytic_line.date,
294
                            'currency_table_id': currency_table}
295
            actual_amount = self.pool.get('res.currency').compute(cr,
296
                                                                  uid,
297
                                                                  analytic_line.currency_id.id,
1994.2.80 by Olivier DOSSMANN
REF-36 [IMP] Clean code for msf_budget module with Eclispe
298
                                                                  output_currency_id,
850.3.1 by Matthieu Dietrich
UF-1087: [IMP] factorize actual calculation for local expense report
299
                                                                  analytic_line.amount_currency or 0.0,
300
                                                                  round=True,
301
                                                                  context=date_context)
302
            # add the amount to correct month
303
            month = datetime.datetime.strptime(analytic_line.date, '%Y-%m-%d').month
1599.19.1 by Matthieu Dietrich
commit to merge with latest unifield-wm
304
            res[analytic_line.general_account_id.id, analytic_line.destination_id.id][month - 1] += round(actual_amount, 2)
1994.2.80 by Olivier DOSSMANN
REF-36 [IMP] Clean code for msf_budget module with Eclispe
305
850.3.1 by Matthieu Dietrich
UF-1087: [IMP] factorize actual calculation for local expense report
306
        # after all lines are parsed, absolute of every column
307
        for line in res.keys():
1035.4.1 by Matthieu Dietrich
UF-1232: [IMP] new budget report + corrections on calculations
308
            res[line] = [-x for x in res[line]]
1994.2.80 by Olivier DOSSMANN
REF-36 [IMP] Clean code for msf_budget module with Eclispe
309
850.3.1 by Matthieu Dietrich
UF-1087: [IMP] factorize actual calculation for local expense report
310
        # do the view lines
1994.2.74 by Olivier DOSSMANN
REF-32 [IMP] Budget import - change method to read lines and import them in the system
311
        self._create_expense_account_line_amounts(cr, uid, account_ids, res, context=context)
1994.2.80 by Olivier DOSSMANN
REF-36 [IMP] Clean code for msf_budget module with Eclispe
312
850.3.1 by Matthieu Dietrich
UF-1087: [IMP] factorize actual calculation for local expense report
313
        return res
1994.2.80 by Olivier DOSSMANN
REF-36 [IMP] Clean code for msf_budget module with Eclispe
314
850.3.1 by Matthieu Dietrich
UF-1087: [IMP] factorize actual calculation for local expense report
315
msf_budget_tools()
316
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: