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])
36 parent_ids = tuple(ids) #We don't want consolidation for each of these fields because those complex computation is resource-greedy.
37 accounts = self.browse(cr, uid, ids, context=context)
41 cr.execute('SELECT MAX(id) FROM res_users')
42 max_user = cr.fetchone()[0]
44 cr.execute('SELECT DISTINCT("user") FROM account_analytic_analysis_summary_user ' \
45 'WHERE account_id IN %s AND unit_amount <> 0.0', (parent_ids,))
46 result = cr.fetchall()
50 res[id][f] = [int((id * max_user) + x[0]) for x in result]
51 elif f == 'month_ids':
53 cr.execute('SELECT DISTINCT(month_id) FROM account_analytic_analysis_summary_month ' \
54 'WHERE account_id IN %s AND unit_amount <> 0.0', (parent_ids,))
55 result = cr.fetchall()
59 res[id][f] = [int(id * 1000000 + int(x[0])) for x in result]
60 elif f == 'last_worked_invoiced_date':
64 cr.execute("SELECT account_analytic_line.account_id, MAX(date) \
65 FROM account_analytic_line \
66 WHERE account_id IN %s \
67 AND invoice_id IS NOT NULL \
68 GROUP BY account_analytic_line.account_id;", (parent_ids,))
69 for account_id, sum in cr.fetchall():
70 if account_id not in res:
72 res[account_id][f] = sum
73 elif f == 'ca_to_invoice':
78 # Amount uninvoiced hours to invoice at sale price
80 # This computation doesn't take care of pricelist !
81 # Just consider list_price
82 cr.execute("""SELECT account_analytic_account.id, \
83 COALESCE(SUM (product_template.list_price * \
84 account_analytic_line.unit_amount * \
85 ((100-hr_timesheet_invoice_factor.factor)/100)), 0.0) \
87 FROM product_template \
88 JOIN product_product \
89 ON product_template.id = product_product.product_tmpl_id \
90 JOIN account_analytic_line \
91 ON account_analytic_line.product_id = product_product.id \
92 JOIN account_analytic_journal \
93 ON account_analytic_line.journal_id = account_analytic_journal.id \
94 JOIN account_analytic_account \
95 ON account_analytic_account.id = account_analytic_line.account_id \
96 JOIN hr_timesheet_invoice_factor \
97 ON hr_timesheet_invoice_factor.id = account_analytic_account.to_invoice \
98 WHERE account_analytic_account.id IN %s \
99 AND account_analytic_line.invoice_id IS NULL \
100 AND account_analytic_line.to_invoice IS NOT NULL \
101 AND account_analytic_journal.type = 'general' \
102 GROUP BY account_analytic_account.id;""", (parent_ids,))
103 for account_id, sum in cr.fetchall():
104 if account_id not in res:
106 res[account_id][f] = round(sum, dp)
108 # sum both result on account_id
110 res[id][f] = round(res.get(id, {}).get(f, 0.0), dp) + round(res2.get(id, 0.0), 2)
111 elif f == 'last_invoice_date':
115 cr.execute ("SELECT account_analytic_line.account_id, \
116 DATE(MAX(account_invoice.date_invoice)) \
117 FROM account_analytic_line \
118 JOIN account_invoice \
119 ON account_analytic_line.invoice_id = account_invoice.id \
120 WHERE account_analytic_line.account_id IN %s \
121 AND account_analytic_line.invoice_id IS NOT NULL \
122 GROUP BY account_analytic_line.account_id",(parent_ids,))
123 for account_id, lid in cr.fetchall():
124 res[account_id][f] = lid
125 elif f == 'last_worked_date':
129 cr.execute("SELECT account_analytic_line.account_id, MAX(date) \
130 FROM account_analytic_line \
131 WHERE account_id IN %s \
132 AND invoice_id IS NULL \
133 GROUP BY account_analytic_line.account_id",(parent_ids,))
134 for account_id, lwd in cr.fetchall():
135 if account_id not in res:
137 res[account_id][f] = lwd
138 elif f == 'hours_qtt_non_invoiced':
142 cr.execute("SELECT account_analytic_line.account_id, COALESCE(SUM(unit_amount), 0.0) \
143 FROM account_analytic_line \
144 JOIN account_analytic_journal \
145 ON account_analytic_line.journal_id = account_analytic_journal.id \
146 WHERE account_analytic_line.account_id IN %s \
147 AND account_analytic_journal.type='general' \
148 AND invoice_id IS NULL \
149 AND to_invoice IS NOT NULL \
150 GROUP BY account_analytic_line.account_id;",(parent_ids,))
151 for account_id, sua in cr.fetchall():
152 if account_id not in res:
154 res[account_id][f] = round(sua, dp)
156 res[id][f] = round(res[id][f], dp)
157 elif f == 'hours_quantity':
161 cr.execute("SELECT account_analytic_line.account_id, COALESCE(SUM(unit_amount), 0.0) \
162 FROM account_analytic_line \
163 JOIN account_analytic_journal \
164 ON account_analytic_line.journal_id = account_analytic_journal.id \
165 WHERE account_analytic_line.account_id IN %s \
166 AND account_analytic_journal.type='general' \
167 GROUP BY account_analytic_line.account_id",(parent_ids,))
169 for account_id, hq in ff:
170 if account_id not in res:
172 res[account_id][f] = round(hq, dp)
174 res[id][f] = round(res[id][f], dp)
175 elif f == 'ca_theorical':
176 # TODO Take care of pricelist and purchase !
180 # This computation doesn't take care of pricelist !
181 # Just consider list_price
183 cr.execute("""SELECT account_analytic_line.account_id AS account_id, \
184 COALESCE(SUM((account_analytic_line.unit_amount * pt.list_price) \
185 - (account_analytic_line.unit_amount * pt.list_price \
186 * hr.factor)), 0.0) AS somme
187 FROM account_analytic_line \
188 LEFT JOIN account_analytic_journal \
189 ON (account_analytic_line.journal_id = account_analytic_journal.id) \
190 JOIN product_product pp \
191 ON (account_analytic_line.product_id = pp.id) \
192 JOIN product_template pt \
193 ON (pp.product_tmpl_id = pt.id) \
194 JOIN account_analytic_account a \
195 ON (a.id=account_analytic_line.account_id) \
196 JOIN hr_timesheet_invoice_factor hr \
197 ON (hr.id=a.to_invoice) \
198 WHERE account_analytic_line.account_id IN %s \
199 AND a.to_invoice IS NOT NULL \
200 AND account_analytic_journal.type IN ('purchase', 'general')
201 GROUP BY account_analytic_line.account_id""",(parent_ids,))
202 for account_id, sum in cr.fetchall():
203 res[account_id][f] = round(sum, dp)
206 def _ca_invoiced_calc(self, cr, uid, ids, name, arg, context=None):
209 child_ids = tuple(ids) #We don't want consolidation for each of these fields because those complex computation is resource-greedy.
216 cr.execute("SELECT account_analytic_line.account_id, COALESCE(SUM(amount), 0.0) \
217 FROM account_analytic_line \
218 JOIN account_analytic_journal \
219 ON account_analytic_line.journal_id = account_analytic_journal.id \
220 WHERE account_analytic_line.account_id IN %s \
221 AND account_analytic_journal.type = 'sale' \
222 GROUP BY account_analytic_line.account_id", (child_ids,))
223 for account_id, sum in cr.fetchall():
224 res[account_id] = round(sum,2)
228 def _total_cost_calc(self, cr, uid, ids, name, arg, context=None):
231 child_ids = tuple(ids) #We don't want consolidation for each of these fields because those complex computation is resource-greedy.
237 cr.execute("""SELECT account_analytic_line.account_id, COALESCE(SUM(amount), 0.0) \
238 FROM account_analytic_line \
239 JOIN account_analytic_journal \
240 ON account_analytic_line.journal_id = account_analytic_journal.id \
241 WHERE account_analytic_line.account_id IN %s \
243 GROUP BY account_analytic_line.account_id""",(child_ids,))
244 for account_id, sum in cr.fetchall():
245 res[account_id] = round(sum,2)
249 def _remaining_hours_calc(self, cr, uid, ids, name, arg, context=None):
251 for account in self.browse(cr, uid, ids, context=context):
252 if account.quantity_max != 0:
253 res[account.id] = account.quantity_max - account.hours_quantity
255 res[account.id] = 0.0
257 res[id] = round(res.get(id, 0.0),2)
260 def _remaining_hours_to_invoice_calc(self, cr, uid, ids, name, arg, context=None):
262 for account in self.browse(cr, uid, ids, context=context):
263 res[account.id] = max(account.hours_qtt_est - account.timesheet_ca_invoiced, account.ca_to_invoice)
266 def _hours_qtt_invoiced_calc(self, cr, uid, ids, name, arg, context=None):
268 for account in self.browse(cr, uid, ids, context=context):
269 res[account.id] = account.hours_quantity - account.hours_qtt_non_invoiced
270 if res[account.id] < 0:
271 res[account.id] = 0.0
273 res[id] = round(res.get(id, 0.0),2)
276 def _revenue_per_hour_calc(self, cr, uid, ids, name, arg, context=None):
278 for account in self.browse(cr, uid, ids, context=context):
279 if account.hours_qtt_invoiced == 0:
282 res[account.id] = account.ca_invoiced / account.hours_qtt_invoiced
284 res[id] = round(res.get(id, 0.0),2)
287 def _real_margin_rate_calc(self, cr, uid, ids, name, arg, context=None):
289 for account in self.browse(cr, uid, ids, context=context):
290 if account.ca_invoiced == 0:
292 elif account.total_cost != 0.0:
293 res[account.id] = -(account.real_margin / account.total_cost) * 100
295 res[account.id] = 0.0
297 res[id] = round(res.get(id, 0.0),2)
300 def _fix_price_to_invoice_calc(self, cr, uid, ids, name, arg, context=None):
301 sale_obj = self.pool.get('sale.order')
303 for account in self.browse(cr, uid, ids, context=context):
304 res[account.id] = 0.0
305 sale_ids = sale_obj.search(cr, uid, [('project_id','=', account.id), ('partner_id', '=', account.partner_id.id)], context=context)
306 for sale in sale_obj.browse(cr, uid, sale_ids, context=context):
307 if not sale.invoiced:
308 res[account.id] += sale.amount_untaxed
309 for invoice in sale.invoice_ids:
310 if invoice.state not in ('draft', 'cancel'):
311 res[account.id] -= invoice.amount_untaxed
314 def _timesheet_ca_invoiced_calc(self, cr, uid, ids, name, arg, context=None):
315 lines_obj = self.pool.get('account.analytic.line')
317 for account in self.browse(cr, uid, ids, context=context):
318 res[account.id] = 0.0
319 line_ids = lines_obj.search(cr, uid, [('account_id','=', account.id), ('invoice_id','!=',False), ('to_invoice','!=', False), ('journal_id.type', '=', 'general')], context=context)
320 for line in lines_obj.browse(cr, uid, line_ids, context=context):
321 res[account.id] += line.invoice_id.amount_untaxed
324 def _remaining_ca_calc(self, cr, uid, ids, name, arg, context=None):
326 for account in self.browse(cr, uid, ids, context=context):
327 res[account.id] = max(account.amount_max - account.ca_invoiced, account.fix_price_to_invoice)
330 def _real_margin_calc(self, cr, uid, ids, name, arg, context=None):
332 for account in self.browse(cr, uid, ids, context=context):
333 res[account.id] = account.ca_invoiced + account.total_cost
335 res[id] = round(res.get(id, 0.0),2)
338 def _theorical_margin_calc(self, cr, uid, ids, name, arg, context=None):
340 for account in self.browse(cr, uid, ids, context=context):
341 res[account.id] = account.ca_theorical + account.total_cost
343 res[id] = round(res.get(id, 0.0),2)
346 def _is_overdue_quantity(self, cr, uid, ids, fieldnames, args, context=None):
347 result = dict.fromkeys(ids, 0)
348 for record in self.browse(cr, uid, ids, context=context):
349 if record.quantity_max > 0.0:
350 result[record.id] = int(record.hours_quantity >= record.quantity_max)
352 result[record.id] = 0
355 def _get_analytic_account(self, cr, uid, ids, context=None):
357 for line in self.pool.get('account.analytic.line').browse(cr, uid, ids, context=context):
358 result.add(line.account_id.id)
361 def _get_total_estimation(self, account):
363 if account.fix_price_invoices:
364 tot_est += account.amount_max
365 if account.invoice_on_timesheets:
366 tot_est += account.hours_qtt_est
369 def _get_total_invoiced(self, account):
371 if account.fix_price_invoices:
372 total_invoiced += account.ca_invoiced
373 if account.invoice_on_timesheets:
374 total_invoiced += account.timesheet_ca_invoiced
375 return total_invoiced
377 def _get_total_remaining(self, account):
378 total_remaining = 0.0
379 if account.fix_price_invoices:
380 total_remaining += account.remaining_ca
381 if account.invoice_on_timesheets:
382 total_remaining += account.remaining_hours_to_invoice
383 return total_remaining
385 def _get_total_toinvoice(self, account):
386 total_toinvoice = 0.0
387 if account.fix_price_invoices:
388 total_toinvoice += account.fix_price_to_invoice
389 if account.invoice_on_timesheets:
390 total_toinvoice += account.ca_to_invoice
391 return total_toinvoice
393 def _sum_of_fields(self, cr, uid, ids, name, arg, context=None):
394 res = dict([(i, {}) for i in ids])
395 for account in self.browse(cr, uid, ids, context=context):
396 res[account.id]['est_total'] = self._get_total_estimation(account)
397 res[account.id]['invoiced_total'] = self._get_total_invoiced(account)
398 res[account.id]['remaining_total'] = self._get_total_remaining(account)
399 res[account.id]['toinvoice_total'] = self._get_total_toinvoice(account)
403 'is_overdue_quantity' : fields.function(_is_overdue_quantity, method=True, type='boolean', string='Overdue Quantity',
405 'account.analytic.line' : (_get_analytic_account, None, 20),
407 'ca_invoiced': fields.function(_ca_invoiced_calc, type='float', string='Invoiced Amount',
408 help="Total customer invoiced amount for this account.",
409 digits_compute=dp.get_precision('Account')),
410 'total_cost': fields.function(_total_cost_calc, type='float', string='Total Costs',
411 help="Total of costs for this account. It includes real costs (from invoices) and indirect costs, like time spent on timesheets.",
412 digits_compute=dp.get_precision('Account')),
413 'ca_to_invoice': fields.function(_analysis_all, multi='analytic_analysis', type='float', string='Uninvoiced Amount',
414 help="If invoice from analytic account, the remaining amount you can invoice to the customer based on the total costs.",
415 digits_compute=dp.get_precision('Account')),
416 'ca_theorical': fields.function(_analysis_all, multi='analytic_analysis', type='float', string='Theoretical Revenue',
417 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.",
418 digits_compute=dp.get_precision('Account')),
419 'hours_quantity': fields.function(_analysis_all, multi='analytic_analysis', type='float', string='Total Worked Time',
420 help="Number of time you spent on the analytic account (from timesheet). It computes quantities on all journal of type 'general'."),
421 'last_invoice_date': fields.function(_analysis_all, multi='analytic_analysis', type='date', string='Last Invoice Date',
422 help="If invoice from the costs, this is the date of the latest invoiced."),
423 'last_worked_invoiced_date': fields.function(_analysis_all, multi='analytic_analysis', type='date', string='Date of Last Invoiced Cost',
424 help="If invoice from the costs, this is the date of the latest work or cost that have been invoiced."),
425 'last_worked_date': fields.function(_analysis_all, multi='analytic_analysis', type='date', string='Date of Last Cost/Work',
426 help="Date of the latest work done on this account."),
427 'hours_qtt_non_invoiced': fields.function(_analysis_all, multi='analytic_analysis', type='float', string='Uninvoiced Time',
428 help="Number of time (hours/days) (from journal of type 'general') that can be invoiced if you invoice based on analytic account."),
429 'hours_qtt_invoiced': fields.function(_hours_qtt_invoiced_calc, type='float', string='Invoiced Time',
430 help="Number of time (hours/days) that can be invoiced plus those that already have been invoiced."),
431 'remaining_hours': fields.function(_remaining_hours_calc, type='float', string='Remaining Time',
432 help="Computed using the formula: Maximum Time - Total Worked Time"),
433 'remaining_hours_to_invoice': fields.function(_remaining_hours_to_invoice_calc, type='float', string='Remaining Time',
434 help="Computed using the formula: Maximum Time - Total Invoiced Time"),
435 'fix_price_to_invoice': fields.function(_fix_price_to_invoice_calc, type='float', string='Remaining Time',
436 help="Sum of quotations for this contract."),
437 'timesheet_ca_invoiced': fields.function(_timesheet_ca_invoiced_calc, type='float', string='Remaining Time',
438 help="Sum of timesheet lines invoiced for this contract."),
439 'remaining_ca': fields.function(_remaining_ca_calc, type='float', string='Remaining Revenue',
440 help="Computed using the formula: Max Invoice Price - Invoiced Amount.",
441 digits_compute=dp.get_precision('Account')),
442 'revenue_per_hour': fields.function(_revenue_per_hour_calc, type='float', string='Revenue per Time (real)',
443 help="Computed using the formula: Invoiced Amount / Total Time",
444 digits_compute=dp.get_precision('Account')),
445 'real_margin': fields.function(_real_margin_calc, type='float', string='Real Margin',
446 help="Computed using the formula: Invoiced Amount - Total Costs.",
447 digits_compute=dp.get_precision('Account')),
448 'theorical_margin': fields.function(_theorical_margin_calc, type='float', string='Theoretical Margin',
449 help="Computed using the formula: Theoretical Revenue - Total Costs",
450 digits_compute=dp.get_precision('Account')),
451 'real_margin_rate': fields.function(_real_margin_rate_calc, type='float', string='Real Margin Rate (%)',
452 help="Computes using the formula: (Real Margin / Total Costs) * 100.",
453 digits_compute=dp.get_precision('Account')),
454 'fix_price_invoices' : fields.boolean('Fix Price Invoices'),
455 'invoice_on_timesheets' : fields.boolean("Invoice On Timesheets"),
456 'month_ids': fields.function(_analysis_all, multi='analytic_analysis', type='many2many', relation='account_analytic_analysis.summary.month', string='Month'),
457 'user_ids': fields.function(_analysis_all, multi='analytic_analysis', type="many2many", relation='account_analytic_analysis.summary.user', string='User'),
458 'hours_qtt_est': fields.float('Estimation of Hours to Invoice'),
459 'est_total' : fields.function(_sum_of_fields, type="float",multi="sum_of_all", string="Total Estimation"),
460 'invoiced_total' : fields.function(_sum_of_fields, type="float",multi="sum_of_all", string="Total Invoiced"),
461 '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"),
462 '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."),
465 def open_sale_order_lines(self,cr,uid,ids,context=None):
468 sale_ids = self.pool.get('sale.order').search(cr,uid,[('project_id','=',context.get('search_default_project_id',False)),('partner_id','=',context.get('search_default_partner_id',False))])
469 names = [record.name for record in self.browse(cr, uid, ids, context=context)]
470 name = _('Sale Order Lines of %s') % ','.join(names)
472 'type': 'ir.actions.act_window',
475 'view_mode': 'tree,form',
477 'domain' : [('order_id','in',sale_ids)],
478 'res_model': 'sale.order.line',
482 def on_change_template(self, cr, uid, ids, template_id, context=None):
485 res = super(account_analytic_account, self).on_change_template(cr, uid, ids, template_id, context=context)
486 if template_id and 'value' in res:
487 template = self.browse(cr, uid, template_id, context=context)
488 res['value']['fix_price_invoices'] = template.fix_price_invoices
489 res['value']['invoice_on_timesheets'] = template.invoice_on_timesheets
490 res['value']['hours_qtt_est'] = template.hours_qtt_est
491 res['value']['amount_max'] = template.amount_max
492 res['value']['to_invoice'] = template.to_invoice.id
493 res['value']['pricelist_id'] = template.pricelist_id.id
495 account_analytic_account()
497 class account_analytic_account_summary_user(osv.osv):
498 _name = "account_analytic_analysis.summary.user"
499 _description = "Hours Summary by User"
504 def _unit_amount(self, cr, uid, ids, name, arg, context=None):
506 account_obj = self.pool.get('account.analytic.account')
507 cr.execute('SELECT MAX(id) FROM res_users')
508 max_user = cr.fetchone()[0]
509 account_ids = [int(str(x/max_user - (x%max_user == 0 and 1 or 0))) for x in ids]
510 user_ids = [int(str(x-((x/max_user - (x%max_user == 0 and 1 or 0)) *max_user))) for x in ids]
511 parent_ids = tuple(account_ids) #We don't want consolidation for each of these fields because those complex computation is resource-greedy.
513 cr.execute('SELECT id, unit_amount ' \
514 'FROM account_analytic_analysis_summary_user ' \
515 'WHERE account_id IN %s ' \
516 'AND "user" IN %s',(parent_ids, tuple(user_ids),))
517 for sum_id, unit_amount in cr.fetchall():
518 res[sum_id] = unit_amount
520 res[id] = round(res.get(id, 0.0), 2)
524 'account_id': fields.many2one('account.analytic.account', 'Analytic Account', readonly=True),
525 'unit_amount': fields.float('Total Time'),
526 'user': fields.many2one('res.users', 'User'),
530 tools.sql.drop_view_if_exists(cr, 'account_analytic_analysis_summary_user')
531 cr.execute('''CREATE OR REPLACE VIEW account_analytic_analysis_summary_user AS (
533 (select max(id) as max_user from res_users)
536 l.account_id AS account_id,
537 coalesce(l.user_id, 0) AS user_id,
538 SUM(l.unit_amount) AS unit_amount
539 FROM account_analytic_line AS l,
540 account_analytic_journal AS j
541 WHERE (j.type = 'general' ) and (j.id=l.journal_id)
542 GROUP BY l.account_id, l.user_id
544 select (lu.account_id * mu.max_user) + lu.user_id as id,
545 lu.account_id as account_id,
546 lu.user_id as "user",
550 account_analytic_account_summary_user()
552 class account_analytic_account_summary_month(osv.osv):
553 _name = "account_analytic_analysis.summary.month"
554 _description = "Hours summary by month"
559 'account_id': fields.many2one('account.analytic.account', 'Analytic Account', readonly=True),
560 'unit_amount': fields.float('Total Time'),
561 'month': fields.char('Month', size=32, readonly=True),
565 tools.sql.drop_view_if_exists(cr, 'account_analytic_analysis_summary_month')
566 cr.execute('CREATE VIEW account_analytic_analysis_summary_month AS (' \
568 '(TO_NUMBER(TO_CHAR(d.month, \'YYYYMM\'), \'999999\') + (d.account_id * 1000000::bigint))::bigint AS id, ' \
569 'd.account_id AS account_id, ' \
570 'TO_CHAR(d.month, \'Mon YYYY\') AS month, ' \
571 'TO_NUMBER(TO_CHAR(d.month, \'YYYYMM\'), \'999999\') AS month_id, ' \
572 'COALESCE(SUM(l.unit_amount), 0.0) AS unit_amount ' \
579 'a.id AS account_id, ' \
580 'l.month AS month ' \
583 'DATE_TRUNC(\'month\', l.date) AS month ' \
584 'FROM account_analytic_line AS l, ' \
585 'account_analytic_journal AS j ' \
586 'WHERE j.type = \'general\' ' \
587 'GROUP BY DATE_TRUNC(\'month\', l.date) ' \
589 'account_analytic_account AS a ' \
590 'GROUP BY l.month, a.id ' \
592 'GROUP BY d2.account_id, d2.month ' \
596 'l.account_id AS account_id, ' \
597 'DATE_TRUNC(\'month\', l.date) AS month, ' \
598 'SUM(l.unit_amount) AS unit_amount ' \
599 'FROM account_analytic_line AS l, ' \
600 'account_analytic_journal AS j ' \
601 'WHERE (j.type = \'general\') and (j.id=l.journal_id) ' \
602 'GROUP BY l.account_id, DATE_TRUNC(\'month\', l.date) ' \
605 'd.account_id = l.account_id ' \
606 'AND d.month = l.month' \
608 'GROUP BY d.month, d.account_id ' \
612 account_analytic_account_summary_month()
613 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: