|
@ -64,20 +64,19 @@ def _python_var(var_str): |
|
|
return re.sub(r'\W|^(?=\d)', '_', var_str).lower() |
|
|
return re.sub(r'\W|^(?=\d)', '_', var_str).lower() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _python_bal_var(account_code): |
|
|
|
|
|
return 'bal_' + re.sub(r'\W', '_', account_code) |
|
|
|
|
|
|
|
|
def _python_bal_var(account_code, is_solde=False): |
|
|
|
|
|
prefix = 'bals_' if is_solde else 'bal_' |
|
|
|
|
|
return prefix + re.sub(r'\W', '_', account_code) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _get_bal_vars_in_expr(expr): |
|
|
|
|
|
return re.findall(r'\bbal_\w+', expr) |
|
|
|
|
|
|
|
|
def _get_bal_code(bal_var, is_solde=False): |
|
|
|
|
|
prefix = 'bals_' if is_solde else 'bal_' |
|
|
|
|
|
return bal_var.replace(prefix, '') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _python_bals_var(account_code): |
|
|
|
|
|
return 'bals_' + re.sub(r'\W', '_', account_code) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _get_bals_vars_in_expr(expr): |
|
|
|
|
|
return re.findall(r'\bbals_\w+', expr) |
|
|
|
|
|
|
|
|
def _get_bal_vars_in_expr(expr, is_solde=False): |
|
|
|
|
|
prefix = 'bals_' if is_solde else 'bal_' |
|
|
|
|
|
return re.findall(r'\b%s\w+' % prefix, expr) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _get_vars_in_expr(expr, varnames=None): |
|
|
def _get_vars_in_expr(expr, varnames=None): |
|
@ -87,22 +86,14 @@ def _get_vars_in_expr(expr, varnames=None): |
|
|
return re.findall(varnames_re, expr) |
|
|
return re.findall(varnames_re, expr) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _get_bal_vars_in_report(report): |
|
|
|
|
|
|
|
|
def _get_bal_vars_in_report(report, is_solde=False): |
|
|
res = set() |
|
|
res = set() |
|
|
for kpi in report.kpi_ids: |
|
|
for kpi in report.kpi_ids: |
|
|
for bal_var in _get_bal_vars_in_expr(kpi.expression): |
|
|
|
|
|
|
|
|
for bal_var in _get_bal_vars_in_expr(kpi.expression, is_solde): |
|
|
res.add(bal_var) |
|
|
res.add(bal_var) |
|
|
return res |
|
|
return res |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _get_bals_vars_in_report(report): |
|
|
|
|
|
res = set() |
|
|
|
|
|
for kpi in report.kpi_ids: |
|
|
|
|
|
for bals_var in _get_bals_vars_in_expr(kpi.expression): |
|
|
|
|
|
res.add(bals_var) |
|
|
|
|
|
return res |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _is_valid_python_var(name): |
|
|
def _is_valid_python_var(name): |
|
|
return re.match("[_A-Za-z][_a-zA-Z0-9]*$", name) \ |
|
|
return re.match("[_A-Za-z][_a-zA-Z0-9]*$", name) \ |
|
|
and not name.startswith('bal_') \ |
|
|
and not name.startswith('bal_') \ |
|
@ -203,60 +194,57 @@ class mis_report_kpi(orm.Model): |
|
|
'dp': 0} |
|
|
'dp': 0} |
|
|
return res |
|
|
return res |
|
|
|
|
|
|
|
|
def _render(self, cr, uid, kpi, value, context=None): |
|
|
|
|
|
|
|
|
def _render(self, cr, uid, lang_id, kpi, value, context=None): |
|
|
""" render a KPI value as a unicode string, ready for display """ |
|
|
""" render a KPI value as a unicode string, ready for display """ |
|
|
if kpi.type == 'num': |
|
|
if kpi.type == 'num': |
|
|
return self._render_num(cr, uid, value, kpi.divider, kpi.dp, |
|
|
|
|
|
kpi.suffix, context=context) |
|
|
|
|
|
|
|
|
return self._render_num(cr, uid, lang_id, value, kpi.divider, |
|
|
|
|
|
kpi.dp, kpi.suffix, context=context) |
|
|
elif kpi.type == 'pct': |
|
|
elif kpi.type == 'pct': |
|
|
return self._render_num(cr, uid, value, 0.01, kpi.dp, '%', |
|
|
|
|
|
context=context) |
|
|
|
|
|
|
|
|
return self._render_num(cr, uid, lang_id, value, 0.01, |
|
|
|
|
|
kpi.dp, '%', context=context) |
|
|
else: |
|
|
else: |
|
|
return unicode(value) |
|
|
return unicode(value) |
|
|
|
|
|
|
|
|
def _render_comparison(self, cr, uid, kpi, value, base_value, |
|
|
|
|
|
|
|
|
def _render_comparison(self, cr, uid, lang_id, kpi, value, base_value, |
|
|
average_value, average_base_value, context=None): |
|
|
average_value, average_base_value, context=None): |
|
|
""" render the comparison of two KPI values, ready for display """ |
|
|
""" render the comparison of two KPI values, ready for display """ |
|
|
if value is None or base_value is None: |
|
|
if value is None or base_value is None: |
|
|
return '' |
|
|
return '' |
|
|
if kpi.type == 'pct': |
|
|
if kpi.type == 'pct': |
|
|
return self._render_num(cr, uid, value - base_value, 0.01, kpi.dp, |
|
|
|
|
|
_('pp'), sign='+', context=context) |
|
|
|
|
|
|
|
|
return self._render_num(cr, uid, lang_id, value - base_value, 0.01, |
|
|
|
|
|
kpi.dp, _('pp'), sign='+', context=context) |
|
|
elif kpi.type == 'num': |
|
|
elif kpi.type == 'num': |
|
|
if average_value: |
|
|
if average_value: |
|
|
value = value / float(average_value) |
|
|
value = value / float(average_value) |
|
|
if average_base_value: |
|
|
if average_base_value: |
|
|
base_value = base_value / float(average_base_value) |
|
|
base_value = base_value / float(average_base_value) |
|
|
if kpi.compare_method == 'diff': |
|
|
if kpi.compare_method == 'diff': |
|
|
return self._render_num(cr, uid, value - base_value, |
|
|
|
|
|
|
|
|
return self._render_num(cr, uid, lang_id, value - base_value, |
|
|
kpi.divider, |
|
|
kpi.divider, |
|
|
kpi.dp, kpi.suffix, sign='+', |
|
|
kpi.dp, kpi.suffix, sign='+', |
|
|
context=context) |
|
|
context=context) |
|
|
elif kpi.compare_method == 'pct' and base_value != 0: |
|
|
elif kpi.compare_method == 'pct' and base_value != 0: |
|
|
return self._render_num(cr, uid, value / base_value - 1, |
|
|
|
|
|
0.01, kpi.dp, '%', |
|
|
|
|
|
sign='+', context=context) |
|
|
|
|
|
|
|
|
return self._render_num(cr, uid, lang_id, |
|
|
|
|
|
value / base_value - 1, 0.01, |
|
|
|
|
|
kpi.dp, '%', sign='+', context=context) |
|
|
return '' |
|
|
return '' |
|
|
|
|
|
|
|
|
def _render_num(self, cr, uid, value, divider, dp, suffix, sign='-', |
|
|
|
|
|
context=None): |
|
|
|
|
|
|
|
|
def _render_num(self, cr, uid, lang_id, value, divider, |
|
|
|
|
|
dp, suffix, sign='-', context=None): |
|
|
divider_label = _get_selection_label( |
|
|
divider_label = _get_selection_label( |
|
|
self._columns['divider'].selection, divider) |
|
|
self._columns['divider'].selection, divider) |
|
|
if divider_label == '1': |
|
|
if divider_label == '1': |
|
|
divider_label = '' |
|
|
divider_label = '' |
|
|
# format number following user language |
|
|
# format number following user language |
|
|
lang = self.pool['res.users'].read( |
|
|
|
|
|
cr, uid, uid, ['lang'], context=context)['lang'] |
|
|
|
|
|
language_id = self.pool['res.lang'].search( |
|
|
|
|
|
cr, uid, [('code', '=', lang)], context=context) |
|
|
|
|
|
value = round(value / float(divider or 1), dp) or 0 |
|
|
value = round(value / float(divider or 1), dp) or 0 |
|
|
return '%s %s%s' % (self.pool['res.lang'].format(cr, uid, language_id, |
|
|
|
|
|
'%%%s.%df' % ( |
|
|
|
|
|
sign, dp), |
|
|
|
|
|
value, |
|
|
|
|
|
grouping=True, |
|
|
|
|
|
context=context), |
|
|
|
|
|
divider_label, suffix or '') |
|
|
|
|
|
|
|
|
return '%s %s%s' % (self.pool['res.lang'].format( |
|
|
|
|
|
cr, uid, lang_id, |
|
|
|
|
|
'%%%s.%df' % ( |
|
|
|
|
|
sign, dp), |
|
|
|
|
|
value, |
|
|
|
|
|
grouping=True, |
|
|
|
|
|
context=context), |
|
|
|
|
|
divider_label, suffix or '') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class mis_report_query(orm.Model): |
|
|
class mis_report_query(orm.Model): |
|
@ -281,10 +269,11 @@ class mis_report_query(orm.Model): |
|
|
def onchange_field_ids(self, cr, uid, ids, field_ids, context=None): |
|
|
def onchange_field_ids(self, cr, uid, ids, field_ids, context=None): |
|
|
# compute field_names |
|
|
# compute field_names |
|
|
field_names = [] |
|
|
field_names = [] |
|
|
for field in self.pool.get('ir.model.fields').read(cr, uid, |
|
|
|
|
|
field_ids[0][2], |
|
|
|
|
|
['name'], |
|
|
|
|
|
context=context): |
|
|
|
|
|
|
|
|
for field in self.pool.get('ir.model.fields').read( |
|
|
|
|
|
cr, uid, |
|
|
|
|
|
field_ids[0][2], |
|
|
|
|
|
['name'], |
|
|
|
|
|
context=context): |
|
|
field_names.append(field['name']) |
|
|
field_names.append(field['name']) |
|
|
return {'value': {'field_names': ', '.join(field_names)}} |
|
|
return {'value': {'field_names': ', '.join(field_names)}} |
|
|
|
|
|
|
|
@ -517,14 +506,41 @@ class mis_report_instance_period(orm.Model): |
|
|
'Period name should be unique by report'), |
|
|
'Period name should be unique by report'), |
|
|
] |
|
|
] |
|
|
|
|
|
|
|
|
|
|
|
def _fetch_bal(self, cr, uid, company_id, bal_vars, context=None, |
|
|
|
|
|
is_solde=False): |
|
|
|
|
|
account_obj = self.pool['account.account'] |
|
|
|
|
|
# TODO: use child of company_id? |
|
|
|
|
|
# first fetch all codes and filter the one we need |
|
|
|
|
|
bal_code = [_get_bal_code(bal, is_solde) for bal in bal_vars] |
|
|
|
|
|
|
|
|
|
|
|
account_ids = account_obj.search( |
|
|
|
|
|
cr, uid, |
|
|
|
|
|
['|', ('company_id', '=', False), |
|
|
|
|
|
('company_id', '=', company_id), |
|
|
|
|
|
('code', 'in', bal_code)], |
|
|
|
|
|
context=context) |
|
|
|
|
|
|
|
|
|
|
|
# fetch balances |
|
|
|
|
|
account_datas = account_obj.read( |
|
|
|
|
|
cr, uid, account_ids, ['code', 'balance'], context=context) |
|
|
|
|
|
balances = {} |
|
|
|
|
|
for account_data in account_datas: |
|
|
|
|
|
key = _python_bal_var(account_data['code'], is_solde=is_solde) |
|
|
|
|
|
assert key not in balances |
|
|
|
|
|
balances[key] = account_data['balance'] |
|
|
|
|
|
|
|
|
|
|
|
return balances |
|
|
|
|
|
|
|
|
def _fetch_balances(self, cr, uid, c, bal_vars, context=None): |
|
|
def _fetch_balances(self, cr, uid, c, bal_vars, 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 not bal_vars: |
|
|
|
|
|
return {} |
|
|
|
|
|
|
|
|
if context is None: |
|
|
if context is None: |
|
|
context = {} |
|
|
context = {} |
|
|
account_obj = self.pool['account.account'] |
|
|
|
|
|
|
|
|
|
|
|
search_ctx = dict(context) |
|
|
search_ctx = dict(context) |
|
|
if c.period_from: |
|
|
if c.period_from: |
|
@ -534,27 +550,8 @@ class mis_report_instance_period(orm.Model): |
|
|
search_ctx.update({'date_from': c.date_from, |
|
|
search_ctx.update({'date_from': c.date_from, |
|
|
'date_to': c.date_to}) |
|
|
'date_to': c.date_to}) |
|
|
|
|
|
|
|
|
# TODO: use child of company_id? |
|
|
|
|
|
account_ids = account_obj.search( |
|
|
|
|
|
cr, uid, |
|
|
|
|
|
['|', ('company_id', '=', False), |
|
|
|
|
|
('company_id', '=', c.company_id.id)], |
|
|
|
|
|
context=search_ctx) |
|
|
|
|
|
# first fetch all codes and filter the one we need |
|
|
|
|
|
account_datas = account_obj.read( |
|
|
|
|
|
cr, uid, account_ids, ['code', ], context=search_ctx) |
|
|
|
|
|
account_ids = [a['id'] for a in account_datas |
|
|
|
|
|
if _python_bal_var(a['code']) in bal_vars] |
|
|
|
|
|
# fetch balances |
|
|
# fetch balances |
|
|
account_datas = account_obj.read( |
|
|
|
|
|
cr, uid, account_ids, ['code', 'balance'], context=search_ctx) |
|
|
|
|
|
balances = {} |
|
|
|
|
|
for account_data in account_datas: |
|
|
|
|
|
key = _python_bal_var(account_data['code']) |
|
|
|
|
|
assert key not in balances |
|
|
|
|
|
balances[key] = account_data['balance'] |
|
|
|
|
|
|
|
|
|
|
|
return balances |
|
|
|
|
|
|
|
|
return self._fetch_bal(cr, uid, c.company_id.id, bal_vars, search_ctx) |
|
|
|
|
|
|
|
|
def _fetch_balances_solde(self, cr, uid, c, bals_vars, context=None): |
|
|
def _fetch_balances_solde(self, cr, uid, c, bals_vars, context=None): |
|
|
""" fetch the general account balances solde at the end of |
|
|
""" fetch the general account balances solde at the end of |
|
@ -568,8 +565,9 @@ class mis_report_instance_period(orm.Model): |
|
|
""" |
|
|
""" |
|
|
if context is None: |
|
|
if context is None: |
|
|
context = {} |
|
|
context = {} |
|
|
account_obj = self.pool['account.account'] |
|
|
|
|
|
balances = {} |
|
|
balances = {} |
|
|
|
|
|
if not bals_vars: |
|
|
|
|
|
return balances |
|
|
|
|
|
|
|
|
search_ctx = dict(context) |
|
|
search_ctx = dict(context) |
|
|
if c.period_to: |
|
|
if c.period_to: |
|
@ -591,27 +589,9 @@ class mis_report_instance_period(orm.Model): |
|
|
else: |
|
|
else: |
|
|
return balances |
|
|
return balances |
|
|
|
|
|
|
|
|
# TODO: use child of company_id? |
|
|
|
|
|
account_ids = account_obj.search( |
|
|
|
|
|
cr, uid, |
|
|
|
|
|
['|', ('company_id', '=', False), |
|
|
|
|
|
('company_id', '=', c.company_id.id)], |
|
|
|
|
|
context=search_ctx) |
|
|
|
|
|
# first fetch all codes and filter the one we need |
|
|
|
|
|
account_datas = account_obj.read( |
|
|
|
|
|
cr, uid, account_ids, ['code', ], context=search_ctx) |
|
|
|
|
|
account_ids = [a['id'] for a in account_datas |
|
|
|
|
|
if _python_bals_var(a['code']) in bals_vars] |
|
|
|
|
|
# fetch balances |
|
|
# fetch balances |
|
|
account_datas = account_obj.read( |
|
|
|
|
|
cr, uid, account_ids, ['code', 'balance'], context=search_ctx) |
|
|
|
|
|
|
|
|
|
|
|
for account_data in account_datas: |
|
|
|
|
|
key = _python_bals_var(account_data['code']) |
|
|
|
|
|
assert key not in balances |
|
|
|
|
|
balances[key] = account_data['balance'] |
|
|
|
|
|
|
|
|
|
|
|
return balances |
|
|
|
|
|
|
|
|
return self._fetch_bal(cr, uid, c.company_id.id, bals_vars, |
|
|
|
|
|
search_ctx, is_solde=True) |
|
|
|
|
|
|
|
|
def _fetch_queries(self, cr, uid, c, context): |
|
|
def _fetch_queries(self, cr, uid, c, context): |
|
|
res = {} |
|
|
res = {} |
|
@ -641,7 +621,7 @@ class mis_report_instance_period(orm.Model): |
|
|
|
|
|
|
|
|
return res |
|
|
return res |
|
|
|
|
|
|
|
|
def _compute(self, cr, uid, c, bal_vars, bals_vars, context=None): |
|
|
|
|
|
|
|
|
def _compute(self, cr, uid, lang_id, c, bal_vars, bals_vars, context=None): |
|
|
if context is None: |
|
|
if context is None: |
|
|
context = {} |
|
|
context = {} |
|
|
|
|
|
|
|
@ -677,7 +657,7 @@ class mis_report_instance_period(orm.Model): |
|
|
kpi_val_comment = traceback.format_exc() |
|
|
kpi_val_comment = traceback.format_exc() |
|
|
else: |
|
|
else: |
|
|
kpi_val_rendered = kpi_obj._render( |
|
|
kpi_val_rendered = kpi_obj._render( |
|
|
cr, uid, kpi, kpi_val, context=context) |
|
|
|
|
|
|
|
|
cr, uid, lang_id, kpi, kpi_val, context=context) |
|
|
kpi_val_comment = None |
|
|
kpi_val_comment = None |
|
|
|
|
|
|
|
|
localdict[kpi.name] = kpi_val |
|
|
localdict[kpi.name] = kpi_val |
|
@ -782,14 +762,10 @@ class mis_report_instance(orm.Model): |
|
|
context=context) |
|
|
context=context) |
|
|
return res |
|
|
return res |
|
|
|
|
|
|
|
|
def _format_date(self, cr, uid, date, context=None): |
|
|
|
|
|
|
|
|
def _format_date(self, cr, uid, lang_id, date, context=None): |
|
|
# format date following user language |
|
|
# format date following user language |
|
|
lang = self.pool['res.users'].read( |
|
|
|
|
|
cr, uid, uid, ['lang'], context=context)['lang'] |
|
|
|
|
|
language_id = self.pool['res.lang'].search( |
|
|
|
|
|
cr, uid, [('code', '=', lang)], context=context)[0] |
|
|
|
|
|
tformat = self.pool['res.lang'].read( |
|
|
tformat = self.pool['res.lang'].read( |
|
|
cr, uid, language_id, ['date_format'])['date_format'] |
|
|
|
|
|
|
|
|
cr, uid, lang_id, ['date_format'])[0]['date_format'] |
|
|
return datetime.strftime(datetime.strptime( |
|
|
return datetime.strftime(datetime.strptime( |
|
|
date, |
|
|
date, |
|
|
tools.DEFAULT_SERVER_DATE_FORMAT), |
|
|
tools.DEFAULT_SERVER_DATE_FORMAT), |
|
@ -820,7 +796,12 @@ class mis_report_instance(orm.Model): |
|
|
period_values = {} |
|
|
period_values = {} |
|
|
|
|
|
|
|
|
bal_vars = _get_bal_vars_in_report(r.report_id) |
|
|
bal_vars = _get_bal_vars_in_report(r.report_id) |
|
|
bals_vars = _get_bals_vars_in_report(r.report_id) |
|
|
|
|
|
|
|
|
bals_vars = _get_bal_vars_in_report(r.report_id, is_solde=True) |
|
|
|
|
|
|
|
|
|
|
|
lang = self.pool['res.users'].read( |
|
|
|
|
|
cr, uid, uid, ['lang'], context=context)['lang'] |
|
|
|
|
|
lang_id = self.pool['res.lang'].search( |
|
|
|
|
|
cr, uid, [('code', '=', lang)], context=context) |
|
|
|
|
|
|
|
|
for period in r.period_ids: |
|
|
for period in r.period_ids: |
|
|
# add the column header |
|
|
# add the column header |
|
@ -829,16 +810,16 @@ class mis_report_instance(orm.Model): |
|
|
date=(period.duration > 1 or period.type == 'w') and |
|
|
date=(period.duration > 1 or period.type == 'w') and |
|
|
_('from %s to %s' % |
|
|
_('from %s to %s' % |
|
|
(period.period_from and period.period_from.name |
|
|
(period.period_from and period.period_from.name |
|
|
or self._format_date(cr, uid, period.date_from, |
|
|
|
|
|
|
|
|
or self._format_date(cr, uid, lang_id, period.date_from, |
|
|
context=context), |
|
|
context=context), |
|
|
period.period_to and period.period_to.name |
|
|
period.period_to and period.period_to.name |
|
|
or self._format_date(cr, uid, period.date_to, |
|
|
|
|
|
|
|
|
or self._format_date(cr, uid, lang_id, period.date_to, |
|
|
context=context))) |
|
|
context=context))) |
|
|
or period.period_from and period.period_from.name or |
|
|
or period.period_from and period.period_from.name or |
|
|
period.date_from)) |
|
|
period.date_from)) |
|
|
# compute kpi values |
|
|
# compute kpi values |
|
|
values = report_instance_period_obj._compute( |
|
|
values = report_instance_period_obj._compute( |
|
|
cr, uid, period, bal_vars, bals_vars, context=context) |
|
|
|
|
|
|
|
|
cr, uid, lang_id, period, bal_vars, bals_vars, context=context) |
|
|
period_values[period.name] = values |
|
|
period_values[period.name] = values |
|
|
for key in values: |
|
|
for key in values: |
|
|
content[key]['default_style'] = values[key]['default_style'] |
|
|
content[key]['default_style'] = values[key]['default_style'] |
|
@ -858,6 +839,7 @@ class mis_report_instance(orm.Model): |
|
|
{'val_r': kpi_obj._render_comparison( |
|
|
{'val_r': kpi_obj._render_comparison( |
|
|
cr, |
|
|
cr, |
|
|
uid, |
|
|
uid, |
|
|
|
|
|
lang_id, |
|
|
kpi, |
|
|
kpi, |
|
|
column1_values[kpi.name]['val'], |
|
|
column1_values[kpi.name]['val'], |
|
|
column2_values[kpi.name]['val'], |
|
|
column2_values[kpi.name]['val'], |
|
|