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