Browse Source

Add demo data. Set computed result. In instance add target_move (all or posted). Add onchange and check function on report

pull/86/head
Laetitia Gangloff 11 years ago
committed by Stéphane Bidoul
parent
commit
c9bb2c64c3
  1. 6
      mis_builder/__openerp__.py
  2. 97
      mis_builder/models/mis_builder.py
  3. 19
      mis_builder/static/src/xml/mis_widget.xml
  4. 2
      mis_builder/tests/mis.report.csv
  5. 2
      mis_builder/tests/mis.report.instance.csv
  6. 7
      mis_builder/tests/mis.report.instance.period.csv
  7. 7
      mis_builder/tests/mis.report.kpi.csv
  8. 2
      mis_builder/tests/mis.report.query.csv
  9. 14
      mis_builder/views/mis_builder.xml

6
mis_builder/__openerp__.py

@ -36,7 +36,11 @@
], ],
'test': [ 'test': [
], ],
'demo': [
'demo': ['tests/mis.report.kpi.csv',
'tests/mis.report.query.csv',
'tests/mis.report.csv',
'tests/mis.report.instance.period.csv',
'tests/mis.report.instance.csv',
], ],
'js': [ 'js': [
'static/src/js/*.js' 'static/src/js/*.js'

97
mis_builder/models/mis_builder.py

@ -26,6 +26,7 @@ from datetime import datetime, timedelta
from dateutil import parser from dateutil import parser
import traceback import traceback
from lxml import etree from lxml import etree
import re
from openerp.osv import orm, fields from openerp.osv import orm, fields
from openerp.tools.safe_eval import safe_eval from openerp.tools.safe_eval import safe_eval
@ -76,11 +77,12 @@ class mis_report_kpi(orm.Model):
string='Type'), string='Type'),
'divider': fields.selection([('1e-6', _('µ')), 'divider': fields.selection([('1e-6', _('µ')),
('1e-3', _('m')), ('1e-3', _('m')),
('1', _('1')),
('1e3', _('k')), ('1e3', _('k')),
('1e6', _('M'))], ('1e6', _('M'))],
string='Factor'), string='Factor'),
'dp': fields.integer(string='Rounding'), 'dp': fields.integer(string='Rounding'),
'suffix': fields.char(size=16, string='Unit'),
'suffix': fields.char(size=16, string='Suffix'),
'compare_method': fields.selection([('diff', _('Difference')), 'compare_method': fields.selection([('diff', _('Difference')),
('pct', _('Percentage')), ('pct', _('Percentage')),
('none', _('None'))], ('none', _('None'))],
@ -99,9 +101,40 @@ class mis_report_kpi(orm.Model):
_order = 'sequence' _order = 'sequence'
# TODO: constraint to check name is a valid python identifier
# TODO: onchange type pct -> force comparison method = diff
# TODO: onchange type str -> divider, dp, suffix, compare_method read only
def _check_name(self, cr, uid, ids, context=None):
for record_name in self.read(cr, uid, ids, ['name']):
if not re.match("[_A-Za-z][_a-zA-Z0-9]*$", record_name['name']):
return False
return True
_constraints = [
(_check_name, 'The name must be a valid python identifier', ['name']),
]
def onchange_name(self, cr, uid, ids, name, context=None):
# check it is a valid python identifier
res = {}
if name and not re.match("[_A-Za-z][_a-zA-Z0-9]*$", name):
res['warning'] = {'title': 'Invalid name', 'message': 'The name must be a valid python identifier'}
return res
def onchange_description(self, cr, uid, ids, description, context=None):
# construct name from description
clean = lambda varStr: re.sub('\W|^(?=\d)', '_', varStr)
res = {}
if description:
res = {'value': {'name': clean(description)}}
return res
def onchange_type(self, cr, uid, ids, kpi_type, context=None):
res = {}
if kpi_type == 'pct':
res['value'] = {'compare_method': 'diff'}
elif kpi_type == 'str':
res['value'] = {'compare_method': 'none',
'divider': '',
'dp': 0}
return res
def _render(self, kpi, value): def _render(self, kpi, value):
""" render a KPI value as a unicode string, ready for display """ """ render a KPI value as a unicode string, ready for display """
@ -207,6 +240,11 @@ class mis_report_instance_period(orm.Model):
if isinstance(ids, (int, long)): if isinstance(ids, (int, long)):
ids = [ids] ids = [ids]
res = {} res = {}
company_id = context.get('force_company')
if not company_id:
user = self.pool.get('res.users').read(cr, uid, uid, ['company_id'], context=context)
if user['company_id']:
company_id = user['company_id'][0]
for c in self.browse(cr, uid, ids, context=context): for c in self.browse(cr, uid, ids, context=context):
d = parser.parse(c.report_instance_id.pivot_date) d = parser.parse(c.report_instance_id.pivot_date)
if c.type == 'd': if c.type == 'd':
@ -223,17 +261,17 @@ class mis_report_instance_period(orm.Model):
date_to = date_to.strftime(tools.DEFAULT_SERVER_DATE_FORMAT) date_to = date_to.strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
period_ids = None period_ids = None
elif c.type == 'fp': elif c.type == 'fp':
# TODO: filter on company_id
# TODO: date! # TODO: date!
period_obj = self.pool['account.period'] period_obj = self.pool['account.period']
all_period_ids = period_obj.search(cr, uid, all_period_ids = period_obj.search(cr, uid,
[('special', '=', False)],
[('special', '=', False), ('company_id', '=', company_id)],
order='date_start', order='date_start',
context=context) context=context)
current_period_ids = period_obj.search(cr, uid, current_period_ids = period_obj.search(cr, uid,
[('special', '=', False), [('special', '=', False),
('date_start', '<=', d), ('date_start', '<=', d),
('date_stop', '>=', d)],
('date_stop', '>=', d),
('company_id', '=', company_id)],
context=context) context=context)
if not current_period_ids: if not current_period_ids:
raise orm.except_orm(_("Error!"), raise orm.except_orm(_("Error!"),
@ -264,7 +302,7 @@ class mis_report_instance_period(orm.Model):
_columns = { _columns = {
'name': fields.char(size=32, required=True, 'name': fields.char(size=32, required=True,
string='Name', translate=True),
string='Description', translate=True),
'type': fields.selection([('d', _('Day')), 'type': fields.selection([('d', _('Day')),
('w', _('Week')), ('w', _('Week')),
('fp', _('Fiscal Period')), ('fp', _('Fiscal Period')),
@ -296,19 +334,23 @@ class mis_report_instance_period(orm.Model):
} }
_defaults = { _defaults = {
'offset':-1,
'offset': -1,
'duration': 1, 'duration': 1,
} }
_order = 'sequence' _order = 'sequence'
# TODO: constraint duration >= 1
_sql_constraints = [
('duration', 'CHECK (duration>0)', 'Wrong duration, it must be positive!')
]
def _fetch_balances(self, cr, uid, c, context=None): def _fetch_balances(self, cr, uid, c, context=None):
""" fetch the general account balances for the given period """ fetch the general account balances for the given period
returns a dictionary {bal_<account.code>: account.balance} returns a dictionary {bal_<account.code>: account.balance}
""" """
if context is None:
context = {}
account_obj = self.pool['account.account'] account_obj = self.pool['account.account']
search_ctx = dict(context) search_ctx = dict(context)
@ -320,8 +362,12 @@ class mis_report_instance_period(orm.Model):
'date_to': c.date_to}) 'date_to': c.date_to})
# TODO: initial balance? # TODO: initial balance?
# TODO: draft or posted?
account_ids = account_obj.search(cr, uid, [])
company_id = context.get('force_company')
if not company_id:
user = self.pool.get('res.users').read(cr, uid, uid, ['company_id'], context=context)
if user['company_id']:
company_id = user['company_id'][0]
account_ids = account_obj.search(cr, uid, [('company_id', '=', company_id)], context=context)
account_datas = account_obj.read(cr, uid, account_ids, account_datas = account_obj.read(cr, uid, account_ids,
['code', 'balance'], ['code', 'balance'],
context=search_ctx) context=search_ctx)
@ -340,6 +386,11 @@ class mis_report_instance_period(orm.Model):
res = {} res = {}
report = c.report_instance_id.report_id report = c.report_instance_id.report_id
company_id = context.get('force_company')
if not company_id:
user = self.pool.get('res.users').read(cr, uid, uid, ['company_id'], context=context)
if user['company_id']:
company_id = user['company_id'][0]
for query in report.query_ids: for query in report.query_ids:
obj = self.pool[query.model_id.model] obj = self.pool[query.model_id.model]
domain = query.domain and safe_eval(query.domain) or [] domain = query.domain and safe_eval(query.domain) or []
@ -353,11 +404,10 @@ class mis_report_instance_period(orm.Model):
# domain.extend([(query.date_field.name, '>=', datetime_from), # domain.extend([(query.date_field.name, '>=', datetime_from),
# (query.date_field.name, '<', datetime_to)]) # (query.date_field.name, '<', datetime_to)])
raise orm.except_orm(_('Error!'), _('Not implemented')) raise orm.except_orm(_('Error!'), _('Not implemented'))
domain.extend([('company_id', '=', company_id)])
field_names = [field.name for field in query.field_ids] field_names = [field.name for field in query.field_ids]
obj_ids = obj.search(cr, uid, domain,
context=context)
obj_datas = obj.read(cr, uid, obj_ids, field_names,
context=context)
obj_ids = obj.search(cr, uid, domain, context=context)
obj_datas = obj.read(cr, uid, obj_ids, field_names, context=context)
res[query.name] = [AutoStruct(**d) for d in obj_datas] res[query.name] = [AutoStruct(**d) for d in obj_datas]
return res return res
@ -381,7 +431,7 @@ class mis_report_instance_period(orm.Model):
localdict.update(self._fetch_balances(cr, uid, c, context=context)) localdict.update(self._fetch_balances(cr, uid, c, context=context))
localdict.update(self._fetch_queries(cr, uid, c, context=context)) localdict.update(self._fetch_queries(cr, uid, c, context=context))
for kpi in c.report_instance_id.report.kpi_ids:
for kpi in c.report_instance_id.report_id.kpi_ids:
try: try:
kpi_val = safe_eval(kpi.expression, localdict) kpi_val = safe_eval(kpi.expression, localdict)
except ZeroDivisionError: except ZeroDivisionError:
@ -443,12 +493,20 @@ class mis_report_instance(orm.Model):
'report_instance_id', 'report_instance_id',
required=True, required=True,
string='Periods'), string='Periods'),
#'comparison_column': fields.many2many('mis.report.instance')
'target_move': fields.selection([('posted', 'All Posted Entries'),
('all', 'All Entries'),
], 'Target Moves', required=True),
}
_defaults = {
'target_move': 'posted',
} }
def compute(self, cr, uid, _ids, context=None): def compute(self, cr, uid, _ids, context=None):
assert isinstance(_ids, (int, long)) assert isinstance(_ids, (int, long))
r = self.browse(cr, uid, _ids, context=context) r = self.browse(cr, uid, _ids, context=context)
context['state'] = r.target_move
res = {} res = {}
@ -459,8 +517,9 @@ class mis_report_instance(orm.Model):
res['rows'] = rows res['rows'] = rows
cols = [] cols = []
report_instance_period_obj = self.pool.get('mis.report.instance.period')
for period in r.period_ids: for period in r.period_ids:
cols.append(dict(name=period.name, description=period.name))
cols.append(dict(name=period.name, values=report_instance_period_obj._compute(cr, uid, period, context=context)))
res['cols'] = cols res['cols'] = cols
return res return res

19
mis_builder/static/src/xml/mis_widget.xml

@ -1,17 +1,28 @@
<template> <template>
<t t-name="mis_builder.MisReport"> <t t-name="mis_builder.MisReport">
<p>Yo!</p>
<p> </p>
<table t-if="widget.mis_report_data"> <table t-if="widget.mis_report_data">
<th> <th>
<td></td> <td></td>
<td t-foreach="widget.mis_report_data.cols" t-as="col" style="padding-right:10px">
<th t-foreach="widget.mis_report_data.cols" t-as="col" style="padding-right:10px">
<t t-esc="col.name"/> <t t-esc="col.name"/>
</td>
</th>
</th> </th>
<tr t-foreach="widget.mis_report_data.rows" t-as="row"> <tr t-foreach="widget.mis_report_data.rows" t-as="row">
<th> <th>
<t t-esc="row.name"/>
<t t-esc="row.description"/>
</th> </th>
<td></td>
<td t-foreach="widget.mis_report_data.cols" t-as="col" style="padding-right:10px">
<t t-foreach="col.values" t-as="value">
<t t-if="value == row.name">
<t t-esc="value_value.val_r"/>
<t t-if="value_value.val_c">
<t t-esc="value_value.val_c"/>
</t>
</t>
</t>
</td>
</tr> </tr>
</table> </table>
</t> </t>

2
mis_builder/tests/mis.report.csv

@ -0,0 +1,2 @@
"id","description","kpi_ids/id","name","query_ids/id"
"mis_report","","mis_report_kpi_1,mis_report_kpi_2,mis_report_kpi_3,mis_report_kpi_4,mis_report_kpi_5,mis_report_kpi_6","Test","mis_report_query"

2
mis_builder/tests/mis.report.instance.csv

@ -0,0 +1,2 @@
"id","date","description","name","period_ids/id","report_id/id"
"mis_report_instance","2014-07-18","","Test-report-instance","mis_report_instance_period_1,mis_report_instance_period_2,mis_report_instance_period_3,mis_report_instance_period_4,mis_report_instance_period_5,mis_report_instance_period_6","mis_report"

7
mis_builder/tests/mis.report.instance.period.csv

@ -0,0 +1,7 @@
"id","duration","name","offset","type","sequence"
"mis_report_instance_period_1","1","today","","Day",""
"mis_report_instance_period_2","1","yesterday","-1","Day",""
"mis_report_instance_period_3","1","last week","-1","Week","2"
"mis_report_instance_period_4","2","last 2 period","-2","Fiscal Period","3"
"mis_report_instance_period_5","2","last 2 week","-2","Week","4"
"mis_report_instance_period_6","1","yesterday","","Day","1"

7
mis_builder/tests/mis.report.kpi.csv

@ -0,0 +1,7 @@
"id","compare_method","description","expression","divider","name","dp","sequence","type","suffix"
"mis_report_kpi_1","Percentage","chiffre d'affaire","-bal_70","","ca","","","Numeric","€"
"mis_report_kpi_2","Percentage","cost","-bal_60 - bal_61","","cost","","","Numeric","€"
"mis_report_kpi_3","Percentage","profit","ca - cost","","profit","","","Numeric","€"
"mis_report_kpi_4","Difference","margin","profit/ca","","margin","","","Percentage","%"
"mis_report_kpi_5","None","couleur","'vert' if profit > 0 else 'rouge'","","couleur","","","String"
"mis_report_kpi_6","Percentage","total invoice","len(inv)","","total_invoice","","","Numeric","€"

2
mis_builder/tests/mis.report.query.csv

@ -0,0 +1,2 @@
"id","date_field/id","domain","field_ids/id","model_id/id","name"
"mis_report_query","account.field_account_invoice_date_invoice","","account.field_account_invoice_amount_untaxed","account.model_account_invoice","inv"

14
mis_builder/views/mis_builder.xml

@ -34,14 +34,14 @@
<field name="kpi_ids"> <field name="kpi_ids">
<tree string="KPI's" editable="bottom"> <tree string="KPI's" editable="bottom">
<field name="sequence" widget="handle"/> <field name="sequence" widget="handle"/>
<field name="name"/>
<field name="description"/>
<field name="description" on_change="onchange_description(description, context)"/>
<field name="name" on_change="onchange_name(name, context)"/>
<field name="expression"/> <field name="expression"/>
<field name="type"/>
<field name="dp"/>
<field name="divider"/>
<field name="type" on_change="onchange_type(type, context)"/>
<field name="dp" attrs="{'invisible': [('type', '=', 'str')]}"/>
<field name="divider" attrs="{'invisible': [('type', '=', 'str')]}"/>
<field name="suffix"/> <field name="suffix"/>
<field name="compare_method"/>
<field name="compare_method" attrs="{'invisible': [('type', '=', 'str')]}"/>
</tree> </tree>
</field> </field>
</group> </group>
@ -99,6 +99,7 @@
<field name="report_id"/> <field name="report_id"/>
<field name="name"/> <field name="name"/>
<field name="description"/> <field name="description"/>
<field name="target_move"/>
</tree> </tree>
</field> </field>
</record> </record>
@ -125,6 +126,7 @@
<group col="2"> <group col="2">
<field name="description"/> <field name="description"/>
<field name="report_id"/> <field name="report_id"/>
<field name="target_move"/>
<field name="date"/> <field name="date"/>
<field name="period_ids"> <field name="period_ids">
<tree string="KPI's" editable="bottom"> <tree string="KPI's" editable="bottom">

Loading…
Cancel
Save