1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
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.
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.
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/>.
20 ##############################################################################
22 from osv import osv, fields
23 from osv.orm import intersect, except_orm
25 from tools.translate import _
26 from decimal_precision import decimal_precision as dp
29 class account_analytic_account(osv.osv):
30 _name = "account.analytic.account"
31 _inherit = "account.analytic.account"
33 def _analysis_all(self, cr, uid, ids, fields, arg, context=None):
35 res = dict([(i, {}) for i in ids])
37 parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)], context=context))
38 res.update(dict([(i, {}) for i in parent_ids]))
39 accounts = self.browse(cr, uid, ids, context=context)
43 cr.execute('SELECT MAX(id) FROM res_users')
44 max_user = cr.fetchone()[0]
46 cr.execute('SELECT DISTINCT("user") FROM account_analytic_analysis_summary_user ' \
47 'WHERE account_id IN %s AND unit_amount <> 0.0', (parent_ids,))
48 result = cr.fetchall()
52 res[id][f] = [int((id * max_user) + x[0]) for x in result]
53 elif f == 'month_ids':
55 cr.execute('SELECT DISTINCT(month_id) FROM account_analytic_analysis_summary_month ' \
56 'WHERE account_id IN %s AND unit_amount <> 0.0', (parent_ids,))
57 result = cr.fetchall()
61 res[id][f] = [int(id * 1000000 + int(x[0])) for x in result]
62 elif f == 'last_worked_invoiced_date':
66 cr.execute("SELECT account_analytic_line.account_id, MAX(date) \
67 FROM account_analytic_line \
68 WHERE account_id IN %s \
69 AND invoice_id IS NOT NULL \
70 GROUP BY account_analytic_line.account_id;", (parent_ids,))
71 for account_id, sum in cr.fetchall():
72 if account_id not in res:
74 res[account_id][f] = sum
75 for account in accounts:
76 for child in account.child_ids:
77 if res[account.id].get(f, '') < res.get(child.id, {}).get(f, ''):
78 res[account.id][f] = res.get(child.id, {}).get(f, '')
79 elif f == 'ca_to_invoice':
84 # Amount uninvoiced hours to invoice at sale price
86 # This computation doesn't take care of pricelist !
87 # Just consider list_price
88 cr.execute("""SELECT account_analytic_account.id, \
89 COALESCE(SUM (product_template.list_price * \
90 account_analytic_line.unit_amount * \
91 ((100-hr_timesheet_invoice_factor.factor)/100)), 0.0) \
93 FROM product_template \
94 JOIN product_product \
95 ON product_template.id = product_product.product_tmpl_id \
96 JOIN account_analytic_line \
97 ON account_analytic_line.product_id = product_product.id \
98 JOIN account_analytic_journal \
99 ON account_analytic_line.journal_id = account_analytic_journal.id \
100 JOIN account_analytic_account \
101 ON account_analytic_account.id = account_analytic_line.account_id \
102 JOIN hr_timesheet_invoice_factor \
103 ON hr_timesheet_invoice_factor.id = account_analytic_account.to_invoice \
104 WHERE account_analytic_account.id IN %s \
105 AND account_analytic_line.invoice_id IS NULL \
106 AND account_analytic_line.to_invoice IS NOT NULL \
107 AND account_analytic_journal.type IN ('purchase','general') \
108 GROUP BY account_analytic_account.id;""", (parent_ids,))
109 for account_id, sum in cr.fetchall():
110 if account_id not in res:
112 res[account_id][f] = round(sum, dp)
114 for account in accounts:
115 #res.setdefault(account.id, 0.0)
116 res2.setdefault(account.id, 0.0)
117 for child in account.child_ids:
118 if child.id != account.id:
119 res[account.id][f] += res.get(child.id, {}).get(f, 0.0)
120 res2[account.id] += res2.get(child.id, 0.0)
121 # sum both result on account_id
123 res[id][f] = round(res.get(id, {}).get(f, 0.0), dp) + round(res2.get(id, 0.0), 2)
124 elif f == 'last_invoice_date':
128 cr.execute ("SELECT account_analytic_line.account_id, \
129 DATE(MAX(account_invoice.date_invoice)) \
130 FROM account_analytic_line \
131 JOIN account_invoice \
132 ON account_analytic_line.invoice_id = account_invoice.id \
133 WHERE account_analytic_line.account_id IN %s \
134 AND account_analytic_line.invoice_id IS NOT NULL \
135 GROUP BY account_analytic_line.account_id",(parent_ids,))
136 for account_id, lid in cr.fetchall():
137 res[account_id][f] = lid
138 for account in accounts:
139 for child in account.child_ids:
140 if res[account.id][f] < res.get(child.id, {}).get(f, ''):
141 res[account.id][f] = res.get(child.id, {}).get(f, '')
142 elif f == 'last_worked_date':
146 cr.execute("SELECT account_analytic_line.account_id, MAX(date) \
147 FROM account_analytic_line \
148 WHERE account_id IN %s \
149 AND invoice_id IS NULL \
150 GROUP BY account_analytic_line.account_id",(parent_ids,))
151 for account_id, lwd in cr.fetchall():
152 if account_id not in res:
154 res[account_id][f] = lwd
155 for account in accounts:
156 for child in account.child_ids:
157 if res[account.id][f] < res.get(child.id, {}).get(f, ''):
158 res[account.id][f] = res.get(child.id, {}).get(f, '')
159 elif f == 'hours_qtt_non_invoiced':
163 cr.execute("SELECT account_analytic_line.account_id, COALESCE(SUM(unit_amount), 0.0) \
164 FROM account_analytic_line \
165 JOIN account_analytic_journal \
166 ON account_analytic_line.journal_id = account_analytic_journal.id \
167 WHERE account_analytic_line.account_id IN %s \
168 AND account_analytic_journal.type='general' \
169 AND invoice_id IS NULL \
170 AND to_invoice IS NOT NULL \
171 GROUP BY account_analytic_line.account_id;",(parent_ids,))
172 for account_id, sua in cr.fetchall():
173 if account_id not in res:
175 res[account_id][f] = round(sua, dp)
176 for account in accounts:
177 for child in account.child_ids:
178 if account.id != child.id:
179 res[account.id][f] += res.get(child.id, {}).get(f, 0.0)
181 res[id][f] = round(res[id][f], dp)
182 elif f == 'hours_quantity':
186 cr.execute("SELECT account_analytic_line.account_id, COALESCE(SUM(unit_amount), 0.0) \
187 FROM account_analytic_line \
188 JOIN account_analytic_journal \
189 ON account_analytic_line.journal_id = account_analytic_journal.id \
190 WHERE account_analytic_line.account_id IN %s \
191 AND account_analytic_journal.type='general' \
192 GROUP BY account_analytic_line.account_id",(parent_ids,))
194 for account_id, hq in ff:
195 if account_id not in res:
197 res[account_id][f] = round(hq, dp)
198 for account in accounts:
199 for child in account.child_ids:
200 if account.id != child.id:
201 if account.id not in res:
202 res[account.id] = {f: 0.0}
203 res[account.id][f] += res.get(child.id, {}).get(f, 0.0)
205 res[id][f] = round(res[id][f], dp)
206 elif f == 'ca_theorical':
207 # TODO Take care of pricelist and purchase !
212 # This computation doesn't take care of pricelist !
213 # Just consider list_price
215 cr.execute("""SELECT account_analytic_line.account_id AS account_id, \
216 COALESCE(SUM((account_analytic_line.unit_amount * pt.list_price) \
217 - (account_analytic_line.unit_amount * pt.list_price \
218 * hr.factor)), 0.0) AS somme
219 FROM account_analytic_line \
220 LEFT JOIN account_analytic_journal \
221 ON (account_analytic_line.journal_id = account_analytic_journal.id) \
222 JOIN product_product pp \
223 ON (account_analytic_line.product_id = pp.id) \
224 JOIN product_template pt \
225 ON (pp.product_tmpl_id = pt.id) \
226 JOIN account_analytic_account a \
227 ON (a.id=account_analytic_line.account_id) \
228 JOIN hr_timesheet_invoice_factor hr \
229 ON (hr.id=a.to_invoice) \
230 WHERE account_analytic_line.account_id IN %s \
231 AND a.to_invoice IS NOT NULL \
232 AND account_analytic_journal.type IN ('purchase', 'general')
233 GROUP BY account_analytic_line.account_id""",(parent_ids,))
234 for account_id, sum in cr.fetchall():
235 res2[account_id] = round(sum, dp)
237 for account in accounts:
238 res2.setdefault(account.id, 0.0)
239 for child in account.child_ids:
240 if account.id != child.id:
241 if account.id not in res:
242 res[account.id] = {f: 0.0}
243 res[account.id][f] += res.get(child.id, {}).get(f, 0.0)
244 res[account.id][f] += res2.get(child.id, 0.0)
246 # sum both result on account_id
248 res[id][f] = round(res[id][f], dp) + round(res2.get(id, 0.0), dp)
252 def _ca_invoiced_calc(self, cr, uid, ids, name, arg, context=None):
255 child_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)], context=context))
264 cr.execute("SELECT account_analytic_line.account_id, COALESCE(SUM(amount), 0.0) \
265 FROM account_analytic_line \
266 JOIN account_analytic_journal \
267 ON account_analytic_line.journal_id = account_analytic_journal.id \
268 WHERE account_analytic_line.account_id IN %s \
269 AND account_analytic_journal.type = 'sale' \
270 GROUP BY account_analytic_line.account_id", (child_ids,))
271 for account_id, sum in cr.fetchall():
272 res[account_id][name] = round(sum,2)
273 data = self._compute_level_tree(cr, uid, ids, child_ids, res, [name], context=context)
275 res_final[i] = data[i][name]
278 def _total_cost_calc(self, cr, uid, ids, name, arg, context=None):
281 child_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)], context=context))
291 cr.execute("""SELECT account_analytic_line.account_id, COALESCE(SUM(amount), 0.0) \
292 FROM account_analytic_line \
293 JOIN account_analytic_journal \
294 ON account_analytic_line.journal_id = account_analytic_journal.id \
295 WHERE account_analytic_line.account_id IN %s \
297 GROUP BY account_analytic_line.account_id""",(child_ids,))
298 for account_id, sum in cr.fetchall():
299 res[account_id][name] = round(sum,2)
300 data = self._compute_level_tree(cr, uid, ids, child_ids, res, [name], context)
302 res_final[i] = data[i][name]
305 def _remaining_hours_calc(self, cr, uid, ids, name, arg, context=None):
307 for account in self.browse(cr, uid, ids, context=context):
308 if account.quantity_max != 0:
309 res[account.id] = account.quantity_max - account.hours_quantity
311 res[account.id] = 0.0
313 res[id] = round(res.get(id, 0.0),2)
316 def _hours_qtt_invoiced_calc(self, cr, uid, ids, name, arg, context=None):
318 for account in self.browse(cr, uid, ids, context=context):
319 res[account.id] = account.hours_quantity - account.hours_qtt_non_invoiced
320 if res[account.id] < 0:
321 res[account.id] = 0.0
323 res[id] = round(res.get(id, 0.0),2)
326 def _revenue_per_hour_calc(self, cr, uid, ids, name, arg, context=None):
328 for account in self.browse(cr, uid, ids, context=context):
329 if account.hours_qtt_invoiced == 0:
332 res[account.id] = account.ca_invoiced / account.hours_qtt_invoiced
334 res[id] = round(res.get(id, 0.0),2)
337 def _real_margin_rate_calc(self, cr, uid, ids, name, arg, context=None):
339 for account in self.browse(cr, uid, ids, context=context):
340 if account.ca_invoiced == 0:
342 elif account.total_cost != 0.0:
343 res[account.id] = -(account.real_margin / account.total_cost) * 100
345 res[account.id] = 0.0
347 res[id] = round(res.get(id, 0.0),2)
350 def _remaining_ca_calc(self, cr, uid, ids, name, arg, context=None):
352 for account in self.browse(cr, uid, ids, context=context):
353 if account.amount_max != 0:
354 res[account.id] = account.amount_max - account.ca_invoiced
358 res[id] = round(res.get(id, 0.0),2)
361 def _real_margin_calc(self, cr, uid, ids, name, arg, context=None):
363 for account in self.browse(cr, uid, ids, context=context):
364 res[account.id] = account.ca_invoiced + account.total_cost
366 res[id] = round(res.get(id, 0.0),2)
369 def _theorical_margin_calc(self, cr, uid, ids, name, arg, context=None):
371 for account in self.browse(cr, uid, ids, context=context):
372 res[account.id] = account.ca_theorical + account.total_cost
374 res[id] = round(res.get(id, 0.0),2)
377 def _is_overdue_quantity(self, cr, uid, ids, fieldnames, args, context=None):
378 result = dict.fromkeys(ids, 0)
380 for record in self.browse(cr, uid, ids, context=context):
381 if record.quantity == 0.0 and record.quantity_max == 0.0:
382 result[record.id] = 0
384 result[record.id] = int(record.quantity >= record.quantity_max)
388 def _get_analytic_account(self, cr, uid, ids, context=None):
390 for line in self.pool.get('account.analytic.line').browse(cr, uid, ids, context=context):
391 result.add(line.account_id.id)
395 'is_overdue_quantity' : fields.function(_is_overdue_quantity, method=True, type='boolean', string='Overdue Quantity',
397 'account.analytic.line' : (_get_analytic_account, None, 20),
399 'ca_invoiced': fields.function(_ca_invoiced_calc, method=True, type='float', string='Invoiced Amount',
400 help="Total customer invoiced amount for this account.",
401 digits_compute=dp.get_precision('Account')),
402 'total_cost': fields.function(_total_cost_calc, method=True, type='float', string='Total Costs',
403 help="Total of costs for this account. It includes real costs (from invoices) and indirect costs, like time spent on timesheets.",
404 digits_compute=dp.get_precision('Account')),
405 'ca_to_invoice': fields.function(_analysis_all, method=True, multi='analytic_analysis', type='float', string='Uninvoiced Amount',
406 help="If invoice from analytic account, the remaining amount you can invoice to the customer based on the total costs.",
407 digits_compute=dp.get_precision('Account')),
408 'ca_theorical': fields.function(_analysis_all, method=True, multi='analytic_analysis', type='float', string='Theoretical Revenue',
409 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.",
410 digits_compute=dp.get_precision('Account')),
411 'hours_quantity': fields.function(_analysis_all, method=True, multi='analytic_analysis', type='float', string='Hours Tot',
412 help="Number of hours you spent on the analytic account (from timesheet). It computes on all journal of type 'general'."),
413 'last_invoice_date': fields.function(_analysis_all, method=True, multi='analytic_analysis', type='date', string='Last Invoice Date',
414 help="If invoice from the costs, this is the date of the latest invoiced."),
415 'last_worked_invoiced_date': fields.function(_analysis_all, method=True, multi='analytic_analysis', type='date', string='Date of Last Invoiced Cost',
416 help="If invoice from the costs, this is the date of the latest work or cost that have been invoiced."),
417 'last_worked_date': fields.function(_analysis_all, method=True, multi='analytic_analysis', type='date', string='Date of Last Cost/Work',
418 help="Date of the latest work done on this account."),
419 'hours_qtt_non_invoiced': fields.function(_analysis_all, method=True, multi='analytic_analysis', type='float', string='Uninvoiced Hours',
420 help="Number of hours (from journal of type 'general') that can be invoiced if you invoice based on analytic account."),
421 'hours_qtt_invoiced': fields.function(_hours_qtt_invoiced_calc, method=True, type='float', string='Invoiced Hours',
422 help="Number of hours that can be invoiced plus those that already have been invoiced."),
423 'remaining_hours': fields.function(_remaining_hours_calc, method=True, type='float', string='Remaining Hours',
424 help="Computed using the formula: Maximum Quantity - Hours Tot."),
425 'remaining_ca': fields.function(_remaining_ca_calc, method=True, type='float', string='Remaining Revenue',
426 help="Computed using the formula: Max Invoice Price - Invoiced Amount.",
427 digits_compute=dp.get_precision('Account')),
428 'revenue_per_hour': fields.function(_revenue_per_hour_calc, method=True, type='float', string='Revenue per Hours (real)',
429 help="Computed using the formula: Invoiced Amount / Hours Tot.",
430 digits_compute=dp.get_precision('Account')),
431 'real_margin': fields.function(_real_margin_calc, method=True, type='float', string='Real Margin',
432 help="Computed using the formula: Invoiced Amount - Total Costs.",
433 digits_compute=dp.get_precision('Account')),
434 'theorical_margin': fields.function(_theorical_margin_calc, method=True, type='float', string='Theoretical Margin',
435 help="Computed using the formula: Theorial Revenue - Total Costs",
436 digits_compute=dp.get_precision('Account')),
437 'real_margin_rate': fields.function(_real_margin_rate_calc, method=True, type='float', string='Real Margin Rate (%)',
438 help="Computes using the formula: (Real Margin / Total Costs) * 100.",
439 digits_compute=dp.get_precision('Account')),
440 'month_ids': fields.function(_analysis_all, method=True, multi='analytic_analysis', type='many2many', relation='account_analytic_analysis.summary.month', string='Month'),
441 'user_ids': fields.function(_analysis_all, method=True, multi='analytic_analysis', type="many2many", relation='account_analytic_analysis.summary.user', string='User'),
444 account_analytic_account()
446 class account_analytic_account_summary_user(osv.osv):
447 _name = "account_analytic_analysis.summary.user"
448 _description = "Hours Summary by User"
453 def _unit_amount(self, cr, uid, ids, name, arg, context=None):
455 account_obj = self.pool.get('account.analytic.account')
456 cr.execute('SELECT MAX(id) FROM res_users')
457 max_user = cr.fetchone()[0]
458 account_ids = [int(str(x/max_user - (x%max_user == 0 and 1 or 0))) for x in ids]
459 user_ids = [int(str(x-((x/max_user - (x%max_user == 0 and 1 or 0)) *max_user))) for x in ids]
460 parent_ids = tuple(account_obj.search(cr, uid, [('parent_id', 'child_of', account_ids)], context=context))
462 cr.execute('SELECT id, unit_amount ' \
463 'FROM account_analytic_analysis_summary_user ' \
464 'WHERE account_id IN %s ' \
465 'AND "user" IN %s',(parent_ids, tuple(user_ids),))
466 for sum_id, unit_amount in cr.fetchall():
467 res[sum_id] = unit_amount
469 res.setdefault(obj_id, 0.0)
470 for child_id in account_obj.search(cr, uid,
471 [('parent_id', 'child_of', [int(str(obj_id/max_user - (obj_id%max_user == 0 and 1 or 0)))])]):
472 if child_id != int(str(obj_id/max_user - (obj_id%max_user == 0 and 1 or 0))):
473 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)
475 res[id] = round(res.get(id, 0.0), 2)
479 'account_id': fields.many2one('account.analytic.account', 'Analytic Account', readonly=True),
480 'unit_amount': fields.function(_unit_amount, method=True, type='float',
481 string='Total Time'),
482 'user': fields.many2one('res.users', 'User'),
486 tools.sql.drop_view_if_exists(cr, 'account_analytic_analysis_summary_user')
487 cr.execute('CREATE OR REPLACE VIEW account_analytic_analysis_summary_user AS (' \
489 '(u.account_id * u.max_user) + u."user" AS id, ' \
490 'u.account_id AS account_id, ' \
491 'u."user" AS "user", ' \
492 'COALESCE(SUM(l.unit_amount), 0.0) AS unit_amount ' \
495 'a.id AS account_id, ' \
496 'u1.id AS "user", ' \
497 'MAX(u2.id) AS max_user ' \
499 'res_users AS u1, ' \
500 'res_users AS u2, ' \
501 'account_analytic_account AS a ' \
502 'GROUP BY u1.id, a.id ' \
506 'l.account_id AS account_id, ' \
507 'l.user_id AS "user", ' \
508 'SUM(l.unit_amount) AS unit_amount ' \
509 'FROM account_analytic_line AS l, ' \
510 'account_analytic_journal AS j ' \
511 'WHERE (j.type = \'general\') and (j.id=l.journal_id) ' \
512 'GROUP BY l.account_id, l.user_id ' \
515 'u.account_id = l.account_id ' \
516 'AND u."user" = l."user"' \
518 'GROUP BY u."user", u.account_id, u.max_user' \
521 def _read_flat(self, cr, user, ids, fields, context=None, load='_classic_read'):
528 fields = self._columns.keys()
529 res_trans_obj = self.pool.get('ir.translation')
531 # construct a clause for the rules:
532 d1, d2, tables = self.pool.get('ir.rule').domain_get(cr, user, self._name, 'read', context=context)
534 # all inherited fields + all non inherited fields for which the attribute whose name is in load is True
535 fields_pre = filter(lambda x: x in self._columns and getattr(self._columns[x],'_classic_write'), fields) + self._inherits.values()
537 cr.execute('SELECT MAX(id) FROM res_users')
538 max_user = cr.fetchone()[0]
540 fields_pre2 = map(lambda x: (x in ('create_date', 'write_date')) and ('date_trunc(\'second\', '+x+') as '+x) or '"'+x+'"', fields_pre)
541 for i in range(0, len(ids), cr.IN_MAX):
542 sub_ids = ids[i:i+cr.IN_MAX]
544 cr.execute('SELECT %s FROM \"%s\" WHERE id IN (%s) ' \
545 'AND account_id IN (%s) ' \
546 'AND "user" IN (%s) AND %s ORDER BY %s' % \
547 (','.join(fields_pre2 + ['id']), self._table,
548 ','.join([str(x) for x in sub_ids]),
549 ','.join([str(x/max_user - (x%max_user == 0 and 1 or 0)) for x in sub_ids]),
550 ','.join([str(x-((x/max_user - (x%max_user == 0 and 1 or 0)) *max_user)) for x in sub_ids]), d1,
552 if not cr.rowcount == len({}.fromkeys(sub_ids)):
553 raise except_orm(_('AccessError'),
554 _('You try to bypass an access rule (Document type: %s).') % self._description)
556 cr.execute('SELECT %s FROM \"%s\" WHERE id IN (%s) ' \
557 'AND account_id IN (%s) ' \
558 'AND "user" IN (%s) ORDER BY %s' % \
559 (','.join(fields_pre2 + ['id']), self._table,
560 ','.join([str(x) for x in sub_ids]),
561 ','.join([str(x/max_user - (x%max_user == 0 and 1 or 0)) for x in sub_ids]),
562 ','.join([str(x-((x/max_user - (x%max_user == 0 and 1 or 0)) *max_user)) for x in sub_ids]),
564 res.extend(cr.dictfetchall())
566 res = map(lambda x: {'id': x}, ids)
568 if self._columns[f].translate:
569 ids = map(lambda x: x['id'], res)
570 res_trans = res_trans_obj._get_ids(cr, user, self._name+','+f, 'model', context.get('lang', False) or 'en_US', ids)
572 r[f] = res_trans.get(r['id'], False) or r[f]
574 for table in self._inherits:
575 col = self._inherits[table]
576 cols = intersect(self._inherit_fields.keys(), fields)
579 res2 = self.pool.get(table).read(cr, user, [x[col] for x in res], cols, context, load)
587 record.update(res3[record[col]])
588 if col not in fields:
591 # all fields which need to be post-processed by a simple function (symbol_get)
592 fields_post = filter(lambda x: x in self._columns and self._columns[x]._symbol_get, fields)
594 # maybe it would be faster to iterate on the fields then on res, so that we wouldn't need
595 # to get the _symbol_get in each occurence
597 for f in fields_post:
598 r[f] = self.columns[f]._symbol_get(r[f])
599 ids = map(lambda x: x['id'], res)
601 # all non inherited fields for which the attribute whose name is in load is False
602 fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields)
603 for f in fields_post:
604 # get the value of that field for all records/ids
605 res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
607 record[f] = res2[record['id']]
611 account_analytic_account_summary_user()
613 class account_analytic_account_summary_month(osv.osv):
614 _name = "account_analytic_analysis.summary.month"
615 _description = "Hours summary by month"
619 def _unit_amount(self, cr, uid, ids, name, arg, context=None):
621 account_obj = self.pool.get('account.analytic.account')
622 account_ids = [int(str(int(x))[:-6]) for x in ids]
623 month_ids = [int(str(int(x))[-6:]) for x in ids]
624 parent_ids = tuple(account_obj.search(cr, uid, [('parent_id', 'child_of', account_ids)], context=context))
626 cr.execute('SELECT id, unit_amount ' \
627 'FROM account_analytic_analysis_summary_month ' \
628 'WHERE account_id IN %s ' \
629 'AND month_id IN %s ',(parent_ids, tuple(month_ids),))
630 for sum_id, unit_amount in cr.fetchall():
631 res[sum_id] = unit_amount
633 res.setdefault(obj_id, 0.0)
634 for child_id in account_obj.search(cr, uid,
635 [('parent_id', 'child_of', [int(str(int(obj_id))[:-6])])]):
636 if child_id != int(str(int(obj_id))[:-6]):
637 res[obj_id] += res.get(int(child_id * 1000000 + int(str(int(obj_id))[-6:])), 0.0)
639 res[id] = round(res.get(id, 0.0), 2)
643 'account_id': fields.many2one('account.analytic.account', 'Analytic Account', readonly=True),
644 'unit_amount': fields.function(_unit_amount, method=True, type='float', string='Total Time'),
645 'month': fields.char('Month', size=32, readonly=True),
649 tools.sql.drop_view_if_exists(cr, 'account_analytic_analysis_summary_month')
650 cr.execute('CREATE VIEW account_analytic_analysis_summary_month AS (' \
652 '(TO_NUMBER(TO_CHAR(d.month, \'YYYYMM\'), \'999999\') + (d.account_id * 1000000::bigint))::bigint AS id, ' \
653 'd.account_id AS account_id, ' \
654 'TO_CHAR(d.month, \'Mon YYYY\') AS month, ' \
655 'TO_NUMBER(TO_CHAR(d.month, \'YYYYMM\'), \'999999\') AS month_id, ' \
656 'COALESCE(SUM(l.unit_amount), 0.0) AS unit_amount ' \
663 'a.id AS account_id, ' \
664 'l.month AS month ' \
667 'DATE_TRUNC(\'month\', l.date) AS month ' \
668 'FROM account_analytic_line AS l, ' \
669 'account_analytic_journal AS j ' \
670 'WHERE j.type = \'general\' ' \
671 'GROUP BY DATE_TRUNC(\'month\', l.date) ' \
673 'account_analytic_account AS a ' \
674 'GROUP BY l.month, a.id ' \
676 'GROUP BY d2.account_id, d2.month ' \
680 'l.account_id AS account_id, ' \
681 'DATE_TRUNC(\'month\', l.date) AS month, ' \
682 'SUM(l.unit_amount) AS unit_amount ' \
683 'FROM account_analytic_line AS l, ' \
684 'account_analytic_journal AS j ' \
685 'WHERE (j.type = \'general\') and (j.id=l.journal_id) ' \
686 'GROUP BY l.account_id, DATE_TRUNC(\'month\', l.date) ' \
689 'd.account_id = l.account_id ' \
690 'AND d.month = l.month' \
692 'GROUP BY d.month, d.account_id ' \
695 def _read_flat(self, cr, user, ids, fields, context=None, load='_classic_read'):
702 fields = self._columns.keys()
703 res_trans_obj = self.pool.get('ir.translation')
704 # construct a clause for the rules:
705 d1, d2, tables= self.pool.get('ir.rule').domain_get(cr, user, self._name)
707 # all inherited fields + all non inherited fields for which the attribute whose name is in load is True
708 fields_pre = filter(lambda x: x in self._columns and getattr(self._columns[x],'_classic_write'), fields) + self._inherits.values()
711 fields_pre2 = map(lambda x: (x in ('create_date', 'write_date')) and ('date_trunc(\'second\', '+x+') as '+x) or '"'+x+'"', fields_pre)
712 for i in range(0, len(ids), cr.IN_MAX):
713 sub_ids = ids[i:i+cr.IN_MAX]
715 cr.execute('SELECT %s FROM \"%s\" WHERE id IN (%s) ' \
716 'AND account_id IN (%s) ' \
717 'AND month_id IN (%s) AND %s ORDER BY %s' % \
718 (','.join(fields_pre2 + ['id']), self._table,
719 ','.join([str(x) for x in sub_ids]),
720 ','.join([str(x)[:-6] for x in sub_ids]),
721 ','.join([str(x)[-6:] for x in sub_ids]), d1,
723 if not cr.rowcount == len({}.fromkeys(sub_ids)):
724 raise except_orm(_('AccessError'),
725 _('You try to bypass an access rule (Document type: %s).') % self._description)
727 cr.execute('SELECT %s FROM \"%s\" WHERE id IN (%s) ' \
728 'AND account_id IN (%s) ' \
729 'AND month_id IN (%s) ORDER BY %s' % \
730 (','.join(fields_pre2 + ['id']), self._table,
731 ','.join([str(x) for x in sub_ids]),
732 ','.join([str(x)[:-6] for x in sub_ids]),
733 ','.join([str(x)[-6:] for x in sub_ids]),
735 res.extend(cr.dictfetchall())
737 res = map(lambda x: {'id': x}, ids)
740 if self._columns[f].translate:
741 ids = map(lambda x: x['id'], res)
742 res_trans = res_trans_obj._get_ids(cr, user, self._name+','+f, 'model', context.get('lang', False) or 'en_US', ids)
744 r[f] = res_trans.get(r['id'], False) or r[f]
746 for table in self._inherits:
747 col = self._inherits[table]
748 cols = intersect(self._inherit_fields.keys(), fields)
751 res2 = self.pool.get(table).read(cr, user, [x[col] for x in res], cols, context, load)
759 record.update(res3[record[col]])
760 if col not in fields:
763 # all fields which need to be post-processed by a simple function (symbol_get)
764 fields_post = filter(lambda x: x in self._columns and self._columns[x]._symbol_get, fields)
766 # maybe it would be faster to iterate on the fields then on res, so that we wouldn't need
767 # to get the _symbol_get in each occurence
769 for f in fields_post:
770 r[f] = self.columns[f]._symbol_get(r[f])
771 ids = map(lambda x: x['id'], res)
773 # all non inherited fields for which the attribute whose name is in load is False
774 fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields)
775 for f in fields_post:
776 # get the value of that field for all records/ids
777 res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
779 record[f] = res2[record['id']]
782 account_analytic_account_summary_month()
784 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: