Browse Source
[ADD] mis_builder: a module to build "Management Information System"-style reports
[ADD] mis_builder: a module to build "Management Information System"-style reports
Such reports combine accounting and operational data. This initial commit is a first proof-of-concept prototype.pull/90/head
Stéphane Bidoul
10 years ago
9 changed files with 770 additions and 0 deletions
-
24mis_builder/__init__.py
-
53mis_builder/__openerp__.py
-
24mis_builder/models/__init__.py
-
466mis_builder/models/mis_builder.py
-
1mis_builder/security/ir.model.access.csv
-
BINmis_builder/static/src/img/icon.png
-
26mis_builder/static/src/js/mis_builder.js
-
18mis_builder/static/src/xml/mis_widget.xml
-
158mis_builder/views/mis_builder.xml
@ -0,0 +1,24 @@ |
|||
#============================================================================== |
|||
# = |
|||
# mis_builder module for OpenERP, Management Information System Builder |
|||
# Copyright (C) 2014 ACSONE SA/NV (<http://acsone.eu>) |
|||
# = |
|||
# This file is a part of mis_builder |
|||
# = |
|||
# mis_builder is free software: you can redistribute it and/or modify |
|||
# it under the terms of the GNU Affero General Public License v3 or later |
|||
# as published by the Free Software Foundation, either version 3 of the |
|||
# License, or (at your option) any later version. |
|||
# = |
|||
# mis_builder 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 Affero General Public License v3 or later for more details. |
|||
# = |
|||
# You should have received a copy of the GNU Affero General Public License |
|||
# v3 or later along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# = |
|||
#============================================================================== |
|||
|
|||
import models |
@ -0,0 +1,53 @@ |
|||
#============================================================================== |
|||
# = |
|||
# mis_builder module for OpenERP, Management Information System Builder |
|||
# Copyright (C) 2014 ACSONE SA/NV (<http://acsone.eu>) |
|||
# = |
|||
# This file is a part of mis_builder |
|||
# = |
|||
# mis_builder is free software: you can redistribute it and/or modify |
|||
# it under the terms of the GNU Affero General Public License v3 or later |
|||
# as published by the Free Software Foundation, either version 3 of the |
|||
# License, or (at your option) any later version. |
|||
# = |
|||
# mis_builder 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 Affero General Public License v3 or later for more details. |
|||
# = |
|||
# You should have received a copy of the GNU Affero General Public License |
|||
# v3 or later along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# = |
|||
#============================================================================== |
|||
{ |
|||
'name': 'mis builder', |
|||
'version': '0.1', |
|||
'category': 'Reporting', |
|||
'description': """ |
|||
Management Information System Builder |
|||
""", |
|||
'author': 'ACSONE SA/NV', |
|||
'website': 'http://acsone.eu', |
|||
'depends': ['account'], |
|||
'data': [ |
|||
'views/mis_builder.xml', |
|||
], |
|||
'test': [ |
|||
], |
|||
'demo': [ |
|||
], |
|||
'js': [ |
|||
'static/src/js/*.js' |
|||
], |
|||
'qweb': [ |
|||
'static/src/xml/*.xml' |
|||
], |
|||
'css': [ |
|||
'static/src/css/*.css' |
|||
], |
|||
'installable': True, |
|||
'application': True, |
|||
'auto_install': False, |
|||
'license': 'AGPL-3', |
|||
} |
@ -0,0 +1,24 @@ |
|||
#============================================================================== |
|||
# = |
|||
# mis_builder module for OpenERP, Management Information System Builder |
|||
# Copyright (C) 2014 ACSONE SA/NV (<http://acsone.eu>) |
|||
# = |
|||
# This file is a part of mis_builder |
|||
# = |
|||
# mis_builder is free software: you can redistribute it and/or modify |
|||
# it under the terms of the GNU Affero General Public License v3 or later |
|||
# as published by the Free Software Foundation, either version 3 of the |
|||
# License, or (at your option) any later version. |
|||
# = |
|||
# mis_builder 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 Affero General Public License v3 or later for more details. |
|||
# = |
|||
# You should have received a copy of the GNU Affero General Public License |
|||
# v3 or later along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# = |
|||
#============================================================================== |
|||
|
|||
import mis_builder |
@ -0,0 +1,466 @@ |
|||
# vim: set fileencoding=utf-8 : |
|||
#============================================================================== |
|||
# = |
|||
# mis_builder module for OpenERP, Management Information System Builder |
|||
# Copyright (C) 2014 ACSONE SA/NV (<http://acsone.eu>) |
|||
# = |
|||
# This file is a part of mis_builder |
|||
# = |
|||
# mis_builder is free software: you can redistribute it and/or modify |
|||
# it under the terms of the GNU Affero General Public License v3 or later |
|||
# as published by the Free Software Foundation, either version 3 of the |
|||
# License, or (at your option) any later version. |
|||
# = |
|||
# mis_builder 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 Affero General Public License v3 or later for more details. |
|||
# = |
|||
# You should have received a copy of the GNU Affero General Public License |
|||
# v3 or later along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# = |
|||
#============================================================================== |
|||
|
|||
from datetime import datetime, timedelta |
|||
from dateutil import parser |
|||
import traceback |
|||
from lxml import etree |
|||
|
|||
from openerp.osv import orm, fields |
|||
from openerp.tools.safe_eval import safe_eval |
|||
from openerp.tools.translate import _ |
|||
from openerp import tools |
|||
|
|||
|
|||
class AutoStruct(object): |
|||
|
|||
def __init__(self, **kwargs): |
|||
for k, v in kwargs.items(): |
|||
setattr(self, k, v) |
|||
|
|||
|
|||
def _get_selection_label(selection, value): |
|||
for v, l in selection: |
|||
if v == value: |
|||
return l |
|||
return '' |
|||
|
|||
|
|||
class mis_report_kpi(orm.Model): |
|||
""" A KPI is an element of a MIS report. |
|||
|
|||
In addition to a name and description, it has an expression |
|||
to compute it based on queries defined in the MIS report. |
|||
It also has various informations defining how to render it |
|||
(numeric or percentage or a string, a suffix, divider) and |
|||
how to render comparison of two values of the KPI. |
|||
KPI are ordered inside the MIS report, as some KPI expressions |
|||
can depend on other KPI that need to be computed before. |
|||
""" |
|||
|
|||
_name = 'mis.report.kpi' |
|||
|
|||
_columns = { |
|||
'name': fields.char(size=32, required=True, |
|||
string='Name'), |
|||
'description': fields.char(required=True, |
|||
string='Description', |
|||
translate=True), |
|||
'expression': fields.char(required=True, |
|||
string='Expression'), |
|||
'type': fields.selection([('num', _('Numeric')), |
|||
('pct', _('Percentage')), |
|||
('str', _('String'))], |
|||
required=True, |
|||
string='Type'), |
|||
'divider': fields.selection([('1e-6', _('µ')), |
|||
('1e-3', _('m')), |
|||
('1e3', _('k')), |
|||
('1e6', _('M'))], |
|||
string='Factor'), |
|||
'dp': fields.integer(string='Rounding'), |
|||
'suffix': fields.char(size=16, string='Unit'), |
|||
'compare_method': fields.selection([('diff', _('Difference')), |
|||
('pct', _('Percentage')), |
|||
('none', _('None'))], |
|||
required=True, |
|||
string='Comparison Method'), |
|||
'sequence': fields.integer(string='Sequence'), |
|||
'report_id': fields.many2one('mis.report', string='Report'), |
|||
} |
|||
|
|||
_defaults = { |
|||
'type': 'num', |
|||
'divider': '1', |
|||
'dp': 0, |
|||
'compare_method': 'pct', |
|||
} |
|||
|
|||
_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 _render(self, kpi, value): |
|||
""" render a KPI value as a unicode string, ready for display """ |
|||
if kpi.type == 'num': |
|||
return self._render_num(value, |
|||
kpi.divider, kpi.dp, kpi.suffix) |
|||
elif kpi.type == 'pct': |
|||
return self._render_num(value, |
|||
100, kpi.dp, '%') |
|||
else: |
|||
return unicode(value) |
|||
|
|||
def _render_comparison(self, kpi, value, base_value): |
|||
""" render the comparison of two KPI values, ready for display """ |
|||
if value is None or base_value is None: |
|||
return '' |
|||
if kpi.type == 'pct': |
|||
return self._render_num(value - base_value, |
|||
0.01, kpi.dp, _('pp'), |
|||
sign='+') |
|||
elif kpi.type == 'num': |
|||
if kpi.compare_method == 'diff': |
|||
return self._render_num(value - base_value, |
|||
kpi.divider, kpi.dp, kpi.suffix, |
|||
sign='+') |
|||
elif kpi.compare_method == 'pct' and base_value != 0: |
|||
return self._render_num(value / base_value - base_value, |
|||
0.01, kpi.dp, '%', |
|||
sign='+') |
|||
return '' |
|||
|
|||
def _render_num(self, value, divider, dp, suffix, sign='-'): |
|||
divider_label = _get_selection_label( |
|||
self._columns['divider'].selection, divider) |
|||
fmt = '{:%s,.%df}%s%s' % (sign, dp, divider_label, suffix or '') |
|||
value = round(value / float(divider or 1), dp) or 0 |
|||
return fmt.format(value) |
|||
|
|||
|
|||
class mis_report_query(orm.Model): |
|||
""" A query to fetch data for a MIS report. |
|||
|
|||
A query works on a model and has a domain and list of fields to fetch. |
|||
At runtime, the domain is expanded with a "and" on the date/datetime field. |
|||
""" |
|||
|
|||
_name = 'mis.report.query' |
|||
|
|||
_columns = { |
|||
'name': fields.char(size=32, required=True, |
|||
string='Name'), |
|||
'model_id': fields.many2one('ir.model', required=True, |
|||
string='Model'), |
|||
'field_ids': fields.many2many('ir.model.fields', required=True, |
|||
string='Fields to fetch'), |
|||
'date_field': fields.many2one('ir.model.fields', required=True, |
|||
string='Date field', |
|||
domain=[('ttype', 'in', ('date', 'datetime'))]), |
|||
'domain': fields.char(string='Domain'), |
|||
'report_id': fields.many2one('mis.report', string='Report'), |
|||
} |
|||
|
|||
_order = 'name' |
|||
|
|||
|
|||
class mis_report(orm.Model): |
|||
""" A MIS report template (without period information) |
|||
|
|||
The MIS report holds: |
|||
* an implicit query fetching allow the account balances; |
|||
for each account, the balance is stored in a variable named |
|||
bal_{code} where {code} is the account code |
|||
* a list of explicit queries; the result of each query is |
|||
stored in a variable with same name as a query, containing as list |
|||
of data structures populated with attributes for each fields to fetch |
|||
* a list of KPI to be evaluated based on the variables resulting |
|||
from the balance and queries |
|||
""" |
|||
|
|||
_name = 'mis.report' |
|||
|
|||
_columns = { |
|||
'name': fields.char(size=32, required=True, |
|||
string='Name', translate=True), |
|||
'description': fields.char(required=False, |
|||
string='Description', translate=True), |
|||
'query_ids': fields.one2many('mis.report.query', 'report_id', |
|||
string='Queries'), |
|||
'kpi_ids': fields.one2many('mis.report.kpi', 'report_id', |
|||
string='KPI\'s'), |
|||
} |
|||
|
|||
|
|||
class mis_report_instance_period(orm.Model): |
|||
""" A MIS report instance has the logic to compute |
|||
a report template for a give date period. |
|||
|
|||
Periods have a duration (day, week, fiscal period) and |
|||
are defined as an offset relative to a pivot date. |
|||
""" |
|||
|
|||
def _get_dates(self, cr, uid, ids, field_names, arg, context=None): |
|||
if isinstance(ids, (int, long)): |
|||
ids = [ids] |
|||
res = {} |
|||
for c in self.browse(cr, uid, ids, context=context): |
|||
d = parser.parse(c.report_instance_id.pivot_date) |
|||
if c.type == 'd': |
|||
date_from = d + timedelta(days=c.offset) |
|||
date_to = date_from + timedelta(days=c.duration - 1) |
|||
date_from = date_from.strftime(tools.DEFAULT_SERVER_DATE_FORMAT) |
|||
date_to = date_to.strftime(tools.DEFAULT_SERVER_DATE_FORMAT) |
|||
period_ids = None |
|||
elif c.type == 'w': |
|||
date_from = d - timedelta(d.weekday()) |
|||
date_from = date_from + timedelta(days=c.offset * 7) |
|||
date_to = date_from + timedelta(days=(7 * c.duration) - 1) |
|||
date_from = date_from.strftime(tools.DEFAULT_SERVER_DATE_FORMAT) |
|||
date_to = date_to.strftime(tools.DEFAULT_SERVER_DATE_FORMAT) |
|||
period_ids = None |
|||
elif c.type == 'fp': |
|||
# TODO: filter on company_id |
|||
# TODO: date! |
|||
period_obj = self.pool['account.period'] |
|||
all_period_ids = period_obj.search(cr, uid, |
|||
[('special', '=', False)], |
|||
order='date_start', |
|||
context=context) |
|||
current_period_ids = period_obj.search(cr, uid, |
|||
[('special', '=', False), |
|||
('date_start', '<=', d), |
|||
('date_stop', '>=', d)], |
|||
context=context) |
|||
if not current_period_ids: |
|||
raise orm.except_orm(_("Error!"), |
|||
_("No current fiscal period for %s") % d) |
|||
p = all_period_ids.index(current_period_ids[0]) + c.offset |
|||
if p < 0 or p >= len(all_period_ids): |
|||
raise orm.except_orm(_("Error!"), |
|||
_("No such fiscal period for %s " |
|||
"with offset %d") % (d, c.offset)) |
|||
period_ids = all_period_ids[p:p + c.duration] |
|||
periods = period_obj.browse(cr, uid, period_ids, |
|||
context=context) |
|||
date_from = periods[0].date_start |
|||
date_to = periods[-1].date_stop |
|||
else: |
|||
raise orm.except_orm(_("Error!"), |
|||
_("Unimplemented period type %s") % |
|||
(c.type,)) |
|||
res[c.id] = { |
|||
'date_from': date_from, |
|||
'date_to': date_to, |
|||
'period_from': period_ids and period_ids[0], |
|||
'period_to': period_ids and period_ids[-1], |
|||
} |
|||
return res |
|||
|
|||
_name = 'mis.report.instance.period' |
|||
|
|||
_columns = { |
|||
'name': fields.char(size=32, required=True, |
|||
string='Name', translate=True), |
|||
'type': fields.selection([('d', _('Day')), |
|||
('w', _('Week')), |
|||
('fp', _('Fiscal Period')), |
|||
# ('fy', _('Fiscal Year')) |
|||
], |
|||
required=True, |
|||
string='Period type'), |
|||
'offset': fields.integer(string='Offset', |
|||
help='Offset from current period'), |
|||
'duration': fields.integer(string='Duration', |
|||
help='Number of periods'), |
|||
'date_from': fields.function(_get_dates, |
|||
type='date', |
|||
multi="dates", |
|||
string="From"), |
|||
'date_to': fields.function(_get_dates, |
|||
type='date', |
|||
multi="dates", |
|||
string="To"), |
|||
'period_from': fields.function(_get_dates, |
|||
type='many2one', obj='account.period', |
|||
multi="dates", string="From period"), |
|||
'period_to': fields.function(_get_dates, |
|||
type='many2one', obj='account.period', |
|||
multi="dates", string="To period"), |
|||
'sequence': fields.integer(string='Sequence'), |
|||
'report_instance_id': fields.many2one('mis.report.instance', |
|||
string='Report Instance'), |
|||
} |
|||
|
|||
_defaults = { |
|||
'offset':-1, |
|||
'duration': 1, |
|||
} |
|||
|
|||
_order = 'sequence' |
|||
|
|||
# TODO: constraint duration >= 1 |
|||
|
|||
def _fetch_balances(self, cr, uid, c, context=None): |
|||
""" fetch the general account balances for the given period |
|||
|
|||
returns a dictionary {bal_<account.code>: account.balance} |
|||
""" |
|||
account_obj = self.pool['account.account'] |
|||
|
|||
search_ctx = dict(context) |
|||
if c.period_from: |
|||
search_ctx.update({'period_from': c.period_from, |
|||
'period_to': c.period_to}) |
|||
else: |
|||
search_ctx.update({'date_from': c.date_from, |
|||
'date_to': c.date_to}) |
|||
|
|||
# TODO: initial balance? |
|||
# TODO: draft or posted? |
|||
account_ids = account_obj.search(cr, uid, []) |
|||
account_datas = account_obj.read(cr, uid, account_ids, |
|||
['code', 'balance'], |
|||
context=search_ctx) |
|||
balances = {} |
|||
|
|||
for account_data in account_datas: |
|||
# TODO: normalize code (strip special chars) |
|||
# TODO: company_id in key |
|||
key = 'bal_' + account_data['code'] |
|||
assert key not in balances |
|||
balances[key] = account_data['balance'] |
|||
|
|||
return balances |
|||
|
|||
def _fetch_queries(self, cr, uid, c, context): |
|||
res = {} |
|||
|
|||
report = c.report_instance_id.report_id |
|||
for query in report.query_ids: |
|||
obj = self.pool[query.model_id.model] |
|||
domain = query.domain and safe_eval(query.domain) or [] |
|||
if query.date_field.ttype == 'date': |
|||
domain.extend([(query.date_field.name, '>=', c.date_from), |
|||
(query.date_field.name, '<=', c.date_to)]) |
|||
else: |
|||
# TODO: datetime support (convert date to utc midnight) |
|||
# datetime_from = utc_midnight(date_from) |
|||
# datetime_to = utc_midnight(date_to + 1) |
|||
# domain.extend([(query.date_field.name, '>=', datetime_from), |
|||
# (query.date_field.name, '<', datetime_to)]) |
|||
raise orm.except_orm(_('Error!'), _('Not implemented')) |
|||
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) |
|||
res[query.name] = [AutoStruct(**d) for d in obj_datas] |
|||
|
|||
return res |
|||
|
|||
def _compute(self, cr, uid, c, context=None): |
|||
if context is None: |
|||
context = {} |
|||
|
|||
kpi_obj = self.pool['mis.report.kpi'] |
|||
|
|||
res = {} |
|||
|
|||
localdict = { |
|||
'registry': self.pool, |
|||
'sum': sum, |
|||
'min': min, |
|||
'max': max, |
|||
'len': len, |
|||
'avg': lambda l: sum(l) / float(len(l)), |
|||
} |
|||
localdict.update(self._fetch_balances(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: |
|||
try: |
|||
kpi_val = safe_eval(kpi.expression, localdict) |
|||
except ZeroDivisionError: |
|||
kpi_val = None |
|||
kpi_val_rendered = '#DIV/0' |
|||
kpi_val_comment = traceback.format_exc() |
|||
except: |
|||
kpi_val = None |
|||
kpi_val_rendered = '#ERR' |
|||
kpi_val_comment = traceback.format_exc() |
|||
else: |
|||
kpi_val_rendered = kpi_obj._render(kpi, kpi_val) |
|||
kpi_val_comment = None |
|||
|
|||
localdict[kpi.name] = kpi_val |
|||
|
|||
res[kpi.name] = { |
|||
'val': kpi_val, |
|||
'val_r': kpi_val_rendered, |
|||
'val_c': kpi_val_comment, |
|||
} |
|||
|
|||
return res |
|||
|
|||
|
|||
class mis_report_instance(orm.Model): |
|||
""" The MIS report instance combines compute and |
|||
display a MIS report template for a set of periods """ |
|||
|
|||
# TODO: mechanism to add comparison columns |
|||
|
|||
def _get_pivot_date(self, cr, uid, ids, field_name, arg, context=None): |
|||
res = {} |
|||
for r in self.browse(cr, uid, ids, context=context): |
|||
if r.date: |
|||
res[r.id] = r.date |
|||
else: |
|||
res[r.id] = fields.date.context_today(self, cr, uid, |
|||
context=context) |
|||
return res |
|||
|
|||
_name = 'mis.report.instance' |
|||
|
|||
_columns = { |
|||
'name': fields.char(size=32, required=True, |
|||
string='Name', translate=True), |
|||
'description': fields.char(required=False, |
|||
string='Description', translate=True), |
|||
'date': fields.date(string='Base date', |
|||
help='Report base date ' |
|||
'(leave empty to use current date)'), |
|||
'pivot_date': fields.function(_get_pivot_date, |
|||
type='date', |
|||
string="Pivot date"), |
|||
'report_id': fields.many2one('mis.report', |
|||
required=True, |
|||
string='Report'), |
|||
'period_ids': fields.one2many('mis.report.instance.period', |
|||
'report_instance_id', |
|||
required=True, |
|||
string='Periods'), |
|||
} |
|||
|
|||
def compute(self, cr, uid, _ids, context=None): |
|||
assert isinstance(_ids, (int, long)) |
|||
|
|||
r = self.browse(cr, uid, _ids, context=context) |
|||
|
|||
res = {} |
|||
|
|||
rows = [] |
|||
for kpi in r.report_id.kpi_ids: |
|||
rows.append(dict(name=kpi.name, |
|||
description=kpi.description)) |
|||
res['rows'] = rows |
|||
|
|||
cols = [] |
|||
for period in r.period_ids: |
|||
col = dict(name=period.name) |
|||
res['cols'] = cols |
|||
|
|||
return res |
@ -0,0 +1 @@ |
|||
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" |
After Width: 64 | Height: 64 | Size: 3.4 KiB |
@ -0,0 +1,26 @@ |
|||
openerp.mis_builder = function(instance) { |
|||
|
|||
instance.mis_builder.MisReport = instance.web.form.FormWidget.extend({ |
|||
template: "mis_builder.MisReport", |
|||
|
|||
init: function() { |
|||
this._super.apply(this, arguments); |
|||
this.mis_report_data = null; |
|||
}, |
|||
|
|||
start: function() { |
|||
this._super.apply(this, arguments); |
|||
var self = this; |
|||
new instance.web.Model("mis.report.instance").call( |
|||
"compute", |
|||
[self.getParent().dataset.context.active_id], |
|||
{'context': new instance.web.CompoundContext()} |
|||
).then(function(result){ |
|||
self.mis_report_data = result; |
|||
self.renderElement(); |
|||
}); |
|||
}, |
|||
}); |
|||
|
|||
instance.web.form.custom_widgets.add('mis_report', 'instance.mis_builder.MisReport'); |
|||
} |
@ -0,0 +1,18 @@ |
|||
<template> |
|||
<t t-name="mis_builder.MisReport"> |
|||
<p>Yo!</p> |
|||
<table t-if="widget.mis_report_data"> |
|||
<th> |
|||
<td></td> |
|||
<td t-foreach="widget.mis_report_data.rows" t-as="col"> |
|||
<t t-esc="col.name"/> |
|||
</td> |
|||
</th> |
|||
<tr t-foreach="widget.mis_report_data.rows" t-as="row"> |
|||
<th> |
|||
<t t-esc="row.name"/> |
|||
</th> |
|||
</tr> |
|||
</table> |
|||
</t> |
|||
</template> |
@ -0,0 +1,158 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<openerp> |
|||
<data> |
|||
|
|||
<record model="ir.ui.view" id="mis_report_view_tree"> |
|||
<field name="name">mis.report.view.tree</field> |
|||
<field name="model">mis.report</field> |
|||
<field name="arch" type="xml"> |
|||
<tree string="MIS Reports"> |
|||
<field name="name"/> |
|||
<field name="description"/> |
|||
</tree> |
|||
</field> |
|||
</record> |
|||
|
|||
<record model="ir.ui.view" id="mis_report_view_form"> |
|||
<field name="name">mis.report.view.form</field> |
|||
<field name="model">mis.report</field> |
|||
<field name="arch" type="xml"> |
|||
<form string="MIS Report" version="7.0"> |
|||
<sheet> |
|||
<group col="2"> |
|||
<field name="name"/> |
|||
<field name="description"/> |
|||
<field name="query_ids"> |
|||
<tree string="Queries" editable="bottom"> |
|||
<field name="name"/> |
|||
<field name="model_id"/> |
|||
<field name="field_ids" domain="[('model_id', '=', model_id)]" widget="many2many_tags"/> |
|||
<field name="date_field" domain="[('model_id', '=', model_id), ('ttype', 'in', ('date', 'datetime'))]"/> |
|||
<field name="domain"/> |
|||
</tree> |
|||
</field> |
|||
<field name="kpi_ids"> |
|||
<tree string="KPI's" editable="bottom"> |
|||
<field name="sequence" widget="handle"/> |
|||
<field name="name"/> |
|||
<field name="description"/> |
|||
<field name="expression"/> |
|||
<field name="type"/> |
|||
<field name="dp"/> |
|||
<field name="divider"/> |
|||
<field name="suffix"/> |
|||
<field name="compare_method"/> |
|||
</tree> |
|||
</field> |
|||
</group> |
|||
</sheet> |
|||
</form> |
|||
</field> |
|||
</record> |
|||
|
|||
<record model="ir.actions.act_window" id="mis_report_view_action"> |
|||
<field name="name">mis.report.view.action</field> |
|||
<field name="view_id" ref="mis_report_view_tree"/> |
|||
<field name="res_model">mis.report</field> |
|||
<field name="view_type">form</field> |
|||
<field name="view_mode">tree,form</field> |
|||
</record> |
|||
|
|||
<menuitem id="mis_report_view_menu" parent="base.menu_reporting_config" name="MIS Reports" action="mis_report_view_action"/> |
|||
|
|||
<record model="ir.ui.view" id="mis_report_instance_result_view_form"> |
|||
<field name="name">mis.report.instance.result.view.form</field> |
|||
<field name="model">mis.report.instance</field> |
|||
<field name="priority" eval="17"/> |
|||
<field name="arch" type="xml"> |
|||
<form string="MIS Report Result" version="7.0"> |
|||
<sheet> |
|||
<div class="oe_title"> |
|||
<h1> |
|||
<field name="name" placeholder="Name"/> |
|||
</h1> |
|||
</div> |
|||
<group> |
|||
<field name="description"/> |
|||
<field name="pivot_date"/> |
|||
</group> |
|||
<widget type="mis_report"></widget> |
|||
</sheet> |
|||
</form> |
|||
</field> |
|||
</record> |
|||
|
|||
<record model="ir.actions.act_window" id="mis_report_instance_result_action"> |
|||
<field name="name">mis.report.instance.result.view.action</field> |
|||
<field name="view_id" ref="mis_report_instance_result_view_form"/> |
|||
<field name="res_model">mis.report.instance</field> |
|||
<field name="view_type">form</field> |
|||
<field name="view_mode">form</field> |
|||
<field name="target">new</field> |
|||
</record> |
|||
|
|||
<record model="ir.ui.view" id="mis_report_instance_view_tree"> |
|||
<field name="name">mis.report.instance.view.tree</field> |
|||
<field name="model">mis.report.instance</field> |
|||
<field name="arch" type="xml"> |
|||
<tree string="MIS Report Instances"> |
|||
<field name="report_id"/> |
|||
<field name="name"/> |
|||
<field name="description"/> |
|||
</tree> |
|||
</field> |
|||
</record> |
|||
|
|||
<record model="ir.ui.view" id="mis_report_instance_view_form"> |
|||
<field name="name">mis.report.instance.view.form</field> |
|||
<field name="model">mis.report.instance</field> |
|||
<field name="priority" eval="16"/> |
|||
<field name="arch" type="xml"> |
|||
<form string="MIS Report Instance" version="7.0"> |
|||
<sheet> |
|||
<div class="oe_title"> |
|||
<div class="oe_edit_only"> |
|||
<label for="name"/> |
|||
</div> |
|||
<h1> |
|||
<field name="name" placeholder="Name"/> |
|||
</h1> |
|||
</div> |
|||
<div class="oe_right oe_button_box" name="buttons"> |
|||
<button type="action" name="%(mis_report_instance_result_action)d" string="Open result" /> |
|||
</div> |
|||
<group col="2"> |
|||
<field name="description"/> |
|||
<field name="report_id"/> |
|||
<field name="date"/> |
|||
<field name="period_ids"> |
|||
<tree string="KPI's" editable="bottom"> |
|||
<field name="sequence" widget="handle"/> |
|||
<field name="name"/> |
|||
<field name="type"/> |
|||
<field name="offset"/> |
|||
<field name="duration"/> |
|||
<field name="date_from"/> |
|||
<field name="date_to"/> |
|||
<field name="period_from"/> |
|||
<field name="period_to"/> |
|||
</tree> |
|||
</field> |
|||
</group> |
|||
</sheet> |
|||
</form> |
|||
</field> |
|||
</record> |
|||
|
|||
<record model="ir.actions.act_window" id="mis_report_instance_view_action"> |
|||
<field name="name">mis.report.instance.view.action</field> |
|||
<field name="view_id" ref="mis_report_instance_view_tree"/> |
|||
<field name="res_model">mis.report.instance</field> |
|||
<field name="view_type">form</field> |
|||
<field name="view_mode">tree,form</field> |
|||
</record> |
|||
|
|||
<menuitem id="mis_report_instance_view_menu" parent="base.menu_reporting_config" name="MIS Report Instances" action="mis_report_instance_view_action"/> |
|||
|
|||
</data> |
|||
</openerp> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue