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 ##############################################################################
25 from openerp.osv import osv, fields
26 from openerp.osv.orm import intersect, except_orm
28 from openerp.tools.translate import _
30 from openerp.addons.decimal_precision import decimal_precision as dp
32 _logger = logging.getLogger(__name__)
34 class account_analytic_account(osv.osv):
35 _name = "account.analytic.account"
36 _inherit = "account.analytic.account"
38 def _analysis_all(self, cr, uid, ids, fields, arg, context=None):
40 res = dict([(i, {}) for i in ids])
41 parent_ids = tuple(ids) #We don't want consolidation for each of these fields because those complex computation is resource-greedy.
42 accounts = self.browse(cr, uid, ids, context=context)
46 cr.execute('SELECT MAX(id) FROM res_users')
47 max_user = cr.fetchone()[0]
49 cr.execute('SELECT DISTINCT("user") FROM account_analytic_analysis_summary_user ' \
50 'WHERE account_id IN %s AND unit_amount <> 0.0', (parent_ids,))
51 result = cr.fetchall()
55 res[id][f] = [int((id * max_user) + x[0]) for x in result]
56 elif f == 'month_ids':
58 cr.execute('SELECT DISTINCT(month_id) FROM account_analytic_analysis_summary_month ' \
59 'WHERE account_id IN %s AND unit_amount <> 0.0', (parent_ids,))
60 result = cr.fetchall()
64 res[id][f] = [int(id * 1000000 + int(x[0])) for x in result]
65 elif f == 'last_worked_invoiced_date':
69 cr.execute("SELECT account_analytic_line.account_id, MAX(date) \
70 FROM account_analytic_line \
71 WHERE account_id IN %s \
72 AND invoice_id IS NOT NULL \
73 GROUP BY account_analytic_line.account_id;", (parent_ids,))
74 for account_id, sum in cr.fetchall():
75 if account_id not in res:
77 res[account_id][f] = sum
78 elif f == 'ca_to_invoice':
82 for account in accounts:
84 SELECT product_id, sum(amount), user_id, to_invoice, sum(unit_amount), product_uom_id, line.name
85 FROM account_analytic_line line
86 LEFT JOIN account_analytic_journal journal ON (journal.id = line.journal_id)
88 AND journal.type != 'purchase'
89 AND invoice_id IS NULL
90 AND to_invoice IS NOT NULL
91 GROUP BY product_id, user_id, to_invoice, product_uom_id, line.name""", (account.id,))
93 res[account.id][f] = 0.0
94 for product_id, price, user_id, factor_id, qty, uom, line_name in cr.fetchall():
97 price = self.pool.get('account.analytic.line')._get_invoice_price(cr, uid, account, product_id, user_id, qty, context)
98 factor = self.pool.get('hr_timesheet_invoice.factor').browse(cr, uid, factor_id, context=context)
99 res[account.id][f] += price * qty * (100-factor.factor or 0.0) / 100.0
101 # sum both result on account_id
103 res[id][f] = round(res.get(id, {}).get(f, 0.0), dp) + round(res2.get(id, 0.0), 2)
104 elif f == 'last_invoice_date':
108 cr.execute ("SELECT account_analytic_line.account_id, \
109 DATE(MAX(account_invoice.date_invoice)) \
110 FROM account_analytic_line \
111 JOIN account_invoice \
112 ON account_analytic_line.invoice_id = account_invoice.id \
113 WHERE account_analytic_line.account_id IN %s \
114 AND account_analytic_line.invoice_id IS NOT NULL \
115 GROUP BY account_analytic_line.account_id",(parent_ids,))
116 for account_id, lid in cr.fetchall():
117 res[account_id][f] = lid
118 elif f == 'last_worked_date':
122 cr.execute("SELECT account_analytic_line.account_id, MAX(date) \
123 FROM account_analytic_line \
124 WHERE account_id IN %s \
125 AND invoice_id IS NULL \
126 GROUP BY account_analytic_line.account_id",(parent_ids,))
127 for account_id, lwd in cr.fetchall():
128 if account_id not in res:
130 res[account_id][f] = lwd
131 elif f == 'hours_qtt_non_invoiced':
135 cr.execute("SELECT account_analytic_line.account_id, COALESCE(SUM(unit_amount), 0.0) \
136 FROM account_analytic_line \
137 JOIN account_analytic_journal \
138 ON account_analytic_line.journal_id = account_analytic_journal.id \
139 WHERE account_analytic_line.account_id IN %s \
140 AND account_analytic_journal.type='general' \
141 AND invoice_id IS NULL \
142 AND to_invoice IS NOT NULL \
143 GROUP BY account_analytic_line.account_id;",(parent_ids,))
144 for account_id, sua in cr.fetchall():
145 if account_id not in res:
147 res[account_id][f] = round(sua, dp)
149 res[id][f] = round(res[id][f], dp)
150 elif f == 'hours_quantity':
154 cr.execute("SELECT account_analytic_line.account_id, COALESCE(SUM(unit_amount), 0.0) \
155 FROM account_analytic_line \
156 JOIN account_analytic_journal \
157 ON account_analytic_line.journal_id = account_analytic_journal.id \
158 WHERE account_analytic_line.account_id IN %s \
159 AND account_analytic_journal.type='general' \
160 GROUP BY account_analytic_line.account_id",(parent_ids,))
162 for account_id, hq in ff:
163 if account_id not in res:
165 res[account_id][f] = round(hq, dp)
167 res[id][f] = round(res[id][f], dp)
168 elif f == 'ca_theorical':
169 # TODO Take care of pricelist and purchase !
173 # This computation doesn't take care of pricelist !
174 # Just consider list_price
176 cr.execute("""SELECT account_analytic_line.account_id AS account_id, \
177 COALESCE(SUM((account_analytic_line.unit_amount * pt.list_price) \
178 - (account_analytic_line.unit_amount * pt.list_price \
179 * hr.factor)), 0.0) AS somme
180 FROM account_analytic_line \
181 LEFT JOIN account_analytic_journal \
182 ON (account_analytic_line.journal_id = account_analytic_journal.id) \
183 JOIN product_product pp \
184 ON (account_analytic_line.product_id = pp.id) \
185 JOIN product_template pt \
186 ON (pp.product_tmpl_id = pt.id) \
187 JOIN account_analytic_account a \
188 ON (a.id=account_analytic_line.account_id) \
189 JOIN hr_timesheet_invoice_factor hr \
190 ON (hr.id=a.to_invoice) \
191 WHERE account_analytic_line.account_id IN %s \
192 AND a.to_invoice IS NOT NULL \
193 AND account_analytic_journal.type IN ('purchase', 'general')
194 GROUP BY account_analytic_line.account_id""",(parent_ids,))
195 for account_id, sum in cr.fetchall():
196 res[account_id][f] = round(sum, dp)
199 def _ca_invoiced_calc(self, cr, uid, ids, name, arg, context=None):
202 child_ids = tuple(ids) #We don't want consolidation for each of these fields because those complex computation is resource-greedy.
209 cr.execute("SELECT account_analytic_line.account_id, COALESCE(SUM(amount), 0.0) \
210 FROM account_analytic_line \
211 JOIN account_analytic_journal \
212 ON account_analytic_line.journal_id = account_analytic_journal.id \
213 WHERE account_analytic_line.account_id IN %s \
214 AND account_analytic_journal.type = 'sale' \
215 GROUP BY account_analytic_line.account_id", (child_ids,))
216 for account_id, sum in cr.fetchall():
217 res[account_id] = round(sum,2)
218 for acc in self.browse(cr, uid, res.keys(), context=context):
219 res[acc.id] = res[acc.id] - (acc.timesheet_ca_invoiced or 0.0)
223 def _total_cost_calc(self, cr, uid, ids, name, arg, context=None):
226 child_ids = tuple(ids) #We don't want consolidation for each of these fields because those complex computation is resource-greedy.
232 cr.execute("""SELECT account_analytic_line.account_id, COALESCE(SUM(amount), 0.0) \
233 FROM account_analytic_line \
234 JOIN account_analytic_journal \
235 ON account_analytic_line.journal_id = account_analytic_journal.id \
236 WHERE account_analytic_line.account_id IN %s \
238 GROUP BY account_analytic_line.account_id""",(child_ids,))
239 for account_id, sum in cr.fetchall():
240 res[account_id] = round(sum,2)
244 def _remaining_hours_calc(self, cr, uid, ids, name, arg, context=None):
246 for account in self.browse(cr, uid, ids, context=context):
247 if account.quantity_max != 0:
248 res[account.id] = account.quantity_max - account.hours_quantity
250 res[account.id] = 0.0
252 res[id] = round(res.get(id, 0.0),2)
255 def _remaining_hours_to_invoice_calc(self, cr, uid, ids, name, arg, context=None):
257 for account in self.browse(cr, uid, ids, context=context):
258 res[account.id] = max(account.hours_qtt_est - account.timesheet_ca_invoiced, account.ca_to_invoice)
261 def _hours_qtt_invoiced_calc(self, cr, uid, ids, name, arg, context=None):
263 for account in self.browse(cr, uid, ids, context=context):
264 res[account.id] = account.hours_quantity - account.hours_qtt_non_invoiced
265 if res[account.id] < 0:
266 res[account.id] = 0.0
268 res[id] = round(res.get(id, 0.0),2)
271 def _revenue_per_hour_calc(self, cr, uid, ids, name, arg, context=None):
273 for account in self.browse(cr, uid, ids, context=context):
274 if account.hours_qtt_invoiced == 0:
277 res[account.id] = account.ca_invoiced / account.hours_qtt_invoiced
279 res[id] = round(res.get(id, 0.0),2)
282 def _real_margin_rate_calc(self, cr, uid, ids, name, arg, context=None):
284 for account in self.browse(cr, uid, ids, context=context):
285 if account.ca_invoiced == 0:
287 elif account.total_cost != 0.0:
288 res[account.id] = -(account.real_margin / account.total_cost) * 100
290 res[account.id] = 0.0
292 res[id] = round(res.get(id, 0.0),2)
295 def _fix_price_to_invoice_calc(self, cr, uid, ids, name, arg, context=None):
296 sale_obj = self.pool.get('sale.order')
298 for account in self.browse(cr, uid, ids, context=context):
299 res[account.id] = 0.0
300 sale_ids = sale_obj.search(cr, uid, [('project_id','=', account.id), ('state', '=', 'manual')], context=context)
301 for sale in sale_obj.browse(cr, uid, sale_ids, context=context):
302 res[account.id] += sale.amount_untaxed
303 for invoice in sale.invoice_ids:
304 if invoice.state != 'cancel':
305 res[account.id] -= invoice.amount_untaxed
308 def _timesheet_ca_invoiced_calc(self, cr, uid, ids, name, arg, context=None):
309 lines_obj = self.pool.get('account.analytic.line')
312 for account in self.browse(cr, uid, ids, context=context):
313 res[account.id] = 0.0
314 line_ids = lines_obj.search(cr, uid, [('account_id','=', account.id), ('invoice_id','!=',False), ('to_invoice','!=', False), ('journal_id.type', '=', 'general')], context=context)
315 for line in lines_obj.browse(cr, uid, line_ids, context=context):
316 if line.invoice_id not in inv_ids:
317 inv_ids.append(line.invoice_id)
318 res[account.id] += line.invoice_id.amount_untaxed
321 def _remaining_ca_calc(self, cr, uid, ids, name, arg, context=None):
323 for account in self.browse(cr, uid, ids, context=context):
324 res[account.id] = max(account.amount_max - account.ca_invoiced, account.fix_price_to_invoice)
327 def _real_margin_calc(self, cr, uid, ids, name, arg, context=None):
329 for account in self.browse(cr, uid, ids, context=context):
330 res[account.id] = account.ca_invoiced + account.total_cost
332 res[id] = round(res.get(id, 0.0),2)
335 def _theorical_margin_calc(self, cr, uid, ids, name, arg, context=None):
337 for account in self.browse(cr, uid, ids, context=context):
338 res[account.id] = account.ca_theorical + account.total_cost
340 res[id] = round(res.get(id, 0.0),2)
343 def _is_overdue_quantity(self, cr, uid, ids, fieldnames, args, context=None):
344 result = dict.fromkeys(ids, 0)
345 for record in self.browse(cr, uid, ids, context=context):
346 if record.quantity_max > 0.0:
347 result[record.id] = int(record.hours_quantity >= record.quantity_max)
349 result[record.id] = 0
352 def _get_analytic_account(self, cr, uid, ids, context=None):
354 for line in self.pool.get('account.analytic.line').browse(cr, uid, ids, context=context):
355 result.add(line.account_id.id)
358 def _get_total_estimation(self, account):
360 if account.fix_price_invoices:
361 tot_est += account.amount_max
362 if account.invoice_on_timesheets:
363 tot_est += account.hours_qtt_est
366 def _get_total_invoiced(self, account):
368 if account.fix_price_invoices:
369 total_invoiced += account.ca_invoiced
370 if account.invoice_on_timesheets:
371 total_invoiced += account.timesheet_ca_invoiced
372 return total_invoiced
374 def _get_total_remaining(self, account):
375 total_remaining = 0.0
376 if account.fix_price_invoices:
377 total_remaining += account.remaining_ca
378 if account.invoice_on_timesheets:
379 total_remaining += account.remaining_hours_to_invoice
380 return total_remaining
382 def _get_total_toinvoice(self, account):
383 total_toinvoice = 0.0
384 if account.fix_price_invoices:
385 total_toinvoice += account.fix_price_to_invoice
386 if account.invoice_on_timesheets:
387 total_toinvoice += account.ca_to_invoice
388 return total_toinvoice
390 def _sum_of_fields(self, cr, uid, ids, name, arg, context=None):
391 res = dict([(i, {}) for i in ids])
392 for account in self.browse(cr, uid, ids, context=context):
393 res[account.id]['est_total'] = self._get_total_estimation(account)
394 res[account.id]['invoiced_total'] = self._get_total_invoiced(account)
395 res[account.id]['remaining_total'] = self._get_total_remaining(account)
396 res[account.id]['toinvoice_total'] = self._get_total_toinvoice(account)
400 'is_overdue_quantity' : fields.function(_is_overdue_quantity, method=True, type='boolean', string='Overdue Quantity',
402 'account.analytic.line' : (_get_analytic_account, None, 20),
404 'ca_invoiced': fields.function(_ca_invoiced_calc, type='float', string='Invoiced Amount',
405 help="Total customer invoiced amount for this account.",
406 digits_compute=dp.get_precision('Account')),
407 'total_cost': fields.function(_total_cost_calc, type='float', string='Total Costs',
408 help="Total of costs for this account. It includes real costs (from invoices) and indirect costs, like time spent on timesheets.",
409 digits_compute=dp.get_precision('Account')),
410 'ca_to_invoice': fields.function(_analysis_all, multi='analytic_analysis', type='float', string='Uninvoiced Amount',
411 help="If invoice from analytic account, the remaining amount you can invoice to the customer based on the total costs.",
412 digits_compute=dp.get_precision('Account')),
413 'ca_theorical': fields.function(_analysis_all, multi='analytic_analysis', type='float', string='Theoretical Revenue',
414 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.",
415 digits_compute=dp.get_precision('Account')),
416 'hours_quantity': fields.function(_analysis_all, multi='analytic_analysis', type='float', string='Total Worked Time',
417 help="Number of time you spent on the analytic account (from timesheet). It computes quantities on all journal of type 'general'."),
418 'last_invoice_date': fields.function(_analysis_all, multi='analytic_analysis', type='date', string='Last Invoice Date',
419 help="If invoice from the costs, this is the date of the latest invoiced."),
420 'last_worked_invoiced_date': fields.function(_analysis_all, multi='analytic_analysis', type='date', string='Date of Last Invoiced Cost',
421 help="If invoice from the costs, this is the date of the latest work or cost that have been invoiced."),
422 'last_worked_date': fields.function(_analysis_all, multi='analytic_analysis', type='date', string='Date of Last Cost/Work',
423 help="Date of the latest work done on this account."),
424 'hours_qtt_non_invoiced': fields.function(_analysis_all, multi='analytic_analysis', type='float', string='Uninvoiced Time',
425 help="Number of time (hours/days) (from journal of type 'general') that can be invoiced if you invoice based on analytic account."),
426 'hours_qtt_invoiced': fields.function(_hours_qtt_invoiced_calc, type='float', string='Invoiced Time',
427 help="Number of time (hours/days) that can be invoiced plus those that already have been invoiced."),
428 'remaining_hours': fields.function(_remaining_hours_calc, type='float', string='Remaining Time',
429 help="Computed using the formula: Maximum Time - Total Worked Time"),
430 'remaining_hours_to_invoice': fields.function(_remaining_hours_to_invoice_calc, type='float', string='Remaining Time',
431 help="Computed using the formula: Maximum Time - Total Invoiced Time"),
432 'fix_price_to_invoice': fields.function(_fix_price_to_invoice_calc, type='float', string='Remaining Time',
433 help="Sum of quotations for this contract."),
434 'timesheet_ca_invoiced': fields.function(_timesheet_ca_invoiced_calc, type='float', string='Remaining Time',
435 help="Sum of timesheet lines invoiced for this contract."),
436 'remaining_ca': fields.function(_remaining_ca_calc, type='float', string='Remaining Revenue',
437 help="Computed using the formula: Max Invoice Price - Invoiced Amount.",
438 digits_compute=dp.get_precision('Account')),
439 'revenue_per_hour': fields.function(_revenue_per_hour_calc, type='float', string='Revenue per Time (real)',
440 help="Computed using the formula: Invoiced Amount / Total Time",
441 digits_compute=dp.get_precision('Account')),
442 'real_margin': fields.function(_real_margin_calc, type='float', string='Real Margin',
443 help="Computed using the formula: Invoiced Amount - Total Costs.",
444 digits_compute=dp.get_precision('Account')),
445 'theorical_margin': fields.function(_theorical_margin_calc, type='float', string='Theoretical Margin',
446 help="Computed using the formula: Theoretical Revenue - Total Costs",
447 digits_compute=dp.get_precision('Account')),
448 'real_margin_rate': fields.function(_real_margin_rate_calc, type='float', string='Real Margin Rate (%)',
449 help="Computes using the formula: (Real Margin / Total Costs) * 100.",
450 digits_compute=dp.get_precision('Account')),
451 'fix_price_invoices' : fields.boolean('Fixed Price'),
452 'invoice_on_timesheets' : fields.boolean("On Timesheets"),
453 'month_ids': fields.function(_analysis_all, multi='analytic_analysis', type='many2many', relation='account_analytic_analysis.summary.month', string='Month'),
454 'user_ids': fields.function(_analysis_all, multi='analytic_analysis', type="many2many", relation='account_analytic_analysis.summary.user', string='User'),
455 'hours_qtt_est': fields.float('Estimation of Hours to Invoice'),
456 'est_total' : fields.function(_sum_of_fields, type="float",multi="sum_of_all", string="Total Estimation"),
457 'invoiced_total' : fields.function(_sum_of_fields, type="float",multi="sum_of_all", string="Total Invoiced"),
458 'remaining_total' : fields.function(_sum_of_fields, type="float",multi="sum_of_all", string="Total Remaining", help="Expectation of remaining income for this contract. Computed as the sum of remaining subtotals which, in turn, are computed as the maximum between '(Estimation - Invoiced)' and 'To Invoice' amounts"),
459 'toinvoice_total' : fields.function(_sum_of_fields, type="float",multi="sum_of_all", string="Total to Invoice", help=" Sum of everything that could be invoiced for this contract."),
462 def open_sale_order_lines(self,cr,uid,ids,context=None):
465 sale_ids = self.pool.get('sale.order').search(cr,uid,[('project_id','=',context.get('search_default_project_id',False)),('partner_id','in',context.get('search_default_partner_id',False))])
466 names = [record.name for record in self.browse(cr, uid, ids, context=context)]
467 name = _('Sales Order Lines of %s') % ','.join(names)
469 'type': 'ir.actions.act_window',
472 'view_mode': 'tree,form',
474 'domain' : [('order_id','in',sale_ids)],
475 'res_model': 'sale.order.line',
479 def on_change_template(self, cr, uid, ids, template_id, context=None):
482 res = super(account_analytic_account, self).on_change_template(cr, uid, ids, template_id, context=context)
483 if template_id and 'value' in res:
484 template = self.browse(cr, uid, template_id, context=context)
485 res['value']['fix_price_invoices'] = template.fix_price_invoices
486 res['value']['invoice_on_timesheets'] = template.invoice_on_timesheets
487 res['value']['hours_qtt_est'] = template.hours_qtt_est
488 res['value']['amount_max'] = template.amount_max
489 res['value']['to_invoice'] = template.to_invoice.id
490 res['value']['pricelist_id'] = template.pricelist_id.id
493 def cron_account_analytic_account(self, cr, uid, context=None):
498 def fill_remind(key, domain, write_pending=False):
500 ('type', '=', 'contract'),
501 ('partner_id', '!=', False),
502 ('manager_id', '!=', False),
503 ('manager_id.email', '!=', False),
505 base_domain.extend(domain)
507 accounts_ids = self.search(cr, uid, base_domain, context=context, order='name asc')
508 accounts = self.browse(cr, uid, accounts_ids, context=context)
509 for account in accounts:
511 account.write({'state' : 'pending'}, context=context)
512 remind_user = remind.setdefault(account.manager_id.id, {})
513 remind_type = remind_user.setdefault(key, {})
514 remind_partner = remind_type.setdefault(account.partner_id, []).append(account)
517 fill_remind("old", [('state', 'in', ['pending'])])
520 fill_remind("new", [('state', 'in', ['draft', 'open']), '|', '&', ('date', '!=', False), ('date', '<=', time.strftime('%Y-%m-%d')), ('is_overdue_quantity', '=', True)], True)
522 # Expires in less than 30 days
523 fill_remind("future", [('state', 'in', ['draft', 'open']), ('date', '!=', False), ('date', '<', (datetime.datetime.now() + datetime.timedelta(30)).strftime("%Y-%m-%d"))])
525 context['base_url'] = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
526 context['action_id'] = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account_analytic_analysis', 'action_account_analytic_overdue_all')[1]
527 template_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account_analytic_analysis', 'account_analytic_cron_email_template')[1]
528 for user_id, data in remind.items():
529 context["data"] = data
530 _logger.debug("Sending reminder to uid %s", user_id)
531 self.pool.get('email.template').send_mail(cr, uid, template_id, user_id, force_send=True, context=context)
535 def onchange_invoice_on_timesheets(self, cr, uid, ids, invoice_on_timesheets, context=None):
536 if not invoice_on_timesheets:
538 result = {'value': {'use_timesheets': True}}
540 to_invoice = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'hr_timesheet_invoice', 'timesheet_invoice_factor1')
541 result['value']['to_invoice'] = to_invoice[1]
546 class account_analytic_account_summary_user(osv.osv):
547 _name = "account_analytic_analysis.summary.user"
548 _description = "Hours Summary by User"
553 def _unit_amount(self, cr, uid, ids, name, arg, context=None):
555 account_obj = self.pool.get('account.analytic.account')
556 cr.execute('SELECT MAX(id) FROM res_users')
557 max_user = cr.fetchone()[0]
558 account_ids = [int(str(x/max_user - (x%max_user == 0 and 1 or 0))) for x in ids]
559 user_ids = [int(str(x-((x/max_user - (x%max_user == 0 and 1 or 0)) *max_user))) for x in ids]
560 parent_ids = tuple(account_ids) #We don't want consolidation for each of these fields because those complex computation is resource-greedy.
562 cr.execute('SELECT id, unit_amount ' \
563 'FROM account_analytic_analysis_summary_user ' \
564 'WHERE account_id IN %s ' \
565 'AND "user" IN %s',(parent_ids, tuple(user_ids),))
566 for sum_id, unit_amount in cr.fetchall():
567 res[sum_id] = unit_amount
569 res[id] = round(res.get(id, 0.0), 2)
573 'account_id': fields.many2one('account.analytic.account', 'Analytic Account', readonly=True),
574 'unit_amount': fields.float('Total Time'),
575 'user': fields.many2one('res.users', 'User'),
579 openerp.tools.sql.drop_view_if_exists(cr, 'account_analytic_analysis_summary_user')
580 cr.execute('''CREATE OR REPLACE VIEW account_analytic_analysis_summary_user AS (
582 (select max(id) as max_user from res_users)
585 l.account_id AS account_id,
586 coalesce(l.user_id, 0) AS user_id,
587 SUM(l.unit_amount) AS unit_amount
588 FROM account_analytic_line AS l,
589 account_analytic_journal AS j
590 WHERE (j.type = 'general' ) and (j.id=l.journal_id)
591 GROUP BY l.account_id, l.user_id
593 select (lu.account_id * mu.max_user) + lu.user_id as id,
594 lu.account_id as account_id,
595 lu.user_id as "user",
599 class account_analytic_account_summary_month(osv.osv):
600 _name = "account_analytic_analysis.summary.month"
601 _description = "Hours summary by month"
606 'account_id': fields.many2one('account.analytic.account', 'Analytic Account', readonly=True),
607 'unit_amount': fields.float('Total Time'),
608 'month': fields.char('Month', size=32, readonly=True),
612 openerp.tools.sql.drop_view_if_exists(cr, 'account_analytic_analysis_summary_month')
613 cr.execute('CREATE VIEW account_analytic_analysis_summary_month AS (' \
615 '(TO_NUMBER(TO_CHAR(d.month, \'YYYYMM\'), \'999999\') + (d.account_id * 1000000::bigint))::bigint AS id, ' \
616 'd.account_id AS account_id, ' \
617 'TO_CHAR(d.month, \'Mon YYYY\') AS month, ' \
618 'TO_NUMBER(TO_CHAR(d.month, \'YYYYMM\'), \'999999\') AS month_id, ' \
619 'COALESCE(SUM(l.unit_amount), 0.0) AS unit_amount ' \
626 'a.id AS account_id, ' \
627 'l.month AS month ' \
630 'DATE_TRUNC(\'month\', l.date) AS month ' \
631 'FROM account_analytic_line AS l, ' \
632 'account_analytic_journal AS j ' \
633 'WHERE j.type = \'general\' ' \
634 'GROUP BY DATE_TRUNC(\'month\', l.date) ' \
636 'account_analytic_account AS a ' \
637 'GROUP BY l.month, a.id ' \
639 'GROUP BY d2.account_id, d2.month ' \
643 'l.account_id AS account_id, ' \
644 'DATE_TRUNC(\'month\', l.date) AS month, ' \
645 'SUM(l.unit_amount) AS unit_amount ' \
646 'FROM account_analytic_line AS l, ' \
647 'account_analytic_journal AS j ' \
648 'WHERE (j.type = \'general\') and (j.id=l.journal_id) ' \
649 'GROUP BY l.account_id, DATE_TRUNC(\'month\', l.date) ' \
652 'd.account_id = l.account_id ' \
653 'AND d.month = l.month' \
655 'GROUP BY d.month, d.account_id ' \
658 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: