4ad34f5b2ccf7e4caa17470c8d8b668978898a54
[odoo/odoo.git] / addons / account_analytic_analysis / account_analytic_analysis.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (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 Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21
22 from osv import osv, fields
23 from osv.orm import intersect
24 import tools.sql
25 from tools.translate import _
26
27 class account_analytic_account(osv.osv):
28     _name = "account.analytic.account"
29     _inherit = "account.analytic.account"
30
31     def _ca_invoiced_calc(self, cr, uid, ids, name, arg, context=None):
32         res = {}
33         parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
34         if parent_ids:
35             cr.execute("select account_analytic_line.account_id, COALESCE(sum(amount_currency),0.0) \
36                     from account_analytic_line \
37                     join account_analytic_journal \
38                         on account_analytic_line.journal_id = account_analytic_journal.id  \
39                     where account_analytic_line.account_id IN %s \
40                         and account_analytic_journal.type = 'sale' \
41                     group by account_analytic_line.account_id" ,(parent_ids,))
42             for account_id, sum in cr.fetchall():
43                 res[account_id] = round(sum,2)
44                 
45         return self._compute_currency_for_level_tree(cr, uid, ids, parent_ids, res, context)
46
47     def _ca_to_invoice_calc(self, cr, uid, ids, name, arg, context=None):
48         res = {}
49         res2 = {}
50         parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
51         if parent_ids:
52             # Amount uninvoiced hours to invoice at sale price
53             # Warning
54             # This computation doesn't take care of pricelist !
55             # Just consider list_price
56             cr.execute("""SELECT account_analytic_account.id, \
57                         COALESCE(sum (product_template.list_price * \
58                             account_analytic_line.unit_amount * \
59                             ((100-hr_timesheet_invoice_factor.factor)/100)),0.0) \
60                             AS ca_to_invoice \
61                     FROM product_template \
62                     join product_product \
63                         on product_template.id = product_product.product_tmpl_id \
64                     JOIN account_analytic_line \
65                         on account_analytic_line.product_id = product_product.id \
66                     JOIN account_analytic_journal \
67                         on account_analytic_line.journal_id = account_analytic_journal.id \
68                     JOIN account_analytic_account \
69                         on account_analytic_account.id = account_analytic_line.account_id \
70                     JOIN hr_timesheet_invoice_factor \
71                         on hr_timesheet_invoice_factor.id = account_analytic_account.to_invoice \
72                     WHERE account_analytic_account.id IN %s \
73                         AND account_analytic_line.invoice_id is null \
74                         AND account_analytic_line.to_invoice IS NOT NULL \
75                         and account_analytic_journal.type in ('purchase','general') \
76                     GROUP BY account_analytic_account.id;""",(parent_ids,))
77             for account_id, sum in cr.fetchall():
78                 res[account_id] = round(sum,2)
79
80         for obj_id in ids:
81             res.setdefault(obj_id, 0.0)
82             res2.setdefault(obj_id, 0.0)
83             for child_id in self.search(cr, uid,
84                     [('parent_id', 'child_of', [obj_id])]):
85                 if child_id != obj_id:
86                     res[obj_id] += res.get(child_id, 0.0)
87                     res2[obj_id] += res2.get(child_id, 0.0)
88         # sum both result on account_id
89         for id in ids:
90             res[id] = round(res.get(id, 0.0),2) + round(res2.get(id, 0.0),2)
91         return res
92
93     def _hours_qtt_non_invoiced_calc (self, cr, uid, ids, name, arg, context=None):
94         res = {}
95         parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
96         if parent_ids:
97             cr.execute("select account_analytic_line.account_id, COALESCE(sum(unit_amount),0.0) \
98                     from account_analytic_line \
99                     join account_analytic_journal \
100                         on account_analytic_line.journal_id = account_analytic_journal.id \
101                     where account_analytic_line.account_id IN %s \
102                         and account_analytic_journal.type='general' \
103                         and invoice_id is null \
104                         AND to_invoice IS NOT NULL \
105                     GROUP BY account_analytic_line.account_id;",(parent_ids,))
106             for account_id, sum in cr.fetchall():
107                 res[account_id] = round(sum,2)
108         for obj_id in ids:
109             res.setdefault(obj_id, 0.0)
110             for child_id in self.search(cr, uid,
111                     [('parent_id', 'child_of', [obj_id])]):
112                 if child_id != obj_id:
113                     res[obj_id] += res.get(child_id, 0.0)
114         for id in ids:
115             res[id] = round(res.get(id, 0.0),2)
116         return res
117
118     def _hours_quantity_calc(self, cr, uid, ids, name, arg, context=None):
119         res = {}
120         parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
121         if parent_ids:
122             cr.execute("select account_analytic_line.account_id,COALESCE(SUM(unit_amount),0.0) \
123                     from account_analytic_line \
124                     join account_analytic_journal \
125                         on account_analytic_line.journal_id = account_analytic_journal.id \
126                     where account_analytic_line.account_id IN %s \
127                         and account_analytic_journal.type='general' \
128                     GROUP BY account_analytic_line.account_id",(parent_ids,))
129             ff =  cr.fetchall()
130             for account_id, sum in ff:
131                 res[account_id] = round(sum,2)
132         for obj_id in ids:
133             res.setdefault(obj_id, 0.0)
134             for child_id in self.search(cr, uid,
135                     [('parent_id', 'child_of', [obj_id])]):
136                 if child_id != obj_id:
137                     res[obj_id] += res.get(child_id, 0.0)
138         for id in ids:
139             res[id] = round(res.get(id, 0.0),2)
140         return res
141
142     def _total_cost_calc(self, cr, uid, ids, name, arg, context=None):
143         res = {}
144         parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
145         if parent_ids:
146             cr.execute("""select account_analytic_line.account_id,COALESCE(sum(amount_currency),0.0) \
147
148                     from account_analytic_line \
149                     join account_analytic_journal \
150                         on account_analytic_line.journal_id = account_analytic_journal.id \
151                     where account_analytic_line.account_id IN %s \
152                         and amount<0 \
153                     GROUP BY account_analytic_line.account_id""",(parent_ids,))
154             for account_id, sum in cr.fetchall():
155                 res[account_id] = round(sum,2)
156         return self._compute_currency_for_level_tree(cr, uid, ids, parent_ids, res, context)
157  
158     # TODO Take care of pricelist and purchase !
159     def _ca_theorical_calc(self, cr, uid, ids, name, arg, context=None):
160         res = {}
161         res2 = {}
162         parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
163         # Warning
164         # This computation doesn't take care of pricelist !
165         # Just consider list_price
166         if parent_ids:
167             cr.execute("""select account_analytic_line.account_id as account_id, \
168                         COALESCE(sum((account_analytic_line.unit_amount * pt.list_price) \
169                             - (account_analytic_line.unit_amount * pt.list_price \
170                                 * hr.factor)),0.0) as somme
171                     from account_analytic_line \
172                     left join account_analytic_journal \
173                         on (account_analytic_line.journal_id = account_analytic_journal.id) \
174                     join product_product pp \
175                         on (account_analytic_line.product_id = pp.id) \
176                     join product_template pt \
177                         on (pp.product_tmpl_id = pt.id) \
178                     join account_analytic_account a \
179                         on (a.id=account_analytic_line.account_id) \
180                     join hr_timesheet_invoice_factor hr \
181                         on (hr.id=a.to_invoice) \
182                 where account_analytic_line.account_id IN %s \
183                     and a.to_invoice IS NOT NULL \
184                     and account_analytic_journal.type IN ('purchase','general')
185                 GROUP BY account_analytic_line.account_id""",(parent_ids,))
186             for account_id, sum in cr.fetchall():
187                 res2[account_id] = round(sum,2)
188                 
189         for obj_id in ids:
190             res.setdefault(obj_id, 0.0)
191             res2.setdefault(obj_id, 0.0)
192             for child_id in self.search(cr, uid,
193                     [('parent_id', 'child_of', [obj_id])]):
194                 if child_id != obj_id:
195                     res[obj_id] += res.get(child_id, 0.0)
196                     res[obj_id] += res2.get(child_id, 0.0)
197         
198         # sum both result on account_id
199         for id in ids:
200             res[id] = round(res.get(id, 0.0),2) + round(res2.get(id, 0.0),2)
201         return res
202
203     def _last_worked_date_calc (self, cr, uid, ids, name, arg, context = None):
204         res = {}
205         parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
206         if parent_ids:
207             cr.execute("select account_analytic_line.account_id, max(date) \
208                     from account_analytic_line \
209                     where account_id IN %s \
210                         and invoice_id is null \
211                     GROUP BY account_analytic_line.account_id" ,(parent_ids,))
212             for account_id, sum in cr.fetchall():
213                 res[account_id] = sum
214         for obj_id in ids:
215             res.setdefault(obj_id, '')
216             for child_id in self.search(cr, uid,
217                     [('parent_id', 'child_of', [obj_id])]):
218                 if res[obj_id] < res.get(child_id, ''):
219                     res[obj_id] = res.get(child_id, '')
220         for id in ids:
221             res[id] = res.get(id, '')
222         return res
223
224     def _last_invoice_date_calc (self, cr, uid, ids, name, arg, context=None):
225         res = {}
226         parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
227         if parent_ids:
228             cr.execute ("select account_analytic_line.account_id, \
229                         date(max(account_invoice.date_invoice)) \
230                     from account_analytic_line \
231                     join account_invoice \
232                         on account_analytic_line.invoice_id = account_invoice.id \
233                     where account_analytic_line.account_id IN %s \
234                         and account_analytic_line.invoice_id is not null \
235                     GROUP BY account_analytic_line.account_id",(parent_ids,))
236             for account_id, sum in cr.fetchall():
237                 res[account_id] = sum
238         for obj_id in ids:
239             res.setdefault(obj_id, '')
240             for child_id in self.search(cr, uid,
241                     [('parent_id', 'child_of', [obj_id])]):
242                 if res[obj_id] < res.get(child_id, ''):
243                     res[obj_id] = res.get(child_id, '')
244         for id in ids:
245             res[id] = res.get(id, '')
246         return res
247
248     def _last_worked_invoiced_date_calc (self, cr, uid, ids, name, arg, context=None):
249         res = {}
250         parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
251         if parent_ids:
252             cr.execute("select account_analytic_line.account_id, max(date) \
253                     from account_analytic_line \
254                     where account_id IN %s \
255                         and invoice_id is not null \
256                     GROUP BY account_analytic_line.account_id;",(parent_ids,))
257             for account_id, sum in cr.fetchall():
258                 res[account_id] = sum
259         for obj_id in ids:
260             res.setdefault(obj_id, '')
261             for child_id in self.search(cr, uid,
262                     [('parent_id', 'child_of', [obj_id])]):
263                 if res[obj_id] < res.get(child_id, ''):
264                     res[obj_id] = res.get(child_id, '')
265         for id in ids:
266             res[id] = res.get(id, '')
267         return res
268
269     def _remaining_hours_calc(self, cr, uid, ids, name, arg, context=None):
270         res = {}
271         for account in self.browse(cr, uid, ids):
272             if account.quantity_max != 0:
273                 res[account.id] = account.quantity_max - account.hours_quantity
274             else:
275                 res[account.id]=0.0
276         for id in ids:
277             res[id] = round(res.get(id, 0.0),2)
278         return res
279
280     def _hours_qtt_invoiced_calc(self, cr, uid, ids, name, arg, context=None):
281         res = {}
282         for account in self.browse(cr, uid, ids):
283             res[account.id] = account.hours_quantity - account.hours_qtt_non_invoiced
284             if res[account.id] < 0:
285                 res[account.id]=0.0
286         for id in ids:
287             res[id] = round(res.get(id, 0.0),2)
288         return res
289
290     def _revenue_per_hour_calc(self, cr, uid, ids, name, arg, context=None):
291         res = {}
292         for account in self.browse(cr, uid, ids):
293             if account.hours_qtt_invoiced == 0:
294                 res[account.id]=0.0
295             else:
296                 res[account.id] = account.ca_invoiced / account.hours_qtt_invoiced
297         for id in ids:
298             res[id] = round(res.get(id, 0.0),2)
299         return res
300
301     def _real_margin_rate_calc(self, cr, uid, ids, name, arg, context=None):
302         res = {}
303         for account in self.browse(cr, uid, ids):
304             if account.ca_invoiced == 0:
305                 res[account.id]=0.0
306             elif account.total_cost != 0.0:
307                 res[account.id] = -(account.real_margin / account.total_cost) * 100
308             else:
309                 res[account.id] = 0.0
310         for id in ids:
311             res[id] = round(res.get(id, 0.0),2)
312         return res
313
314     def _remaining_ca_calc(self, cr, uid, ids, name, arg, context=None):
315         res = {}
316         for account in self.browse(cr, uid, ids):
317             if account.amount_max != 0:
318                 res[account.id] = account.amount_max - account.ca_invoiced
319             else:
320                 res[account.id]=0.0
321         for id in ids:
322             res[id] = round(res.get(id, 0.0),2)
323         return res
324
325     def _real_margin_calc(self, cr, uid, ids, name, arg, context=None):
326         res = {}
327         for account in self.browse(cr, uid, ids):
328             res[account.id] = account.ca_invoiced + account.total_cost
329         for id in ids:
330             res[id] = round(res.get(id, 0.0),2)
331         return res
332
333     def _theorical_margin_calc(self, cr, uid, ids, name, arg, context=None):
334         res = {}
335         for account in self.browse(cr, uid, ids):
336             res[account.id] = account.ca_theorical + account.total_cost
337         for id in ids:
338             res[id] = round(res.get(id, 0.0),2)
339         return res
340
341     def _month(self, cr, uid, ids, name, arg, context=None):
342         res = {}
343         for id in ids:
344             parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
345             if parent_ids:
346                 cr.execute('SELECT DISTINCT(month_id) FROM account_analytic_analysis_summary_month ' \
347                         'WHERE account_id IN %s AND unit_amount <> 0.0',(parent_ids,))
348                 res[id] = [int(id * 1000000 + int(x[0])) for x in cr.fetchall()]
349             else:
350                 res[id] = []
351         return res
352
353     def _user(self, cr, uid, ids, name, arg, context=None):
354         res = {}
355         cr.execute('SELECT MAX(id) FROM res_users')
356         max_user = cr.fetchone()[0]
357         for id in ids:
358             parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
359             if parent_ids:
360                 cr.execute('SELECT DISTINCT("user") FROM account_analytic_analysis_summary_user ' \
361                         'WHERE account_id IN %s AND unit_amount <> 0.0',(parent_ids,))
362                 res[id] = [int((id * max_user) + x[0]) for x in cr.fetchall()]
363             else:
364                 res[id] = []
365         return res
366
367     _columns ={
368         'ca_invoiced': fields.function(_ca_invoiced_calc, method=True, type='float', string='Invoiced Amount', help="Total customer invoiced amount for this account."),
369         'total_cost': fields.function(_total_cost_calc, method=True, type='float', string='Total Costs', help="Total of costs for this account. It includes real costs (from invoices) and indirect costs, like time spent on timesheets."),
370         'ca_to_invoice': fields.function(_ca_to_invoice_calc, method=True, type='float', string='Uninvoiced Amount', help="If invoice from analytic account, the remaining amount you can invoice to the customer based on the total costs."),
371         'ca_theorical': fields.function(_ca_theorical_calc, method=True, type='float', string='Theorical Revenue', help="Based on the costs you had on the project, what would have been the revenue if all these costs have been invoiced at the normal sale price provided by the pricelist."),
372         'hours_quantity': fields.function(_hours_quantity_calc, method=True, type='float', string='Hours Tot', help="Number of hours you spent on the analytic account (from timesheet). It computes on all journal of type 'general'."),
373         'last_invoice_date': fields.function(_last_invoice_date_calc, method=True, type='date', string='Last Invoice Date', help="Date of the last invoice created for this analytic account."),
374         'last_worked_invoiced_date': fields.function(_last_worked_invoiced_date_calc, method=True, type='date', string='Date of Last Invoiced Cost', help="If invoice from the costs, this is the date of the latest work or cost that have been invoiced."),
375         'last_worked_date': fields.function(_last_worked_date_calc, method=True, type='date', string='Date of Last Cost/Work', help="Date of the latest work done on this account."),
376         'hours_qtt_non_invoiced': fields.function(_hours_qtt_non_invoiced_calc, method=True, type='float', string='Uninvoiced Hours', help="Number of hours (from journal of type 'general') that can be invoiced if you invoice based on analytic account."),
377         'hours_qtt_invoiced': fields.function(_hours_qtt_invoiced_calc, method=True, type='float', string='Invoiced Hours', help="Number of hours that can be invoiced plus those that already have been invoiced."),
378         'remaining_hours': fields.function(_remaining_hours_calc, method=True, type='float', string='Remaining Hours', help="Computed using the formula: Maximum Quantity - Hours Tot."),
379         'remaining_ca': fields.function(_remaining_ca_calc, method=True, type='float', string='Remaining Revenue', help="Computed using the formula: Max Invoice Price - Invoiced Amount."),
380         'revenue_per_hour': fields.function(_revenue_per_hour_calc, method=True, type='float', string='Revenue per Hours (real)', help="Computed using the formula: Invoiced Amount / Hours Tot."),
381         'real_margin': fields.function(_real_margin_calc, method=True, type='float', string='Real Margin', help="Computed using the formula: Invoiced Amount - Total Costs."),
382         'theorical_margin': fields.function(_theorical_margin_calc, method=True, type='float', string='Theorical Margin', help="Computed using the formula: Theorial Revenue - Total Costs"),
383         'real_margin_rate': fields.function(_real_margin_rate_calc, method=True, type='float', string='Real Margin Rate (%)', help="Computes using the formula: (Real Margin / Total Costs) * 100."),
384         'month_ids': fields.function(_month, method=True, type='many2many', relation='account_analytic_analysis.summary.month', string='Month'),
385         'user_ids': fields.function(_user, method=True, type="many2many", relation='account_analytic_analysis.summary.user', string='User'),
386     }
387 account_analytic_account()
388
389 class account_analytic_account_summary_user(osv.osv):
390     _name = "account_analytic_analysis.summary.user"
391     _description = "Hours Summary by User"
392     _order='user'
393     _auto = False
394     _rec_name = 'user'
395
396     def _unit_amount(self, cr, uid, ids, name, arg, context=None):
397         res = {}
398         account_obj = self.pool.get('account.analytic.account')
399         cr.execute('SELECT MAX(id) FROM res_users')
400         max_user = cr.fetchone()[0]
401         account_ids = [int(str(x/max_user - (x%max_user == 0 and 1 or 0))) for x in ids]
402         user_ids = [int(str(x-((x/max_user - (x%max_user == 0 and 1 or 0)) *max_user))) for x in ids]
403         parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', account_ids)]))
404         if parent_ids:
405             cr.execute('SELECT id, unit_amount ' \
406                     'FROM account_analytic_analysis_summary_user ' \
407                     'WHERE account_id IN %s ' \
408                         'AND "user" IN %s',(parent_ids, user_ids,))
409             for sum_id, unit_amount in cr.fetchall():
410                 res[sum_id] = unit_amount
411         for obj_id in ids:
412             res.setdefault(obj_id, 0.0)
413             for child_id in account_obj.search(cr, uid,
414                     [('parent_id', 'child_of', [int(str(obj_id/max_user - (obj_id%max_user == 0 and 1 or 0)))])]):
415                 if child_id != int(str(obj_id/max_user - (obj_id%max_user == 0 and 1 or 0))):
416                     res[obj_id] += res.get((child_id * max_user) + obj_id -((obj_id/max_user - (obj_id%max_user == 0 and 1 or 0)) * max_user), 0.0)
417         for id in ids:
418             res[id] = round(res.get(id, 0.0), 2)
419         return res
420
421     _columns = {
422         'account_id': fields.many2one('account.analytic.account', 'Analytic Account', readonly=True),
423         'unit_amount': fields.function(_unit_amount, method=True, type='float',
424             string='Total Time'),
425         'user' : fields.many2one('res.users', 'User'),
426     }
427     
428     def init(self, cr):
429         tools.sql.drop_view_if_exists(cr, 'account_analytic_analysis_summary_user')
430         cr.execute('CREATE OR REPLACE VIEW account_analytic_analysis_summary_user AS (' \
431                 'SELECT ' \
432                     '(u.account_id * u.max_user) + u."user" AS id, ' \
433                     'u.account_id AS account_id, ' \
434                     'u."user" AS "user", ' \
435                     'COALESCE(SUM(l.unit_amount), 0.0) AS unit_amount ' \
436                 'FROM ' \
437                     '(SELECT ' \
438                         'a.id AS account_id, ' \
439                         'u1.id AS "user", ' \
440                         'MAX(u2.id) AS max_user ' \
441                     'FROM ' \
442                         'res_users AS u1, ' \
443                         'res_users AS u2, ' \
444                         'account_analytic_account AS a ' \
445                     'GROUP BY u1.id, a.id ' \
446                     ') AS u ' \
447                 'LEFT JOIN ' \
448                     '(SELECT ' \
449                         'l.account_id AS account_id, ' \
450                         'l.user_id AS "user", ' \
451                         'SUM(l.unit_amount) AS unit_amount ' \
452                     'FROM account_analytic_line AS l, ' \
453                         'account_analytic_journal AS j ' \
454                     'WHERE (j.type = \'general\') and (j.id=l.journal_id) ' \
455                     'GROUP BY l.account_id, l.user_id ' \
456                     ') AS l '
457                     'ON (' \
458                         'u.account_id = l.account_id ' \
459                         'AND u."user" = l."user"' \
460                     ') ' \
461                 'GROUP BY u."user", u.account_id, u.max_user' \
462                 ')')
463
464     def _read_flat(self, cr, user, ids, fields, context=None, load='_classic_read'):
465         if not context:
466             context={}
467         if not ids:
468             return []
469
470         if fields==None:
471             fields = self._columns.keys()
472
473         # construct a clause for the rules :
474         d1, d2 = self.pool.get('ir.rule').domain_get(cr, user, self._name)
475
476         # all inherited fields + all non inherited fields for which the attribute whose name is in load is True
477         fields_pre = filter(lambda x: x in self._columns and getattr(self._columns[x],'_classic_write'), fields) + self._inherits.values()
478
479         res = []
480         cr.execute('SELECT MAX(id) FROM res_users')
481         max_user = cr.fetchone()[0]
482         if len(fields_pre) :
483             fields_pre2 = map(lambda x: (x in ('create_date', 'write_date')) and ('date_trunc(\'second\', '+x+') as '+x) or '"'+x+'"', fields_pre)
484             for i in range(0, len(ids), cr.IN_MAX):
485                 sub_ids = ids[i:i+cr.IN_MAX]
486                 if d1:
487                     cr.execute('select %s from \"%s\" where id IN (%s) ' \
488                             'and account_id IN (%s) ' \
489                             'and "user" IN (%s) and %s order by %s' % \
490                             (','.join(fields_pre2 + ['id']), self._table,
491                                 ','.join([str(x) for x in sub_ids]),
492                                 ','.join([str(x/max_user - (x%max_user == 0 and 1 or 0)) for x in sub_ids]),
493                                 ','.join([str(x-((x/max_user - (x%max_user == 0 and 1 or 0)) *max_user)) for x in sub_ids]), d1,
494                                 self._order),d2)
495                     if not cr.rowcount == len({}.fromkeys(sub_ids)):
496                         raise except_orm(_('AccessError'),
497                                 _('You try to bypass an access rule (Document type: %s).') % self._description)
498                 else:
499                     cr.execute('select %s from \"%s\" where id IN (%s) ' \
500                             'and account_id IN (%s) ' \
501                             'and "user" IN (%s) order by %s' % \
502                             (','.join(fields_pre2 + ['id']), self._table,
503                                 ','.join([str(x) for x in sub_ids]),
504                                 ','.join([str(x/max_user - (x%max_user == 0 and 1 or 0)) for x in sub_ids]),
505                                 ','.join([str(x-((x/max_user - (x%max_user == 0 and 1 or 0)) *max_user)) for x in sub_ids]),
506                                 self._order))
507                 res.extend(cr.dictfetchall())
508         else:
509             res = map(lambda x: {'id': x}, ids)
510
511         for f in fields_pre:
512             if self._columns[f].translate:
513                 ids = map(lambda x: x['id'], res)
514                 res_trans = self.pool.get('ir.translation')._get_ids(cr, user, self._name+','+f, 'model', context.get('lang', False) or 'en_US', ids)
515                 for r in res:
516                     r[f] = res_trans.get(r['id'], False) or r[f]
517
518         for table in self._inherits:
519             col = self._inherits[table]
520             cols = intersect(self._inherit_fields.keys(), fields)
521             if not cols:
522                 continue
523             res2 = self.pool.get(table).read(cr, user, [x[col] for x in res], cols, context, load)
524
525             res3 = {}
526             for r in res2:
527                 res3[r['id']] = r
528                 del r['id']
529
530             for record in res:
531                 record.update(res3[record[col]])
532                 if col not in fields:
533                     del record[col]
534
535         # all fields which need to be post-processed by a simple function (symbol_get)
536         fields_post = filter(lambda x: x in self._columns and self._columns[x]._symbol_get, fields)
537         if fields_post:
538             # maybe it would be faster to iterate on the fields then on res, so that we wouldn't need
539             # to get the _symbol_get in each occurence
540             for r in res:
541                 for f in fields_post:
542                     r[f] = self.columns[f]._symbol_get(r[f])
543         ids = map(lambda x: x['id'], res)
544
545         # all non inherited fields for which the attribute whose name is in load is False
546         fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields)
547         for f in fields_post:
548             # get the value of that field for all records/ids
549             res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
550             for record in res:
551                 record[f] = res2[record['id']]
552
553         return res
554
555 account_analytic_account_summary_user()
556
557 class account_analytic_account_summary_month(osv.osv):
558     _name = "account_analytic_analysis.summary.month"
559     _description = "Hours summary by month"
560     _auto = False
561     _rec_name = 'month'
562 #    _order = 'month'
563
564     def _unit_amount(self, cr, uid, ids, name, arg, context=None):
565         res = {}
566         account_obj = self.pool.get('account.analytic.account')
567         account_ids = [int(str(int(x))[:-6]) for x in ids]
568         month_ids = [int(str(int(x))[-6:]) for x in ids]
569         parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', account_ids)]))
570         if parent_ids:
571             cr.execute('SELECT id, unit_amount ' \
572                     'FROM account_analytic_analysis_summary_month ' \
573                     'WHERE account_id IN %s ' \
574                         'AND month_id IN %s ',(parent_ids, month_ids,))
575             for sum_id, unit_amount in cr.fetchall():
576                 res[sum_id] = unit_amount
577         for obj_id in ids:
578             res.setdefault(obj_id, 0.0)
579             for child_id in account_obj.search(cr, uid,
580                     [('parent_id', 'child_of', [int(str(int(obj_id))[:-6])])]):
581                 if child_id != int(str(int(obj_id))[:-6]):
582                     res[obj_id] += res.get(int(child_id * 1000000 + int(str(int(obj_id))[-6:])), 0.0)
583         for id in ids:
584             res[id] = round(res.get(id, 0.0), 2)
585         return res
586
587     _columns = {
588         'account_id': fields.many2one('account.analytic.account', 'Analytic Account',
589             readonly=True),
590         'unit_amount': fields.function(_unit_amount, method=True, type='float',
591             string='Total Time'),
592         'month': fields.char('Month', size=25, readonly=True),
593     }
594
595     def init(self, cr):
596         tools.sql.drop_view_if_exists(cr, 'account_analytic_analysis_summary_month')
597         cr.execute('CREATE VIEW account_analytic_analysis_summary_month AS (' \
598                 'SELECT ' \
599                     '(TO_NUMBER(TO_CHAR(d.month, \'YYYYMM\'), \'999999\') + (d.account_id  * 1000000))::integer AS id, ' \
600                     'd.account_id AS account_id, ' \
601                     'TO_CHAR(d.month, \'Mon YYYY\') AS month, ' \
602                     'TO_NUMBER(TO_CHAR(d.month, \'YYYYMM\'), \'999999\') AS month_id, ' \
603                     'COALESCE(SUM(l.unit_amount), 0.0) AS unit_amount ' \
604                 'FROM ' \
605                     '(SELECT ' \
606                         'd2.account_id, ' \
607                         'd2.month ' \
608                     'FROM ' \
609                         '(SELECT ' \
610                             'a.id AS account_id, ' \
611                             'l.month AS month ' \
612                         'FROM ' \
613                             '(SELECT ' \
614                                 'DATE_TRUNC(\'month\', l.date) AS month ' \
615                             'FROM account_analytic_line AS l, ' \
616                                 'account_analytic_journal AS j ' \
617                             'WHERE j.type = \'general\' ' \
618                             'GROUP BY DATE_TRUNC(\'month\', l.date) ' \
619                             ') AS l, ' \
620                             'account_analytic_account AS a ' \
621                         'GROUP BY l.month, a.id ' \
622                         ') AS d2 ' \
623                     'GROUP BY d2.account_id, d2.month ' \
624                     ') AS d ' \
625                 'LEFT JOIN ' \
626                     '(SELECT ' \
627                         'l.account_id AS account_id, ' \
628                         'DATE_TRUNC(\'month\', l.date) AS month, ' \
629                         'SUM(l.unit_amount) AS unit_amount ' \
630                     'FROM account_analytic_line AS l, ' \
631                         'account_analytic_journal AS j ' \
632                     'WHERE (j.type = \'general\') and (j.id=l.journal_id) ' \
633                     'GROUP BY l.account_id, DATE_TRUNC(\'month\', l.date) ' \
634                     ') AS l '
635                     'ON (' \
636                         'd.account_id = l.account_id ' \
637                         'AND d.month = l.month' \
638                     ') ' \
639                 'GROUP BY d.month, d.account_id ' \
640                 ')')
641
642     def _read_flat(self, cr, user, ids, fields, context=None, load='_classic_read'):
643         if not context:
644             context={}
645         if not ids:
646             return []
647
648         if fields==None:
649             fields = self._columns.keys()
650
651         # construct a clause for the rules :
652         d1, d2 = self.pool.get('ir.rule').domain_get(cr, user, self._name)
653
654         # all inherited fields + all non inherited fields for which the attribute whose name is in load is True
655         fields_pre = filter(lambda x: x in self._columns and getattr(self._columns[x],'_classic_write'), fields) + self._inherits.values()
656
657         res = []
658         if len(fields_pre) :
659             fields_pre2 = map(lambda x: (x in ('create_date', 'write_date')) and ('date_trunc(\'second\', '+x+') as '+x) or '"'+x+'"', fields_pre)
660             for i in range(0, len(ids), cr.IN_MAX):
661                 sub_ids = ids[i:i+cr.IN_MAX]
662                 if d1:
663                     cr.execute('select %s from \"%s\" where id IN (%s) ' \
664                             'and account_id IN (%s) ' \
665                             'and month_id IN (%s) and %s order by %s' % \
666                             (','.join(fields_pre2 + ['id']), self._table,
667                                 ','.join([str(x) for x in sub_ids]),
668                                 ','.join([str(x)[:-6] for x in sub_ids]),
669                                 ','.join([str(x)[-6:] for x in sub_ids]), d1,
670                                 self._order),d2)
671                     if not cr.rowcount == len({}.fromkeys(sub_ids)):
672                         raise except_orm(_('AccessError'),
673                                 _('You try to bypass an access rule (Document type: %s).') % self._description)
674                 else:
675                     cr.execute('select %s from \"%s\" where id IN (%s) ' \
676                             'and account_id IN (%s) ' \
677                             'and month_id IN (%s) order by %s' % \
678                             (','.join(fields_pre2 + ['id']), self._table,
679                                 ','.join([str(x) for x in sub_ids]),
680                                 ','.join([str(x)[:-6] for x in sub_ids]),
681                                 ','.join([str(x)[-6:] for x in sub_ids]),
682                                 self._order))
683                 res.extend(cr.dictfetchall())
684         else:
685             res = map(lambda x: {'id': x}, ids)
686
687         for f in fields_pre:
688             if self._columns[f].translate:
689                 ids = map(lambda x: x['id'], res)
690                 res_trans = self.pool.get('ir.translation')._get_ids(cr, user, self._name+','+f, 'model', context.get('lang', False) or 'en_US', ids)
691                 for r in res:
692                     r[f] = res_trans.get(r['id'], False) or r[f]
693
694         for table in self._inherits:
695             col = self._inherits[table]
696             cols = intersect(self._inherit_fields.keys(), fields)
697             if not cols:
698                 continue
699             res2 = self.pool.get(table).read(cr, user, [x[col] for x in res], cols, context, load)
700
701             res3 = {}
702             for r in res2:
703                 res3[r['id']] = r
704                 del r['id']
705
706             for record in res:
707                 record.update(res3[record[col]])
708                 if col not in fields:
709                     del record[col]
710
711         # all fields which need to be post-processed by a simple function (symbol_get)
712         fields_post = filter(lambda x: x in self._columns and self._columns[x]._symbol_get, fields)
713         if fields_post:
714             # maybe it would be faster to iterate on the fields then on res, so that we wouldn't need
715             # to get the _symbol_get in each occurence
716             for r in res:
717                 for f in fields_post:
718                     r[f] = self.columns[f]._symbol_get(r[f])
719         ids = map(lambda x: x['id'], res)
720
721         # all non inherited fields for which the attribute whose name is in load is False
722         fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields)
723         for f in fields_post:
724             # get the value of that field for all records/ids
725             res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
726             for record in res:
727                 record[f] = res2[record['id']]
728
729         return res
730
731 account_analytic_account_summary_month()
732
733 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
734