[FIX] Missing imports of _ of tools.translate provided
[odoo/odoo.git] / bin / report / custom.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #    
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 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 os
23 import time
24 import netsvc
25
26 import tools
27 import print_xml
28 import render
29 from interface import report_int
30 import common
31 from osv.osv import except_osv
32 from osv.orm import browse_null
33 from osv.orm import browse_record_list
34 import pooler
35 from pychart import *
36 import misc
37 import cStringIO
38 from lxml import etree
39 from tools.translate import _
40
41 class external_pdf(render.render):
42     def __init__(self, pdf):
43         render.render.__init__(self)
44         self.pdf = pdf
45         self.output_type='pdf'
46     def _render(self):
47         return self.pdf
48
49 theme.use_color = 1
50
51
52 #TODO: devrait heriter de report_rml a la place de report_int 
53 # -> pourrait overrider que create_xml a la place de tout create
54 # heuu, ca marche pas ds tous les cas car graphs sont generes en pdf directment
55 # par pychart, et on passe donc pas par du rml
56 class report_custom(report_int):
57     def __init__(self, name):
58         report_int.__init__(self, name)
59     #
60     # PRE:
61     #    fields = [['address','city'],['name'], ['zip']]
62     #    conditions = [[('zip','==','3'),(,)],(,),(,)] #same structure as fields
63     #    row_canvas = ['Rue', None, None]
64     # POST:
65     #    [ ['ville','name','zip'] ]
66     #
67     def _row_get(self, cr, uid, objs, fields, conditions, row_canvas=None, group_by=None):
68         result = []
69         tmp = []
70         for obj in objs:
71             tobreak = False
72             for cond in conditions:
73                 if cond and cond[0]:
74                     c = cond[0]
75                     temp = c[0](eval('obj.'+c[1]))
76                     if not eval('\''+temp+'\''+' '+c[2]+' '+'\''+str(c[3])+'\''):
77                         tobreak = True
78             if tobreak:
79                 break
80             levels = {}
81             row = []
82             for i in range(len(fields)):
83                 if not fields[i]:
84                     row.append(row_canvas and row_canvas[i])
85                     if row_canvas[i]:
86                         row_canvas[i]=False
87                 elif len(fields[i])==1:
88                     if not isinstance(obj, browse_null):
89                         row.append(str(eval('obj.'+fields[i][0])))
90                     else:
91                         row.append(None)
92                 else:
93                     row.append(None)
94                     levels[fields[i][0]]=True
95             if not levels:
96                 result.append(row)
97             else:
98                 # Process group_by data first
99                 key = []
100                 if group_by != None and fields[group_by] != None:
101                     if fields[group_by][0] in levels.keys():
102                         key.append(fields[group_by][0])
103                     for l in levels.keys():
104                         if l != fields[group_by][0]:
105                             key.append(l)
106                 else:
107                     key = levels.keys()
108                 for l in key:
109                     objs = eval('obj.'+l)
110                     if not isinstance(objs, browse_record_list) and type(objs) <> type([]):
111                         objs = [objs]
112                     field_new = []
113                     cond_new = []
114                     for f in range(len(fields)):
115                         if (fields[f] and fields[f][0])==l:
116                             field_new.append(fields[f][1:])
117                             cond_new.append(conditions[f][1:])
118                         else:
119                             field_new.append(None)
120                             cond_new.append(None)
121                     if len(objs):
122                         result += self._row_get(cr, uid, objs, field_new, cond_new, row, group_by)
123                     else:
124                         result.append(row)
125         return result 
126
127
128     def create(self, cr, uid, ids, datas, context=None):
129         if not context:
130             context={}
131         self.pool = pooler.get_pool(cr.dbname)
132         report = self.pool.get('ir.report.custom').browse(cr, uid, [datas['report_id']])[0]
133         datas['model'] = report.model_id.model
134         if report.menu_id:
135             ids = self.pool.get(report.model_id.model).search(cr, uid, [])
136             datas['ids'] = ids
137
138         service = netsvc.LocalService("object_proxy")
139         report_id = datas['report_id']
140         report = service.execute(cr.dbname, uid, 'ir.report.custom', 'read', [report_id], context=context)[0]
141         fields = service.execute(cr.dbname, uid, 'ir.report.custom.fields', 'read', report['fields_child0'], context=context)
142
143         fields.sort(lambda x,y : x['sequence'] - y['sequence'])
144
145         if report['field_parent']:
146             parent_field = service.execute(cr.dbname, uid, 'ir.model.fields', 'read', [report['field_parent'][0]],['model'])
147         model_name = service.execute(cr.dbname, uid, 'ir.model', 'read', [report['model_id'][0]], ['model'],context=context)[0]['model']
148
149         fct = {}
150         fct['id'] = lambda x : x
151         fct['gety'] = lambda x: x.split('-')[0]
152         fct['in'] = lambda x: x.split(',')
153         new_fields = []
154         new_cond = []
155         for f in fields:
156             row = []
157             cond = []
158             for i in range(4):
159                 field_child = f['field_child'+str(i)]
160                 if field_child:
161                     row.append(
162                         service.execute(cr.dbname, uid, 
163                                         'ir.model.fields', 'read', [field_child[0]],
164                                         ['name'], context=context)[0]['name']
165                     )
166                     if f['fc'+str(i)+'_operande']:
167                         fct_name = 'id'
168                         cond_op =  f['fc'+str(i)+'_op']
169                         if len(f['fc'+str(i)+'_op'].split(',')) == 2:
170                             cond_op =  f['fc'+str(i)+'_op'].split(',')[1]
171                             fct_name = f['fc'+str(i)+'_op'].split(',')[0]
172                         cond.append((fct[fct_name], f['fc'+str(i)+'_operande'][1], cond_op, f['fc'+str(i)+'_condition']))
173                     else:
174                         cond.append(None)
175             new_fields.append(row)
176             new_cond.append(cond)
177         objs = self.pool.get(model_name).browse(cr, uid, ids)
178
179         # Group by
180         groupby = None
181         idx = 0
182         for f in fields:
183             if f['groupby']:
184                 groupby = idx
185             idx += 1
186
187
188         results = []
189         if report['field_parent']:
190             level = []
191             def build_tree(obj, level, depth):
192                 res = self._row_get(cr, uid,[obj], new_fields, new_cond)
193                 level.append(depth)
194                 new_obj = eval('obj.'+report['field_parent'][1])
195                 if not isinstance(new_obj, list) :
196                     new_obj = [new_obj]
197                 for o in  new_obj:
198                     if not isinstance(o, browse_null):
199                         res += build_tree(o, level, depth+1)
200                 return res
201
202             for obj in objs:
203                 results += build_tree(obj, level, 0)
204         else:
205             results = self._row_get(cr, uid,objs, new_fields, new_cond, group_by=groupby)
206
207         fct = {
208             'calc_sum': lambda l: reduce(lambda x,y: float(x)+float(y), filter(None, l), 0),
209             'calc_avg': lambda l: reduce(lambda x,y: float(x)+float(y), filter(None, l), 0) / (len(filter(None, l)) or 1.0),
210             'calc_max': lambda l: reduce(lambda x,y: max(x,y), [(i or 0.0) for i in l], 0),
211             'calc_min': lambda l: reduce(lambda x,y: min(x,y), [(i or 0.0) for i in l], 0),
212             'calc_count': lambda l: len(filter(None, l)),
213             'False': lambda l: '\r\n'.join(filter(None, l)),
214             'groupby': lambda l: reduce(lambda x,y: x or y, l)
215         }
216         new_res = []
217
218         prev = None
219         if groupby != None:
220             res_dic = {}
221             for line in results:
222                 if not line[groupby] and prev in res_dic:
223                     res_dic[prev].append(line)
224                 else:
225                     prev = line[groupby]
226                     if res_dic.has_key(line[groupby]):
227                         res_dic[line[groupby]].append(line)
228                     else:
229                         res_dic[line[groupby]] = []
230                         res_dic[line[groupby]].append(line)
231             #we use the keys in results since they are ordered, whereas in res_dic.heys() they aren't
232             for key in filter(None, [x[groupby] for x in results]):
233                 row = []
234                 for col in range(len(fields)):
235                     if col == groupby:
236                         row.append(fct['groupby'](map(lambda x: x[col], res_dic[key])))
237                     else:
238                         row.append(fct[str(fields[col]['operation'])](map(lambda x: x[col], res_dic[key])))
239                 new_res.append(row)
240             results = new_res
241         
242         if report['type']=='table':
243             if report['field_parent']:
244                 res = self._create_tree(uid, ids, report, fields, level, results, context)
245             else:
246                 sort_idx = 0
247                 for idx in range(len(fields)):
248                     if fields[idx]['name'] == report['sortby']:
249                         sort_idx = idx
250                         break
251                 try :
252                     results.sort(lambda x,y : cmp(float(x[sort_idx]),float(y[sort_idx])))
253                 except :
254                     results.sort(lambda x,y : cmp(x[sort_idx],y[sort_idx]))
255                 if report['limitt']:
256                     results = results[:int(report['limitt'])]
257                 res = self._create_table(uid, ids, report, fields, None, results, context)
258         elif report['type'] in ('pie','bar', 'line'):
259             results2 = []
260             prev = False
261             for r in results:
262                 row = []
263                 for j in range(len(r)):
264                     if j == 0 and not r[j]:
265                         row.append(prev)
266                     elif j == 0 and r[j]:
267                         prev = r[j]
268                         row.append(r[j])
269                     else:
270                         try:
271                             row.append(float(r[j]))
272                         except:
273                             row.append(r[j])
274                 results2.append(row)
275             if report['type']=='pie':
276                 res = self._create_pie(cr,uid, ids, report, fields, results2, context)
277             elif report['type']=='bar':
278                 res = self._create_bars(cr,uid, ids, report, fields, results2, context)
279             elif report['type']=='line':
280                 res = self._create_lines(cr,uid, ids, report, fields, results2, context)
281         return (self.obj.get(), 'pdf')
282
283     def _create_tree(self, uid, ids, report, fields, level, results, context):
284         pageSize=common.pageSize.get(report['print_format'], [210.0,297.0])
285         if report['print_orientation']=='landscape':
286             pageSize=[pageSize[1],pageSize[0]]
287
288         new_doc = etree.Element('report')
289         
290         config = etree.SubElement(new_doc, 'config')
291
292         def _append_node(name, text):
293             n = etree.SubElement(config, name)
294             n.text = text
295
296         _append_node('date', time.strftime('%d/%m/%Y'))
297         _append_node('PageFormat', '%s' % report['print_format'])
298         _append_node('PageSize', '%.2fmm,%.2fmm' % tuple(pageSize))
299         _append_node('PageWidth', '%.2f' % (pageSize[0] * 2.8346,))
300         _append_node('PageHeight', '%.2f' %(pageSize[1] * 2.8346,))
301
302         length = pageSize[0]-30-reduce(lambda x,y:x+(y['width'] or 0), fields, 0)
303         count = 0
304         for f in fields:
305             if not f['width']: count+=1
306         for f in fields:
307             if not f['width']:
308                 f['width']=round((float(length)/count)-0.5)
309
310         _append_node('tableSize', '%s' %  ','.join(map(lambda x: '%.2fmm' % (x['width'],), fields)))
311         _append_node('report-header', '%s' % (report['title'],))
312         _append_node('report-footer', '%s' % (report['footer'],))
313
314         header = etree.SubElement(new_doc, 'header')
315         for f in fields:
316             field = etree.SubElement(header, 'field')
317             field.text = f['name']
318
319         lines = etree.SubElement(new_doc, 'lines')
320         level.reverse()
321         for line in results:
322             shift = level.pop()
323             node_line = etree.SubElement(lines, 'row')
324             prefix = '+'
325             for f in range(len(fields)):
326                 col = etree.SubElement(node_line, 'col')
327                 if f == 0:
328                     col.attrib.update(para='yes',
329                                       tree='yes',
330                                       space=str(3*shift)+'mm')
331                 if line[f] != None:
332                     col.text = prefix+str(line[f]) or ''
333                 else:
334                     col.text = '/'
335                 prefix = ''
336
337         transform = etree.XSLT(
338             etree.parse(os.path.join(tools.config['root_path'],
339                                      'addons/base/report/custom_new.xsl')))
340         rml = etree.tostring(transform(new_doc))
341
342         self.obj = render.rml(rml)
343         self.obj.render()
344         return True
345
346
347     def _create_lines(self, cr, uid, ids, report, fields, results, context):
348         service = netsvc.LocalService("object_proxy")
349         pdf_string = cStringIO.StringIO()
350         can = canvas.init(fname=pdf_string, format='pdf')
351         
352         can.show(80,380,'/16/H'+report['title'])
353         
354         ar = area.T(size=(350,350),
355         #x_coord = category_coord.T(['2005-09-01','2005-10-22'],0),
356         x_axis = axis.X(label = fields[0]['name'], format="/a-30{}%s"),
357         y_axis = axis.Y(label = ', '.join(map(lambda x : x['name'], fields[1:]))))
358         
359         process_date = {}
360         process_date['D'] = lambda x : reduce(lambda xx,yy : xx+'-'+yy,x.split('-')[1:3])
361         process_date['M'] = lambda x : x.split('-')[1]
362         process_date['Y'] = lambda x : x.split('-')[0]
363
364         order_date = {}
365         order_date['D'] = lambda x : time.mktime((2005,int(x.split('-')[0]), int(x.split('-')[1]),0,0,0,0,0,0))
366         order_date['M'] = lambda x : x
367         order_date['Y'] = lambda x : x
368
369         abscissa = []
370         tmp = {}
371         
372         idx = 0 
373         date_idx = None
374         fct = {}
375         for f in fields:
376             field_id = (f['field_child3'] and f['field_child3'][0]) or (f['field_child2'] and f['field_child2'][0]) or (f['field_child1'] and f['field_child1'][0]) or (f['field_child0'] and f['field_child0'][0])
377             if field_id:
378                 type = service.execute(cr.dbname, uid, 'ir.model.fields', 'read', [field_id],['ttype'])
379                 if type[0]['ttype'] == 'date':
380                     date_idx = idx
381                     fct[idx] = process_date[report['frequency']] 
382                 else:
383                     fct[idx] = lambda x : x
384             else:
385                 fct[idx] = lambda x : x
386             idx+=1
387
388         # plots are usually displayed year by year
389         # so we do so if the first field is a date
390         data_by_year = {}
391         if date_idx != None:
392             for r in results:
393                 key = process_date['Y'](r[date_idx])
394                 if not data_by_year.has_key(key):
395                     data_by_year[key] = []
396                 for i in range(len(r)):
397                     r[i] = fct[i](r[i])
398                 data_by_year[key].append(r)
399         else:
400             data_by_year[''] = results
401
402         idx0 = 0
403         nb_bar = len(data_by_year)*(len(fields)-1)
404         colors = map(lambda x:line_style.T(color=x), misc.choice_colors(nb_bar))
405         abscissa = {}
406         for line in data_by_year.keys():
407             fields_bar = []
408             # sum data and save it in a list. An item for a fields
409             for d in data_by_year[line]:
410                 for idx in range(len(fields)-1):
411                     fields_bar.append({})
412                     if fields_bar[idx].has_key(d[0]):
413                         fields_bar[idx][d[0]] += d[idx+1]
414                     else:
415                         fields_bar[idx][d[0]] = d[idx+1]
416             for idx  in range(len(fields)-1):
417                 data = {}
418                 for k in fields_bar[idx].keys():
419                     if data.has_key(k):
420                         data[k] += fields_bar[idx][k]
421                     else:
422                         data[k] = fields_bar[idx][k]
423                 data_cum = []
424                 prev = 0.0
425                 keys = data.keys()
426                 keys.sort()
427                 # cumulate if necessary
428                 for k in keys:
429                     data_cum.append([k, float(data[k])+float(prev)])
430                     if fields[idx+1]['cumulate']:
431                         prev += data[k]
432                 idx0 = 0
433                 plot = line_plot.T(label=fields[idx+1]['name']+' '+str(line), data = data_cum, line_style=colors[idx0*(len(fields)-1)+idx])
434                 ar.add_plot(plot)
435                 abscissa.update(fields_bar[idx])
436                 idx0 += 1
437         
438         abscissa = map(lambda x : [x, None], abscissa)
439         ar.x_coord = category_coord.T(abscissa,0)
440         ar.draw(can)
441
442         can.close()
443         self.obj = external_pdf(pdf_string.getvalue())
444         self.obj.render()
445         pdf_string.close()
446         return True
447
448
449
450     def _create_bars(self, cr, uid, ids, report, fields, results, context):
451         service = netsvc.LocalService("object_proxy")
452         pdf_string = cStringIO.StringIO()
453         can = canvas.init(fname=pdf_string, format='pdf')
454         
455         can.show(80,380,'/16/H'+report['title'])
456         
457         process_date = {}
458         process_date['D'] = lambda x : reduce(lambda xx,yy : xx+'-'+yy,x.split('-')[1:3])
459         process_date['M'] = lambda x : x.split('-')[1]
460         process_date['Y'] = lambda x : x.split('-')[0]
461
462         order_date = {}
463         order_date['D'] = lambda x : time.mktime((2005,int(x.split('-')[0]), int(x.split('-')[1]),0,0,0,0,0,0))
464         order_date['M'] = lambda x : x
465         order_date['Y'] = lambda x : x
466
467         ar = area.T(size=(350,350),
468             x_axis = axis.X(label = fields[0]['name'], format="/a-30{}%s"),
469             y_axis = axis.Y(label = ', '.join(map(lambda x : x['name'], fields[1:]))))
470
471         idx = 0 
472         date_idx = None
473         fct = {}
474         for f in fields:
475             field_id = (f['field_child3'] and f['field_child3'][0]) or (f['field_child2'] and f['field_child2'][0]) or (f['field_child1'] and f['field_child1'][0]) or (f['field_child0'] and f['field_child0'][0])
476             if field_id:
477                 type = service.execute(cr.dbname, uid, 'ir.model.fields', 'read', [field_id],['ttype'])
478                 if type[0]['ttype'] == 'date':
479                     date_idx = idx
480                     fct[idx] = process_date[report['frequency']] 
481                 else:
482                     fct[idx] = lambda x : x
483             else:
484                 fct[idx] = lambda x : x
485             idx+=1
486         
487         # plot are usually displayed year by year
488         # so we do so if the first field is a date
489         data_by_year = {}
490         if date_idx != None:
491             for r in results:
492                 key = process_date['Y'](r[date_idx])
493                 if not data_by_year.has_key(key):
494                     data_by_year[key] = []
495                 for i in range(len(r)):
496                     r[i] = fct[i](r[i])
497                 data_by_year[key].append(r)
498         else:
499             data_by_year[''] = results
500
501
502         nb_bar = len(data_by_year)*(len(fields)-1)
503         colors = map(lambda x:fill_style.Plain(bgcolor=x), misc.choice_colors(nb_bar))
504         
505         abscissa = {}
506         for line in data_by_year.keys():
507             fields_bar = []
508             # sum data and save it in a list. An item for a fields
509             for d in data_by_year[line]:
510                 for idx in range(len(fields)-1):
511                     fields_bar.append({})
512                     if fields_bar[idx].has_key(d[0]):
513                         fields_bar[idx][d[0]] += d[idx+1]
514                     else:
515                         fields_bar[idx][d[0]] = d[idx+1]
516             for idx  in range(len(fields)-1):
517                 data = {}
518                 for k in fields_bar[idx].keys():
519                     if data.has_key(k):
520                         data[k] += fields_bar[idx][k]
521                     else:
522                         data[k] = fields_bar[idx][k]
523                 data_cum = []
524                 prev = 0.0
525                 keys = data.keys()
526                 keys.sort()
527                 # cumulate if necessary
528                 for k in keys:
529                     data_cum.append([k, float(data[k])+float(prev)])
530                     if fields[idx+1]['cumulate']:
531                         prev += data[k]
532                         
533                 idx0 = 0
534                 plot = bar_plot.T(label=fields[idx+1]['name']+' '+str(line), data = data_cum, cluster=(idx0*(len(fields)-1)+idx,nb_bar), fill_style=colors[idx0*(len(fields)-1)+idx])
535                 ar.add_plot(plot)
536                 abscissa.update(fields_bar[idx])
537             idx0 += 1
538         abscissa = map(lambda x : [x, None], abscissa)
539         abscissa.sort()
540         ar.x_coord = category_coord.T(abscissa,0)
541         ar.draw(can)
542
543         can.close()
544         self.obj = external_pdf(pdf_string.getvalue())
545         self.obj.render()
546         pdf_string.close()
547         return True
548
549     def _create_pie(self, cr, uid, ids, report, fields, results, context):
550         pdf_string = cStringIO.StringIO()
551         can = canvas.init(fname=pdf_string, format='pdf')
552         ar = area.T(size=(350,350), legend=legend.T(),
553                     x_grid_style = None, y_grid_style = None)
554         colors = map(lambda x:fill_style.Plain(bgcolor=x), misc.choice_colors(len(results)))
555
556         if reduce(lambda x,y : x+y, map(lambda x : x[1],results)) == 0.0:
557             raise except_osv(_('Error'), _("The sum of the data (2nd field) is null.\nWe can't draw a pie chart !"))
558
559         plot = pie_plot.T(data=results, arc_offsets=[0,10,0,10],
560                           shadow = (2, -2, fill_style.gray50),
561                           label_offset = 25,
562                           arrow_style = arrow.a3,
563                           fill_styles=colors)
564         ar.add_plot(plot)
565         ar.draw(can)
566         can.close()
567         self.obj = external_pdf(pdf_string.getvalue())
568         self.obj.render()
569         pdf_string.close()
570         return True
571
572     def _create_table(self, uid, ids, report, fields, tree, results, context):
573         pageSize=common.pageSize.get(report['print_format'], [210.0,297.0])
574         if report['print_orientation']=='landscape':
575             pageSize=[pageSize[1],pageSize[0]]
576
577         new_doc = etree.Element('report')
578         config = etree.SubElement(new_doc, 'config')
579
580         def _append_node(name, text):
581             n = etree.SubElement(config, name)
582             n.text = text
583
584         _append_node('date', time.strftime('%d/%m/%Y'))
585         _append_node('PageSize', '%.2fmm,%.2fmm' % tuple(pageSize))
586         _append_node('PageFormat', '%s' % report['print_format'])
587         _append_node('PageWidth', '%.2f' % (pageSize[0] * 2.8346,))
588         _append_node('PageHeight', '%.2f' %(pageSize[1] * 2.8346,))
589
590         length = pageSize[0]-30-reduce(lambda x,y:x+(y['width'] or 0), fields, 0)
591         count = 0
592         for f in fields:
593             if not f['width']: count+=1
594         for f in fields:
595             if not f['width']:
596                 f['width']=round((float(length)/count)-0.5)
597
598         _append_node('tableSize', '%s' %  ','.join(map(lambda x: '%.2fmm' % (x['width'],), fields)))
599         _append_node('report-header', '%s' % (report['title'],))
600         _append_node('report-footer', '%s' % (report['footer'],))
601
602         header = etree.SubElement(new_doc, 'header')
603         for f in fields:
604             field = etree.SubElement(header, 'field')
605             field.text = f['name']
606
607         lines = etree.SubElement(new_doc, 'lines')
608         for line in results:
609             node_line = etree.SubElement(lines, 'row')
610             for f in range(len(fields)):
611                 col = etree.SubElement(node_line, 'col', tree='no')
612                 if line[f] != None:
613                     col.text = line[f] or ''
614                 else:
615                     col.text = '/'
616
617         transform = etree.XSLT(
618             etree.parse(os.path.join(tools.config['root_path'],
619                                      'addons/base/report/custom_new.xsl')))
620         rml = etree.tostring(transform(new_doc))
621
622         self.obj = render.rml(rml)
623         self.obj.render()
624         return True
625 report_custom('report.custom')
626
627
628 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
629