Browse Source

[ADD] backport mis_builder from 8.0

pull/90/head
Laurent Mignon (ACSONE) 9 years ago
committed by Stéphane Bidoul
parent
commit
495693dacc
  1. 9
      mis_builder/__openerp__.py
  2. 161
      mis_builder/models/aep.py
  3. 820
      mis_builder/models/mis_builder.py
  4. 1
      mis_builder/report/__init__.py
  5. 66
      mis_builder/report/report_mis_report_instance.py
  6. 55
      mis_builder/report/report_mis_report_instance.xml
  7. 4
      mis_builder/tests/__init__.py
  8. 18
      mis_builder/views/mis_builder.xml

9
mis_builder/__openerp__.py

@ -29,6 +29,8 @@
'summary': """ 'summary': """
Build 'Management Information System' Reports and Dashboards Build 'Management Information System' Reports and Dashboards
""", """,
'description': """
""",
'author': 'ACSONE SA/NV', 'author': 'ACSONE SA/NV',
'website': 'http://acsone.eu', 'website': 'http://acsone.eu',
'depends': [ 'depends': [
@ -40,7 +42,6 @@
'views/mis_builder.xml', 'views/mis_builder.xml',
'security/ir.model.access.csv', 'security/ir.model.access.csv',
'security/mis_builder_security.xml', 'security/mis_builder_security.xml',
'report/report_mis_report_instance.xml',
], ],
'test': [ 'test': [
], ],
@ -51,6 +52,12 @@
'tests/mis.report.instance.period.csv', 'tests/mis.report.instance.period.csv',
'tests/mis.report.instance.csv', 'tests/mis.report.instance.csv',
], ],
'js': [
'static/src/js/*.js'
],
'css': [
'static/src/css/*.css'
],
'qweb': [ 'qweb': [
'static/src/xml/*.xml' 'static/src/xml/*.xml'
], ],

161
mis_builder/models/aep.py

@ -26,6 +26,7 @@ import re
from collections import defaultdict from collections import defaultdict
from openerp.exceptions import Warning from openerp.exceptions import Warning
from openerp import pooler
from openerp.osv import expression from openerp.osv import expression
from openerp.tools.safe_eval import safe_eval from openerp.tools.safe_eval import safe_eval
from openerp.tools.translate import _ from openerp.tools.translate import _
@ -82,18 +83,16 @@ class AccountingExpressionProcessor(object):
r"(?P<accounts>_[a-zA-Z0-9]+|\[.*?\])" r"(?P<accounts>_[a-zA-Z0-9]+|\[.*?\])"
r"(?P<domain>\[.*?\])?") r"(?P<domain>\[.*?\])?")
def __init__(self, env):
self.env = env
def __init__(self, cursor):
self.pool = pooler.get_pool(cursor.dbname)
# before done_parsing: {(domain, mode): set(account_codes)} # before done_parsing: {(domain, mode): set(account_codes)}
# after done_parsing: {(domain, mode): list(account_ids)} # after done_parsing: {(domain, mode): list(account_ids)}
self._map_account_ids = defaultdict(set) self._map_account_ids = defaultdict(set)
self._account_ids_by_code = defaultdict(set) self._account_ids_by_code = defaultdict(set)
def _load_account_codes(self, account_codes, root_account):
account_model = self.env['account.account']
# TODO: account_obj is necessary because _get_children_and_consol
# does not work in new API?
account_obj = self.env.registry('account.account')
def _load_account_codes(self, cr, uid, account_codes, root_account,
context=None):
account_obj = self.pool['account.account']
exact_codes = set() exact_codes = set()
like_codes = set() like_codes = set()
for account_code in account_codes: for account_code in account_codes:
@ -109,9 +108,13 @@ class AccountingExpressionProcessor(object):
like_codes.add(account_code) like_codes.add(account_code)
else: else:
exact_codes.add(account_code) exact_codes.add(account_code)
for account in account_model.\
search([('code', 'in', list(exact_codes)),
('parent_id', 'child_of', root_account.id)]):
account_ids = account_obj.search(
cr, uid,
[('code', 'in', list(exact_codes)),
('parent_id', 'child_of', root_account.id)],
context=context)
for account in account_obj.browse(
cr, uid, account_ids, context=context):
if account.code == root_account.code: if account.code == root_account.code:
code = None code = None
else: else:
@ -119,21 +122,26 @@ class AccountingExpressionProcessor(object):
if account.type in ('view', 'consolidation'): if account.type in ('view', 'consolidation'):
self._account_ids_by_code[code].update( self._account_ids_by_code[code].update(
account_obj._get_children_and_consol( account_obj._get_children_and_consol(
self.env.cr, self.env.uid,
cr, uid,
[account.id], [account.id],
self.env.context))
context=context))
else: else:
self._account_ids_by_code[code].add(account.id) self._account_ids_by_code[code].add(account.id)
for like_code in like_codes: for like_code in like_codes:
for account in account_model.\
search([('code', 'like', like_code),
('parent_id', 'child_of', root_account.id)]):
for account_id in account_obj.\
search(cr, uid,
[('code', 'like', like_code),
('parent_id', 'child_of', root_account.id)],
context=context):
account = account_obj.browse(cr, uid, account_id,
context=context)
if account.type in ('view', 'consolidation'): if account.type in ('view', 'consolidation'):
self._account_ids_by_code[like_code].update( self._account_ids_by_code[like_code].update(
account_obj._get_children_and_consol( account_obj._get_children_and_consol(
cr, uid,
self.env.cr, self.env.uid, self.env.cr, self.env.uid,
[account.id], [account.id],
self.env.context))
context=context))
else: else:
self._account_ids_by_code[like_code].add(account.id) self._account_ids_by_code[like_code].add(account.id)
@ -171,11 +179,12 @@ class AccountingExpressionProcessor(object):
key = (domain, mode) key = (domain, mode)
self._map_account_ids[key].update(account_codes) self._map_account_ids[key].update(account_codes)
def done_parsing(self, root_account):
def done_parsing(self, cr, uid, root_account, context=None):
"""Load account codes and replace account codes by """Load account codes and replace account codes by
account ids in map.""" account ids in map."""
for key, account_codes in self._map_account_ids.items(): for key, account_codes in self._map_account_ids.items():
self._load_account_codes(account_codes, root_account)
self._load_account_codes(cr, uid, account_codes, root_account,
context=context)
account_ids = set() account_ids = set()
for account_code in account_codes: for account_code in account_codes:
account_ids.update(self._account_ids_by_code[account_code]) account_ids.update(self._account_ids_by_code[account_code])
@ -219,78 +228,105 @@ class AccountingExpressionProcessor(object):
expression.OR(date_domain_by_mode.values()) expression.OR(date_domain_by_mode.values())
def _period_has_moves(self, period): def _period_has_moves(self, period):
move_model = self.env['account.move']
move_model = self.pool['account.move']
return bool(move_model.search([('period_id', '=', period.id)], return bool(move_model.search([('period_id', '=', period.id)],
limit=1)) limit=1))
def _get_previous_opening_period(self, period, company_id):
period_model = self.env['account.period']
periods = period_model.search(
def _get_previous_opening_period(self, cr, uid, period, company_id,
context=None):
period_model = self.pool['account.period']
period_ids = period_model.search(
cr, uid,
[('date_start', '<=', period.date_start), [('date_start', '<=', period.date_start),
('special', '=', True), ('special', '=', True),
('company_id', '=', company_id)], ('company_id', '=', company_id)],
order="date_start desc", order="date_start desc",
limit=1)
limit=1,
context=context)
periods = period_model.browse(cr, uid, period_ids, context=context)
return periods and periods[0] return periods and periods[0]
def _get_previous_normal_period(self, period, company_id):
period_model = self.env['account.period']
periods = period_model.search(
def _get_previous_normal_period(self, cr, uid, period, company_id,
context=None):
period_model = self.pool['account.period']
period_ids = period_model.search(
cr, uid,
[('date_start', '<', period.date_start), [('date_start', '<', period.date_start),
('special', '=', False), ('special', '=', False),
('company_id', '=', company_id)], ('company_id', '=', company_id)],
order="date_start desc", order="date_start desc",
limit=1)
limit=1,
context=context)
periods = period_model.browse(cr, uid, period_ids, context=context)
return periods and periods[0] return periods and periods[0]
def _get_first_normal_period(self, company_id):
period_model = self.env['account.period']
periods = period_model.search(
def _get_first_normal_period(self, cr, uid, company_id, context=None):
period_model = self.pool['account.period']
period_ids = period_model.search(
cr, uid,
[('special', '=', False), [('special', '=', False),
('company_id', '=', company_id)], ('company_id', '=', company_id)],
order="date_start asc", order="date_start asc",
limit=1)
limit=1,
context=context)
periods = period_model.browse(cr, uid, period_ids, context=context)
return periods and periods[0] return periods and periods[0]
def _get_period_ids_between(self, period_from, period_to, company_id):
period_model = self.env['account.period']
periods = period_model.search(
def _get_period_ids_between(self, cr, uid, period_from, period_to,
company_id, context=None):
period_model = self.pool['account.period']
period_ids = period_model.search(
cr, uid,
[('date_start', '>=', period_from.date_start), [('date_start', '>=', period_from.date_start),
('date_stop', '<=', period_to.date_stop), ('date_stop', '<=', period_to.date_stop),
('special', '=', False), ('special', '=', False),
('company_id', '=', company_id)])
period_ids = [p.id for p in periods]
('company_id', '=', company_id)],
context=context)
if period_from.special: if period_from.special:
period_ids.append(period_from.id) period_ids.append(period_from.id)
return period_ids return period_ids
def _get_period_company_ids(self, period_from, period_to):
period_model = self.env['account.period']
periods = period_model.search(
def _get_period_company_ids(self, cr, uid, period_from, period_to,
context=None):
period_model = self.pool['account.period']
period_ids = period_model.search(
cr, uid,
[('date_start', '>=', period_from.date_start), [('date_start', '>=', period_from.date_start),
('date_stop', '<=', period_to.date_stop), ('date_stop', '<=', period_to.date_stop),
('special', '=', False)])
('special', '=', False)],
context=context)
periods = period_model.browse(cr, uid, period_ids, context=context)
return set([p.company_id.id for p in periods]) return set([p.company_id.id for p in periods])
def _get_period_ids_for_mode(self, period_from, period_to, mode):
def _get_period_ids_for_mode(self, cr, uid, period_from, period_to, mode,
context=None):
assert not period_from.special assert not period_from.special
assert not period_to.special assert not period_to.special
assert period_from.company_id == period_to.company_id assert period_from.company_id == period_to.company_id
assert period_from.date_start <= period_to.date_start assert period_from.date_start <= period_to.date_start
period_ids = [] period_ids = []
for company_id in self._get_period_company_ids(period_from, period_to):
for company_id in self._get_period_company_ids(cr, uid,
period_from, period_to,
context=context):
if mode == MODE_VARIATION: if mode == MODE_VARIATION:
period_ids.extend(self._get_period_ids_between( period_ids.extend(self._get_period_ids_between(
period_from, period_to, company_id))
cr, uid,
period_from, period_to, company_id,
context=context))
else: else:
if mode == MODE_INITIAL: if mode == MODE_INITIAL:
period_to = self._get_previous_normal_period( period_to = self._get_previous_normal_period(
period_from, company_id)
cr, uid,
period_from, company_id,
context=context)
# look for opening period with moves # look for opening period with moves
opening_period = self._get_previous_opening_period( opening_period = self._get_previous_opening_period(
period_from, company_id)
cr, uid,
period_from, company_id,
context=context)
if opening_period and \ if opening_period and \
self._period_has_moves(opening_period[0]):
self._period_has_moves(cr, uid, opening_period[0],
context=context):
# found opening period with moves # found opening period with moves
if opening_period.date_start == period_from.date_start and \ if opening_period.date_start == period_from.date_start and \
mode == MODE_INITIAL: mode == MODE_INITIAL:
@ -303,19 +339,25 @@ class AccountingExpressionProcessor(object):
else: else:
# no opening period with moves, # no opening period with moves,
# use very first normal period # use very first normal period
period_from = self._get_first_normal_period(company_id)
period_from = self._get_first_normal_period(
cr, uid, company_id, context=context)
if period_to: if period_to:
period_ids.extend(self._get_period_ids_between( period_ids.extend(self._get_period_ids_between(
period_from, period_to, company_id))
cr, uid,
period_from, period_to, company_id,
context=context))
return period_ids return period_ids
def get_aml_domain_for_dates(self, date_from, date_to,
def get_aml_domain_for_dates(self, cr, uid, date_from, date_to,
period_from, period_to, period_from, period_to,
mode, mode,
target_move):
target_move,
context=None):
if period_from and period_to: if period_from and period_to:
period_ids = self._get_period_ids_for_mode( period_ids = self._get_period_ids_for_mode(
period_from, period_to, mode)
cr, uid,
period_from, period_to, mode,
context=context)
domain = [('period_id', 'in', period_ids)] domain = [('period_id', 'in', period_ids)]
else: else:
if mode == MODE_VARIATION: if mode == MODE_VARIATION:
@ -327,14 +369,14 @@ class AccountingExpressionProcessor(object):
domain.append(('move_id.state', '=', 'posted')) domain.append(('move_id.state', '=', 'posted'))
return expression.normalize_domain(domain) return expression.normalize_domain(domain)
def do_queries(self, date_from, date_to, period_from, period_to,
target_move):
def do_queries(self, cr, uid, date_from, date_to, period_from, period_to,
target_move, context=None):
"""Query sums of debit and credit for all accounts and domains """Query sums of debit and credit for all accounts and domains
used in expressions. used in expressions.
This method must be executed after done_parsing(). This method must be executed after done_parsing().
""" """
aml_model = self.env['account.move.line']
aml_model = self.pool['account.move.line']
# {(domain, mode): {account_id: (debit, credit)}} # {(domain, mode): {account_id: (debit, credit)}}
self._data = defaultdict(dict) self._data = defaultdict(dict)
domain_by_mode = {} domain_by_mode = {}
@ -342,15 +384,18 @@ class AccountingExpressionProcessor(object):
domain, mode = key domain, mode = key
if mode not in domain_by_mode: if mode not in domain_by_mode:
domain_by_mode[mode] = \ domain_by_mode[mode] = \
self.get_aml_domain_for_dates(date_from, date_to,
self.get_aml_domain_for_dates(cr, uid,
date_from, date_to,
period_from, period_to, period_from, period_to,
mode, target_move)
mode, target_move,
context=context)
domain = list(domain) + domain_by_mode[mode] domain = list(domain) + domain_by_mode[mode]
domain.append(('account_id', 'in', self._map_account_ids[key])) domain.append(('account_id', 'in', self._map_account_ids[key]))
# fetch sum of debit/credit, grouped by account_id # fetch sum of debit/credit, grouped by account_id
accs = aml_model.read_group(domain,
accs = aml_model.read_group(cr, uid, domain,
['debit', 'credit', 'account_id'], ['debit', 'credit', 'account_id'],
['account_id'])
['account_id'],
context=context)
for acc in accs: for acc in accs:
self._data[key][acc['account_id'][0]] = \ self._data[key][acc['account_id'][0]] = \
(acc['debit'] or 0.0, acc['credit'] or 0.0) (acc['debit'] or 0.0, acc['credit'] or 0.0)

820
mis_builder/models/mis_builder.py
File diff suppressed because it is too large
View File

1
mis_builder/report/__init__.py

@ -23,4 +23,3 @@
############################################################################## ##############################################################################
from . import mis_builder_xls from . import mis_builder_xls
from . import report_mis_report_instance

66
mis_builder/report/report_mis_report_instance.py

@ -1,66 +0,0 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# mis_builder module for Odoo, Management Information System Builder
# Copyright (C) 2014-2015 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 logging
from openerp import api, models
_logger = logging.getLogger(__name__)
class ReportMisReportInstance(models.AbstractModel):
_name = 'report.mis_builder.report_mis_report_instance'
@api.multi
def render_html(self, data=None):
docs = self.env['mis.report.instance'].browse(self._ids)
docs_computed = {}
for doc in docs:
docs_computed[doc.id] = doc.compute()
docargs = {
'doc_ids': self._ids,
'doc_model': 'mis.report.instance',
'docs': docs,
'docs_computed': docs_computed,
}
return self.env['report'].\
render('mis_builder.report_mis_report_instance', docargs)
class Report(models.Model):
_inherit = "report"
@api.v7
def get_pdf(self, cr, uid, ids, report_name, html=None, data=None,
context=None):
report = self._get_report_from_name(cr, uid, report_name)
obj = self.pool[report.model].browse(cr, uid, ids,
context=context)[0]
context = context.copy()
if hasattr(obj, 'landscape_pdf') and obj.landscape_pdf:
context.update({'landscape': True})
return super(Report, self).get_pdf(cr, uid, ids, report_name,
html=html, data=data,
context=context)

55
mis_builder/report/report_mis_report_instance.xml

@ -1,55 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<template id="report_mis_report_instance">
<t t-call="report.html_container">
<t t-foreach="docs" t-as="o">
<t t-call="report.internal_layout">
<div class="page">
<h2 t-field="o.name"></h2>
<table class="table table-condensed text-right">
<thead>
<tr>
<t t-foreach="docs_computed[o.id]['header']" t-as="h">
<th>
<div>
<t t-esc="h_value['kpi_name']"/>
</div>
</th>
<th t-foreach="h_value['cols']" t-as="col">
<div>
<t t-esc="col['name']"/>
</div>
<div>
<t t-esc="col['date']"/>
</div>
</th>
</t>
</tr>
</thead>
<tbody>
<tr t-foreach="docs_computed[o.id]['content']" t-as="c">
<td t-att-style="c_value['default_style']">
<div>
<t t-esc="c_value['kpi_name']"/>
</div>
</td>
<t t-foreach="c_value['cols']" t-as="value">
<td t-att-style="c_value['default_style']">
<div t-att-style="value_value.get('style')">
<t t-esc="value_value['val_r']"/>
</div>
</td>
</t>
</tr>
</tbody>
</table>
</div>
</t>
</t>
</t>
</template>
</data>
</openerp>

4
mis_builder/tests/__init__.py

@ -23,3 +23,7 @@
############################################################################## ##############################################################################
from . import test_mis_builder from . import test_mis_builder
checks = [
test_mis_builder,
]

18
mis_builder/views/mis_builder.xml

@ -2,13 +2,6 @@
<openerp> <openerp>
<data> <data>
<template id="assets_backend" name="mis_builder" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link rel="stylesheet" href="/mis_builder/static/src/css/custom.css"/>
<script type="text/javascript" src="/mis_builder/static/src/js/mis_builder.js"></script>
</xpath>
</template>
<record model="ir.ui.view" id="mis_report_view_tree"> <record model="ir.ui.view" id="mis_report_view_tree">
<field name="name">mis.report.view.tree</field> <field name="name">mis.report.view.tree</field>
<field name="model">mis.report</field> <field name="model">mis.report</field>
@ -107,15 +100,6 @@
<field name="auto" eval="False"/> <field name="auto" eval="False"/>
</record> </record>
<record id="qweb_pdf_export" model="ir.actions.report.xml">
<field name="name">MIS report instance QWEB PDF report</field>
<field name="model">mis.report.instance</field>
<field name="type">ir.actions.report.xml</field>
<field name="report_name">mis_builder.report_mis_report_instance</field>
<field name="report_type">qweb-pdf</field>
<field name="auto" eval="False"/>
</record>
<record model="ir.ui.view" id="mis_report_instance_result_view_form"> <record model="ir.ui.view" id="mis_report_instance_result_view_form">
<field name="name">mis.report.instance.result.view.form</field> <field name="name">mis.report.instance.result.view.form</field>
<field name="model">mis.report.instance</field> <field name="model">mis.report.instance</field>
@ -123,7 +107,6 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="MIS Report Result" version="7.0"> <form string="MIS Report Result" version="7.0">
<widget type="mis_report"></widget> <widget type="mis_report"></widget>
<button icon="gtk-print" name="%(qweb_pdf_export)d" string="Print" type="action" colspan="2"/>
<button icon="gtk-execute" name="%(xls_export)d" string="Export" type="action" colspan="2"/> <button icon="gtk-execute" name="%(xls_export)d" string="Export" type="action" colspan="2"/>
</form> </form>
</field> </field>
@ -161,7 +144,6 @@
</div> </div>
<div class="oe_right oe_button_box" name="buttons"> <div class="oe_right oe_button_box" name="buttons">
<button type="object" name="preview" string="Preview" icon="gtk-print-preview" /> <button type="object" name="preview" string="Preview" icon="gtk-print-preview" />
<button type="action" name="%(qweb_pdf_export)d" string="Print" icon="gtk-print" />
<button type="action" name="%(xls_export)d" string="Export" icon="gtk-execute" /> <button type="action" name="%(xls_export)d" string="Export" icon="gtk-execute" />
<button type="action" name="%(mis_report_instance_add_to_dashboard_action)d" string="Add to dashboard" icon="gtk-add" /> <button type="action" name="%(mis_report_instance_add_to_dashboard_action)d" string="Add to dashboard" icon="gtk-add" />
</div> </div>

Loading…
Cancel
Save