1 # -*- encoding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 ##############################################################################
30 from interface import report_int
32 from osv.osv import except_osv
33 from osv.orm import browse_null
34 from osv.orm import browse_record_list
36 from xml.dom import minidom
43 class external_pdf(render.render):
44 def __init__(self, pdf):
45 render.render.__init__(self)
47 self.output_type='pdf'
54 #TODO: devrait heriter de report_rml a la place de report_int
55 # -> pourrait overrider que create_xml a la place de tout create
56 # heuu, ca marche pas ds tous les cas car graphs sont generes en pdf directment
57 # par pychart, et on passe donc pas par du rml
58 class report_custom(report_int):
59 def __init__(self, name):
60 report_int.__init__(self, name)
63 # fields = [['address','city'],['name'], ['zip']]
64 # conditions = [[('zip','==','3'),(,)],(,),(,)] #same structure as fields
65 # row_canvas = ['Rue', None, None]
67 # [ ['ville','name','zip'] ]
69 def _row_get(self, cr, uid, objs, fields, conditions, row_canvas=None, group_by=None):
74 for cond in conditions:
77 temp = c[0](eval('obj.'+c[1]))
78 if not eval('\''+temp+'\''+' '+c[2]+' '+'\''+str(c[3])+'\''):
84 for i in range(len(fields)):
86 row.append(row_canvas and row_canvas[i])
89 elif len(fields[i])==1:
90 if not isinstance(obj, browse_null):
91 row.append(str(eval('obj.'+fields[i][0])))
96 levels[fields[i][0]]=True
100 # Process group_by data first
102 if group_by != None and fields[group_by] != None:
103 if fields[group_by][0] in levels.keys():
104 key.append(fields[group_by][0])
105 for l in levels.keys():
106 if l != fields[group_by][0]:
111 objs = eval('obj.'+l)
112 if not isinstance(objs, browse_record_list) and type(objs) <> type([]):
116 for f in range(len(fields)):
117 if (fields[f] and fields[f][0])==l:
118 field_new.append(fields[f][1:])
119 cond_new.append(conditions[f][1:])
121 field_new.append(None)
122 cond_new.append(None)
124 result += self._row_get(cr, uid, objs, field_new, cond_new, row, group_by)
130 def create(self, cr, uid, ids, datas, context=None):
133 self.pool = pooler.get_pool(cr.dbname)
134 report = self.pool.get('ir.report.custom').browse(cr, uid, [datas['report_id']])[0]
135 datas['model'] = report.model_id.model
137 ids = self.pool.get(report.model_id.model).search(cr, uid, [])
140 service = netsvc.LocalService("object_proxy")
141 report_id = datas['report_id']
142 report = service.execute(cr.dbname, uid, 'ir.report.custom', 'read', [report_id], context=context)[0]
143 fields = service.execute(cr.dbname, uid, 'ir.report.custom.fields', 'read', report['fields_child0'], context=context)
145 fields.sort(lambda x,y : x['sequence'] - y['sequence'])
147 if report['field_parent']:
148 parent_field = service.execute(cr.dbname, uid, 'ir.model.fields', 'read', [report['field_parent'][0]],['model'])
149 model_name = service.execute(cr.dbname, uid, 'ir.model', 'read', [report['model_id'][0]], ['model'],context=context)[0]['model']
152 fct['id'] = lambda x : x
153 fct['gety'] = lambda x: x.split('-')[0]
154 fct['in'] = lambda x: x.split(',')
161 field_child = f['field_child'+str(i)]
164 service.execute(cr.dbname, uid,
165 'ir.model.fields', 'read', [field_child[0]],
166 ['name'], context=context)[0]['name']
168 if f['fc'+str(i)+'_operande']:
170 cond_op = f['fc'+str(i)+'_op']
171 if len(f['fc'+str(i)+'_op'].split(',')) == 2:
172 cond_op = f['fc'+str(i)+'_op'].split(',')[1]
173 fct_name = f['fc'+str(i)+'_op'].split(',')[0]
174 cond.append((fct[fct_name], f['fc'+str(i)+'_operande'][1], cond_op, f['fc'+str(i)+'_condition']))
177 new_fields.append(row)
178 new_cond.append(cond)
179 objs = self.pool.get(model_name).browse(cr, uid, ids)
191 if report['field_parent']:
193 def build_tree(obj, level, depth):
194 res = self._row_get(cr, uid,[obj], new_fields, new_cond)
196 new_obj = eval('obj.'+report['field_parent'][1])
197 if not isinstance(new_obj, list) :
200 if not isinstance(o, browse_null):
201 res += build_tree(o, level, depth+1)
205 results += build_tree(obj, level, 0)
207 results = self._row_get(cr, uid,objs, new_fields, new_cond, group_by=groupby)
210 'calc_sum': lambda l: reduce(lambda x,y: float(x)+float(y), filter(None, l), 0),
211 'calc_avg': lambda l: reduce(lambda x,y: float(x)+float(y), filter(None, l), 0) / (len(filter(None, l)) or 1.0),
212 'calc_max': lambda l: reduce(lambda x,y: max(x,y), [(i or 0.0) for i in l], 0),
213 'calc_min': lambda l: reduce(lambda x,y: min(x,y), [(i or 0.0) for i in l], 0),
214 'calc_count': lambda l: len(filter(None, l)),
215 'False': lambda l: '\r\n'.join(filter(None, l)),
216 'groupby': lambda l: reduce(lambda x,y: x or y, l)
224 if not line[groupby] and prev in res_dic:
225 res_dic[prev].append(line)
228 if res_dic.has_key(line[groupby]):
229 res_dic[line[groupby]].append(line)
231 res_dic[line[groupby]] = []
232 res_dic[line[groupby]].append(line)
233 #we use the keys in results since they are ordered, whereas in res_dic.heys() they aren't
234 for key in filter(None, [x[groupby] for x in results]):
236 for col in range(len(fields)):
238 row.append(fct['groupby'](map(lambda x: x[col], res_dic[key])))
240 row.append(fct[str(fields[col]['operation'])](map(lambda x: x[col], res_dic[key])))
244 if report['type']=='table':
245 if report['field_parent']:
246 res = self._create_tree(uid, ids, report, fields, level, results, context)
249 for idx in range(len(fields)):
250 if fields[idx]['name'] == report['sortby']:
254 results.sort(lambda x,y : cmp(float(x[sort_idx]),float(y[sort_idx])))
256 results.sort(lambda x,y : cmp(x[sort_idx],y[sort_idx]))
258 results = results[:int(report['limitt'])]
259 res = self._create_table(uid, ids, report, fields, None, results, context)
260 elif report['type'] in ('pie','bar', 'line'):
265 for j in range(len(r)):
266 if j == 0 and not r[j]:
268 elif j == 0 and r[j]:
273 row.append(float(r[j]))
277 if report['type']=='pie':
278 res = self._create_pie(cr,uid, ids, report, fields, results2, context)
279 elif report['type']=='bar':
280 res = self._create_bars(cr,uid, ids, report, fields, results2, context)
281 elif report['type']=='line':
282 res = self._create_lines(cr,uid, ids, report, fields, results2, context)
283 return (self.obj.get(), 'pdf')
285 def _create_tree(self, uid, ids, report, fields, level, results, context):
286 pageSize=common.pageSize.get(report['print_format'], [210.0,297.0])
287 if report['print_orientation']=='landscape':
288 pageSize=[pageSize[1],pageSize[0]]
290 impl = minidom.getDOMImplementation()
291 new_doc = impl.createDocument(None, "report", None)
294 config = new_doc.createElement("config")
296 def _append_node(name, text):
297 n = new_doc.createElement(name)
298 t = new_doc.createTextNode(text)
300 config.appendChild(n)
302 _append_node('date', time.strftime('%d/%m/%Y'))
303 _append_node('PageFormat', '%s' % report['print_format'])
304 _append_node('PageSize', '%.2fmm,%.2fmm' % tuple(pageSize))
305 _append_node('PageWidth', '%.2f' % (pageSize[0] * 2.8346,))
306 _append_node('PageHeight', '%.2f' %(pageSize[1] * 2.8346,))
308 length = pageSize[0]-30-reduce(lambda x,y:x+(y['width'] or 0), fields, 0)
311 if not f['width']: count+=1
314 f['width']=round((float(length)/count)-0.5)
316 _append_node('tableSize', '%s' % ','.join(map(lambda x: '%.2fmm' % (x['width'],), fields)))
317 _append_node('report-header', '%s' % (report['title'],))
318 _append_node('report-footer', '%s' % (report['footer'],))
320 new_doc.childNodes[0].appendChild(config)
321 header = new_doc.createElement("header")
324 field = new_doc.createElement("field")
325 field_txt = new_doc.createTextNode('%s' % (f['name'],))
326 field.appendChild(field_txt)
327 header.appendChild(field)
329 new_doc.childNodes[0].appendChild(header)
331 lines = new_doc.createElement("lines")
335 node_line = new_doc.createElement("row")
337 for f in range(len(fields)):
338 col = new_doc.createElement("col")
340 col.setAttribute('para','yes')
341 col.setAttribute('tree','yes')
342 col.setAttribute('space',str(3*shift)+'mm')
344 txt = new_doc.createTextNode(prefix+str(line[f]) or '')
346 txt = new_doc.createTextNode('/')
348 node_line.appendChild(col)
350 lines.appendChild(node_line)
352 new_doc.childNodes[0].appendChild(lines)
354 styledoc = libxml2.parseFile(os.path.join(tools.config['root_path'],'addons/base/report/custom_new.xsl'))
355 style = libxslt.parseStylesheetDoc(styledoc)
356 doc = libxml2.parseDoc(new_doc.toxml())
357 rml_obj = style.applyStylesheet(doc, None)
358 rml = style.saveResultToString(rml_obj)
360 self.obj = render.rml(rml)
365 def _create_lines(self, cr, uid, ids, report, fields, results, context):
366 service = netsvc.LocalService("object_proxy")
367 pdf_string = cStringIO.StringIO()
368 can = canvas.init(fname=pdf_string, format='pdf')
370 can.show(80,380,'/16/H'+report['title'])
372 ar = area.T(size=(350,350),
373 #x_coord = category_coord.T(['2005-09-01','2005-10-22'],0),
374 x_axis = axis.X(label = fields[0]['name'], format="/a-30{}%s"),
375 y_axis = axis.Y(label = ', '.join(map(lambda x : x['name'], fields[1:]))))
378 process_date['D'] = lambda x : reduce(lambda xx,yy : xx+'-'+yy,x.split('-')[1:3])
379 process_date['M'] = lambda x : x.split('-')[1]
380 process_date['Y'] = lambda x : x.split('-')[0]
383 order_date['D'] = lambda x : time.mktime((2005,int(x.split('-')[0]), int(x.split('-')[1]),0,0,0,0,0,0))
384 order_date['M'] = lambda x : x
385 order_date['Y'] = lambda x : x
394 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])
396 type = service.execute(cr.dbname, uid, 'ir.model.fields', 'read', [field_id],['ttype'])
397 if type[0]['ttype'] == 'date':
399 fct[idx] = process_date[report['frequency']]
401 fct[idx] = lambda x : x
403 fct[idx] = lambda x : x
406 # plots are usually displayed year by year
407 # so we do so if the first field is a date
411 key = process_date['Y'](r[date_idx])
412 if not data_by_year.has_key(key):
413 data_by_year[key] = []
414 for i in range(len(r)):
416 data_by_year[key].append(r)
418 data_by_year[''] = results
421 nb_bar = len(data_by_year)*(len(fields)-1)
422 colors = map(lambda x:line_style.T(color=x), misc.choice_colors(nb_bar))
424 for line in data_by_year.keys():
426 # sum data and save it in a list. An item for a fields
427 for d in data_by_year[line]:
428 for idx in range(len(fields)-1):
429 fields_bar.append({})
430 if fields_bar[idx].has_key(d[0]):
431 fields_bar[idx][d[0]] += d[idx+1]
433 fields_bar[idx][d[0]] = d[idx+1]
434 for idx in range(len(fields)-1):
436 for k in fields_bar[idx].keys():
438 data[k] += fields_bar[idx][k]
440 data[k] = fields_bar[idx][k]
445 # cumulate if necessary
447 data_cum.append([k, float(data[k])+float(prev)])
448 if fields[idx+1]['cumulate']:
451 plot = line_plot.T(label=fields[idx+1]['name']+' '+str(line), data = data_cum, line_style=colors[idx0*(len(fields)-1)+idx])
453 abscissa.update(fields_bar[idx])
456 abscissa = map(lambda x : [x, None], abscissa)
457 ar.x_coord = category_coord.T(abscissa,0)
461 self.obj = external_pdf(pdf_string.getvalue())
468 def _create_bars(self, cr, uid, ids, report, fields, results, context):
469 service = netsvc.LocalService("object_proxy")
470 pdf_string = cStringIO.StringIO()
471 can = canvas.init(fname=pdf_string, format='pdf')
473 can.show(80,380,'/16/H'+report['title'])
476 process_date['D'] = lambda x : reduce(lambda xx,yy : xx+'-'+yy,x.split('-')[1:3])
477 process_date['M'] = lambda x : x.split('-')[1]
478 process_date['Y'] = lambda x : x.split('-')[0]
481 order_date['D'] = lambda x : time.mktime((2005,int(x.split('-')[0]), int(x.split('-')[1]),0,0,0,0,0,0))
482 order_date['M'] = lambda x : x
483 order_date['Y'] = lambda x : x
485 ar = area.T(size=(350,350),
486 x_axis = axis.X(label = fields[0]['name'], format="/a-30{}%s"),
487 y_axis = axis.Y(label = ', '.join(map(lambda x : x['name'], fields[1:]))))
493 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])
495 type = service.execute(cr.dbname, uid, 'ir.model.fields', 'read', [field_id],['ttype'])
496 if type[0]['ttype'] == 'date':
498 fct[idx] = process_date[report['frequency']]
500 fct[idx] = lambda x : x
502 fct[idx] = lambda x : x
505 # plot are usually displayed year by year
506 # so we do so if the first field is a date
510 key = process_date['Y'](r[date_idx])
511 if not data_by_year.has_key(key):
512 data_by_year[key] = []
513 for i in range(len(r)):
515 data_by_year[key].append(r)
517 data_by_year[''] = results
520 nb_bar = len(data_by_year)*(len(fields)-1)
521 colors = map(lambda x:fill_style.Plain(bgcolor=x), misc.choice_colors(nb_bar))
524 for line in data_by_year.keys():
526 # sum data and save it in a list. An item for a fields
527 for d in data_by_year[line]:
528 for idx in range(len(fields)-1):
529 fields_bar.append({})
530 if fields_bar[idx].has_key(d[0]):
531 fields_bar[idx][d[0]] += d[idx+1]
533 fields_bar[idx][d[0]] = d[idx+1]
534 for idx in range(len(fields)-1):
536 for k in fields_bar[idx].keys():
538 data[k] += fields_bar[idx][k]
540 data[k] = fields_bar[idx][k]
545 # cumulate if necessary
547 data_cum.append([k, float(data[k])+float(prev)])
548 if fields[idx+1]['cumulate']:
552 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])
554 abscissa.update(fields_bar[idx])
556 abscissa = map(lambda x : [x, None], abscissa)
557 ar.x_coord = category_coord.T(abscissa,0)
561 self.obj = external_pdf(pdf_string.getvalue())
566 def _create_pie(self, cr, uid, ids, report, fields, results, context):
567 pdf_string = cStringIO.StringIO()
568 can = canvas.init(fname=pdf_string, format='pdf')
569 ar = area.T(size=(350,350), legend=legend.T(),
570 x_grid_style = None, y_grid_style = None)
571 colors = map(lambda x:fill_style.Plain(bgcolor=x), misc.choice_colors(len(results)))
573 if reduce(lambda x,y : x+y, map(lambda x : x[1],results)) == 0.0:
574 raise except_osv(_('Error'), _("The sum of the data (2nd field) is null.\nWe can't draw a pie chart !"))
576 plot = pie_plot.T(data=results, arc_offsets=[0,10,0,10],
577 shadow = (2, -2, fill_style.gray50),
579 arrow_style = arrow.a3,
584 self.obj = external_pdf(pdf_string.getvalue())
589 def _create_table(self, uid, ids, report, fields, tree, results, context):
590 pageSize=common.pageSize.get(report['print_format'], [210.0,297.0])
591 if report['print_orientation']=='landscape':
592 pageSize=[pageSize[1],pageSize[0]]
594 impl = minidom.getDOMImplementation()
595 new_doc = impl.createDocument(None, "report", None)
598 config = new_doc.createElement("config")
600 def _append_node(name, text):
601 n = new_doc.createElement(name)
602 t = new_doc.createTextNode(text)
604 config.appendChild(n)
606 _append_node('date', time.strftime('%d/%m/%Y'))
607 _append_node('PageSize', '%.2fmm,%.2fmm' % tuple(pageSize))
608 _append_node('PageFormat', '%s' % report['print_format'])
609 _append_node('PageWidth', '%.2f' % (pageSize[0] * 2.8346,))
610 _append_node('PageHeight', '%.2f' %(pageSize[1] * 2.8346,))
612 length = pageSize[0]-30-reduce(lambda x,y:x+(y['width'] or 0), fields, 0)
615 if not f['width']: count+=1
618 f['width']=round((float(length)/count)-0.5)
620 _append_node('tableSize', '%s' % ','.join(map(lambda x: '%.2fmm' % (x['width'],), fields)))
621 _append_node('report-header', '%s' % (report['title'],))
622 _append_node('report-footer', '%s' % (report['footer'],))
624 new_doc.childNodes[0].appendChild(config)
625 header = new_doc.createElement("header")
628 field = new_doc.createElement("field")
629 field_txt = new_doc.createTextNode('%s' % (f['name'],))
630 field.appendChild(field_txt)
631 header.appendChild(field)
633 new_doc.childNodes[0].appendChild(header)
635 lines = new_doc.createElement("lines")
637 node_line = new_doc.createElement("row")
638 for f in range(len(fields)):
639 col = new_doc.createElement("col")
640 col.setAttribute('tree','no')
642 txt = new_doc.createTextNode(str(line[f] or ''))
644 txt = new_doc.createTextNode('/')
646 node_line.appendChild(col)
647 lines.appendChild(node_line)
649 new_doc.childNodes[0].appendChild(lines)
651 # file('/tmp/terp.xml','w+').write(new_doc.toxml())
653 styledoc = libxml2.parseFile(os.path.join(tools.config['root_path'],'addons/base/report/custom_new.xsl'))
654 style = libxslt.parseStylesheetDoc(styledoc)
655 doc = libxml2.parseDoc(new_doc.toxml())
656 rml_obj = style.applyStylesheet(doc, None)
657 rml = style.saveResultToString(rml_obj)
659 self.obj = render.rml(rml)
662 report_custom('report.custom')
665 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: