diff options
Diffstat (limited to 'bin/report/custom.py')
| -rw-r--r-- | bin/report/custom.py | 661 |
1 files changed, 661 insertions, 0 deletions
diff --git a/bin/report/custom.py b/bin/report/custom.py new file mode 100644 index 00000000000..2d4aef9c137 --- /dev/null +++ b/bin/report/custom.py @@ -0,0 +1,661 @@ +############################################################################## +# +# Copyright (c) 2004-2005 TINY SPRL. (http://tiny.be) All Rights Reserved. +# +# $Id: custom.py 1305 2005-09-08 14:39:51Z kayhman $ +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# garantees and support are strongly adviced to contract a Free Software +# Service Company +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + +import os, time +import netsvc + +import tools +import print_xml +import render +from interface import report_int +import common +from osv.orm import browse_null +from osv.orm import browse_record_list +import pooler +from xml.dom import minidom +import libxml2 +import libxslt +from pychart import * +import misc +import StringIO + +class external_pdf(render.render): + def __init__(self, pdf): + render.render.__init__(self) + self.pdf = pdf + self.output_type='pdf' + def _render(self): + return self.pdf + +theme.use_color = 1 + + +#TODO: devrait heriter de report_rml a la place de report_int +# -> pourrait overrider que create_xml a la place de tout create +# heuu, ca marche pas ds tous les cas car graphs sont generes en pdf directment +# par pychart, et on passe donc pas par du rml +class report_custom(report_int): + def __init__(self, name): + report_int.__init__(self, name) + # + # PRE: + # fields = [['address','city'],['name'], ['zip']] + # conditions = [[('zip','==','3'),(,)],(,),(,)] #same structure as fields + # row_canvas = ['Rue', None, None] + # POST: + # [ ['ville','name','zip'] ] + # + def _row_get(self, cr, uid, objs, fields, conditions, row_canvas=None, group_by=None): + result = [] + tmp = [] + for obj in objs: + tobreak = False + for cond in conditions: + if cond and cond[0]: + c = cond[0] + temp = c[0](eval('obj.'+c[1])) + if not eval('\''+temp+'\''+' '+c[2]+' '+'\''+str(c[3])+'\''): + tobreak = True + if tobreak: + break + levels = {} + row = [] + for i in range(len(fields)): + if not fields[i]: + row.append(row_canvas and row_canvas[i]) + if row_canvas[i]: + row_canvas[i]=False + elif len(fields[i])==1: + if not isinstance(obj, browse_null): + row.append(str(eval('obj.'+fields[i][0]))) + else: + row.append(None) + else: + row.append(None) + levels[fields[i][0]]=True + if not levels: + result.append(row) + else: + # Process group_by data first + key = [] + if group_by != None and fields[group_by] != None: + if fields[group_by][0] in levels.keys(): + key.append(fields[group_by][0]) + for l in levels.keys(): + if l != fields[group_by][0]: + key.append(l) + else: + key = levels.keys() + for l in key: + objs = eval('obj.'+l) + if not isinstance(objs, browse_record_list) and type(objs) <> type([]): + objs = [objs] + field_new = [] + cond_new = [] + for f in range(len(fields)): + if (fields[f] and fields[f][0])==l: + field_new.append(fields[f][1:]) + cond_new.append(conditions[f][1:]) + else: + field_new.append(None) + cond_new.append(None) + if len(objs): + result += self._row_get(cr, uid, objs, field_new, cond_new, row, group_by) + else: + result.append(row) + return result + + + def create(self, cr, uid, ids, datas, context={}): + self.pool = pooler.get_pool(cr.dbname) + report = self.pool.get('ir.report.custom').browse(cr, uid, [datas['report_id']])[0] + datas['model'] = report.model_id.model + if report.menu_id: + ids = self.pool.get(report.model_id.model).search(cr, uid, []) + datas['ids'] = ids + + service = netsvc.LocalService("object_proxy") + report_id = datas['report_id'] + report = service.execute(cr.dbname, uid, 'ir.report.custom', 'read', [report_id], context=context)[0] + fields = service.execute(cr.dbname, uid, 'ir.report.custom.fields', 'read', report['fields_child0'], context=context) + + fields.sort(lambda x,y : x['sequence'] - y['sequence']) + + if report['field_parent']: + parent_field = service.execute(cr.dbname, uid, 'ir.model.fields', 'read', [report['field_parent'][0]],['model']) + model_name = service.execute(cr.dbname, uid, 'ir.model', 'read', [report['model_id'][0]], ['model'],context=context)[0]['model'] + + fct = {} + fct['id'] = lambda x : x + fct['gety'] = lambda x: x.split('-')[0] + fct['in'] = lambda x: x.split(',') + new_fields = [] + new_cond = [] + for f in fields: + row = [] + cond = [] + for i in range(4): + if f['field_child'+str(i)]: + row.append(f['field_child'+str(i)][1]) + if f['fc'+str(i)+'_operande']: + fct_name = 'id' + cond_op = f['fc'+str(i)+'_op'] + if len(f['fc'+str(i)+'_op'].split(',')) == 2: + cond_op = f['fc'+str(i)+'_op'].split(',')[1] + fct_name = f['fc'+str(i)+'_op'].split(',')[0] + cond.append((fct[fct_name], f['fc'+str(i)+'_operande'][1], cond_op, f['fc'+str(i)+'_condition'])) + else: + cond.append(None) + new_fields.append(row) + new_cond.append(cond) + objs = self.pool.get(model_name).browse(cr, uid, ids) + + # Group by + groupby = None + idx = 0 + for f in fields: + if f['groupby']: + groupby = idx + idx += 1 + + + results = [] + if report['field_parent']: + level = [] + def build_tree(obj, level, depth): + res = self._row_get(cr, uid,[obj], new_fields, new_cond) + level.append(depth) + new_obj = eval('obj.'+report['field_parent'][1]) + if not isinstance(new_obj, list) : + new_obj = [new_obj] + for o in new_obj: + if not isinstance(o, browse_null): + res += build_tree(o, level, depth+1) + return res + + for obj in objs: + results += build_tree(obj, level, 0) + else: + results = self._row_get(cr, uid,objs, new_fields, new_cond, group_by=groupby) + + fct = { + 'calc_sum': lambda l: reduce(lambda x,y: float(x)+float(y), filter(None, l), 0), + 'calc_avg': lambda l: reduce(lambda x,y: float(x)+float(y), filter(None, l), 0) / (len(filter(None, l)) or 1.0), + 'calc_max': lambda l: reduce(lambda x,y: max(x,y), [(i or 0.0) for i in l], 0), + 'calc_min': lambda l: reduce(lambda x,y: min(x,y), [(i or 0.0) for i in l], 0), + 'calc_count': lambda l: len(filter(None, l)), + 'False': lambda l: '\r\n'.join(filter(None, l)), + 'groupby': lambda l: reduce(lambda x,y: x or y, l) + } + new_res = [] + + prev = None + if groupby != None: + res_dic = {} + for line in results: + if not line[groupby] and prev in res_dic: + res_dic[prev].append(line) + else: + prev = line[groupby] + if res_dic.has_key(line[groupby]): + res_dic[line[groupby]].append(line) + else: + res_dic[line[groupby]] = [] + res_dic[line[groupby]].append(line) + #we use the keys in results since they are ordered, whereas in res_dic.heys() they aren't + for key in filter(None, [x[groupby] for x in results]): + row = [] + for col in range(len(fields)): + if col == groupby: + row.append(fct['groupby'](map(lambda x: x[col], res_dic[key]))) + else: + row.append(fct[str(fields[col]['operation'])](map(lambda x: x[col], res_dic[key]))) + new_res.append(row) + results = new_res + + if report['type']=='table': + if report['field_parent']: + res = self._create_tree(uid, ids, report, fields, level, results, context) + else: + sort_idx = 0 + for idx in range(len(fields)): + if fields[idx]['name'] == report['sortby']: + sort_idx = idx + break + try : + results.sort(lambda x,y : cmp(float(x[sort_idx]),float(y[sort_idx]))) + except : + results.sort(lambda x,y : cmp(x[sort_idx],y[sort_idx])) + if report['limitt']: + results = results[:int(report['limitt'])] + res = self._create_table(uid, ids, report, fields, None, results, context) + elif report['type'] in ('pie','bar', 'line'): + results2 = [] + prev = False + for r in results: + row = [] + for j in range(len(r)): + if j == 0 and not r[j]: + row.append(prev) + elif j == 0 and r[j]: + prev = r[j] + row.append(r[j]) + else: + try: + row.append(float(r[j])) + except: + row.append(r[j]) + results2.append(row) + if report['type']=='pie': + res = self._create_pie(cr,uid, ids, report, fields, results2, context) + elif report['type']=='bar': + res = self._create_bars(cr,uid, ids, report, fields, results2, context) + elif report['type']=='line': + res = self._create_lines(cr,uid, ids, report, fields, results2, context) + return (self.obj.get(), 'pdf') + + def _create_tree(self, uid, ids, report, fields, level, results, context): + pageSize=common.pageSize.get(report['print_format'], [210.0,297.0]) + if report['print_orientation']=='landscape': + pageSize=[pageSize[1],pageSize[0]] + + impl = minidom.getDOMImplementation() + new_doc = impl.createDocument(None, "report", None) + + # build header + config = new_doc.createElement("config") + + def _append_node(name, text): + n = new_doc.createElement(name) + t = new_doc.createTextNode(text) + n.appendChild(t) + config.appendChild(n) + + _append_node('date', time.strftime('%d/%m/%Y')) + _append_node('PageFormat', '%s' % report['print_format']) + _append_node('PageSize', '%.2fmm,%.2fmm' % tuple(pageSize)) + _append_node('PageWidth', '%.2f' % (pageSize[0] * 2.8346,)) + _append_node('PageHeight', '%.2f' %(pageSize[1] * 2.8346,)) + + length = pageSize[0]-30-reduce(lambda x,y:x+(y['width'] or 0), fields, 0) + count = 0 + for f in fields: + if not f['width']: count+=1 + for f in fields: + if not f['width']: + f['width']=round((float(length)/count)-0.5) + + _append_node('tableSize', '%s' % ','.join(map(lambda x: '%.2fmm' % (x['width'],), fields))) + _append_node('report-header', '%s' % (report['title'],)) + _append_node('report-footer', '%s' % (report['footer'],)) + + new_doc.childNodes[0].appendChild(config) + header = new_doc.createElement("header") + + for f in fields: + field = new_doc.createElement("field") + field_txt = new_doc.createTextNode('%s' % (f['name'],)) + field.appendChild(field_txt) + header.appendChild(field) + + new_doc.childNodes[0].appendChild(header) + + lines = new_doc.createElement("lines") + level.reverse() + for line in results: + shift = level.pop() + node_line = new_doc.createElement("row") + prefix = '+' + for f in range(len(fields)): + col = new_doc.createElement("col") + if f == 0: + col.setAttribute('para','yes') + col.setAttribute('tree','yes') + col.setAttribute('space',str(3*shift)+'mm') + if line[f] != None: + txt = new_doc.createTextNode(prefix+str(line[f]) or '') + else: + txt = new_doc.createTextNode('/') + col.appendChild(txt) + node_line.appendChild(col) + prefix = '' + lines.appendChild(node_line) + + new_doc.childNodes[0].appendChild(lines) + + styledoc = libxml2.parseFile(os.path.join(tools.config['root_path'],'addons/base/report/custom_new.xsl')) + style = libxslt.parseStylesheetDoc(styledoc) + doc = libxml2.parseDoc(new_doc.toxml()) + rml_obj = style.applyStylesheet(doc, None) + rml = style.saveResultToString(rml_obj) + + self.obj = render.rml(rml) + self.obj.render() + return True + + + def _create_lines(self, cr, uid, ids, report, fields, results, context): + service = netsvc.LocalService("object_proxy") + pdf_string = StringIO.StringIO() + can = canvas.init(fname=pdf_string, format='pdf') + + can.show(80,380,'/16/H'+report['title']) + + ar = area.T(size=(350,350), + #x_coord = category_coord.T(['2005-09-01','2005-10-22'],0), + x_axis = axis.X(label = fields[0]['name'], format="/a-30{}%s"), + y_axis = axis.Y(label = ', '.join(map(lambda x : x['name'], fields[1:])))) + + process_date = {} + process_date['D'] = lambda x : reduce(lambda xx,yy : xx+'-'+yy,x.split('-')[1:3]) + process_date['M'] = lambda x : x.split('-')[1] + process_date['Y'] = lambda x : x.split('-')[0] + + order_date = {} + order_date['D'] = lambda x : time.mktime((2005,int(x.split('-')[0]), int(x.split('-')[1]),0,0,0,0,0,0)) + order_date['M'] = lambda x : x + order_date['Y'] = lambda x : x + + abscissa = [] + tmp = {} + + idx = 0 + date_idx = None + fct = {} + for f in fields: + 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]) + if field_id: + type = service.execute(cr.dbname, uid, 'ir.model.fields', 'read', [field_id],['ttype']) + if type[0]['ttype'] == 'date': + date_idx = idx + fct[idx] = process_date[report['frequency']] + else: + fct[idx] = lambda x : x + else: + fct[idx] = lambda x : x + idx+=1 + + # plots are usually displayed year by year + # so we do so if the first field is a date + data_by_year = {} + if date_idx != None: + for r in results: + key = process_date['Y'](r[date_idx]) + if not data_by_year.has_key(key): + data_by_year[key] = [] + for i in range(len(r)): + r[i] = fct[i](r[i]) + data_by_year[key].append(r) + else: + data_by_year[''] = results + + idx0 = 0 + nb_bar = len(data_by_year)*(len(fields)-1) + colors = map(lambda x:line_style.T(color=x), misc.choice_colors(nb_bar)) + abscissa = {} + for line in data_by_year.keys(): + fields_bar = [] + # sum data and save it in a list. An item for a fields + for d in data_by_year[line]: + for idx in range(len(fields)-1): + fields_bar.append({}) + if fields_bar[idx].has_key(d[0]): + fields_bar[idx][d[0]] += d[idx+1] + else: + fields_bar[idx][d[0]] = d[idx+1] + for idx in range(len(fields)-1): + data = {} + for k in fields_bar[idx].keys(): + if data.has_key(k): + data[k] += fields_bar[idx][k] + else: + data[k] = fields_bar[idx][k] + data_cum = [] + prev = 0.0 + keys = data.keys() + keys.sort() + # cumulate if necessary + for k in keys: + data_cum.append([k, float(data[k])+float(prev)]) + if fields[idx+1]['cumulate']: + prev += data[k] + idx0 = 0 + plot = line_plot.T(label=fields[idx+1]['name']+' '+str(line), data = data_cum, line_style=colors[idx0*(len(fields)-1)+idx]) + ar.add_plot(plot) + abscissa.update(fields_bar[idx]) + idx0 += 1 + + abscissa = map(lambda x : [x, None], abscissa) + ar.x_coord = category_coord.T(abscissa,0) + ar.draw(can) + + can.close() + self.obj = external_pdf(pdf_string.getvalue()) + self.obj.render() + pdf_string.close() + return True + + + + def _create_bars(self, cr, uid, ids, report, fields, results, context): + service = netsvc.LocalService("object_proxy") + pdf_string = StringIO.StringIO() + can = canvas.init(fname=pdf_string, format='pdf') + + can.show(80,380,'/16/H'+report['title']) + + process_date = {} + process_date['D'] = lambda x : reduce(lambda xx,yy : xx+'-'+yy,x.split('-')[1:3]) + process_date['M'] = lambda x : x.split('-')[1] + process_date['Y'] = lambda x : x.split('-')[0] + + order_date = {} + order_date['D'] = lambda x : time.mktime((2005,int(x.split('-')[0]), int(x.split('-')[1]),0,0,0,0,0,0)) + order_date['M'] = lambda x : x + order_date['Y'] = lambda x : x + + ar = area.T(size=(350,350), + x_axis = axis.X(label = fields[0]['name'], format="/a-30{}%s"), + y_axis = axis.Y(label = ', '.join(map(lambda x : x['name'], fields[1:])))) + + idx = 0 + date_idx = None + fct = {} + for f in fields: + 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]) + if field_id: + type = service.execute(cr.dbname, uid, 'ir.model.fields', 'read', [field_id],['ttype']) + if type[0]['ttype'] == 'date': + date_idx = idx + fct[idx] = process_date[report['frequency']] + else: + fct[idx] = lambda x : x + else: + fct[idx] = lambda x : x + idx+=1 + + # plot are usually displayed year by year + # so we do so if the first field is a date + data_by_year = {} + if date_idx != None: + for r in results: + key = process_date['Y'](r[date_idx]) + if not data_by_year.has_key(key): + data_by_year[key] = [] + for i in range(len(r)): + r[i] = fct[i](r[i]) + data_by_year[key].append(r) + else: + data_by_year[''] = results + + + nb_bar = len(data_by_year)*(len(fields)-1) + colors = map(lambda x:fill_style.Plain(bgcolor=x), misc.choice_colors(nb_bar)) + + abscissa = {} + for line in data_by_year.keys(): + fields_bar = [] + # sum data and save it in a list. An item for a fields + for d in data_by_year[line]: + for idx in range(len(fields)-1): + fields_bar.append({}) + if fields_bar[idx].has_key(d[0]): + fields_bar[idx][d[0]] += d[idx+1] + else: + fields_bar[idx][d[0]] = d[idx+1] + for idx in range(len(fields)-1): + data = {} + for k in fields_bar[idx].keys(): + if data.has_key(k): + data[k] += fields_bar[idx][k] + else: + data[k] = fields_bar[idx][k] + data_cum = [] + prev = 0.0 + keys = data.keys() + keys.sort() + # cumulate if necessary + for k in keys: + data_cum.append([k, float(data[k])+float(prev)]) + if fields[idx+1]['cumulate']: + prev += data[k] + + idx0 = 0 + 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]) + ar.add_plot(plot) + abscissa.update(fields_bar[idx]) + idx0 += 1 + abscissa = map(lambda x : [x, None], abscissa) + ar.x_coord = category_coord.T(abscissa,0) + ar.draw(can) + + can.close() + self.obj = external_pdf(pdf_string.getvalue()) + self.obj.render() + pdf_string.close() + return True + + def _create_pie(self, cr, uid, ids, report, fields, results, context): + pdf_string = StringIO.StringIO() + can = canvas.init(fname=pdf_string, format='pdf') + ar = area.T(size=(350,350), legend=legend.T(), + x_grid_style = None, y_grid_style = None) + colors = map(lambda x:fill_style.Plain(bgcolor=x), misc.choice_colors(len(results))) + + if reduce(lambda x,y : x+y, map(lambda x : x[1],results)) == 0.0: + raise('The sum of the data (2nd field) is null. \nWe can draw a pie chart !') + + plot = pie_plot.T(data=results, arc_offsets=[0,10,0,10], + shadow = (2, -2, fill_style.gray50), + label_offset = 25, + arrow_style = arrow.a3, + fill_styles=colors) + ar.add_plot(plot) + ar.draw(can) + can.close() + self.obj = external_pdf(pdf_string.getvalue()) + self.obj.render() + pdf_string.close() + return True + + def _create_table(self, uid, ids, report, fields, tree, results, context): + pageSize=common.pageSize.get(report['print_format'], [210.0,297.0]) + if report['print_orientation']=='landscape': + pageSize=[pageSize[1],pageSize[0]] + + impl = minidom.getDOMImplementation() + new_doc = impl.createDocument(None, "report", None) + + # build header + config = new_doc.createElement("config") + + def _append_node(name, text): + n = new_doc.createElement(name) + t = new_doc.createTextNode(text) + n.appendChild(t) + config.appendChild(n) + + _append_node('date', time.strftime('%d/%m/%Y')) + _append_node('PageSize', '%.2fmm,%.2fmm' % tuple(pageSize)) + _append_node('PageFormat', '%s' % report['print_format']) + _append_node('PageWidth', '%.2f' % (pageSize[0] * 2.8346,)) + _append_node('PageHeight', '%.2f' %(pageSize[1] * 2.8346,)) + + length = pageSize[0]-30-reduce(lambda x,y:x+(y['width'] or 0), fields, 0) + count = 0 + for f in fields: + if not f['width']: count+=1 + for f in fields: + if not f['width']: + f['width']=round((float(length)/count)-0.5) + + _append_node('tableSize', '%s' % ','.join(map(lambda x: '%.2fmm' % (x['width'],), fields))) + _append_node('report-header', '%s' % (report['title'],)) + _append_node('report-footer', '%s' % (report['footer'],)) + + new_doc.childNodes[0].appendChild(config) + header = new_doc.createElement("header") + + for f in fields: + field = new_doc.createElement("field") + field_txt = new_doc.createTextNode('%s' % (f['name'],)) + field.appendChild(field_txt) + header.appendChild(field) + + new_doc.childNodes[0].appendChild(header) + + lines = new_doc.createElement("lines") + for line in results: + node_line = new_doc.createElement("row") + for f in range(len(fields)): + col = new_doc.createElement("col") + col.setAttribute('tree','no') + if line[f] != None: + txt = new_doc.createTextNode(str(line[f] or '')) + else: + txt = new_doc.createTextNode('/') + col.appendChild(txt) + node_line.appendChild(col) + lines.appendChild(node_line) + + new_doc.childNodes[0].appendChild(lines) + +# file('/tmp/terp.xml','w+').write(new_doc.toxml()) + + styledoc = libxml2.parseFile(os.path.join(tools.config['root_path'],'addons/base/report/custom_new.xsl')) + style = libxslt.parseStylesheetDoc(styledoc) + doc = libxml2.parseDoc(new_doc.toxml()) + rml_obj = style.applyStylesheet(doc, None) + rml = style.saveResultToString(rml_obj) + + self.obj = render.rml(rml) + self.obj.render() + return True +report_custom('report.custom') + |
