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