1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 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 import openerp.tools as tools
26 from openerp.tools.safe_eval import safe_eval as eval
29 from interface import report_int
31 from openerp.osv.osv import except_osv
32 from openerp.osv.orm import browse_null
33 from openerp.osv.orm import browse_record_list
34 import openerp.pooler as pooler
38 from lxml import etree
39 from openerp.tools.translate import _
41 class external_pdf(render.render):
42 def __init__(self, pdf):
43 render.render.__init__(self)
45 self.output_type='pdf'
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)
61 # fields = [['address','city'],['name'], ['zip']]
62 # conditions = [[('zip','==','3'),(,)],(,),(,)] #same structure as fields
63 # row_canvas = ['Rue', None, None]
65 # [ ['ville','name','zip'] ]
67 def _row_get(self, cr, uid, objs, fields, conditions, row_canvas=None, group_by=None):
71 for cond in conditions:
74 temp = c[0](eval('obj.'+c[1],{'obj': obj}))
75 if not eval('\''+temp+'\''+' '+c[2]+' '+'\''+str(c[3])+'\''):
81 for i in range(len(fields)):
83 row.append(row_canvas and row_canvas[i])
86 elif len(fields[i])==1:
87 if not isinstance(obj, browse_null):
88 row.append(str(eval('obj.'+fields[i][0],{'obj': obj})))
93 levels[fields[i][0]]=True
97 # Process group_by data first
99 if group_by is not None and fields[group_by] is not None:
100 if fields[group_by][0] in levels.keys():
101 key.append(fields[group_by][0])
102 for l in levels.keys():
103 if l != fields[group_by][0]:
108 objs = eval('obj.'+l,{'obj': obj})
109 if not isinstance(objs, (browse_record_list, list)):
113 for f in range(len(fields)):
114 if (fields[f] and fields[f][0])==l:
115 field_new.append(fields[f][1:])
116 cond_new.append(conditions[f][1:])
118 field_new.append(None)
119 cond_new.append(None)
121 result += self._row_get(cr, uid, objs, field_new, cond_new, row, group_by)
127 def create(self, cr, uid, ids, datas, context=None):
130 self.pool = pooler.get_pool(cr.dbname)
131 report = self.pool.get('ir.report.custom').browse(cr, uid, [datas['report_id']])[0]
132 datas['model'] = report.model_id.model
134 ids = self.pool.get(report.model_id.model).search(cr, uid, [])
137 report_id = datas['report_id']
138 report = self.pool.get('ir.report.custom').read(cr, uid, [report_id], context=context)[0]
139 fields = self.pool.get('ir.report.custom.fields').read(cr, uid, report['fields_child0'], context=context)
141 fields.sort(lambda x,y : x['sequence'] - y['sequence'])
143 if report['field_parent']:
144 parent_field = self.pool.get('ir.model.fields').read(cr, uid, [report['field_parent'][0]], ['model'])
145 model_name = self.pool.get('ir.model').read(cr, uid, [report['model_id'][0]], ['model'], context=context)[0]['model']
149 'gety': lambda x: x.split('-')[0],
150 'in': lambda x: x.split(',')
158 field_child = f['field_child'+str(i)]
161 self.pool.get('ir.model.fields').read(cr, uid, [field_child[0]], ['name'], context=context)[0]['name']
163 if f['fc'+str(i)+'_operande']:
165 cond_op = f['fc'+str(i)+'_op']
166 if len(f['fc'+str(i)+'_op'].split(',')) == 2:
167 cond_op = f['fc'+str(i)+'_op'].split(',')[1]
168 fct_name = f['fc'+str(i)+'_op'].split(',')[0]
169 cond.append((fct[fct_name], f['fc'+str(i)+'_operande'][1], cond_op, f['fc'+str(i)+'_condition']))
172 new_fields.append(row)
173 new_cond.append(cond)
174 objs = self.pool.get(model_name).browse(cr, uid, ids)
186 if report['field_parent']:
188 def build_tree(obj, level, depth):
189 res = self._row_get(cr, uid,[obj], new_fields, new_cond)
191 new_obj = eval('obj.'+report['field_parent'][1],{'obj': obj})
192 if not isinstance(new_obj, list) :
195 if not isinstance(o, browse_null):
196 res += build_tree(o, level, depth+1)
200 results += build_tree(obj, level, 0)
202 results = self._row_get(cr, uid,objs, new_fields, new_cond, group_by=groupby)
205 'calc_sum': lambda l: reduce(lambda x,y: float(x)+float(y), filter(None, l), 0),
206 'calc_avg': lambda l: reduce(lambda x,y: float(x)+float(y), filter(None, l), 0) / (len(filter(None, l)) or 1.0),
207 'calc_max': lambda l: reduce(lambda x,y: max(x,y), [(i or 0.0) for i in l], 0),
208 'calc_min': lambda l: reduce(lambda x,y: min(x,y), [(i or 0.0) for i in l], 0),
209 'calc_count': lambda l: len(filter(None, l)),
210 'False': lambda l: '\r\n'.join(filter(None, l)),
211 'groupby': lambda l: reduce(lambda x,y: x or y, l)
216 if groupby is not None:
219 if not line[groupby] and prev in res_dic:
220 res_dic[prev].append(line)
223 res_dic.setdefault(line[groupby], [])
224 res_dic[line[groupby]].append(line)
226 #we use the keys in results since they are ordered, whereas in res_dic.heys() they aren't
227 for key in filter(None, [x[groupby] for x in results]):
229 for col in range(len(fields)):
231 row.append(fct['groupby'](map(lambda x: x[col], res_dic[key])))
233 row.append(fct[str(fields[col]['operation'])](map(lambda x: x[col], res_dic[key])))
237 if report['type']=='table':
238 if report['field_parent']:
239 res = self._create_tree(uid, ids, report, fields, level, results, context)
242 for idx in range(len(fields)):
243 if fields[idx]['name'] == report['sortby']:
247 results.sort(lambda x,y : cmp(float(x[sort_idx]),float(y[sort_idx])))
249 results.sort(lambda x,y : cmp(x[sort_idx],y[sort_idx]))
251 results = results[:int(report['limitt'])]
252 res = self._create_table(uid, ids, report, fields, None, results, context)
253 elif report['type'] in ('pie','bar', 'line'):
258 for j in range(len(r)):
259 if j == 0 and not r[j]:
261 elif j == 0 and r[j]:
266 row.append(float(r[j]))
270 if report['type']=='pie':
271 res = self._create_pie(cr,uid, ids, report, fields, results2, context)
272 elif report['type']=='bar':
273 res = self._create_bars(cr,uid, ids, report, fields, results2, context)
274 elif report['type']=='line':
275 res = self._create_lines(cr,uid, ids, report, fields, results2, context)
276 return self.obj.get(), 'pdf'
278 def _create_tree(self, uid, ids, report, fields, level, results, context):
279 pageSize=common.pageSize.get(report['print_format'], [210.0,297.0])
280 if report['print_orientation']=='landscape':
281 pageSize=[pageSize[1],pageSize[0]]
283 new_doc = etree.Element('report')
285 config = etree.SubElement(new_doc, 'config')
287 def _append_node(name, text):
288 n = etree.SubElement(config, name)
291 _append_node('date', time.strftime('%d/%m/%Y'))
292 _append_node('PageFormat', '%s' % report['print_format'])
293 _append_node('PageSize', '%.2fmm,%.2fmm' % tuple(pageSize))
294 _append_node('PageWidth', '%.2f' % (pageSize[0] * 2.8346,))
295 _append_node('PageHeight', '%.2f' %(pageSize[1] * 2.8346,))
297 length = pageSize[0]-30-reduce(lambda x,y:x+(y['width'] or 0), fields, 0)
300 if not f['width']: count+=1
303 f['width']=round((float(length)/count)-0.5)
305 _append_node('tableSize', '%s' % ','.join(map(lambda x: '%.2fmm' % (x['width'],), fields)))
306 _append_node('report-header', '%s' % (report['title'],))
307 _append_node('report-footer', '%s' % (report['footer'],))
309 header = etree.SubElement(new_doc, 'header')
311 field = etree.SubElement(header, 'field')
312 field.text = f['name']
314 lines = etree.SubElement(new_doc, 'lines')
318 node_line = etree.SubElement(lines, 'row')
320 for f in range(len(fields)):
321 col = etree.SubElement(node_line, 'col')
323 col.attrib.update(para='yes',
325 space=str(3*shift)+'mm')
326 if line[f] is not None:
327 col.text = prefix+str(line[f]) or ''
332 transform = etree.XSLT(
333 etree.parse(os.path.join(tools.config['root_path'],
334 'addons/base/report/custom_new.xsl')))
335 rml = etree.tostring(transform(new_doc))
337 self.obj = render.rml(rml)
342 def _create_lines(self, cr, uid, ids, report, fields, results, context):
343 pool = pooler.get_pool(cr.dbname)
344 pdf_string = cStringIO.StringIO()
345 can = canvas.init(fname=pdf_string, format='pdf')
347 can.show(80,380,'/16/H'+report['title'])
349 ar = area.T(size=(350,350),
350 #x_coord = category_coord.T(['2005-09-01','2005-10-22'],0),
351 x_axis = axis.X(label = fields[0]['name'], format="/a-30{}%s"),
352 y_axis = axis.Y(label = ', '.join(map(lambda x : x['name'], fields[1:]))))
355 'D': lambda x: reduce(lambda xx, yy: xx + '-' + yy, x.split('-')[1:3]),
356 'M': lambda x: x.split('-')[1],
357 'Y': lambda x: x.split('-')[0]
361 'D': lambda x: time.mktime((2005, int(x.split('-')[0]), int(x.split('-')[1]), 0, 0, 0, 0, 0, 0)),
372 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])
374 type = pool.get('ir.model.fields').read(cr, uid, [field_id],['ttype'])
375 if type[0]['ttype'] == 'date':
377 fct[idx] = process_date[report['frequency']]
379 fct[idx] = lambda x : x
381 fct[idx] = lambda x : x
384 # plots are usually displayed year by year
385 # so we do so if the first field is a date
387 if date_idx is not None:
389 key = process_date['Y'](r[date_idx])
390 if key not in data_by_year:
391 data_by_year[key] = []
392 for i in range(len(r)):
394 data_by_year[key].append(r)
396 data_by_year[''] = results
399 nb_bar = len(data_by_year)*(len(fields)-1)
400 colors = map(lambda x:line_style.T(color=x), misc.choice_colors(nb_bar))
402 for line in data_by_year.keys():
404 # sum data and save it in a list. An item for a fields
405 for d in data_by_year[line]:
406 for idx in range(len(fields)-1):
407 fields_bar.append({})
408 if d[0] in fields_bar[idx]:
409 fields_bar[idx][d[0]] += d[idx+1]
411 fields_bar[idx][d[0]] = d[idx+1]
412 for idx in range(len(fields)-1):
414 for k in fields_bar[idx].keys():
416 data[k] += fields_bar[idx][k]
418 data[k] = fields_bar[idx][k]
423 # cumulate if necessary
425 data_cum.append([k, float(data[k])+float(prev)])
426 if fields[idx+1]['cumulate']:
429 plot = line_plot.T(label=fields[idx+1]['name']+' '+str(line), data = data_cum, line_style=colors[idx0*(len(fields)-1)+idx])
431 abscissa.update(fields_bar[idx])
434 abscissa = map(lambda x : [x, None], abscissa)
435 ar.x_coord = category_coord.T(abscissa,0)
439 self.obj = external_pdf(pdf_string.getvalue())
446 def _create_bars(self, cr, uid, ids, report, fields, results, context):
447 pool = pooler.get_pool(cr.dbname)
448 pdf_string = cStringIO.StringIO()
449 can = canvas.init(fname=pdf_string, format='pdf')
451 can.show(80,380,'/16/H'+report['title'])
454 'D': lambda x: reduce(lambda xx, yy: xx + '-' + yy, x.split('-')[1:3]),
455 'M': lambda x: x.split('-')[1],
456 'Y': lambda x: x.split('-')[0]
460 'D': lambda x: time.mktime((2005, int(x.split('-')[0]), int(x.split('-')[1]), 0, 0, 0, 0, 0, 0)),
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:]))))
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])
475 type = pool.get('ir.model.fields').read(cr, uid, [field_id],['ttype'])
476 if type[0]['ttype'] == 'date':
478 fct[idx] = process_date[report['frequency']]
480 fct[idx] = lambda x : x
482 fct[idx] = lambda x : x
485 # plot are usually displayed year by year
486 # so we do so if the first field is a date
488 if date_idx is not None:
490 key = process_date['Y'](r[date_idx])
491 if key not in data_by_year:
492 data_by_year[key] = []
493 for i in range(len(r)):
495 data_by_year[key].append(r)
497 data_by_year[''] = results
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))
504 for line in data_by_year.keys():
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 d[0] in fields_bar[idx]:
511 fields_bar[idx][d[0]] += d[idx+1]
513 fields_bar[idx][d[0]] = d[idx+1]
514 for idx in range(len(fields)-1):
516 for k in fields_bar[idx].keys():
518 data[k] += fields_bar[idx][k]
520 data[k] = fields_bar[idx][k]
525 # cumulate if necessary
527 data_cum.append([k, float(data[k])+float(prev)])
528 if fields[idx+1]['cumulate']:
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])
534 abscissa.update(fields_bar[idx])
536 abscissa = map(lambda x : [x, None], abscissa)
538 ar.x_coord = category_coord.T(abscissa,0)
542 self.obj = external_pdf(pdf_string.getvalue())
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)))
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 !"))
557 plot = pie_plot.T(data=results, arc_offsets=[0,10,0,10],
558 shadow = (2, -2, fill_style.gray50),
560 arrow_style = arrow.a3,
565 self.obj = external_pdf(pdf_string.getvalue())
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]]
575 new_doc = etree.Element('report')
576 config = etree.SubElement(new_doc, 'config')
578 def _append_node(name, text):
579 n = etree.SubElement(config, name)
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,))
588 length = pageSize[0]-30-reduce(lambda x,y:x+(y['width'] or 0), fields, 0)
591 if not f['width']: count+=1
594 f['width']=round((float(length)/count)-0.5)
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'],))
600 header = etree.SubElement(new_doc, 'header')
602 field = etree.SubElement(header, 'field')
603 field.text = f['name']
605 lines = etree.SubElement(new_doc, 'lines')
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] is not None:
611 col.text = line[f] or ''
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))
620 self.obj = render.rml(rml)
623 report_custom('report.custom')
626 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: