├── mis_builder ├── readme │ ├── newsfragments │ │ └── .gitignore │ ├── ROADMAP.rst │ ├── INSTALL.rst │ ├── DESCRIPTION.rst │ ├── DEVELOP.rst │ ├── CONTRIBUTORS.rst │ └── USAGE.rst ├── static │ ├── description │ │ ├── icon.png │ │ ├── ex_report_preview.png │ │ ├── ex_report_settings.png │ │ └── ex_report_template.png │ └── src │ │ ├── css │ │ ├── custom.css │ │ └── report.css │ │ └── xml │ │ └── mis_report_widget.xml ├── wizard │ ├── __init__.py │ ├── mis_builder_dashboard.xml │ └── mis_builder_dashboard.py ├── __init__.py ├── report │ ├── __init__.py │ ├── mis_report_instance_xlsx.xml │ └── mis_report_instance_qweb.py ├── tests │ ├── test_aggregate.py │ ├── test_simple_array.py │ ├── test_accounting_none.py │ ├── __init__.py │ ├── test_utc_midnight.py │ ├── test_mis_safe_eval.py │ ├── common.py │ ├── test_subreport.py │ └── test_analytic_filters.py ├── models │ ├── __init__.py │ ├── data_error.py │ ├── mis_safe_eval.py │ ├── mis_report_subreport.py │ ├── expression_evaluator.py │ ├── aggregate.py │ ├── prorata_read_group_mixin.py │ ├── mis_kpi_data.py │ └── accounting_none.py ├── security │ ├── mis_builder_security.xml │ └── ir.model.access.csv ├── datas │ └── ir_cron.xml ├── migrations │ └── 8.0.2.0.0 │ │ ├── pre-migration.py │ │ └── post-migration.py └── __manifest__.py ├── mis_builder_demo ├── readme │ ├── newsfragments │ │ └── .gitignore │ ├── DESCRIPTION.rst │ ├── CONTRIBUTORS.rst │ ├── ROADMAP.rst │ ├── USAGE.rst │ └── HISTORY.rst ├── models │ ├── __init__.py │ └── mis_committed_purchase.py ├── static │ └── description │ │ └── icon.png ├── __init__.py ├── security │ └── mis_committed_purchase.xml ├── __manifest__.py ├── views │ └── mis_committed_purchase.xml ├── data │ ├── mis_report_style.xml │ ├── mis_budget.xml │ ├── mis_report.xml │ └── mis_report_instance.xml └── i18n │ ├── mis_builder_demo.pot │ ├── pt.po │ ├── nl.po │ ├── de.po │ └── ca.po ├── setup ├── _metapackage │ ├── VERSION.txt │ └── setup.py ├── mis_builder │ ├── odoo │ │ └── addons │ │ │ └── mis_builder │ └── setup.py ├── mis_builder_demo │ ├── odoo │ │ └── addons │ │ │ └── mis_builder_demo │ └── setup.py ├── mis_builder_budget │ ├── odoo │ │ └── addons │ │ │ └── mis_builder_budget │ └── setup.py ├── README └── .setuptools-odoo-make-default-ignore ├── mis_builder_budget ├── readme │ ├── newsfragments │ │ └── .gitignore │ ├── CONTRIBUTORS.rst │ ├── ROADMAP.rst │ ├── DESCRIPTION.rst │ ├── USAGE.rst │ └── HISTORY.rst ├── __init__.py ├── tests │ ├── __init__.py │ └── test_mis_budget_by_account.py ├── static │ └── description │ │ └── icon.png ├── models │ ├── mis_report_kpi.py │ ├── __init__.py │ ├── mis_budget_by_account.py │ ├── mis_budget.py │ ├── mis_report_kpi_expression.py │ ├── mis_budget_item.py │ ├── mis_budget_abstract.py │ ├── mis_report_instance_period.py │ ├── mis_budget_by_account_item.py │ ├── mis_budget_item_abstract.py │ └── mis_report_instance.py ├── __manifest__.py ├── views │ ├── mis_report.xml │ ├── mis_report_instance_period.xml │ ├── mis_budget_item.xml │ ├── mis_budget_by_account_item.xml │ ├── mis_budget.xml │ └── mis_budget_by_account.xml └── security │ ├── mis_budget.xml │ ├── mis_budget_item.xml │ ├── mis_budget_by_account.xml │ └── mis_budget_by_account_item.xml ├── oca_dependencies.txt ├── docs ├── _static │ ├── logo.png │ └── images │ │ ├── 01.png │ │ ├── 02.png │ │ ├── 03.png │ │ ├── 04.png │ │ ├── 05.png │ │ ├── 06.png │ │ ├── 07.png │ │ ├── 08.png │ │ ├── 09.png │ │ ├── 10.png │ │ ├── 11.png │ │ ├── 12.png │ │ ├── 13.png │ │ ├── 14.png │ │ ├── 15.png │ │ ├── 16.png │ │ ├── 17.png │ │ ├── 18.png │ │ ├── 19.png │ │ ├── 20.png │ │ ├── 21.png │ │ ├── 22.png │ │ ├── 23.png │ │ ├── 24.png │ │ ├── query_1.png │ │ ├── query_2.png │ │ ├── query_3.png │ │ ├── analytic.jpg │ │ └── analytic1.jpg ├── quickstart.rst ├── develop.rst ├── history.rst ├── roadmap.rst ├── _templates │ └── layout.html ├── Makefile ├── index.rst ├── make.bat ├── contributors.rst ├── description.rst └── install.rst ├── .prettierrc.yml ├── .gitattributes ├── .github ├── workflows │ ├── pre-commit.yml │ └── tests.yml └── pull_request_template.md ├── CONTRIBUTING.md ├── .flake8 ├── .isort.cfg ├── .coveragerc ├── .editorconfig ├── .travis.yml ├── .gitignore ├── .pylintrc-mandatory ├── .pylintrc ├── README.md ├── .pre-commit-config.yaml └── .eslintrc.yml /mis_builder/readme/newsfragments/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mis_builder_demo/readme/newsfragments/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /setup/_metapackage/VERSION.txt: -------------------------------------------------------------------------------- 1 | 14.0.20210406.0 -------------------------------------------------------------------------------- /mis_builder_budget/readme/newsfragments/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mis_builder_budget/__init__.py: -------------------------------------------------------------------------------- 1 | from . import models 2 | -------------------------------------------------------------------------------- /oca_dependencies.txt: -------------------------------------------------------------------------------- 1 | reporting-engine 2 | server-ux 3 | web 4 | -------------------------------------------------------------------------------- /setup/mis_builder/odoo/addons/mis_builder: -------------------------------------------------------------------------------- 1 | ../../../../mis_builder -------------------------------------------------------------------------------- /mis_builder_demo/readme/DESCRIPTION.rst: -------------------------------------------------------------------------------- 1 | Demo addon for MIS Builder. 2 | -------------------------------------------------------------------------------- /mis_builder_demo/models/__init__.py: -------------------------------------------------------------------------------- 1 | from . import mis_committed_purchase 2 | -------------------------------------------------------------------------------- /setup/mis_builder_demo/odoo/addons/mis_builder_demo: -------------------------------------------------------------------------------- 1 | ../../../../mis_builder_demo -------------------------------------------------------------------------------- /setup/mis_builder_budget/odoo/addons/mis_builder_budget: -------------------------------------------------------------------------------- 1 | ../../../../mis_builder_budget -------------------------------------------------------------------------------- /docs/_static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertelab/mis-builder/14.0/docs/_static/logo.png -------------------------------------------------------------------------------- /docs/_static/images/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertelab/mis-builder/14.0/docs/_static/images/01.png -------------------------------------------------------------------------------- /docs/_static/images/02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertelab/mis-builder/14.0/docs/_static/images/02.png -------------------------------------------------------------------------------- /docs/_static/images/03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertelab/mis-builder/14.0/docs/_static/images/03.png -------------------------------------------------------------------------------- /docs/_static/images/04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertelab/mis-builder/14.0/docs/_static/images/04.png -------------------------------------------------------------------------------- /docs/_static/images/05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertelab/mis-builder/14.0/docs/_static/images/05.png -------------------------------------------------------------------------------- /docs/_static/images/06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertelab/mis-builder/14.0/docs/_static/images/06.png -------------------------------------------------------------------------------- /docs/_static/images/07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertelab/mis-builder/14.0/docs/_static/images/07.png -------------------------------------------------------------------------------- /docs/_static/images/08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertelab/mis-builder/14.0/docs/_static/images/08.png -------------------------------------------------------------------------------- /docs/_static/images/09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertelab/mis-builder/14.0/docs/_static/images/09.png -------------------------------------------------------------------------------- /docs/_static/images/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertelab/mis-builder/14.0/docs/_static/images/10.png -------------------------------------------------------------------------------- /docs/_static/images/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertelab/mis-builder/14.0/docs/_static/images/11.png -------------------------------------------------------------------------------- /docs/_static/images/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertelab/mis-builder/14.0/docs/_static/images/12.png -------------------------------------------------------------------------------- /docs/_static/images/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertelab/mis-builder/14.0/docs/_static/images/13.png -------------------------------------------------------------------------------- /docs/_static/images/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertelab/mis-builder/14.0/docs/_static/images/14.png -------------------------------------------------------------------------------- /docs/_static/images/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertelab/mis-builder/14.0/docs/_static/images/15.png -------------------------------------------------------------------------------- /docs/_static/images/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertelab/mis-builder/14.0/docs/_static/images/16.png -------------------------------------------------------------------------------- /docs/_static/images/17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertelab/mis-builder/14.0/docs/_static/images/17.png -------------------------------------------------------------------------------- /docs/_static/images/18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertelab/mis-builder/14.0/docs/_static/images/18.png -------------------------------------------------------------------------------- /docs/_static/images/19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertelab/mis-builder/14.0/docs/_static/images/19.png -------------------------------------------------------------------------------- /docs/_static/images/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertelab/mis-builder/14.0/docs/_static/images/20.png -------------------------------------------------------------------------------- /docs/_static/images/21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertelab/mis-builder/14.0/docs/_static/images/21.png -------------------------------------------------------------------------------- /docs/_static/images/22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertelab/mis-builder/14.0/docs/_static/images/22.png -------------------------------------------------------------------------------- /docs/_static/images/23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertelab/mis-builder/14.0/docs/_static/images/23.png -------------------------------------------------------------------------------- /docs/_static/images/24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertelab/mis-builder/14.0/docs/_static/images/24.png -------------------------------------------------------------------------------- /docs/quickstart.rst: -------------------------------------------------------------------------------- 1 | Quick Start 2 | =========== 3 | 4 | .. include:: ../mis_builder/readme/USAGE.rst 5 | -------------------------------------------------------------------------------- /docs/_static/images/query_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertelab/mis-builder/14.0/docs/_static/images/query_1.png -------------------------------------------------------------------------------- /docs/_static/images/query_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertelab/mis-builder/14.0/docs/_static/images/query_2.png -------------------------------------------------------------------------------- /docs/_static/images/query_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertelab/mis-builder/14.0/docs/_static/images/query_3.png -------------------------------------------------------------------------------- /mis_builder_budget/tests/__init__.py: -------------------------------------------------------------------------------- 1 | from . import test_mis_budget 2 | from . import test_mis_budget_by_account 3 | -------------------------------------------------------------------------------- /setup/README: -------------------------------------------------------------------------------- 1 | To learn more about this directory, please visit 2 | https://pypi.python.org/pypi/setuptools-odoo 3 | -------------------------------------------------------------------------------- /docs/_static/images/analytic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertelab/mis-builder/14.0/docs/_static/images/analytic.jpg -------------------------------------------------------------------------------- /docs/_static/images/analytic1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertelab/mis-builder/14.0/docs/_static/images/analytic1.jpg -------------------------------------------------------------------------------- /mis_builder/static/description/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertelab/mis-builder/14.0/mis_builder/static/description/icon.png -------------------------------------------------------------------------------- /mis_builder_demo/readme/CONTRIBUTORS.rst: -------------------------------------------------------------------------------- 1 | * Stéphane Bidoul 2 | * Arnaud Pineux 3 | -------------------------------------------------------------------------------- /mis_builder_budget/static/description/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertelab/mis-builder/14.0/mis_builder_budget/static/description/icon.png -------------------------------------------------------------------------------- /mis_builder_demo/static/description/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertelab/mis-builder/14.0/mis_builder_demo/static/description/icon.png -------------------------------------------------------------------------------- /setup/.setuptools-odoo-make-default-ignore: -------------------------------------------------------------------------------- 1 | # addons listed in this file are ignored by 2 | # setuptools-odoo-make-default (one addon per line) 3 | -------------------------------------------------------------------------------- /docs/develop.rst: -------------------------------------------------------------------------------- 1 | Developer's notes 2 | ================= 3 | 4 | mis_builder 5 | ----------- 6 | 7 | .. include:: ../mis_builder/readme/DEVELOP.rst 8 | -------------------------------------------------------------------------------- /setup/mis_builder/setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | setuptools.setup( 4 | setup_requires=['setuptools-odoo'], 5 | odoo_addon=True, 6 | ) 7 | -------------------------------------------------------------------------------- /setup/mis_builder_budget/setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | setuptools.setup( 4 | setup_requires=['setuptools-odoo'], 5 | odoo_addon=True, 6 | ) 7 | -------------------------------------------------------------------------------- /setup/mis_builder_demo/setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | setuptools.setup( 4 | setup_requires=['setuptools-odoo'], 5 | odoo_addon=True, 6 | ) 7 | -------------------------------------------------------------------------------- /mis_builder/static/description/ex_report_preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertelab/mis-builder/14.0/mis_builder/static/description/ex_report_preview.png -------------------------------------------------------------------------------- /mis_builder/static/description/ex_report_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertelab/mis-builder/14.0/mis_builder/static/description/ex_report_settings.png -------------------------------------------------------------------------------- /mis_builder/static/description/ex_report_template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertelab/mis-builder/14.0/mis_builder/static/description/ex_report_template.png -------------------------------------------------------------------------------- /mis_builder/wizard/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 ACSONE SA/NV () 2 | # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). 3 | 4 | from . import mis_builder_dashboard 5 | -------------------------------------------------------------------------------- /mis_builder/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 ACSONE SA/NV () 2 | # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). 3 | 4 | from . import models 5 | from . import wizard 6 | from . import report 7 | -------------------------------------------------------------------------------- /mis_builder_budget/readme/CONTRIBUTORS.rst: -------------------------------------------------------------------------------- 1 | * Stéphane Bidoul 2 | * Adrien Peiffer 3 | * Benjamin Willig 4 | * Artem Kostyuk 5 | -------------------------------------------------------------------------------- /mis_builder_demo/__init__.py: -------------------------------------------------------------------------------- 1 | from . import models 2 | 3 | 4 | def uninstall_hook(cr, registry): 5 | # drop relation view manually because Odoo does not know about it 6 | cr.execute("DROP VIEW IF EXISTS mis_committed_purchase_tag_rel") 7 | -------------------------------------------------------------------------------- /mis_builder/report/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 ACSONE SA/NV () 2 | # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). 3 | 4 | from . import mis_report_instance_qweb 5 | from . import mis_report_instance_xlsx 6 | -------------------------------------------------------------------------------- /docs/history.rst: -------------------------------------------------------------------------------- 1 | History 2 | ======= 3 | 4 | mis_builder 5 | ----------- 6 | 7 | .. include:: ../mis_builder/readme/HISTORY.rst 8 | 9 | mis_builder_budget 10 | ------------------ 11 | 12 | .. include:: ../mis_builder_budget/readme/HISTORY.rst 13 | -------------------------------------------------------------------------------- /mis_builder/readme/ROADMAP.rst: -------------------------------------------------------------------------------- 1 | The mis_builder `roadmap `_ 2 | and `known issues `_ can 3 | be found on GitHub. 4 | -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | # Defaults for all prettier-supported languages. 2 | # Prettier will complete this with settings from .editorconfig file. 3 | bracketSpacing: false 4 | printWidth: 88 5 | proseWrap: always 6 | semi: true 7 | trailingComma: "es5" 8 | xmlWhitespaceSensitivity: "strict" 9 | -------------------------------------------------------------------------------- /mis_builder_budget/readme/ROADMAP.rst: -------------------------------------------------------------------------------- 1 | The mis_builder `roadmap `_ 2 | and `known issues `_ can 3 | be found on GitHub. 4 | -------------------------------------------------------------------------------- /mis_builder_demo/readme/ROADMAP.rst: -------------------------------------------------------------------------------- 1 | The mis_builder `roadmap `_ 2 | and `known issues `_ can 3 | be found on github. 4 | -------------------------------------------------------------------------------- /mis_builder/tests/test_aggregate.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 ACSONE SA/NV () 2 | # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). 3 | 4 | from ..models import aggregate 5 | from .common import load_doctests 6 | 7 | load_tests = load_doctests(aggregate) 8 | -------------------------------------------------------------------------------- /mis_builder/tests/test_simple_array.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 ACSONE SA/NV () 2 | # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). 3 | 4 | from ..models import simple_array 5 | from .common import load_doctests 6 | 7 | load_tests = load_doctests(simple_array) 8 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # ignore some autogenerated files when merging 2 | # git config --global merge.ours.driver true 3 | /README.md merge=ours 4 | /*/__manifest__.py merge=ours 5 | /*/i18n/* merge=ours 6 | /*/readme/HISTORY.rst merge=ours 7 | /*/README.rst merge=ours 8 | /*/static/description/index.html merge=ours 9 | -------------------------------------------------------------------------------- /mis_builder/tests/test_accounting_none.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 ACSONE SA/NV () 2 | # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). 3 | 4 | 5 | from ..models import accounting_none 6 | from .common import load_doctests 7 | 8 | load_tests = load_doctests(accounting_none) 9 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yml: -------------------------------------------------------------------------------- 1 | name: pre-commit 2 | 3 | on: 4 | pull_request: 5 | push: 6 | 7 | jobs: 8 | pre-commit: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: actions/setup-python@v2 13 | - uses: pre-commit/action@v2.0.0 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # OCA Guidelines 2 | 3 | Please follow the official guide from the 4 | [OCA Guidelines page](https://odoo-community.org/page/contributing). 5 | 6 | ## Project Specific Guidelines 7 | 8 | 9 | 10 | This project does not have specific coding guidelines. 11 | -------------------------------------------------------------------------------- /docs/roadmap.rst: -------------------------------------------------------------------------------- 1 | Known issues / Roadmap 2 | ====================== 3 | 4 | The mis_builder `roadmap `_ 5 | and `known issues `_ can 6 | be found on GitHub. 7 | -------------------------------------------------------------------------------- /mis_builder_budget/models/mis_report_kpi.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 ACSONE SA/NV 2 | # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). 3 | 4 | from odoo import fields, models 5 | 6 | 7 | class MisReportKpi(models.Model): 8 | 9 | _inherit = "mis.report.kpi" 10 | 11 | budgetable = fields.Boolean(default=False) 12 | -------------------------------------------------------------------------------- /mis_builder/readme/INSTALL.rst: -------------------------------------------------------------------------------- 1 | Your preferred way to install addons will work with MIS Builder. 2 | 3 | An easy way to install it with all its dependencies is using pip: 4 | 5 | * ``pip install --pre odoo12-addon-mis_builder`` 6 | * then restart Odoo, update the addons list in your database, and install 7 | the MIS Builder application. 8 | -------------------------------------------------------------------------------- /mis_builder/readme/DESCRIPTION.rst: -------------------------------------------------------------------------------- 1 | This module allows you to build Management Information Systems dashboards. 2 | Such style of reports presents KPI in rows and time periods in columns. 3 | Reports mainly fetch data from account moves, but can also combine data coming 4 | from arbitrary Odoo models. Reports can be exported to PDF, Excel and they 5 | can be added to Odoo dashboards. 6 | -------------------------------------------------------------------------------- /mis_builder/models/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 ACSONE SA/NV () 2 | # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). 3 | 4 | from . import mis_report 5 | from . import mis_report_subreport 6 | from . import mis_report_instance 7 | from . import mis_report_style 8 | from . import aep 9 | from . import mis_kpi_data 10 | from . import prorata_read_group_mixin 11 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 88 3 | max-complexity = 16 4 | # B = bugbear 5 | # B9 = bugbear opinionated (incl line length) 6 | select = C,E,F,W,B,B9 7 | # E203: whitespace before ':' (black behaviour) 8 | # E501: flake8 line length (covered by bugbear B950) 9 | # W503: line break before binary operator (black behaviour) 10 | ignore = E203,E501,W503 11 | per-file-ignores= 12 | __init__.py:F401 13 | -------------------------------------------------------------------------------- /.isort.cfg: -------------------------------------------------------------------------------- 1 | [settings] 2 | ; see https://github.com/psf/black 3 | multi_line_output=3 4 | include_trailing_comma=True 5 | force_grid_wrap=0 6 | combine_as_imports=True 7 | use_parentheses=True 8 | line_length=88 9 | known_odoo=odoo 10 | known_odoo_addons=odoo.addons 11 | sections=FUTURE,STDLIB,THIRDPARTY,ODOO,ODOO_ADDONS,FIRSTPARTY,LOCALFOLDER 12 | default_section=THIRDPARTY 13 | ensure_newline_before_comments = True 14 | -------------------------------------------------------------------------------- /mis_builder_budget/models/__init__.py: -------------------------------------------------------------------------------- 1 | from . import mis_budget_abstract 2 | from . import mis_budget_item_abstract 3 | from . import mis_budget 4 | from . import mis_budget_item 5 | from . import mis_budget_by_account 6 | from . import mis_budget_by_account_item 7 | from . import mis_report_kpi 8 | from . import mis_report_instance 9 | from . import mis_report_instance_period 10 | from . import mis_report_kpi_expression 11 | -------------------------------------------------------------------------------- /mis_builder_demo/readme/USAGE.rst: -------------------------------------------------------------------------------- 1 | This module provide the following demo data based on the Odoo generic 2 | chart of accounts: 3 | 4 | * A few styles. 5 | * A budget. 6 | * A report template showing expenses by category 7 | * A sample committed purchase view model, showing uninvoiced purchase 8 | order lines and draft invoice lines. 9 | * A report instance showing budget, committed purchases, actuals 10 | and available. 11 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | # Config file .coveragerc 2 | # adapt the include for your project 3 | 4 | [report] 5 | include = 6 | */OCA/mis-builder/* 7 | 8 | omit = 9 | */tests/* 10 | *__init__.py 11 | 12 | # Regexes for lines to exclude from consideration 13 | exclude_lines = 14 | # Have to re-enable the standard pragma 15 | pragma: no cover 16 | 17 | # Don't complain about null context checking 18 | if context is None: 19 | -------------------------------------------------------------------------------- /mis_builder/security/mis_builder_security.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Mis Report Instance multi company 5 | 6 | 7 | ['|',('company_id','=',False),('company_id','in',company_ids)] 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /mis_builder_budget/readme/DESCRIPTION.rst: -------------------------------------------------------------------------------- 1 | Create budgets for MIS reports. 2 | 3 | This module lets you create budgets for any MIS report. Several budgets can be 4 | created for a given report template (ie one budget per year). Budget figures 5 | are provided by KPI or by GL account, with different time periods. A budget can 6 | then be selected as a data source for a MIS report column, and the report will 7 | show the budgeted values for each KPI, adjusted for the period of the column. 8 | -------------------------------------------------------------------------------- /mis_builder/readme/DEVELOP.rst: -------------------------------------------------------------------------------- 1 | A typical extension is to provide a mechanism to filter reports on analytic dimensions 2 | or operational units. To implement this, you can override _get_additional_move_line_filter 3 | and _get_additional_filter to further filter move lines or queries based on a user 4 | selection. A typical use case could be to add an analytic account field on mis.report.instance, 5 | or even on mis.report.instance.period if you want different columns to show different 6 | analytic accounts. 7 | -------------------------------------------------------------------------------- /docs/_templates/layout.html: -------------------------------------------------------------------------------- 1 | {%- extends '!layout.html' %} 2 | 3 | {%- block footer %} 4 | 5 | 8 | {{ super() }} 9 | {%- endblock %} 10 | -------------------------------------------------------------------------------- /mis_builder/models/data_error.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 ACSONE SA/NV () 2 | # Copyright 2016 Akretion () 3 | # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). 4 | 5 | 6 | class DataError(Exception): 7 | def __init__(self, name, msg): 8 | super(DataError, self).__init__() 9 | self.name = name 10 | self.msg = msg 11 | 12 | def __repr__(self): 13 | return "{}({})".format(self.__class__.__name__, repr(self.name)) 14 | 15 | 16 | class NameDataError(DataError): 17 | pass 18 | -------------------------------------------------------------------------------- /mis_builder/report/mis_report_instance_xlsx.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | MIS report instance XLS report 5 | mis.report.instance 6 | ir.actions.report 7 | mis_builder.mis_report_instance_xlsx 8 | xlsx 9 | mis_report_instance 10 | 11 | 12 | -------------------------------------------------------------------------------- /setup/_metapackage/setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | with open('VERSION.txt', 'r') as f: 4 | version = f.read().strip() 5 | 6 | setuptools.setup( 7 | name="odoo14-addons-oca-mis-builder", 8 | description="Meta package for oca-mis-builder Odoo addons", 9 | version=version, 10 | install_requires=[ 11 | 'odoo14-addon-mis_builder', 12 | 'odoo14-addon-mis_builder_budget', 13 | 'odoo14-addon-mis_builder_demo', 14 | ], 15 | classifiers=[ 16 | 'Programming Language :: Python', 17 | 'Framework :: Odoo', 18 | ] 19 | ) 20 | -------------------------------------------------------------------------------- /mis_builder_budget/models/mis_budget_by_account.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017-2020 ACSONE SA/NV 2 | # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). 3 | 4 | from odoo import fields, models 5 | 6 | 7 | class MisBudgetByAccount(models.Model): 8 | 9 | _name = "mis.budget.by.account" 10 | _description = "MIS Budget by Account" 11 | _inherit = ["mis.budget.abstract", "mail.thread"] 12 | 13 | item_ids = fields.One2many( 14 | comodel_name="mis.budget.by.account.item", inverse_name="budget_id", copy=True 15 | ) 16 | company_id = fields.Many2one(required=True) 17 | -------------------------------------------------------------------------------- /mis_builder_budget/models/mis_budget.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 ACSONE SA/NV 2 | # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). 3 | 4 | from odoo import fields, models 5 | 6 | 7 | class MisBudget(models.Model): 8 | 9 | _name = "mis.budget" 10 | _description = "MIS Budget by KPI" 11 | _inherit = ["mis.budget.abstract", "mail.thread"] 12 | 13 | report_id = fields.Many2one( 14 | comodel_name="mis.report", string="MIS Report Template", required=True 15 | ) 16 | item_ids = fields.One2many( 17 | comodel_name="mis.budget.item", inverse_name="budget_id", copy=True 18 | ) 19 | -------------------------------------------------------------------------------- /mis_builder/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 ACSONE SA/NV () 2 | # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). 3 | 4 | from . import test_accounting_none 5 | from . import test_analytic_filters 6 | from . import test_aep 7 | from . import test_multi_company_aep 8 | from . import test_aggregate 9 | from . import test_data_sources 10 | from . import test_kpi_data 11 | from . import test_mis_report_instance 12 | from . import test_mis_safe_eval 13 | from . import test_period_dates 14 | from . import test_render 15 | from . import test_simple_array 16 | from . import test_utc_midnight 17 | -------------------------------------------------------------------------------- /mis_builder/datas/ir_cron.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Vacuum temporary reports 5 | 4 6 | hours 7 | -1 8 | 9 | 10 | model._vacuum_report() 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Configuration for known file extensions 2 | [*.{css,js,json,less,md,py,rst,sass,scss,xml,yaml,yml}] 3 | charset = utf-8 4 | end_of_line = lf 5 | indent_size = 4 6 | indent_style = space 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [*.{json,yml,yaml,rst,md}] 11 | indent_size = 2 12 | 13 | # Do not configure editor for libs and autogenerated content 14 | [{*/static/{lib,src/lib}/**,*/static/description/index.html,*/readme/../README.rst}] 15 | charset = unset 16 | end_of_line = unset 17 | indent_size = unset 18 | indent_style = unset 19 | insert_final_newline = false 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /mis_builder/migrations/8.0.2.0.0/pre-migration.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 ACSONE SA/NV () 2 | # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). 3 | 4 | 5 | def migrate(cr, version): 6 | cr.execute( 7 | """ 8 | ALTER TABLE mis_report_kpi 9 | RENAME COLUMN expression TO old_expression 10 | """ 11 | ) 12 | # this migration to date_range type is partial, 13 | # actual date ranges needs to be created manually 14 | cr.execute( 15 | """ 16 | UPDATE mis_report_instance_period 17 | SET type='date_range' 18 | WHERE type='fp' 19 | """ 20 | ) 21 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = MISBuilder 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /mis_builder/static/src/css/custom.css: -------------------------------------------------------------------------------- 1 | .o_web_client .mis_builder_amount { 2 | text-align: right; 3 | } 4 | 5 | .o_web_client .mis_builder_collabel { 6 | text-align: center; 7 | } 8 | 9 | .o_web_client .mis_builder_rowlabel { 10 | text-align: left; 11 | } 12 | 13 | .o_web_client .mis_builder a { 14 | /* we don't want the link color, to respect user styles */ 15 | color: inherit; 16 | } 17 | 18 | .o_web_client .mis_builder a:hover { 19 | /* underline links on hover to give a visual cue */ 20 | text-decoration: underline; 21 | } 22 | 23 | .oe_mis_builder_content { 24 | padding: 10px; 25 | } 26 | 27 | .oe_mis_builder_analytic_filter_box { 28 | padding-bottom: 10px; 29 | } 30 | -------------------------------------------------------------------------------- /mis_builder_demo/security/mis_committed_purchase.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | mis.committed.purchase access name 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. MIS Builder documentation master file, created by 2 | sphinx-quickstart on Sat Apr 14 14:08:55 2018. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to MIS Builder's documentation! 7 | ======================================= 8 | 9 | Management Information System reports for Odoo: easily build super fast, 10 | beautiful, custom reports such as P&L, Balance Sheets and more. 11 | 12 | .. toctree:: 13 | :maxdepth: 3 14 | :caption: Contents: 15 | 16 | description 17 | quickstart 18 | install 19 | usage 20 | develop 21 | roadmap 22 | contributors 23 | history 24 | 25 | Indices and tables 26 | ================== 27 | 28 | * :ref:`genindex` 29 | * :ref:`modindex` 30 | * :ref:`search` 31 | -------------------------------------------------------------------------------- /mis_builder_demo/__manifest__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017-2018 ACSONE SA/NV 2 | # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). 3 | 4 | { 5 | "name": "MIS Builder Demo", 6 | "summary": """ 7 | Demo addon for MIS Builder""", 8 | "version": "14.0.3.1.3", 9 | "license": "AGPL-3", 10 | "author": "ACSONE SA/NV, " "Odoo Community Association (OCA)", 11 | "website": "https://github.com/OCA/mis-builder", 12 | "depends": ["mis_builder_budget", "purchase"], 13 | "data": [ 14 | "security/mis_committed_purchase.xml", 15 | "views/mis_committed_purchase.xml", 16 | "data/mis_report_style.xml", 17 | "data/mis_report.xml", 18 | "data/mis_budget.xml", 19 | "data/mis_report_instance.xml", 20 | ], 21 | "installable": True, 22 | "maintainers": ["sbidoul"], 23 | "development_status": "Alpha", 24 | } 25 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=MISBuilder 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 20 | echo.installed, then set the SPHINXBUILD environment variable to point 21 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 22 | echo.may add the Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | cache: 3 | directories: 4 | - $HOME/.cache/pip 5 | - $HOME/.cache/pre-commit 6 | 7 | python: 8 | - "3.6" 9 | 10 | addons: 11 | postgresql: "9.6" 12 | apt: 13 | packages: 14 | - expect-dev # provides unbuffer utility 15 | 16 | stages: 17 | - test 18 | 19 | jobs: 20 | include: 21 | - stage: test 22 | env: 23 | - TESTS=1 ODOO_REPO="odoo/odoo" MAKEPOT="1" 24 | - stage: test 25 | env: 26 | - TESTS=1 ODOO_REPO="OCA/OCB" 27 | env: 28 | global: 29 | - VERSION="14.0" TESTS="0" LINT_CHECK="0" MAKEPOT="0" 30 | - MQT_DEP=PIP 31 | 32 | install: 33 | - git clone --depth=1 https://github.com/OCA/maintainer-quality-tools.git 34 | ${HOME}/maintainer-quality-tools 35 | - export PATH=${HOME}/maintainer-quality-tools/travis:${PATH} 36 | - travis_install_nightly 37 | 38 | script: 39 | - travis_run_tests 40 | 41 | after_success: 42 | - travis_after_tests_success 43 | -------------------------------------------------------------------------------- /mis_builder/migrations/8.0.2.0.0/post-migration.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 ACSONE SA/NV () 2 | # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). 3 | 4 | 5 | def migrate(cr, version): 6 | cr.execute( 7 | """ 8 | INSERT INTO mis_report_kpi_expression 9 | (create_uid, create_date, write_uid, write_date, 10 | kpi_id, name, sequence) 11 | SELECT create_uid, create_date, write_uid, write_date, 12 | id, old_expression, sequence 13 | FROM mis_report_kpi 14 | """ 15 | ) 16 | cr.execute( 17 | """ 18 | ALTER TABLE mis_report_kpi 19 | DROP COLUMN old_expression 20 | """ 21 | ) 22 | # set default mode to relative for existing periods 23 | # as it was the only mode in previous versions 24 | cr.execute( 25 | """ 26 | UPDATE mis_report_instance_period 27 | SET mode='relative' 28 | """ 29 | ) 30 | -------------------------------------------------------------------------------- /mis_builder_budget/models/mis_report_kpi_expression.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 ACSONE SA/NV 2 | # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). 3 | 4 | from odoo import api, models 5 | 6 | 7 | class MisReportKpiExpression(models.Model): 8 | 9 | _inherit = "mis.report.kpi.expression" 10 | 11 | @api.model 12 | def name_search(self, name="", args=None, operator="ilike", limit=100): 13 | args = args or [] 14 | if "default_budget_id" in self.env.context: 15 | report_id = ( 16 | self.env["mis.budget"] 17 | .browse(self.env.context["default_budget_id"]) 18 | .report_id.id 19 | ) 20 | if report_id: 21 | args += [("kpi_id.report_id", "=", report_id)] 22 | if "." in name: 23 | args += [("subkpi_id.report_id", "=", report_id)] 24 | return super(MisReportKpiExpression, self).name_search( 25 | name, args, operator, limit 26 | ) 27 | -------------------------------------------------------------------------------- /mis_builder/tests/test_utc_midnight.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 ACSONE SA/NV () 2 | # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). 3 | 4 | import odoo.tests.common as common 5 | 6 | from ..models.mis_report import _utc_midnight 7 | 8 | 9 | class TestUtcMidnight(common.TransactionCase): 10 | def test_utc_midnight(self): 11 | date_to_convert = "2014-07-05" 12 | date_time_convert = _utc_midnight(date_to_convert, "Europe/Brussels") 13 | self.assertEqual(date_time_convert, "2014-07-04 22:00:00") 14 | date_time_convert = _utc_midnight(date_to_convert, "Europe/Brussels", add_day=1) 15 | self.assertEqual(date_time_convert, "2014-07-05 22:00:00") 16 | date_time_convert = _utc_midnight(date_to_convert, "US/Pacific") 17 | self.assertEqual(date_time_convert, "2014-07-05 07:00:00") 18 | date_time_convert = _utc_midnight(date_to_convert, "US/Pacific", add_day=1) 19 | self.assertEqual(date_time_convert, "2014-07-06 07:00:00") 20 | -------------------------------------------------------------------------------- /mis_builder/tests/test_mis_safe_eval.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 ACSONE SA/NV () 2 | # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). 3 | 4 | import odoo.tests.common as common 5 | 6 | from ..models.mis_safe_eval import DataError, NameDataError, mis_safe_eval 7 | 8 | 9 | class TestMisSafeEval(common.TransactionCase): 10 | def test_nominal(self): 11 | val = mis_safe_eval("a + 1", {"a": 1}) 12 | self.assertEqual(val, 2) 13 | 14 | def test_exceptions(self): 15 | val = mis_safe_eval("1/0", {}) # division by zero 16 | self.assertTrue(isinstance(val, DataError)) 17 | self.assertEqual(val.name, "#DIV/0") 18 | val = mis_safe_eval("1a", {}) # syntax error 19 | self.assertTrue(isinstance(val, DataError)) 20 | self.assertEqual(val.name, "#ERR") 21 | 22 | def test_name_error(self): 23 | val = mis_safe_eval("a + 1", {}) 24 | self.assertTrue(isinstance(val, NameDataError)) 25 | self.assertEqual(val.name, "#NAME") 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | /.venv 5 | /.pytest_cache 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | env/ 13 | bin/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | *.eggs 27 | 28 | # Installer logs 29 | pip-log.txt 30 | pip-delete-this-directory.txt 31 | 32 | # Unit test / coverage reports 33 | htmlcov/ 34 | .tox/ 35 | .coverage 36 | .cache 37 | nosetests.xml 38 | coverage.xml 39 | 40 | # Translations 41 | *.mo 42 | 43 | # Pycharm 44 | .idea 45 | 46 | # Eclipse 47 | .settings 48 | 49 | # Visual Studio cache/options directory 50 | .vs/ 51 | .vscode 52 | 53 | # OSX Files 54 | .DS_Store 55 | 56 | # Django stuff: 57 | *.log 58 | 59 | # Mr Developer 60 | .mr.developer.cfg 61 | .project 62 | .pydevproject 63 | 64 | # Rope 65 | .ropeproject 66 | 67 | # Sphinx documentation 68 | docs/_build/ 69 | 70 | # Backup files 71 | *~ 72 | *.swp 73 | 74 | # OCA rules 75 | !static/lib/ 76 | -------------------------------------------------------------------------------- /mis_builder_budget/__manifest__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017-2018 ACSONE SA/NV 2 | # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). 3 | 4 | { 5 | "name": "MIS Builder Budget", 6 | "summary": """ 7 | Create budgets for MIS reports""", 8 | "version": "14.0.3.5.1", 9 | "license": "AGPL-3", 10 | "author": "ACSONE SA/NV, " "Odoo Community Association (OCA)", 11 | "website": "https://github.com/OCA/mis-builder", 12 | "depends": ["mis_builder", "account"], 13 | "data": [ 14 | "views/mis_report_instance_period.xml", 15 | "views/mis_report.xml", 16 | "security/mis_budget_item.xml", 17 | "views/mis_budget_item.xml", 18 | "security/mis_budget.xml", 19 | "views/mis_budget.xml", 20 | "security/mis_budget_by_account_item.xml", 21 | "views/mis_budget_by_account_item.xml", 22 | "security/mis_budget_by_account.xml", 23 | "views/mis_budget_by_account.xml", 24 | ], 25 | "installable": True, 26 | "development_status": "Production/Stable", 27 | "maintainers": ["sbidoul"], 28 | } 29 | -------------------------------------------------------------------------------- /mis_builder/static/src/css/report.css: -------------------------------------------------------------------------------- 1 | .mis_table { 2 | display: table; 3 | width: 100%; 4 | table-layout: fixed; 5 | } 6 | .mis_row { 7 | display: table-row; 8 | page-break-inside: avoid; 9 | } 10 | .mis_cell { 11 | display: table-cell; 12 | page-break-inside: avoid; 13 | } 14 | .mis_thead { 15 | display: table-header-group; 16 | } 17 | .mis_tbody { 18 | display: table-row-group; 19 | } 20 | .mis_table, 21 | .mis_table .mis_row { 22 | border-left: 0px; 23 | border-right: 0px; 24 | text-align: left; 25 | padding-right: 3px; 26 | padding-left: 3px; 27 | padding-top: 2px; 28 | padding-bottom: 2px; 29 | border-collapse: collapse; 30 | } 31 | .mis_table .mis_row { 32 | border-color: grey; 33 | border-bottom: 1px solid lightGrey; 34 | } 35 | .mis_table .mis_cell.mis_collabel { 36 | font-weight: bold; 37 | background-color: #f0f0f0; 38 | text-align: center; 39 | } 40 | .mis_table .mis_cell.mis_rowlabel { 41 | text-align: left; 42 | /*white-space: nowrap;*/ 43 | } 44 | .mis_table .mis_cell.mis_amount { 45 | text-align: right; 46 | } 47 | -------------------------------------------------------------------------------- /mis_builder/readme/CONTRIBUTORS.rst: -------------------------------------------------------------------------------- 1 | * Stéphane Bidoul 2 | * Laetitia Gangloff 3 | * Adrien Peiffer 4 | * Alexis de Lattre 5 | * Alexandre Fayolle 6 | * Jordi Ballester 7 | * Thomas Binsfeld 8 | * Giovanni Capalbo 9 | * Marco Calcagni 10 | * Sébastien Beau 11 | * Laurent Mignon 12 | * Luc De Meyer 13 | * Benjamin Willig 14 | * Martronic SA 15 | * nicomacr 16 | * Juan Jose Scarafia 17 | * Richard deMeester 18 | * Eric Caudal 19 | * Andrea Stirpe 20 | * Maxence Groine 21 | * Arnaud Pineux 22 | * Ernesto Tejeda 23 | * Pedro M. Baeza 24 | -------------------------------------------------------------------------------- /mis_builder/report/mis_report_instance_qweb.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 ACSONE SA/NV () 2 | # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). 3 | 4 | import logging 5 | 6 | from odoo import models 7 | 8 | _logger = logging.getLogger(__name__) 9 | 10 | 11 | class Report(models.Model): 12 | _inherit = "ir.actions.report" 13 | 14 | def _render_qweb_pdf(self, res_ids=None, data=None): 15 | if self.report_name == "mis_builder.report_mis_report_instance": 16 | if not res_ids: 17 | res_ids = self.env.context.get("active_ids") 18 | mis_report_instance = self.env["mis.report.instance"].browse(res_ids)[0] 19 | context = dict( 20 | mis_report_instance._context_with_filters(), 21 | landscape=mis_report_instance.landscape_pdf, 22 | ) 23 | # data=None, because it was there only to force Odoo 24 | # to propagate context 25 | return super(Report, self.with_context(context))._render_qweb_pdf( 26 | res_ids, data=None 27 | ) 28 | return super(Report, self)._render_qweb_pdf(res_ids, data) 29 | -------------------------------------------------------------------------------- /mis_builder_demo/views/mis_committed_purchase.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | mis.committed.purchase.tree (in mis_builder_demo) 7 | mis.committed.purchase 8 | 9 | 10 | 11 | 12 | 13 | 14 | 18 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /mis_builder/models/mis_safe_eval.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 ACSONE SA/NV () 2 | # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). 3 | 4 | import traceback 5 | 6 | from odoo.tools.safe_eval import _BUILTINS, _SAFE_OPCODES, test_expr 7 | 8 | from .data_error import DataError, NameDataError 9 | 10 | __all__ = ["mis_safe_eval"] 11 | 12 | 13 | def mis_safe_eval(expr, locals_dict): 14 | """Evaluate an expression using safe_eval 15 | 16 | Returns the evaluated value or DataError. 17 | 18 | Raises NameError if the evaluation depends on a variable that is not 19 | present in local_dict. 20 | """ 21 | try: 22 | c = test_expr(expr, _SAFE_OPCODES, mode="eval") 23 | globals_dict = {"__builtins__": _BUILTINS} 24 | # pylint: disable=eval-used,eval-referenced 25 | val = eval(c, globals_dict, locals_dict) 26 | except NameError: 27 | val = NameDataError("#NAME", traceback.format_exc()) 28 | except ZeroDivisionError: 29 | # pylint: disable=redefined-variable-type 30 | val = DataError("#DIV/0", traceback.format_exc()) 31 | except Exception: 32 | val = DataError("#ERR", traceback.format_exc()) 33 | return val 34 | -------------------------------------------------------------------------------- /mis_builder/readme/USAGE.rst: -------------------------------------------------------------------------------- 1 | To configure this module, you need to: 2 | 3 | * Go to Accounting > Configuration > MIS Reporting > MIS Report Templates where 4 | you can create report templates by defining KPI's. KPI's constitute the rows of your 5 | reports. Such report templates are time independent. 6 | 7 | .. figure:: https://raw.githubusercontent.com/OCA/mis-builder/10.0/mis_builder/static/description/ex_report_template.png 8 | :alt: Sample report template 9 | :width: 80 % 10 | :align: center 11 | 12 | * Then in Accounting > Reports > MIS Reporting > MIS Reports you can create report instance by 13 | binding the templates to time periods, hence defining the columns of your reports. 14 | 15 | .. figure:: https://raw.githubusercontent.com/OCA/mis-builder/10.0/mis_builder/static/description/ex_report_settings.png 16 | :alt: Sample report configuration 17 | :width: 80 % 18 | :align: center 19 | 20 | * From the MIS Reports view, you can preview the report, add it to and Odoo dashboard, 21 | and export it to PDF or Excel. 22 | 23 | .. figure:: https://raw.githubusercontent.com/OCA/mis-builder/10.0/mis_builder/static/description/ex_report_preview.png 24 | :alt: Sample preview 25 | :width: 80 % 26 | :align: center 27 | -------------------------------------------------------------------------------- /mis_builder_budget/views/mis_report.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | mis.report.view.form (in mis_builder_budget) 7 | mis.report 8 | 9 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | mis.report.view.kpi.form (in mis_builder_budget) 20 | mis.report.kpi 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /mis_builder/__manifest__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2018 ACSONE SA/NV () 2 | # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). 3 | 4 | { 5 | "name": "MIS Builder", 6 | "version": "14.0.3.6.7", 7 | "category": "Reporting", 8 | "summary": """ 9 | Build 'Management Information System' Reports and Dashboards 10 | """, 11 | "author": "ACSONE SA/NV, " "Odoo Community Association (OCA)", 12 | "website": "https://github.com/OCA/mis-builder", 13 | "depends": [ 14 | "account", 15 | "board", 16 | "report_xlsx", # OCA/reporting-engine 17 | "date_range", # OCA/server-ux 18 | ], 19 | "data": [ 20 | "wizard/mis_builder_dashboard.xml", 21 | "views/mis_report.xml", 22 | "views/mis_report_instance.xml", 23 | "views/mis_report_style.xml", 24 | "datas/ir_cron.xml", 25 | "security/ir.model.access.csv", 26 | "security/mis_builder_security.xml", 27 | "report/mis_report_instance_qweb.xml", 28 | "report/mis_report_instance_xlsx.xml", 29 | ], 30 | "qweb": ["static/src/xml/mis_report_widget.xml"], 31 | "installable": True, 32 | "application": True, 33 | "license": "AGPL-3", 34 | "development_status": "Production/Stable", 35 | "maintainers": ["sbidoul"], 36 | } 37 | -------------------------------------------------------------------------------- /mis_builder_budget/views/mis_report_instance_period.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | mis.report.instance.period.form (in mis_builder_budget) 8 | 9 | mis.report.instance.period 10 | 14 | 15 | 16 | 21 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/contributors.rst: -------------------------------------------------------------------------------- 1 | Authors and Contributors 2 | ======================== 3 | Authors 4 | ------- 5 | * ACSONE SA/NV 6 | 7 | mis_builder 8 | ----------- 9 | 10 | .. include:: ../mis_builder/readme/CONTRIBUTORS.rst 11 | 12 | mis_builder_budget 13 | ------------------ 14 | 15 | .. include:: ../mis_builder_budget/readme/CONTRIBUTORS.rst 16 | 17 | mis_builder_demo 18 | ------------------ 19 | 20 | .. include:: ../mis_builder_demo/readme/CONTRIBUTORS.rst 21 | 22 | Maintainers 23 | ----------- 24 | 25 | This module is maintained by the OCA. 26 | 27 | .. image:: https://odoo-community.org/logo.png 28 | :alt: Odoo Community Association 29 | :target: https://odoo-community.org 30 | 31 | OCA, or the Odoo Community Association, is a nonprofit organization whose 32 | mission is to support the collaborative development of Odoo features and 33 | promote its widespread use. 34 | 35 | .. |maintainer-sbidoul| image:: https://github.com/sbidoul.png?size=40px 36 | :target: https://github.com/sbidoul 37 | :alt: sbidoul 38 | 39 | Current `maintainer `_: 40 | 41 | |maintainer-sbidoul| 42 | 43 | This module is part of the `OCA/mis-builder `_ project on GitHub. 44 | 45 | You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. 46 | -------------------------------------------------------------------------------- /mis_builder_demo/data/mis_report_style.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | Demo style $ 7 | 8 | $ 9 | 10 | 2 11 | 12 | 13 | Demo style account detail 14 | 15 | 1 16 | 17 | italic 18 | 19 | 20 | Demo style total 21 | 22 | #967C8B 23 | 24 | #FFFFFF 25 | 26 | bold 27 | 28 | 29 | -------------------------------------------------------------------------------- /mis_builder_budget/models/mis_budget_item.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 ACSONE SA/NV 2 | # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). 3 | 4 | from odoo import api, fields, models 5 | 6 | 7 | class MisBudgetItem(models.Model): 8 | 9 | _inherit = ["mis.budget.item.abstract", "mis.kpi.data"] 10 | _name = "mis.budget.item" 11 | _description = "MIS Budget Item (by KPI)" 12 | _order = "budget_id, date_from, seq1, seq2" 13 | 14 | budget_id = fields.Many2one(comodel_name="mis.budget") 15 | report_id = fields.Many2one(related="budget_id.report_id", readonly=True) 16 | kpi_expression_id = fields.Many2one( 17 | domain=( 18 | "[('kpi_id.report_id', '=', report_id)," 19 | " ('kpi_id.budgetable', '=', True)]" 20 | ) 21 | ) 22 | 23 | def _prepare_overlap_domain(self): 24 | """Prepare a domain to check for overlapping budget items.""" 25 | domain = super(MisBudgetItem, self)._prepare_overlap_domain() 26 | domain.extend([("kpi_expression_id", "=", self.kpi_expression_id.id)]) 27 | return domain 28 | 29 | @api.constrains( 30 | "date_range_id", 31 | "date_from", 32 | "date_to", 33 | "budget_id", 34 | "analytic_account_id", 35 | "analytic_tag_ids", 36 | "kpi_expression_id", 37 | ) 38 | def _check_dates(self): 39 | super(MisBudgetItem, self)._check_dates() 40 | -------------------------------------------------------------------------------- /mis_builder_budget/security/mis_budget.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | mis.budget access base user 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | mis.budget access adviser 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | mis.budget multi company 25 | 26 | 27 | ['|',('company_id','=',False),('company_id','in',company_ids)] 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /mis_builder_demo/readme/HISTORY.rst: -------------------------------------------------------------------------------- 1 | 13.0.3.1.2 (2020-04-22) 2 | ~~~~~~~~~~~~~~~~~~~~~~~ 3 | 4 | **Bugfixes** 5 | 6 | - Fix currency rate in uninvoiced purchases. (`#274 `_) 7 | 8 | 9 | 13.0.3.1.0 (2020-01-??) 10 | ~~~~~~~~~~~~~~~~~~~~~~~ 11 | 12 | Migration to odoo 13.0. 13 | 14 | 12.0.3.1.0 (2019-10-26) 15 | ~~~~~~~~~~~~~~~~~~~~~~~ 16 | 17 | **Features** 18 | 19 | - Handle multi currency for commited purchase view. The amount in this 20 | view are now converted to the base currency (the one with rate 1), 21 | so summing them has some meaning. As a consequence, this view has 22 | less usefulness if the company currency is not the one with rate 1, 23 | Debit and credit being assumed to be in company currency. 24 | 25 | Add the M2M to account.analytic.tag in the commited purchase view. 26 | 27 | Fix sign issue in commited purchase view. 28 | 29 | Include customer invoice in commited purchase view. The view is therefore 30 | not only about purchases anymore. This should not be an issue because 31 | GL accounts are differents for purchases and income anyway and generally 32 | used in different KPI. 33 | 34 | These are breaking changes. Change the status of ``mis_builder_demo`` to alpha, 35 | since it is a demo module and it's content can change at any time without 36 | any compatibility guarantees. (`#222 `_) 37 | 38 | 39 | **Bugfixes** 40 | 41 | - Fix date casting error on committed expenses drilldown. (`#185 `_) 42 | -------------------------------------------------------------------------------- /mis_builder_budget/security/mis_budget_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | mis.budget.item access base user 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | mis.budget.item access adviser 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | mis.budget.item multi company 25 | 26 | 27 | ['|',('budget_id.company_id','=',False),('budget_id.company_id','in',company_ids)] 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /mis_builder/wizard/mis_builder_dashboard.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | add.mis.report.instance.dashboard.wizard.view 5 | add.mis.report.instance.dashboard.wizard 6 | 7 |
8 | 9 | 10 | 11 | 12 |
13 |
23 |
24 |
25 |
26 | 30 | Add to dashboard 31 | add.mis.report.instance.dashboard.wizard 32 | form 33 | 34 | new 35 | 36 |
37 | -------------------------------------------------------------------------------- /mis_builder_budget/security/mis_budget_by_account.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | mis.budget.by.account access base user 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | mis.budget.by.account access adviser 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | mis.budget.by.account multi company 25 | 26 | 27 | ['|',('company_id','=',False),('company_id', 'in', company_ids)] 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /mis_builder_budget/readme/USAGE.rst: -------------------------------------------------------------------------------- 1 | There are two ways to use this module: create budgets by KPI or budgets by 2 | GL accounts. Currently, the two methods cannot be combined in the same budget. 3 | 4 | **Budget by KPIs** 5 | 6 | To use this this mode, you first need to flag at least some KPI in a MIS report 7 | to be budgetable. You also need to configure the accumulation method on the KPI 8 | according to their type. 9 | 10 | The accumulation method determines how budgeted values spanning over a 11 | time period are transformed to match the reporting period. 12 | 13 | * Sum: values of shorter period are added, values of longest or partially overlapping 14 | periods are adjusted pro-rata temporis (eg monetary amount such as revenue). 15 | * Average: values of included period are averaged with a pro-rata temporis weight. 16 | Typically used for values that do not accumulate over time (eg a number of employees). 17 | 18 | When KPI are configured, you need to create a budget, using the MIS Budget (by 19 | KPIs) menu, then click on the budget items button to create or import the 20 | budgeted amounts for all your KPI and time periods. 21 | 22 | **Budget by GL accounts** 23 | 24 | You can also create budgets by GL accounts. In this case, the budget is 25 | populated with one line per GL account (and optionally analytic account and/or 26 | tags) and time period. 27 | 28 | **Add budget columns to report instances** 29 | 30 | Finally, a column (aka period) must be added to a MIS report instance, 31 | selecting your newly created budget as a data source. The data will be adjusted 32 | to the reporting period when displayed. Columns can be compared by adding a 33 | column of type "comparison" or "sum". 34 | -------------------------------------------------------------------------------- /mis_builder_budget/security/mis_budget_by_account_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | mis.budget.by.account.item access base user 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | mis.budget.by.account.item access adviser 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | mis.budget.by.account.item multi company 25 | 26 | 27 | ['|',('budget_id.company_id','=',False),('budget_id.company_id','in',company_ids)] 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: test Odoo addons 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - "14.0*" 7 | push: 8 | branches: 9 | - "14.0*" 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | container: ${{ matrix.container }} 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | include: 19 | - container: ghcr.io/oca/oca-ci/py3.6-odoo14.0:latest 20 | makepot: "true" 21 | - container: ghcr.io/oca/oca-ci/py3.6-ocb14.0:latest 22 | services: 23 | postgres: 24 | image: postgres:9.6 25 | env: 26 | POSTGRES_USER: odoo 27 | POSTGRES_PASSWORD: odoo 28 | POSTGRES_DB: odoo 29 | ports: 30 | - 5432:5432 31 | steps: 32 | - uses: actions/checkout@v2 33 | with: 34 | persist-credentials: false 35 | - name: Install addons and dependencies 36 | run: oca_install_addons 37 | - name: Initialize test db 38 | run: oca_init_test_database 39 | - name: Check licenses 40 | run: manifestoo -d . check-licenses 41 | - name: Check development status 42 | run: manifestoo -d . check-dev-status --default-dev-status=Beta 43 | - name: Run tests 44 | run: oca_run_tests 45 | - uses: codecov/codecov-action@v1 46 | - name: Update .pot files 47 | run: | 48 | oca_export_and_commit_pot 49 | git fetch --unshallow --all 50 | git push https://x-access-token:${{ secrets.GIT_PUSH_TOKEN }}@github.com/${{ github.repository }} 51 | if: ${{ matrix.makepot == 'true' && github.event_name == 'push' && github.repository_owner == 'OCA' }} 52 | -------------------------------------------------------------------------------- /.pylintrc-mandatory: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | load-plugins=pylint_odoo 3 | score=n 4 | 5 | [ODOOLINT] 6 | readme_template_url="https://github.com/OCA/maintainer-tools/blob/master/template/module/README.rst" 7 | manifest_required_authors=Odoo Community Association (OCA) 8 | manifest_required_keys=license 9 | manifest_deprecated_keys=description,active 10 | license_allowed=AGPL-3,GPL-2,GPL-2 or any later version,GPL-3,GPL-3 or any later version,LGPL-3 11 | valid_odoo_versions=14.0 12 | 13 | [MESSAGES CONTROL] 14 | disable=all 15 | 16 | enable=anomalous-backslash-in-string, 17 | api-one-deprecated, 18 | api-one-multi-together, 19 | assignment-from-none, 20 | attribute-deprecated, 21 | class-camelcase, 22 | dangerous-default-value, 23 | dangerous-view-replace-wo-priority, 24 | development-status-allowed, 25 | duplicate-id-csv, 26 | duplicate-key, 27 | duplicate-xml-fields, 28 | duplicate-xml-record-id, 29 | eval-referenced, 30 | eval-used, 31 | incoherent-interpreter-exec-perm, 32 | license-allowed, 33 | manifest-author-string, 34 | manifest-deprecated-key, 35 | manifest-required-author, 36 | manifest-required-key, 37 | manifest-version-format, 38 | method-compute, 39 | method-inverse, 40 | method-required-super, 41 | method-search, 42 | openerp-exception-warning, 43 | pointless-statement, 44 | pointless-string-statement, 45 | print-used, 46 | redundant-keyword-arg, 47 | redundant-modulename-xml, 48 | reimported, 49 | relative-import, 50 | return-in-init, 51 | rst-syntax-error, 52 | sql-injection, 53 | too-few-format-args, 54 | translation-field, 55 | translation-required, 56 | unreachable, 57 | use-vim-comment, 58 | wrong-tabs-instead-of-spaces, 59 | xml-syntax-error 60 | 61 | [REPORTS] 62 | msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg} 63 | output-format=colorized 64 | reports=no 65 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Thanks for your pull request. Please take a moment to review if it meets the following 2 | guidelines. 3 | 4 | ## Description 5 | 6 | Before coding, it is recommended to create an issue to discuss the problem or feature 7 | you want to add. If you did not, don't worry, we can discuss on the PR too. 8 | 9 | If there is a pre-existing issue that your PR implements or fixes, add a pointer to it 10 | in the PR description. 11 | 12 | If your PR is simple enough that it does not require a preliminary discussion, then make 13 | sure to explain what it does (i.e. _why_ the change is necessary). 14 | 15 | ## Test 16 | 17 | Don't forget to add unit tests. If your PR fixes a bug, prefer creating a separate 18 | commit for the test so we one see that your test reproduces the bug and is fixed by the 19 | PR. 20 | 21 | ## Target branch 22 | 23 | MIS Builder is actively maintained for Odoo versions 9, 10, 11 and 12. 24 | 25 | If your feature is applicable with the same implementation to all these versions, please 26 | target branch 10.0. Maintainers will port it to 9, 11 and 12 soon after merging. 27 | 28 | In the rare cases your feature or implementation is specific to an Odoo version, then 29 | target the corresponding branch. 30 | 31 | ## CLA 32 | 33 | Have you signed the OCA Contributor License Agreement? If not, please visit 34 | https://odoo-community.org/page/cla to learn how. 35 | 36 | ## Changelog entry 37 | 38 | This projects uses [towncrier](https://pypi.org/project/towncrier/) to generate it's 39 | changelog. Make sure your PR includes a changelog entry in 40 | `/readme/newsfragments/`. It must have the issue or PR number as name, and one of 41 | `.feature`, `.bugfix`, `.doc` (for documentation improvements), `.misc` (if a ticket has 42 | been closed, but it is not of interest to users). The changelog entry must be reasonably 43 | short and phrased in a way that is understandable by end users. 44 | 45 | ## Documentation 46 | 47 | Consider improving the documention in `docs/`. 48 | -------------------------------------------------------------------------------- /mis_builder_budget/readme/HISTORY.rst: -------------------------------------------------------------------------------- 1 | 14.0.3.5.1 (2021-04-06) 2 | ~~~~~~~~~~~~~~~~~~~~~~~ 3 | 4 | **Bugfixes** 5 | 6 | - Fix incorrect budget by account multi company security rules. (`#347 `_) 7 | 8 | 9 | 13.0.3.5.0 (2020-03-28) 10 | ~~~~~~~~~~~~~~~~~~~~~~~ 11 | 12 | **Features** 13 | 14 | - Budget by GL account: allow budgeting by GL account in addition to the 15 | existing mechanism to budget by KPI. Budget items have a begin and end 16 | date, and when reporting a pro-rata temporis adjustment is made to match 17 | the reporting period. (`#259 `_) 18 | 19 | 20 | 13.0.3.4.0 (2020-01-??) 21 | ~~~~~~~~~~~~~~~~~~~~~~~ 22 | 23 | Migration to odoo 13.0. 24 | 25 | 12.0.3.4.0 (2019-10-26) 26 | ~~~~~~~~~~~~~~~~~~~~~~~ 27 | 28 | **Bugfixes** 29 | 30 | - Consider analytic tags too when detecting overlapping budget items. 31 | Previously only analytic account was considered, and this overlap detection 32 | mechanism was overlooked when analytic tags were added to budget items. (`#241 `_) 33 | 34 | 35 | 11.0.3.3.0 (2019-01-13) 36 | ~~~~~~~~~~~~~~~~~~~~~~~ 37 | 38 | **Features** 39 | 40 | - Support analytic filters. (`#15 `_) 41 | 42 | 43 | 11.0.3.2.1 (2018-06-30) 44 | ~~~~~~~~~~~~~~~~~~~~~~~ 45 | 46 | - [IMP] Support analytic tags in budget items 47 | (`#100 `_) 48 | 49 | 11.0.3.2.0 (2018-05-02) 50 | ~~~~~~~~~~~~~~~~~~~~~~~ 51 | 52 | - [FIX] #NAME error in out-of-order computation of non 53 | budgetable items in budget columns 54 | (`#68 `_) 55 | 56 | 11.0.3.1.1 (2018-02-04) 57 | ~~~~~~~~~~~~~~~~~~~~~~~ 58 | 59 | Migration to Odoo 11. No new feature. 60 | (`#67 `_) 61 | 62 | 10.0.3.1.0 (2017-11-14) 63 | ~~~~~~~~~~~~~~~~~~~~~~~ 64 | 65 | New features: 66 | 67 | - [ADD] multi-company record rule for MIS Budgets 68 | (`#27 `_) 69 | 70 | 10.0.1.1.1 (2017-10-01) 71 | ~~~~~~~~~~~~~~~~~~~~~~~ 72 | 73 | First version. 74 | -------------------------------------------------------------------------------- /mis_builder/models/mis_report_subreport.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 ACSONE SA/NV () 2 | # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). 3 | 4 | from odoo import _, api, fields, models 5 | from odoo.exceptions import ValidationError 6 | 7 | from odoo.addons.mis_builder.models.mis_report import _is_valid_python_var 8 | 9 | 10 | class ParentLoopError(ValidationError): 11 | pass 12 | 13 | 14 | class InvalidNameError(ValidationError): 15 | pass 16 | 17 | 18 | class MisReportSubReport(models.Model): 19 | _name = "mis.report.subreport" 20 | _description = "MIS Report - Sub Reports Relation" 21 | 22 | name = fields.Char(required=True) 23 | report_id = fields.Many2one(comodel_name="mis.report", required=True) 24 | subreport_id = fields.Many2one(comodel_name="mis.report", required=True) 25 | 26 | _sql_constraints = [ 27 | ( 28 | "name_unique", 29 | "unique(name, report_id)", 30 | "Subreport name should be unique by report", 31 | ), 32 | ( 33 | "subreport_unique", 34 | "unique(subreport_id, report_id)", 35 | "Should not include the same report more than once as sub report " 36 | "of a given report", 37 | ), 38 | ] 39 | 40 | @api.constrains("name") 41 | def _check_name(self): 42 | for rec in self: 43 | if not _is_valid_python_var(rec.name): 44 | raise InvalidNameError( 45 | _("Subreport name ({}) must be a valid python identifier").format( 46 | rec.name 47 | ) 48 | ) 49 | 50 | @api.constrains("report_id", "subreport_id") 51 | def _check_loop(self): 52 | def _has_subreport(reports, report): 53 | if not reports: 54 | return False 55 | if report in reports: 56 | return True 57 | return any( 58 | _has_subreport(r.subreport_ids.mapped("subreport_id"), report) 59 | for r in reports 60 | ) 61 | 62 | for rec in self: 63 | if _has_subreport(rec.subreport_id, rec.report_id): 64 | raise ParentLoopError(_("Subreport loop detected")) 65 | 66 | # TODO check subkpi compatibility in subreports 67 | -------------------------------------------------------------------------------- /mis_builder_budget/views/mis_budget_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | mis.budget.item.search (in mis_builder_budget) 7 | mis.budget.item 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | mis.budget.item.tree (in mis_builder_budget) 18 | mis.budget.item 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 34 | 39 | 40 | 41 | 42 | 43 | MIS Budget Items (by KPIs) 44 | mis.budget.item 45 | tree,form 46 | [('budget_id', '=', active_id)] 47 | {'default_budget_id': active_id} 48 | 49 | 50 | -------------------------------------------------------------------------------- /mis_builder_budget/models/mis_budget_abstract.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017-2020 ACSONE SA/NV 2 | # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). 3 | 4 | from odoo import _, api, fields, models 5 | 6 | 7 | class MisBudgetAbstract(models.AbstractModel): 8 | 9 | _name = "mis.budget.abstract" 10 | _description = "MIS Budget (Abstract Base Class)" 11 | _inherit = ["mail.thread"] 12 | 13 | name = fields.Char(required=True, tracking=True) 14 | description = fields.Char(tracking=True) 15 | date_range_id = fields.Many2one(comodel_name="date.range", string="Date range") 16 | date_from = fields.Date(required=True, string="From", tracking=True) 17 | date_to = fields.Date(required=True, string="To", tracking=True) 18 | state = fields.Selection( 19 | [("draft", "Draft"), ("confirmed", "Confirmed"), ("cancelled", "Cancelled")], 20 | required=True, 21 | default="draft", 22 | tracking=True, 23 | ) 24 | company_id = fields.Many2one( 25 | comodel_name="res.company", 26 | string="Company", 27 | default=lambda self: self.env.company, 28 | ) 29 | 30 | def copy(self, default=None): 31 | self.ensure_one() 32 | if default is None: 33 | default = {} 34 | if "name" not in default: 35 | default["name"] = _("%s (copy)") % self.name 36 | return super(MisBudgetAbstract, self).copy(default=default) 37 | 38 | @api.onchange("date_range_id") 39 | def _onchange_date_range(self): 40 | for rec in self: 41 | if rec.date_range_id: 42 | rec.date_from = rec.date_range_id.date_start 43 | rec.date_to = rec.date_range_id.date_end 44 | 45 | @api.onchange("date_from", "date_to") 46 | def _onchange_dates(self): 47 | for rec in self: 48 | if rec.date_range_id: 49 | if ( 50 | rec.date_from != rec.date_range_id.date_start 51 | or rec.date_to != rec.date_range_id.date_end 52 | ): 53 | rec.date_range_id = False 54 | 55 | def action_draft(self): 56 | self.write({"state": "draft"}) 57 | 58 | def action_cancel(self): 59 | self.write({"state": "cancelled"}) 60 | 61 | def action_confirm(self): 62 | self.write({"state": "confirmed"}) 63 | -------------------------------------------------------------------------------- /mis_builder_budget/views/mis_budget_by_account_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | mis.budget.by.account.item.search (in mis_builder_budget) 8 | 9 | mis.budget.by.account.item 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | mis.budget.by.account.item.tree (in mis_builder_budget) 20 | 21 | mis.budget.by.account.item 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 37 | 42 | 43 | 44 | 45 | 46 | MIS Budget Items (by accounts) 47 | mis.budget.by.account.item 48 | tree,form 49 | [('budget_id', '=', active_id)] 50 | {'default_budget_id': active_id} 51 | 52 | 53 | -------------------------------------------------------------------------------- /mis_builder/security/ir.model.access.csv: -------------------------------------------------------------------------------- 1 | "id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" 2 | manage_mis_report_kpi,manage_mis_report_kpi,model_mis_report_kpi,account.group_account_manager,1,1,1,1 3 | access_mis_report_kpi,access_mis_report_kpi,model_mis_report_kpi,base.group_user,1,0,0,0 4 | manage_mis_report_query,manage_mis_report_query,model_mis_report_query,account.group_account_manager,1,1,1,1 5 | access_mis_report_query,access_mis_report_query,model_mis_report_query,base.group_user,1,0,0,0 6 | manage_mis_report,manage_mis_report,model_mis_report,account.group_account_manager,1,1,1,1 7 | access_mis_report,access_mis_report,model_mis_report,base.group_user,1,0,0,0 8 | manage_mis_report_instance_period,manage_mis_report_instance_period,model_mis_report_instance_period,account.group_account_manager,1,1,1,1 9 | access_mis_report_instance_period,access_mis_report_instance_period,model_mis_report_instance_period,base.group_user,1,0,0,0 10 | manage_mis_report_instance_period_sum,manage_mis_report_instance_period_sum,model_mis_report_instance_period_sum,account.group_account_manager,1,1,1,1 11 | access_mis_report_instance_period_sum,access_mis_report_instance_period_sum,model_mis_report_instance_period_sum,base.group_user,1,0,0,0 12 | manage_mis_report_instance,manage_mis_report_instance,model_mis_report_instance,account.group_account_manager,1,1,1,1 13 | access_mis_report_instance,access_mis_report_instance,model_mis_report_instance,base.group_user,1,0,0,0 14 | manage_mis_report_subkpi,access_mis_report_subkpi,model_mis_report_subkpi,account.group_account_manager,1,1,1,1 15 | access_mis_report_subkpi,access_mis_report_subkpi,model_mis_report_subkpi,base.group_user,1,0,0,0 16 | manage_mis_report_kpi_expression,access_mis_report_kpi_expression,model_mis_report_kpi_expression,account.group_account_manager,1,1,1,1 17 | access_mis_report_kpi_expression,access_mis_report_kpi_expression,model_mis_report_kpi_expression,base.group_user,1,0,0,0 18 | manage_mis_report_subreport,access_mis_report_subreport,model_mis_report_subreport,account.group_account_manager,1,1,1,1 19 | access_mis_report_subreport,access_mis_report_subreport,model_mis_report_subreport,base.group_user,1,0,0,0 20 | manage_mis_report_style,access_mis_report_style,model_mis_report_style,account.group_account_manager,1,1,1,1 21 | access_mis_report_style,access_mis_report_style,model_mis_report_style,base.group_user,1,0,0,0 22 | access_add_to_dashboard_wizard,access_add_to_dashboard_wizard,model_add_mis_report_instance_dashboard_wizard,base.group_user,1,1,1,0 23 | -------------------------------------------------------------------------------- /mis_builder_demo/data/mis_budget.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 12 | 16 | 17 | 18 | 19 | 20 | 24 | 28 | 100000 29 | 30 | 31 | 32 | 33 | 37 | 41 | 200000 42 | 43 | 44 | 45 | 46 | 50 | 54 | 50000 55 | 56 | 57 | -------------------------------------------------------------------------------- /mis_builder/tests/common.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 ACSONE SA/NV () 2 | # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). 3 | 4 | import doctest 5 | 6 | from odoo.tests import BaseCase, tagged 7 | 8 | 9 | def setup_test_model(env, model_cls): 10 | model_cls._build_model(env.registry, env.cr) 11 | env.registry.setup_models(env.cr) 12 | env.registry.init_models( 13 | env.cr, [model_cls._name], dict(env.context, update_custom_fields=True) 14 | ) 15 | 16 | 17 | def teardown_test_model(env, model_cls): 18 | del env.registry.models[model_cls._name] 19 | env.registry.setup_models(env.cr) 20 | 21 | 22 | def _zip(iter1, iter2): 23 | i = 0 24 | iter1 = iter(iter1) 25 | iter2 = iter(iter2) 26 | while True: 27 | i1 = next(iter1, None) 28 | i2 = next(iter2, None) 29 | if i1 is None and i2 is None: 30 | return 31 | yield i, i1, i2 32 | i += 1 33 | 34 | 35 | def assert_matrix(matrix, expected): 36 | for i, row, expected_row in _zip(matrix.iter_rows(), expected): 37 | if row is None and expected_row is not None: 38 | raise AssertionError("not enough rows") 39 | if row is not None and expected_row is None: 40 | raise AssertionError("too many rows") 41 | for j, cell, expected_val in _zip(row.iter_cells(), expected_row): 42 | assert ( 43 | cell and cell.val 44 | ) == expected_val, "{} != {} in row {} col {}".format( 45 | cell and cell.val, expected_val, i, j 46 | ) 47 | 48 | 49 | @tagged("doctest") 50 | class OdooDocTestCase(BaseCase): 51 | """ 52 | We need a custom DocTestCase class in order to: 53 | - define test_tags to run as part of standard tests 54 | - output a more meaningful test name than default "DocTestCase.runTest" 55 | """ 56 | 57 | __qualname__ = "doctests for " 58 | 59 | def __init__(self, test): 60 | self.__test = test 61 | self.__name = test._dt_test.name 62 | super().__init__(self.__name) 63 | 64 | def __getattr__(self, item): 65 | if item == self.__name: 66 | return self.__test 67 | 68 | 69 | def load_doctests(module): 70 | """ 71 | Generates a tests loading method for the doctests of the given module 72 | https://docs.python.org/3/library/unittest.html#load-tests-protocol 73 | """ 74 | 75 | def load_tests(loader, tests, ignore): 76 | for test in doctest.DocTestSuite(module): 77 | tests.addTest(OdooDocTestCase(test)) 78 | return tests 79 | 80 | return load_tests 81 | -------------------------------------------------------------------------------- /mis_builder_budget/models/mis_report_instance_period.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 ACSONE SA/NV 2 | # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). 3 | 4 | from odoo import fields, models 5 | 6 | SRC_MIS_BUDGET = "mis_budget" 7 | SRC_MIS_BUDGET_BY_ACCOUNT = "mis_budget_by_account" 8 | 9 | 10 | class MisReportInstancePeriod(models.Model): 11 | 12 | _inherit = "mis.report.instance.period" 13 | 14 | source = fields.Selection( 15 | selection_add=[ 16 | (SRC_MIS_BUDGET, "MIS Budget by KPI"), 17 | (SRC_MIS_BUDGET_BY_ACCOUNT, "MIS Budget by Account"), 18 | ], 19 | ondelete={ 20 | SRC_MIS_BUDGET: "cascade", 21 | SRC_MIS_BUDGET_BY_ACCOUNT: "cascade", 22 | }, 23 | ) 24 | source_mis_budget_id = fields.Many2one( 25 | comodel_name="mis.budget", string="Budget by KPI" 26 | ) 27 | source_mis_budget_by_account_id = fields.Many2one( 28 | comodel_name="mis.budget.by.account", string="Budget by Account" 29 | ) 30 | 31 | def _get_aml_model_name(self): 32 | if self.source == SRC_MIS_BUDGET_BY_ACCOUNT: 33 | return "mis.budget.by.account.item" 34 | return super(MisReportInstancePeriod, self)._get_aml_model_name() 35 | 36 | def _get_additional_move_line_filter(self): 37 | domain = super(MisReportInstancePeriod, self)._get_additional_move_line_filter() 38 | if self.source == SRC_MIS_BUDGET_BY_ACCOUNT: 39 | domain.extend([("budget_id", "=", self.source_mis_budget_by_account_id.id)]) 40 | return domain 41 | 42 | def _get_additional_budget_item_filter(self): 43 | """Prepare a filter to apply on all budget items 44 | 45 | This filter is applied with a AND operator on all 46 | budget items. This hook is intended 47 | to be inherited, and is useful to implement filtering 48 | on analytic dimensions or operational units. 49 | 50 | The default filter is built from a ``mis_report_filters context`` 51 | key, which is a list set by the analytic filtering mechanism 52 | of the mis report widget:: 53 | 54 | [(field_name, {'value': value, 'operator': operator})] 55 | 56 | This default filter is the same as the one set by 57 | _get_additional_move_line_filter on mis.report.instance, so 58 | a budget.item is expected to have the same analytic fields as 59 | a move line. 60 | 61 | Returns an Odoo domain expression (a python list) 62 | compatible with mis.budget.item.""" 63 | self.ensure_one() 64 | filters = self._get_additional_move_line_filter() 65 | return filters 66 | -------------------------------------------------------------------------------- /docs/description.rst: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | What is MIS Builder 4 | ------------------- 5 | MIS Builder is an Odoo module that implements a class of Odoo reports where KPI 6 | (Key Performance Indicators) are displayed in rows, and time periods in columns. 7 | It focuses on very fast reporting on accounting data but can also use data from 8 | any other Odoo model. 9 | 10 | It features the following key characteristics: 11 | 12 | - User-friendly configuration: end users can create new report templates without 13 | development, using simple Excel-like formulas. 14 | - Very fast balance reporting for accounting data, even on million-line databases 15 | with very complex account charts. 16 | - Reusability: Use the same template for different reports. 17 | - Multi-period comparisons: Compare data over different time periods. 18 | - User-configurable styles: perfectly rendered in the UI as well as in Excel and 19 | PDF exports. 20 | - Interactive display with drill-down. 21 | - WYSIWYG Export to PDF and Excel. 22 | - A budgeting module. 23 | - KPI Evaluation over various data sources, such as actuals, simulation, committed 24 | costs (custom developments are required to create the data source). 25 | - Easy-to-use API for developers for the accounting balance computation engine. 26 | 27 | Modules information 28 | ------------------- 29 | `MIS Builder` is a set of modules, part of `Odoo Business Suite of Applications `_. 30 | 31 | The source code of the modules can be found in the `OCA Official Github repository `_: 32 | 33 | The main modules are the following: 34 | 35 | * `mis_builder` module installs the "MIS Reports", "MIS Reports Styles" and "MIS 36 | report Template" menu. This module is the base one and is necessary for any other 37 | module. 38 | * `mis_builder_budget` module installs the "MIS Budget" menu. 39 | * `mis_builder_demo` module installs the demo data reports in existing MIS (not 40 | suitable for production environment). 41 | 42 | The repository is licensed under `AGPLv3 `_. 43 | 44 | `MIS Builder` is available for the following versions (at the time of this document): 45 | 46 | * 8.0, 47 | * 9.0, 48 | * 10.0, 49 | * 11.0 50 | * in both Community and Enterprise Edition. 51 | 52 | About this documentation 53 | ------------------------ 54 | This documentation intends to provide the basic knowledge for the user to create 55 | financial reports from the standard Odoo financial entries. 56 | 57 | The user must have standard knowledge of Odoo usability and functions (Accounting). 58 | 59 | No development skills are necessary to use the module but some set up parts requires 60 | light technical knowledge. 61 | -------------------------------------------------------------------------------- /mis_builder_budget/models/mis_budget_by_account_item.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017-2020 ACSONE SA/NV 2 | # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). 3 | 4 | from odoo import api, fields, models 5 | 6 | 7 | class MisBudgetByAccountItem(models.Model): 8 | 9 | _inherit = ["mis.budget.item.abstract", "prorata.read_group.mixin"] 10 | _name = "mis.budget.by.account.item" 11 | _description = "MIS Budget Item (by Account)" 12 | _order = "budget_id, date_from, account_id" 13 | 14 | budget_id = fields.Many2one(comodel_name="mis.budget.by.account") 15 | debit = fields.Monetary(default=0.0, currency_field="company_currency_id") 16 | credit = fields.Monetary(default=0.0, currency_field="company_currency_id") 17 | balance = fields.Monetary( 18 | compute="_compute_balance", store=True, currency_field="company_currency_id" 19 | ) 20 | company_id = fields.Many2one( 21 | "res.company", 22 | related="budget_id.company_id", 23 | readonly=True, 24 | store=True, 25 | ) 26 | company_currency_id = fields.Many2one( 27 | "res.currency", 28 | related="budget_id.company_id.currency_id", 29 | string="Company Currency", 30 | readonly=True, 31 | help="Utility field to express amount currency", 32 | store=True, 33 | ) 34 | account_id = fields.Many2one( 35 | comodel_name="account.account", 36 | string="Account", 37 | required=True, 38 | # TODO domain (company_id) 39 | ) 40 | 41 | _sql_constraints = [ 42 | ( 43 | "credit_debit1", 44 | "CHECK (credit*debit=0)", 45 | "Wrong credit or debit value in budget item! " 46 | "Credit or debit should be zero.", 47 | ), 48 | ( 49 | "credit_debit2", 50 | "CHECK (credit+debit>=0)", 51 | "Wrong credit or debit value in budget item! " 52 | "Credit and debit should be positive.", 53 | ), 54 | ] 55 | 56 | @api.depends("debit", "credit") 57 | def _compute_balance(self): 58 | for rec in self: 59 | rec.balance = rec.debit - rec.credit 60 | 61 | def _prepare_overlap_domain(self): 62 | """Prepare a domain to check for overlapping budget items.""" 63 | domain = super(MisBudgetByAccountItem, self)._prepare_overlap_domain() 64 | domain.extend([("account_id", "=", self.account_id.id)]) 65 | return domain 66 | 67 | @api.constrains( 68 | "date_range_id", 69 | "date_from", 70 | "date_to", 71 | "budget_id", 72 | "analytic_account_id", 73 | "analytic_tag_ids", 74 | "account_id", 75 | ) 76 | def _check_dates(self): 77 | super(MisBudgetByAccountItem, self)._check_dates() 78 | -------------------------------------------------------------------------------- /mis_builder_demo/models/mis_committed_purchase.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 ACSONE SA/NV 2 | # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). 3 | 4 | import os 5 | from os.path import join as opj 6 | 7 | from odoo import fields, models, tools 8 | 9 | 10 | class MisCommittedPurchase(models.Model): 11 | 12 | _name = "mis.committed.purchase" 13 | _description = "MIS Commitment" 14 | _auto = False 15 | 16 | line_type = fields.Char() 17 | name = fields.Char() 18 | analytic_account_id = fields.Many2one( 19 | comodel_name="account.analytic.account", string="Analytic Account" 20 | ) 21 | account_id = fields.Many2one(comodel_name="account.account", string="Account") 22 | company_id = fields.Many2one(comodel_name="res.company", string="Company") 23 | credit = fields.Float() 24 | debit = fields.Float() 25 | date = fields.Date() 26 | 27 | # resource can be purchase.order.line or account.move.line 28 | res_id = fields.Integer(string="Resource ID") 29 | res_model = fields.Char(string="Resource Model Name") 30 | 31 | analytic_tag_ids = fields.Many2many( 32 | comodel_name="account.analytic.tag", 33 | relation="mis_committed_purchase_tag_rel", 34 | column1="mis_committed_purchase_id", 35 | column2="account_analytic_tag_id", 36 | string="Analytic Tags", 37 | ) 38 | 39 | def init(self): 40 | script = opj(os.path.dirname(__file__), "mis_committed_purchase.sql") 41 | with open(script) as f: 42 | tools.drop_view_if_exists(self.env.cr, "mis_committed_purchase") 43 | self.env.cr.execute(f.read()) 44 | 45 | # Create many2many relation for account.analytic.tag 46 | tools.drop_view_if_exists(self.env.cr, "mis_committed_purchase_tag_rel") 47 | self.env.cr.execute( 48 | """ 49 | CREATE OR REPLACE VIEW mis_committed_purchase_tag_rel AS 50 | (SELECT 51 | po_mcp.id AS mis_committed_purchase_id, 52 | po_rel.account_analytic_tag_id AS account_analytic_tag_id 53 | FROM account_analytic_tag_purchase_order_line_rel AS po_rel 54 | INNER JOIN mis_committed_purchase AS po_mcp ON 55 | po_mcp.res_id = po_rel.purchase_order_line_id 56 | WHERE po_mcp.res_model = 'purchase.order.line' 57 | UNION ALL 58 | SELECT 59 | inv_mcp.id AS mis_committed_purchase_id, 60 | inv_rel.account_analytic_tag_id AS account_analytic_tag_id 61 | FROM account_analytic_tag_account_move_line_rel AS inv_rel 62 | INNER JOIN mis_committed_purchase AS inv_mcp ON 63 | inv_mcp.res_id = inv_rel.account_move_line_id 64 | WHERE inv_mcp.res_model = 'account.move.line') 65 | """ 66 | ) 67 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | load-plugins=pylint_odoo 3 | score=n 4 | 5 | [ODOOLINT] 6 | readme_template_url="https://github.com/OCA/maintainer-tools/blob/master/template/module/README.rst" 7 | manifest_required_authors=Odoo Community Association (OCA) 8 | manifest_required_keys=license 9 | manifest_deprecated_keys=description,active 10 | license_allowed=AGPL-3,GPL-2,GPL-2 or any later version,GPL-3,GPL-3 or any later version,LGPL-3 11 | valid_odoo_versions=14.0 12 | 13 | [MESSAGES CONTROL] 14 | disable=all 15 | 16 | # This .pylintrc contains optional AND mandatory checks and is meant to be 17 | # loaded in an IDE to have it check everything, in the hope this will make 18 | # optional checks more visible to contributors who otherwise never look at a 19 | # green travis to see optional checks that failed. 20 | # .pylintrc-mandatory containing only mandatory checks is used the pre-commit 21 | # config as a blocking check. 22 | 23 | enable=anomalous-backslash-in-string, 24 | api-one-deprecated, 25 | api-one-multi-together, 26 | assignment-from-none, 27 | attribute-deprecated, 28 | class-camelcase, 29 | dangerous-default-value, 30 | dangerous-view-replace-wo-priority, 31 | development-status-allowed, 32 | duplicate-id-csv, 33 | duplicate-key, 34 | duplicate-xml-fields, 35 | duplicate-xml-record-id, 36 | eval-referenced, 37 | eval-used, 38 | incoherent-interpreter-exec-perm, 39 | license-allowed, 40 | manifest-author-string, 41 | manifest-deprecated-key, 42 | manifest-required-author, 43 | manifest-required-key, 44 | manifest-version-format, 45 | method-compute, 46 | method-inverse, 47 | method-required-super, 48 | method-search, 49 | openerp-exception-warning, 50 | pointless-statement, 51 | pointless-string-statement, 52 | print-used, 53 | redundant-keyword-arg, 54 | redundant-modulename-xml, 55 | reimported, 56 | relative-import, 57 | return-in-init, 58 | rst-syntax-error, 59 | sql-injection, 60 | too-few-format-args, 61 | translation-field, 62 | translation-required, 63 | unreachable, 64 | use-vim-comment, 65 | wrong-tabs-instead-of-spaces, 66 | xml-syntax-error, 67 | # messages that do not cause the lint step to fail 68 | consider-merging-classes-inherited, 69 | create-user-wo-reset-password, 70 | dangerous-filter-wo-user, 71 | deprecated-module, 72 | file-not-used, 73 | invalid-commit, 74 | missing-manifest-dependency, 75 | missing-newline-extrafiles, 76 | missing-readme, 77 | no-utf8-coding-comment, 78 | odoo-addons-relative-import, 79 | old-api7-method-defined, 80 | redefined-builtin, 81 | too-complex, 82 | unnecessary-utf8-coding-comment 83 | 84 | 85 | [REPORTS] 86 | msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg} 87 | output-format=colorized 88 | reports=no 89 | -------------------------------------------------------------------------------- /mis_builder/models/expression_evaluator.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 ACSONE SA/NV () 2 | # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). 3 | 4 | from .mis_safe_eval import NameDataError, mis_safe_eval 5 | 6 | try: 7 | import itertools.izip as zip 8 | except ImportError: 9 | pass # python 3 10 | 11 | 12 | class ExpressionEvaluator(object): 13 | def __init__( 14 | self, 15 | aep, 16 | date_from, 17 | date_to, 18 | target_move=None, 19 | additional_move_line_filter=None, 20 | aml_model=None, 21 | ): 22 | self.aep = aep 23 | self.date_from = date_from 24 | self.date_to = date_to 25 | self.target_move = target_move 26 | self.additional_move_line_filter = additional_move_line_filter 27 | self.aml_model = aml_model 28 | self._aep_queries_done = False 29 | 30 | def aep_do_queries(self): 31 | if self.aep and not self._aep_queries_done: 32 | self.aep.do_queries( 33 | self.date_from, 34 | self.date_to, 35 | self.target_move, 36 | self.additional_move_line_filter, 37 | self.aml_model, 38 | ) 39 | self._aep_queries_done = True 40 | 41 | def eval_expressions(self, expressions, locals_dict): 42 | vals = [] 43 | drilldown_args = [] 44 | name_error = False 45 | for expression in expressions: 46 | expr = expression and expression.name or "AccountingNone" 47 | if self.aep: 48 | replaced_expr = self.aep.replace_expr(expr) 49 | else: 50 | replaced_expr = expr 51 | val = mis_safe_eval(replaced_expr, locals_dict) 52 | vals.append(val) 53 | if isinstance(val, NameDataError): 54 | name_error = True 55 | if replaced_expr != expr: 56 | drilldown_args.append({"expr": expr}) 57 | else: 58 | drilldown_args.append(None) 59 | return vals, drilldown_args, name_error 60 | 61 | def eval_expressions_by_account(self, expressions, locals_dict): 62 | if not self.aep: 63 | return 64 | exprs = [e and e.name or "AccountingNone" for e in expressions] 65 | for account_id, replaced_exprs in self.aep.replace_exprs_by_account_id(exprs): 66 | vals = [] 67 | drilldown_args = [] 68 | name_error = False 69 | for expr, replaced_expr in zip(exprs, replaced_exprs): 70 | val = mis_safe_eval(replaced_expr, locals_dict) 71 | vals.append(val) 72 | if replaced_expr != expr: 73 | drilldown_args.append({"expr": expr, "account_id": account_id}) 74 | else: 75 | drilldown_args.append(None) 76 | yield account_id, vals, drilldown_args, name_error 77 | -------------------------------------------------------------------------------- /mis_builder_demo/data/mis_report.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | Demo Expenses 7 | 8 | 9 | 10 | 11 | exp 12 | Expenses 13 | True 14 | 15 | 16 | 1 17 | 18 | 19 | 20 | balp[220000] 21 | 22 | 23 | 24 | equip 25 | Equipment 26 | True 27 | 28 | 29 | 2 30 | 31 | 32 | 33 | balp[212200] 34 | 35 | 36 | 37 | other 38 | Other 39 | True 40 | 41 | 42 | 3 43 | 44 | 45 | 46 | balp[211000,212100,212300] 47 | 48 | 49 | 50 | total 51 | Total 52 | exp + equip + other 53 | False 54 | 55 | 56 | 4 57 | 58 | 59 | -------------------------------------------------------------------------------- /mis_builder/models/aggregate.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 ACSONE SA/NV () 2 | # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). 3 | 4 | 5 | def _sum(lst): 6 | """Same as stdlib sum but returns None instead of 0 7 | in case of empty sequence. 8 | 9 | >>> sum([1]) 10 | 1 11 | >>> _sum([1]) 12 | 1 13 | >>> sum([1, 2]) 14 | 3 15 | >>> _sum([1, 2]) 16 | 3 17 | >>> sum([]) 18 | 0 19 | >>> _sum([]) 20 | """ 21 | if not lst: 22 | return None 23 | return sum(lst) 24 | 25 | 26 | def _avg(lst): 27 | """Arithmetic mean of a sequence. Returns None in case of empty sequence. 28 | 29 | >>> _avg([1]) 30 | 1.0 31 | >>> _avg([1, 2]) 32 | 1.5 33 | >>> _avg([]) 34 | """ 35 | if not lst: 36 | return None 37 | return sum(lst) / float(len(lst)) 38 | 39 | 40 | def _min(*args): 41 | """Same as stdlib min but returns None instead of exception 42 | in case of empty sequence. 43 | 44 | >>> min(1, 2) 45 | 1 46 | >>> _min(1, 2) 47 | 1 48 | >>> min([1, 2]) 49 | 1 50 | >>> _min([1, 2]) 51 | 1 52 | >>> min(1) 53 | Traceback (most recent call last): 54 | File "", line 1, in ? 55 | TypeError: 'int' object is not iterable 56 | >>> _min(1) 57 | Traceback (most recent call last): 58 | File "", line 1, in ? 59 | TypeError: 'int' object is not iterable 60 | >>> min([1]) 61 | 1 62 | >>> _min([1]) 63 | 1 64 | >>> min() 65 | Traceback (most recent call last): 66 | File "", line 1, in ? 67 | TypeError: min expected 1 arguments, got 0 68 | >>> _min() 69 | Traceback (most recent call last): 70 | File "", line 1, in ? 71 | TypeError: min expected 1 arguments, got 0 72 | >>> min([]) 73 | Traceback (most recent call last): 74 | File "", line 1, in ? 75 | ValueError: min() arg is an empty sequence 76 | >>> _min([]) 77 | """ 78 | if len(args) == 1 and not args[0]: 79 | return None 80 | return min(*args) 81 | 82 | 83 | def _max(*args): 84 | """Same as stdlib max but returns None instead of exception 85 | in case of empty sequence. 86 | 87 | >>> max(1, 2) 88 | 2 89 | >>> _max(1, 2) 90 | 2 91 | >>> max([1, 2]) 92 | 2 93 | >>> _max([1, 2]) 94 | 2 95 | >>> max(1) 96 | Traceback (most recent call last): 97 | File "", line 1, in ? 98 | TypeError: 'int' object is not iterable 99 | >>> _max(1) 100 | Traceback (most recent call last): 101 | File "", line 1, in ? 102 | TypeError: 'int' object is not iterable 103 | >>> max([1]) 104 | 1 105 | >>> _max([1]) 106 | 1 107 | >>> max() 108 | Traceback (most recent call last): 109 | File "", line 1, in ? 110 | TypeError: max expected 1 arguments, got 0 111 | >>> _max() 112 | Traceback (most recent call last): 113 | File "", line 1, in ? 114 | TypeError: max expected 1 arguments, got 0 115 | >>> max([]) 116 | Traceback (most recent call last): 117 | File "", line 1, in ? 118 | ValueError: max() arg is an empty sequence 119 | >>> _max([]) 120 | """ 121 | if len(args) == 1 and not args[0]: 122 | return None 123 | return max(*args) 124 | 125 | 126 | if __name__ == "__main__": # pragma: no cover 127 | import doctest 128 | 129 | doctest.testmod() 130 | -------------------------------------------------------------------------------- /docs/install.rst: -------------------------------------------------------------------------------- 1 | Install the module in Odoo 2 | ========================== 3 | If you already have an Odoo instance up and running, your preferred way to install 4 | addons will work with `MIS Builder`. 5 | 6 | Using git 7 | --------- 8 | The most common way to install the module is to clone the git repository in your 9 | server and add the directory to your odoo.conf: 10 | 11 | #. Clone the git repository 12 | 13 | .. code-block:: sh 14 | 15 | cd your-addons-path 16 | git clone https://github.com/OCA/mis-builder 17 | cd mis-builder 18 | git checkout 10.0 #for the version 10.0 19 | 20 | #. Update the addon path of `odoo.conf` 21 | #. Restart Odoo 22 | #. Update the addons list in your database 23 | #. Install the MIS Builder application. 24 | 25 | Using pip 26 | --------- 27 | An easy way to install it with all its dependencies is using pip: 28 | 29 | #. Recover the code from pip repository 30 | 31 | .. code-block:: sh 32 | 33 | pip install --pre odoo10-addon-mis_builder odoo-autodiscover 34 | 35 | #. Restart Odoo 36 | #. Update the addons list in your database 37 | #. Install the MIS Builder application. 38 | 39 | Fresh install with Docker 40 | ------------------------- 41 | If you do not have any Odoo server installed, you can start your own Odoo in few 42 | minutes via Docker in command line. 43 | 44 | Here is the basic how-to (based on https://github.com/Elico-Corp/odoo-docker), valid 45 | for Ubuntu OS but could also easily be replicated in MacOS or Windows: 46 | 47 | #. Install docker and docker-compose in your system 48 | #. Create the directory structure (assuming the base directory is `odoo`) 49 | 50 | .. code-block:: sh 51 | 52 | mkdir odoo && cd odoo 53 | mkdir -p ./volumes/postgres ./volumes/odoo/addons ./volumes/odoo/filestore \ 54 | ./volumes/odoo/sessions 55 | 56 | #. Create a `docker-compose.yml` file in `odoo` directory with following content: 57 | 58 | .. code-block:: xml 59 | 60 | version: '3.3' 61 | services: 62 | 63 | postgres: 64 | image: postgres:9.5 65 | volumes: 66 | - ./volumes/postgres:/var/lib/postgresql/data 67 | environment: 68 | - POSTGRES_USER=odoo 69 | 70 | odoo: 71 | image: elicocorp/odoo:11.0 72 | command: start 73 | ports: 74 | - 127.0.0.1:8069:8069 75 | volumes: 76 | - ./volumes/odoo/addons:/opt/odoo/additional_addons 77 | - ./volumes/odoo/filestore:/opt/odoo/data/filestore 78 | - ./volumes/odoo/sessions:/opt/odoo/data/sessions 79 | links: 80 | - postgres:db 81 | environment: 82 | - ADDONS_REPO=https://github.com/OCA/mis-builder.git 83 | - ODOO_DB_USER=odoo 84 | 85 | #. Fire up your container (in `odoo` directory) 86 | 87 | .. code-block:: sh 88 | 89 | docker-compose up -d odoo 90 | 91 | #. Open a web browser and navigate the URL you have set up in your `docker-compose.yml` 92 | file (http://127.0.0.1:8069 in this particular example) 93 | #. Create a new database 94 | #. Update the addons list in your database (Menu `Apps > Update Apps List` in developer mode) 95 | #. Install the MIS Builder application. 96 | #. Improve your Odoo environment (add parameters, change default passwords etc.) 97 | under Docker: https://github.com/Elico-Corp/odoo-docker 98 | 99 | More about `Odoo `_. 100 | -------------------------------------------------------------------------------- /mis_builder/tests/test_subreport.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 ACSONE SA/NV () 2 | # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). 3 | 4 | from odoo.tests.common import SavepointCase 5 | 6 | from odoo.addons.mis_builder.models.expression_evaluator import ExpressionEvaluator 7 | from odoo.addons.mis_builder.models.mis_report_subreport import ( 8 | InvalidNameError, 9 | ParentLoopError, 10 | ) 11 | 12 | 13 | class TestMisSubreport(SavepointCase): 14 | @classmethod 15 | def setUpClass(cls): 16 | super(TestMisSubreport, cls).setUpClass() 17 | # create report 18 | cls.subreport = cls.env["mis.report"].create(dict(name="test subreport")) 19 | cls.subreport_kpi1 = cls.env["mis.report.kpi"].create( 20 | dict( 21 | report_id=cls.subreport.id, 22 | name="sk1", 23 | description="subreport kpi 1", 24 | expression="11", 25 | ) 26 | ) 27 | cls.report = cls.env["mis.report"].create( 28 | dict( 29 | name="test report", 30 | subreport_ids=[ 31 | (0, 0, dict(name="subreport", subreport_id=cls.subreport.id)) 32 | ], 33 | ) 34 | ) 35 | cls.report_kpi1 = cls.env["mis.report.kpi"].create( 36 | dict( 37 | report_id=cls.report.id, 38 | name="k1", 39 | description="report kpi 1", 40 | expression="subreport.sk1 + 1", 41 | ) 42 | ) 43 | cls.parent_report = cls.env["mis.report"].create( 44 | dict( 45 | name="parent report", 46 | subreport_ids=[(0, 0, dict(name="report", subreport_id=cls.report.id))], 47 | ) 48 | ) 49 | cls.parent_report_kpi1 = cls.env["mis.report.kpi"].create( 50 | dict( 51 | report_id=cls.parent_report.id, 52 | name="pk1", 53 | description="parent report kpi 1", 54 | expression="report.k1 + 1", 55 | ) 56 | ) 57 | 58 | def test_basic(self): 59 | ee = ExpressionEvaluator(aep=None, date_from="2017-01-01", date_to="2017-01-16") 60 | d = self.report._evaluate(ee) 61 | assert d["k1"] == 12 62 | 63 | def test_two_levels(self): 64 | ee = ExpressionEvaluator(aep=None, date_from="2017-01-01", date_to="2017-01-16") 65 | d = self.parent_report._evaluate(ee) 66 | assert d["pk1"] == 13 67 | 68 | def test_detect_loop(self): 69 | with self.assertRaises(ParentLoopError): 70 | self.report.write( 71 | dict( 72 | subreport_ids=[ 73 | ( 74 | 0, 75 | 0, 76 | dict(name="preport1", subreport_id=self.parent_report.id), 77 | ) 78 | ] 79 | ) 80 | ) 81 | with self.assertRaises(ParentLoopError): 82 | self.report.write( 83 | dict( 84 | subreport_ids=[ 85 | ( 86 | 0, 87 | 0, 88 | dict(name="preport2", subreport_id=self.report.id), 89 | ) 90 | ] 91 | ) 92 | ) 93 | 94 | def test_invalid_name(self): 95 | with self.assertRaises(InvalidNameError): 96 | self.report.subreport_ids[0].name = "ab c" 97 | -------------------------------------------------------------------------------- /mis_builder/wizard/mis_builder_dashboard.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 ACSONE SA/NV () 2 | # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). 3 | 4 | from lxml import etree 5 | 6 | from odoo import api, fields, models 7 | 8 | 9 | class AddMisReportInstanceDashboard(models.TransientModel): 10 | _name = "add.mis.report.instance.dashboard.wizard" 11 | _description = "MIS Report Add to Dashboard Wizard" 12 | 13 | name = fields.Char("Name", size=32, required=True) 14 | 15 | dashboard_id = fields.Many2one( 16 | "ir.actions.act_window", 17 | string="Dashboard", 18 | required=True, 19 | domain="[('res_model', '=', " "'board.board')]", 20 | ) 21 | 22 | @api.model 23 | def default_get(self, fields_list): 24 | res = {} 25 | if self.env.context.get("active_id", False): 26 | res = super(AddMisReportInstanceDashboard, self).default_get(fields_list) 27 | # get report instance name 28 | res["name"] = ( 29 | self.env["mis.report.instance"] 30 | .browse(self.env.context["active_id"]) 31 | .name 32 | ) 33 | return res 34 | 35 | def action_add_to_dashboard(self): 36 | active_model = self.env.context.get("active_model") 37 | assert active_model == "mis.report.instance" 38 | active_id = self.env.context.get("active_id") 39 | assert active_id 40 | mis_report_instance = self.env[active_model].browse(active_id) 41 | # create the act_window corresponding to this report 42 | self.env.ref("mis_builder.mis_report_instance_result_view_form") 43 | view = self.env.ref("mis_builder.mis_report_instance_result_view_form") 44 | report_result = ( 45 | self.env["ir.actions.act_window"] 46 | .sudo() 47 | .create( 48 | { 49 | "name": "mis.report.instance.result.view.action.%d" 50 | % self.env.context["active_id"], 51 | "res_model": active_model, 52 | "res_id": active_id, 53 | "target": "current", 54 | "view_mode": "form", 55 | "view_id": view.id, 56 | "context": mis_report_instance._context_with_filters(), 57 | } 58 | ) 59 | ) 60 | # add this result in the selected dashboard 61 | last_customization = self.env["ir.ui.view.custom"].search( 62 | [ 63 | ("user_id", "=", self.env.uid), 64 | ("ref_id", "=", self.dashboard_id.view_id.id), 65 | ], 66 | limit=1, 67 | ) 68 | arch = self.dashboard_id.view_id.arch 69 | if last_customization: 70 | arch = self.env["ir.ui.view.custom"].browse(last_customization[0].id).arch 71 | new_arch = etree.fromstring(arch) 72 | column = new_arch.xpath("//column")[0] 73 | column.append( 74 | etree.Element( 75 | "action", 76 | { 77 | "context": str(self.env.context), 78 | "name": str(report_result.id), 79 | "string": self.name, 80 | "view_mode": "form", 81 | }, 82 | ) 83 | ) 84 | self.env["ir.ui.view.custom"].create( 85 | { 86 | "user_id": self.env.uid, 87 | "ref_id": self.dashboard_id.view_id.id, 88 | "arch": etree.tostring(new_arch, pretty_print=True), 89 | } 90 | ) 91 | 92 | return {"type": "ir.actions.act_window_close"} 93 | -------------------------------------------------------------------------------- /mis_builder_budget/models/mis_budget_item_abstract.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017-2020 ACSONE SA/NV 2 | # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). 3 | 4 | from odoo import _, api, fields, models 5 | from odoo.exceptions import ValidationError 6 | 7 | 8 | class MisBudgetItemAbstract(models.AbstractModel): 9 | 10 | _name = "mis.budget.item.abstract" 11 | _description = "MIS Budget Item (Abstract Base Class)" 12 | 13 | budget_id = fields.Many2one( 14 | comodel_name="mis.budget.abstract", 15 | string="Budget", 16 | required=True, 17 | ondelete="cascade", 18 | index=True, 19 | ) 20 | budget_date_from = fields.Date( 21 | related="budget_id.date_from", readonly=True, string="Budget Date From" 22 | ) 23 | budget_date_to = fields.Date( 24 | related="budget_id.date_to", readonly=True, string="Budget Date To" 25 | ) 26 | date_range_id = fields.Many2one( 27 | comodel_name="date.range", 28 | domain="[('date_start', '>=', budget_date_from)," 29 | " ('date_end', '<=', budget_date_to)]", 30 | string="Date range", 31 | ) 32 | date_from = fields.Date(required=True, string="From") 33 | date_to = fields.Date(required=True, string="To") 34 | analytic_account_id = fields.Many2one( 35 | comodel_name="account.analytic.account", string="Analytic account" 36 | ) 37 | analytic_tag_ids = fields.Many2many( 38 | comodel_name="account.analytic.tag", string="Analytic Tags" 39 | ) 40 | 41 | @api.onchange("date_range_id") 42 | def _onchange_date_range(self): 43 | for rec in self: 44 | if rec.date_range_id: 45 | rec.date_from = rec.date_range_id.date_start 46 | rec.date_to = rec.date_range_id.date_end 47 | 48 | @api.onchange("date_from", "date_to") 49 | def _onchange_dates(self): 50 | for rec in self: 51 | if rec.date_range_id: 52 | if ( 53 | rec.date_from != rec.date_range_id.date_start 54 | or rec.date_to != rec.date_range_id.date_end 55 | ): 56 | rec.date_range_id = False 57 | 58 | def _prepare_overlap_domain(self): 59 | """Prepare a domain to check for overlapping budget items.""" 60 | self.ensure_one() 61 | domain = [ 62 | ("id", "!=", self.id), 63 | ("budget_id", "=", self.budget_id.id), 64 | ("date_from", "<=", self.date_to), 65 | ("date_to", ">=", self.date_from), 66 | ("analytic_account_id", "=", self.analytic_account_id.id), 67 | ] 68 | for tag in self.analytic_tag_ids: 69 | domain.append(("analytic_tag_ids", "in", [tag.id])) 70 | return domain 71 | 72 | def _check_dates(self): 73 | for rec in self: 74 | # date_from <= date_to 75 | if rec.date_from > rec.date_to: 76 | raise ValidationError( 77 | _("%s start date must not be after end date") % (rec.display_name,) 78 | ) 79 | # within budget dates 80 | if rec.date_from < rec.budget_date_from or rec.date_to > rec.budget_date_to: 81 | raise ValidationError( 82 | _("%s is not within budget %s date range.") 83 | % (rec.display_name, rec.budget_id.display_name) 84 | ) 85 | # overlaps 86 | domain = rec._prepare_overlap_domain() 87 | res = self.search(domain, limit=1) 88 | if res: 89 | raise ValidationError( 90 | _("%s overlaps %s in budget %s") 91 | % (rec.display_name, res.display_name, rec.budget_id.display_name) 92 | ) 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Runbot Status](https://runbot.odoo-community.org/runbot/badge/flat/248/14.0.svg)](https://runbot.odoo-community.org/runbot/repo/github-com-oca-mis-builder-248) 2 | [![Build Status](https://travis-ci.com/OCA/mis-builder.svg?branch=14.0)](https://travis-ci.com/OCA/mis-builder) 3 | [![codecov](https://codecov.io/gh/OCA/mis-builder/branch/14.0/graph/badge.svg)](https://codecov.io/gh/OCA/mis-builder) 4 | [![Translation Status](https://translation.odoo-community.org/widgets/mis-builder-14-0/-/svg-badge.svg)](https://translation.odoo-community.org/engage/mis-builder-14-0/?utm_source=widget) 5 | 6 | 7 | 8 | # MIS Builder 9 | 10 | Management Information System reports for Odoo: easily build super fast, 11 | beautiful, custom reports such as P&L, Balance Sheets and more. 12 | 13 | **This is the 14.0 branch. Please note the development of new features occurs mainly on 14 | the 10.0 branch, to be forward-ported to 14.0. Please submit pull requests to the 10.0 15 | branch in priority, unless they are 14.0 specific bugs, or they rely on 16 | Odoo features that are not present in 10.0.** 17 | 18 | This project implements a class of reports where KPI (Key Performance Indicators) 19 | are displayed in rows, and time periods in columns. It focuses on very fast reporting 20 | on accounting data but can also use data from any other Odoo model. 21 | 22 | It features the following key characteristics: 23 | 24 | - User configurable: end users can create new report templates without development, 25 | using simple Excel-like formulas. 26 | - Very fast balance reporting for accounting data, even on million lines databases 27 | and very complex account charts. 28 | - Use the same template for different reports. 29 | - Compare data over different time periods. 30 | - User-configurable styles, rendered perfectly in the UI as well as Excel and PDF exports. 31 | - Interactive display with drill-down. 32 | - Export to PDF and Excel. 33 | - A budgeting module. 34 | - Evaluate KPI over various data sources, such as actuals, simulation, committed costs 35 | (some custom development is required to create the data source). 36 | - For developers, the accounting balance computation engine is exposed as an easy 37 | to use API. 38 | 39 | Here are some presentations: 40 | 41 | - Odoo Experience 2017 ([slides](https://www.slideshare.net/acsone/budget-control-with-misbuilder-3-2017), [video](https://youtu.be/0PpxGAf2l-0)) 42 | - Odoo Experience 2016 ([slides](https://www.slideshare.net/acsone/misbuilder-2016)) 43 | - Odoo Experience 2015 ([slides](https://www.slideshare.net/acsone/misbuilder)) 44 | 45 | 46 | 47 | 48 | 49 | [//]: # (addons) 50 | 51 | Available addons 52 | ---------------- 53 | addon | version | maintainers | summary 54 | --- | --- | --- | --- 55 | [mis_builder](mis_builder/) | 14.0.3.6.7 | [![sbidoul](https://github.com/sbidoul.png?size=30px)](https://github.com/sbidoul) | Build 'Management Information System' Reports and Dashboards 56 | [mis_builder_budget](mis_builder_budget/) | 14.0.3.5.1 | [![sbidoul](https://github.com/sbidoul.png?size=30px)](https://github.com/sbidoul) | Create budgets for MIS reports 57 | [mis_builder_demo](mis_builder_demo/) | 14.0.3.1.3 | [![sbidoul](https://github.com/sbidoul.png?size=30px)](https://github.com/sbidoul) | Demo addon for MIS Builder 58 | 59 | [//]: # (end addons) 60 | 61 | 62 | 63 | ## Licenses 64 | 65 | This repository is licensed under [AGPL-3.0](LICENSE). 66 | 67 | However, each module can have a totally different license, as long as they adhere to OCA 68 | policy. Consult each module's `__manifest__.py` file, which contains a `license` key 69 | that explains its license. 70 | 71 | ---- 72 | 73 | OCA, or the [Odoo Community Association](http://odoo-community.org/), is a nonprofit 74 | organization whose mission is to support the collaborative development of Odoo features 75 | and promote its widespread use. 76 | -------------------------------------------------------------------------------- /mis_builder/models/prorata_read_group_mixin.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 ACSONE SA/NV 2 | # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). 3 | 4 | from odoo import _, api, fields, models 5 | from odoo.exceptions import UserError 6 | from odoo.fields import Date 7 | 8 | from .mis_kpi_data import intersect_days 9 | 10 | 11 | class ProRataReadGroupMixin(models.AbstractModel): 12 | _name = "prorata.read_group.mixin" 13 | _description = "Adapt model with date_from/date_to for pro-rata temporis read_group" 14 | 15 | date_from = fields.Date(required=True) 16 | date_to = fields.Date(required=True) 17 | date = fields.Date( 18 | compute=lambda self: None, 19 | search="_search_date", 20 | help=( 21 | "Dummy field that adapts searches on date " 22 | "to searches on date_from/date_to." 23 | ), 24 | ) 25 | 26 | def _search_date(self, operator, value): 27 | if operator in (">=", ">"): 28 | return [("date_to", operator, value)] 29 | elif operator in ("<=", "<"): 30 | return [("date_from", operator, value)] 31 | raise UserError( 32 | _("Unsupported operator %s for searching on date") % (operator,) 33 | ) 34 | 35 | @api.model 36 | def _intersect_days(self, item_dt_from, item_dt_to, dt_from, dt_to): 37 | return intersect_days(item_dt_from, item_dt_to, dt_from, dt_to) 38 | 39 | @api.model 40 | def read_group( 41 | self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True 42 | ): 43 | """Override read_group to perform pro-rata temporis adjustments. 44 | 45 | When read_group is invoked with a domain that filters on 46 | a time period (date >= from and date <= to, or 47 | date_from <= to and date_to >= from), adjust the accumulated 48 | values pro-rata temporis. 49 | """ 50 | date_from = None 51 | date_to = None 52 | assert isinstance(domain, list) 53 | for domain_item in domain: 54 | if isinstance(domain_item, (list, tuple)): 55 | field, op, value = domain_item 56 | if field == "date" and op == ">=": 57 | date_from = value 58 | elif field == "date_to" and op == ">=": 59 | date_from = value 60 | elif field == "date" and op == "<=": 61 | date_to = value 62 | elif field == "date_from" and op == "<=": 63 | date_to = value 64 | if ( 65 | date_from is not None 66 | and date_to is not None 67 | and not any(":" in f for f in fields) 68 | ): 69 | dt_from = Date.from_string(date_from) 70 | dt_to = Date.from_string(date_to) 71 | res = {} 72 | sum_fields = set(fields) - set(groupby) 73 | read_fields = set(fields + ["date_from", "date_to"]) 74 | for item in self.search(domain).read(read_fields): 75 | key = tuple(item[k] for k in groupby) 76 | if key not in res: 77 | res[key] = {k: item[k] for k in groupby} 78 | res[key].update({k: 0.0 for k in sum_fields}) 79 | res_item = res[key] 80 | for sum_field in sum_fields: 81 | item_dt_from = Date.from_string(item["date_from"]) 82 | item_dt_to = Date.from_string(item["date_to"]) 83 | i_days, item_days = self._intersect_days( 84 | item_dt_from, item_dt_to, dt_from, dt_to 85 | ) 86 | res_item[sum_field] += item[sum_field] * i_days / item_days 87 | return res.values() 88 | return super(ProRataReadGroupMixin, self).read_group( 89 | domain, 90 | fields, 91 | groupby, 92 | offset=offset, 93 | limit=limit, 94 | orderby=orderby, 95 | lazy=lazy, 96 | ) 97 | -------------------------------------------------------------------------------- /mis_builder_budget/views/mis_budget.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | mis.budget.form (in mis_builder_budget) 7 | mis.budget 8 | 9 |
10 |
11 |
32 | 33 |
34 |
42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
54 |
55 | 56 | 57 |
58 |
59 |
60 |
61 | 62 | mis.budget.search (in mis_builder_budget) 63 | mis.budget 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | mis.budget.tree (in mis_builder_budget) 73 | mis.budget 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | MIS Budgets (by KPIs) 86 | mis.budget 87 | tree,form 88 | [] 89 | {} 90 | 91 | 92 | MIS Budgets (by KPIs) 93 | 94 | 95 | 96 | 97 |
98 | -------------------------------------------------------------------------------- /mis_builder_budget/views/mis_budget_by_account.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | mis.budget.by.account.form (in mis_builder_budget) 7 | mis.budget.by.account 8 | 9 |
10 |
11 |
32 | 33 |
34 |
42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 |
53 |
54 | 55 | 56 |
57 |
58 |
59 |
60 | 61 | mis.budget.by.account.search (in mis_builder_budget) 62 | mis.budget.by.account 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | mis.budget.by.account.tree (in mis_builder_budget) 72 | mis.budget.by.account 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | MIS Budgets (by accounts) 84 | mis.budget.by.account 85 | tree,form 86 | [] 87 | {} 88 | 89 | 90 | MIS Budgets (by accounts) 91 | 92 | 93 | 94 | 95 |
96 | -------------------------------------------------------------------------------- /mis_builder/models/mis_kpi_data.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 ACSONE SA/NV 2 | # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). 3 | 4 | from collections import defaultdict 5 | 6 | from odoo import _, api, fields, models 7 | from odoo.exceptions import UserError 8 | from odoo.osv import expression 9 | 10 | ACC_SUM = "sum" 11 | ACC_AVG = "avg" 12 | ACC_NONE = "none" 13 | 14 | 15 | def intersect_days(item_dt_from, item_dt_to, dt_from, dt_to): 16 | item_days = (item_dt_to - item_dt_from).days + 1.0 17 | i_dt_from = max(dt_from, item_dt_from) 18 | i_dt_to = min(dt_to, item_dt_to) 19 | i_days = (i_dt_to - i_dt_from).days + 1.0 20 | return i_days, item_days 21 | 22 | 23 | class MisKpiData(models.AbstractModel): 24 | """Abstract class for manually entered KPI values.""" 25 | 26 | _name = "mis.kpi.data" 27 | _description = "MIS Kpi Data Abtract class" 28 | 29 | name = fields.Char(compute="_compute_name", required=False, readonly=True) 30 | kpi_expression_id = fields.Many2one( 31 | comodel_name="mis.report.kpi.expression", 32 | required=True, 33 | ondelete="restrict", 34 | string="KPI", 35 | ) 36 | date_from = fields.Date(required=True, string="From") 37 | date_to = fields.Date(required=True, string="To") 38 | amount = fields.Float() 39 | seq1 = fields.Integer( 40 | related="kpi_expression_id.kpi_id.sequence", 41 | store=True, 42 | readonly=True, 43 | string="KPI Sequence", 44 | ) 45 | seq2 = fields.Integer( 46 | related="kpi_expression_id.subkpi_id.sequence", 47 | store=True, 48 | readonly=True, 49 | string="Sub-KPI Sequence", 50 | ) 51 | 52 | @api.depends( 53 | "kpi_expression_id.subkpi_id.name", 54 | "kpi_expression_id.kpi_id.name", 55 | "date_from", 56 | "date_to", 57 | ) 58 | def _compute_name(self): 59 | for rec in self: 60 | subkpi_name = rec.kpi_expression_id.subkpi_id.name 61 | if subkpi_name: 62 | subkpi_name = "." + subkpi_name 63 | else: 64 | subkpi_name = "" 65 | rec.name = u"{}{}: {} - {}".format( 66 | rec.kpi_expression_id.kpi_id.name, 67 | subkpi_name, 68 | rec.date_from, 69 | rec.date_to, 70 | ) 71 | 72 | @api.model 73 | def _intersect_days(self, item_dt_from, item_dt_to, dt_from, dt_to): 74 | return intersect_days(item_dt_from, item_dt_to, dt_from, dt_to) 75 | 76 | @api.model 77 | def _query_kpi_data(self, date_from, date_to, base_domain): 78 | """Query mis.kpi.data over a time period. 79 | 80 | Returns {mis.report.kpi.expression: amount} 81 | """ 82 | dt_from = fields.Date.from_string(date_from) 83 | dt_to = fields.Date.from_string(date_to) 84 | # all data items within or overlapping [date_from, date_to] 85 | date_domain = [("date_from", "<=", date_to), ("date_to", ">=", date_from)] 86 | domain = expression.AND([date_domain, base_domain]) 87 | res = defaultdict(float) 88 | res_avg = defaultdict(list) 89 | for item in self.search(domain): 90 | item_dt_from = fields.Date.from_string(item.date_from) 91 | item_dt_to = fields.Date.from_string(item.date_to) 92 | i_days, item_days = self._intersect_days( 93 | item_dt_from, item_dt_to, dt_from, dt_to 94 | ) 95 | if item.kpi_expression_id.kpi_id.accumulation_method == ACC_SUM: 96 | # accumulate pro-rata overlap between item and reporting period 97 | res[item.kpi_expression_id] += item.amount * i_days / item_days 98 | elif item.kpi_expression_id.kpi_id.accumulation_method == ACC_AVG: 99 | # memorize the amount and number of days overlapping 100 | # the reporting period (used as weight in average) 101 | res_avg[item.kpi_expression_id].append((i_days, item.amount)) 102 | else: 103 | raise UserError( 104 | _("Unexpected accumulation method %s for %s.") 105 | % (item.kpi_expression_id.kpi_id.accumulation_method, item.name) 106 | ) 107 | # compute weighted average for ACC_AVG 108 | for kpi_expression, amounts in res_avg.items(): 109 | res[kpi_expression] = sum(d * a for d, a in amounts) / sum( 110 | d for d, a in amounts 111 | ) 112 | return res 113 | -------------------------------------------------------------------------------- /mis_builder_budget/models/mis_report_instance.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 ACSONE SA/NV 2 | # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). 3 | 4 | from odoo import models 5 | from odoo.osv.expression import AND 6 | 7 | from odoo.addons.mis_builder.models.accounting_none import AccountingNone 8 | from odoo.addons.mis_builder.models.expression_evaluator import ExpressionEvaluator 9 | 10 | from .mis_report_instance_period import SRC_MIS_BUDGET, SRC_MIS_BUDGET_BY_ACCOUNT 11 | 12 | 13 | class MisBudgetAwareExpressionEvaluator(ExpressionEvaluator): 14 | def __init__(self, date_from, date_to, kpi_data, additional_move_line_filter): 15 | super(MisBudgetAwareExpressionEvaluator, self).__init__( 16 | aep=None, 17 | date_from=date_from, 18 | date_to=date_to, 19 | target_move=None, 20 | additional_move_line_filter=additional_move_line_filter, 21 | aml_model=None, 22 | ) 23 | self.kpi_data = kpi_data 24 | 25 | def eval_expressions(self, expressions, locals_dict): 26 | kpi_id = expressions.mapped("kpi_id") 27 | assert len(kpi_id) == 1 28 | if kpi_id.budgetable: 29 | vals = [] 30 | drilldown_args = [] 31 | for expression in expressions: 32 | vals.append(self.kpi_data.get(expression, AccountingNone)) 33 | drilldown_args.append({"expr_id": expression.id}) 34 | return vals, drilldown_args, False 35 | return super(MisBudgetAwareExpressionEvaluator, self).eval_expressions( 36 | expressions, locals_dict 37 | ) 38 | 39 | 40 | class MisReportInstance(models.Model): 41 | 42 | _inherit = "mis.report.instance" 43 | 44 | def _add_column_mis_budget(self, aep, kpi_matrix, period, label, description): 45 | 46 | # fetch budget data for the period 47 | base_domain = AND( 48 | [ 49 | [("budget_id", "=", period.source_mis_budget_id.id)], 50 | period._get_additional_budget_item_filter(), 51 | ] 52 | ) 53 | kpi_data = self.env["mis.budget.item"]._query_kpi_data( 54 | period.date_from, period.date_to, base_domain 55 | ) 56 | 57 | expression_evaluator = MisBudgetAwareExpressionEvaluator( 58 | period.date_from, 59 | period.date_to, 60 | kpi_data, 61 | period._get_additional_move_line_filter(), 62 | ) 63 | return self.report_id._declare_and_compute_period( 64 | expression_evaluator, 65 | kpi_matrix, 66 | period.id, 67 | label, 68 | description, 69 | period.subkpi_ids, 70 | period._get_additional_query_filter, 71 | no_auto_expand_accounts=self.no_auto_expand_accounts, 72 | ) 73 | 74 | def _add_column(self, aep, kpi_matrix, period, label, description): 75 | if period.source == SRC_MIS_BUDGET: 76 | return self._add_column_mis_budget( 77 | aep, kpi_matrix, period, label, description 78 | ) 79 | elif period.source == SRC_MIS_BUDGET_BY_ACCOUNT: 80 | return self._add_column_move_lines( 81 | aep, kpi_matrix, period, label, description 82 | ) 83 | else: 84 | return super(MisReportInstance, self)._add_column( 85 | aep, kpi_matrix, period, label, description 86 | ) 87 | 88 | def drilldown(self, arg): 89 | self.ensure_one() 90 | period_id = arg.get("period_id") 91 | if period_id: 92 | period = self.env["mis.report.instance.period"].browse(period_id) 93 | if period.source == SRC_MIS_BUDGET: 94 | expr_id = arg.get("expr_id") 95 | if not expr_id: 96 | return False 97 | domain = [ 98 | ("date_from", "<=", period.date_to), 99 | ("date_to", ">=", period.date_from), 100 | ("kpi_expression_id", "=", expr_id), 101 | ("budget_id", "=", period.source_mis_budget_id.id), 102 | ] 103 | domain.extend(period._get_additional_budget_item_filter()) 104 | return { 105 | "name": period.name, 106 | "domain": domain, 107 | "type": "ir.actions.act_window", 108 | "res_model": "mis.budget.item", 109 | "views": [[False, "list"], [False, "form"]], 110 | "view_mode": "list", 111 | "target": "current", 112 | } 113 | return super(MisReportInstance, self).drilldown(arg) 114 | -------------------------------------------------------------------------------- /mis_builder/static/src/xml/mis_report_widget.xml: -------------------------------------------------------------------------------- 1 | 99 | -------------------------------------------------------------------------------- /mis_builder_demo/i18n/mis_builder_demo.pot: -------------------------------------------------------------------------------- 1 | # Translation of Odoo Server. 2 | # This file contains the translation of the following modules: 3 | # * mis_builder_demo 4 | # 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: Odoo Server 14.0\n" 8 | "Report-Msgid-Bugs-To: \n" 9 | "Last-Translator: \n" 10 | "Language-Team: \n" 11 | "MIME-Version: 1.0\n" 12 | "Content-Type: text/plain; charset=UTF-8\n" 13 | "Content-Transfer-Encoding: \n" 14 | "Plural-Forms: \n" 15 | 16 | #. module: mis_builder_demo 17 | #: model:mis.report.instance.period,name:mis_builder_demo.mis_report_instance_expenses_bud 18 | msgid "3 M Budget" 19 | msgstr "" 20 | 21 | #. module: mis_builder_demo 22 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__account_id 23 | msgid "Account" 24 | msgstr "" 25 | 26 | #. module: mis_builder_demo 27 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__analytic_account_id 28 | msgid "Analytic Account" 29 | msgstr "" 30 | 31 | #. module: mis_builder_demo 32 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__analytic_tag_ids 33 | msgid "Analytic Tags" 34 | msgstr "" 35 | 36 | #. module: mis_builder_demo 37 | #: model:mis.report.instance.period,name:mis_builder_demo.mis_report_instance_expenses_avail 38 | msgid "Available" 39 | msgstr "" 40 | 41 | #. module: mis_builder_demo 42 | #: model:mis.report.instance.period,name:mis_builder_demo.mis_report_instance_expenses_commit 43 | msgid "Committed" 44 | msgstr "" 45 | 46 | #. module: mis_builder_demo 47 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__company_id 48 | msgid "Company" 49 | msgstr "" 50 | 51 | #. module: mis_builder_demo 52 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__credit 53 | msgid "Credit" 54 | msgstr "" 55 | 56 | #. module: mis_builder_demo 57 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__date 58 | msgid "Date" 59 | msgstr "" 60 | 61 | #. module: mis_builder_demo 62 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__debit 63 | msgid "Debit" 64 | msgstr "" 65 | 66 | #. module: mis_builder_demo 67 | #: model:mis.report,name:mis_builder_demo.mis_report_expenses 68 | msgid "Demo Expenses" 69 | msgstr "" 70 | 71 | #. module: mis_builder_demo 72 | #: model:mis.report.instance,name:mis_builder_demo.mis_report_instance_expenses 73 | msgid "Demo Expenses vs Budget" 74 | msgstr "" 75 | 76 | #. module: mis_builder_demo 77 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__display_name 78 | msgid "Display Name" 79 | msgstr "" 80 | 81 | #. module: mis_builder_demo 82 | #: model:mis.report.kpi,description:mis_builder_demo.mis_report_expenses_kpi_equip 83 | msgid "Equipment" 84 | msgstr "" 85 | 86 | #. module: mis_builder_demo 87 | #: model:mis.report.kpi,description:mis_builder_demo.mis_report_expenses_kpi_exp 88 | msgid "Expenses" 89 | msgstr "" 90 | 91 | #. module: mis_builder_demo 92 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__id 93 | msgid "ID" 94 | msgstr "" 95 | 96 | #. module: mis_builder_demo 97 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase____last_update 98 | msgid "Last Modified on" 99 | msgstr "" 100 | 101 | #. module: mis_builder_demo 102 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__line_type 103 | msgid "Line Type" 104 | msgstr "" 105 | 106 | #. module: mis_builder_demo 107 | #: model:mis.report.instance.period,name:mis_builder_demo.mis_report_instance_expenses_act_mm1 108 | msgid "M-1" 109 | msgstr "" 110 | 111 | #. module: mis_builder_demo 112 | #: model:mis.report.instance.period,name:mis_builder_demo.mis_report_instance_expenses_act_mm2 113 | msgid "M-2" 114 | msgstr "" 115 | 116 | #. module: mis_builder_demo 117 | #: model:ir.model,name:mis_builder_demo.model_mis_committed_purchase 118 | msgid "MIS Commitment" 119 | msgstr "" 120 | 121 | #. module: mis_builder_demo 122 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__name 123 | msgid "Name" 124 | msgstr "" 125 | 126 | #. module: mis_builder_demo 127 | #: model:mis.report.kpi,description:mis_builder_demo.mis_report_expenses_kpi_other 128 | msgid "Other" 129 | msgstr "" 130 | 131 | #. module: mis_builder_demo 132 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__res_id 133 | msgid "Resource ID" 134 | msgstr "" 135 | 136 | #. module: mis_builder_demo 137 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__res_model 138 | msgid "Resource Model Name" 139 | msgstr "" 140 | 141 | #. module: mis_builder_demo 142 | #: model:mis.report.kpi,description:mis_builder_demo.mis_report_expenses_kpi_total 143 | msgid "Total" 144 | msgstr "" 145 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | exclude: | 2 | (?x) 3 | # NOT INSTALLABLE ADDONS 4 | # END NOT INSTALLABLE ADDONS 5 | # Files and folders generated by bots, to avoid loops 6 | ^setup/|/static/description/index\.html$| 7 | # We don't want to mess with tool-generated files 8 | .svg$|/tests/([^/]+/)?cassettes/|^.copier-answers.yml$|^.github/| 9 | # Maybe reactivate this when all README files include prettier ignore tags? 10 | ^README\.md$| 11 | # Library files can have extraneous formatting (even minimized) 12 | /static/(src/)?lib/| 13 | # Repos using Sphinx to generate docs don't need prettying 14 | ^docs/_templates/.*\.html$| 15 | # You don't usually want a bot to modify your legal texts 16 | (LICENSE.*|COPYING.*) 17 | default_language_version: 18 | python: python3 19 | node: "14.13.0" 20 | repos: 21 | - repo: local 22 | hooks: 23 | # These files are most likely copier diff rejection junks; if found, 24 | # review them manually, fix the problem (if needed) and remove them 25 | - id: forbidden-files 26 | name: forbidden files 27 | entry: found forbidden files; remove them 28 | language: fail 29 | files: "\\.rej$" 30 | - repo: https://github.com/oca/maintainer-tools 31 | rev: ab1d7f6 32 | hooks: 33 | # update the NOT INSTALLABLE ADDONS section above 34 | - id: oca-update-pre-commit-excluded-addons 35 | - id: oca-fix-manifest-website 36 | args: ["https://github.com/OCA/mis-builder"] 37 | - repo: https://github.com/myint/autoflake 38 | rev: v1.4 39 | hooks: 40 | - id: autoflake 41 | args: 42 | - --expand-star-imports 43 | - --ignore-init-module-imports 44 | - --in-place 45 | - --remove-all-unused-imports 46 | - --remove-duplicate-keys 47 | - --remove-unused-variables 48 | - repo: https://github.com/psf/black 49 | rev: 20.8b1 50 | hooks: 51 | - id: black 52 | - repo: https://github.com/pre-commit/mirrors-prettier 53 | rev: v2.1.2 54 | hooks: 55 | - id: prettier 56 | name: prettier (with plugin-xml) 57 | additional_dependencies: 58 | - "prettier@2.1.2" 59 | - "@prettier/plugin-xml@0.12.0" 60 | args: 61 | - --plugin=@prettier/plugin-xml 62 | files: \.(css|htm|html|js|json|jsx|less|md|scss|toml|ts|xml|yaml|yml)$ 63 | - repo: https://github.com/pre-commit/mirrors-eslint 64 | rev: v7.8.1 65 | hooks: 66 | - id: eslint 67 | verbose: true 68 | args: 69 | - --color 70 | - --fix 71 | - repo: https://github.com/pre-commit/pre-commit-hooks 72 | rev: v3.2.0 73 | hooks: 74 | - id: trailing-whitespace 75 | # exclude autogenerated files 76 | exclude: /README\.rst$|\.pot?$ 77 | - id: end-of-file-fixer 78 | # exclude autogenerated files 79 | exclude: /README\.rst$|\.pot?$ 80 | - id: debug-statements 81 | - id: fix-encoding-pragma 82 | args: ["--remove"] 83 | - id: check-case-conflict 84 | - id: check-docstring-first 85 | - id: check-executables-have-shebangs 86 | - id: check-merge-conflict 87 | # exclude files where underlines are not distinguishable from merge conflicts 88 | exclude: /README\.rst$|^docs/.*\.rst$ 89 | - id: check-symlinks 90 | - id: check-xml 91 | - id: mixed-line-ending 92 | args: ["--fix=lf"] 93 | - repo: https://github.com/asottile/pyupgrade 94 | rev: v2.7.2 95 | hooks: 96 | - id: pyupgrade 97 | args: ["--keep-percent-format"] 98 | - repo: https://github.com/PyCQA/isort 99 | rev: 5.5.1 100 | hooks: 101 | - id: isort 102 | name: isort except __init__.py 103 | args: 104 | - --settings=. 105 | exclude: /__init__\.py$ 106 | - repo: https://github.com/acsone/setuptools-odoo 107 | rev: 2.6.0 108 | hooks: 109 | - id: setuptools-odoo-make-default 110 | - id: setuptools-odoo-get-requirements 111 | args: 112 | - --output 113 | - requirements.txt 114 | - --header 115 | - "# generated from manifests external_dependencies" 116 | - repo: https://gitlab.com/PyCQA/flake8 117 | rev: 3.8.3 118 | hooks: 119 | - id: flake8 120 | name: flake8 121 | additional_dependencies: ["flake8-bugbear==20.1.4"] 122 | - repo: https://github.com/PyCQA/pylint 123 | rev: pylint-2.5.3 124 | hooks: 125 | - id: pylint 126 | name: pylint with optional checks 127 | args: 128 | - --rcfile=.pylintrc 129 | - --exit-zero 130 | verbose: true 131 | additional_dependencies: &pylint_deps 132 | - pylint-odoo==3.5.0 133 | - id: pylint 134 | name: pylint with mandatory checks 135 | args: 136 | - --rcfile=.pylintrc-mandatory 137 | additional_dependencies: *pylint_deps 138 | -------------------------------------------------------------------------------- /mis_builder_budget/tests/test_mis_budget_by_account.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 ACSONE SA/NV () 2 | # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). 3 | 4 | from odoo.tests.common import SavepointCase 5 | 6 | from odoo.addons.mis_builder.models.expression_evaluator import ExpressionEvaluator 7 | from odoo.addons.mis_builder.tests.common import assert_matrix 8 | 9 | from ..models.mis_report_instance_period import SRC_MIS_BUDGET_BY_ACCOUNT 10 | 11 | 12 | class TestMisBudgetByAccount(SavepointCase): 13 | @classmethod 14 | def setUpClass(cls): 15 | super(TestMisBudgetByAccount, cls).setUpClass() 16 | # create account 17 | account = cls.env["account.account"].create( 18 | dict( 19 | name="test account", 20 | code="1", 21 | user_type_id=cls.env.ref("account.data_account_type_revenue").id, 22 | ) 23 | ) 24 | # create report 25 | cls.report = cls.env["mis.report"].create(dict(name="test report")) 26 | cls.kpi1 = cls.env["mis.report.kpi"].create( 27 | dict( 28 | report_id=cls.report.id, 29 | name="k1", 30 | description="kpi 1", 31 | expression="balp[('id', '=', {})]".format(account.id), 32 | ) 33 | ) 34 | # budget 35 | cls.budget = cls.env["mis.budget.by.account"].create( 36 | dict( 37 | name="the budget", 38 | date_from="2017-01-01", 39 | date_to="2017-12-31", 40 | company_id=cls.env.ref("base.main_company").id, 41 | item_ids=[ 42 | ( 43 | 0, 44 | 0, 45 | dict( 46 | account_id=account.id, 47 | date_from="2017-01-01", 48 | date_to="2017-01-08", 49 | debit=11, 50 | ), 51 | ), 52 | ( 53 | 0, 54 | 0, 55 | dict( 56 | account_id=account.id, 57 | date_from="2017-01-09", 58 | date_to="2017-01-16", 59 | debit=13, 60 | ), 61 | ), 62 | ], 63 | ) 64 | ) 65 | 66 | def test_basic(self): 67 | """ Sum all budget items in period """ 68 | aep = self.report._prepare_aep(self.env.ref("base.main_company")) 69 | ee = ExpressionEvaluator( 70 | aep=aep, 71 | date_from="2017-01-01", 72 | date_to="2017-01-16", 73 | aml_model="mis.budget.by.account.item", 74 | ) 75 | d = self.report._evaluate(ee) 76 | assert d["k1"] == 24.0 77 | 78 | def test_one_item(self): 79 | aep = self.report._prepare_aep(self.env.ref("base.main_company")) 80 | ee = ExpressionEvaluator( 81 | aep=aep, 82 | date_from="2017-01-01", 83 | date_to="2017-01-08", 84 | aml_model="mis.budget.by.account.item", 85 | ) 86 | d = self.report._evaluate(ee) 87 | assert d["k1"] == 11.0 88 | 89 | def test_one_item_and_prorata_second(self): 90 | aep = self.report._prepare_aep(self.env.ref("base.main_company")) 91 | ee = ExpressionEvaluator( 92 | aep=aep, 93 | date_from="2017-01-01", 94 | date_to="2017-01-12", 95 | aml_model="mis.budget.by.account.item", 96 | ) 97 | d = self.report._evaluate(ee) 98 | assert d["k1"] == 11.0 + 13.0 / 2 99 | 100 | def test_with_report_instance(self): 101 | instance = self.env["mis.report.instance"].create( 102 | dict(name="test instance", report_id=self.report.id, comparison_mode=True) 103 | ) 104 | self.pbud1 = self.env["mis.report.instance.period"].create( 105 | dict( 106 | name="pbud1", 107 | report_instance_id=instance.id, 108 | source=SRC_MIS_BUDGET_BY_ACCOUNT, 109 | source_mis_budget_by_account_id=self.budget.id, 110 | manual_date_from="2017-01-01", 111 | manual_date_to="2017-01-31", 112 | ) 113 | ) 114 | matrix = instance._compute_matrix() 115 | assert_matrix(matrix, [[24]]) 116 | 117 | def test_copy(self): 118 | budget2 = self.budget.copy() 119 | self.assertEqual(len(budget2.item_ids), len(self.budget.item_ids)) 120 | 121 | def test_workflow(self): 122 | self.assertEqual(self.budget.state, "draft") 123 | self.budget.action_confirm() 124 | self.assertEqual(self.budget.state, "confirmed") 125 | self.budget.action_cancel() 126 | self.assertEqual(self.budget.state, "cancelled") 127 | self.budget.action_draft() 128 | self.assertEqual(self.budget.state, "draft") 129 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | browser: true 3 | es6: true 4 | 5 | # See https://github.com/OCA/odoo-community.org/issues/37#issuecomment-470686449 6 | parserOptions: 7 | ecmaVersion: 2017 8 | 9 | # Globals available in Odoo that shouldn't produce errorings 10 | globals: 11 | _: readonly 12 | $: readonly 13 | fuzzy: readonly 14 | jQuery: readonly 15 | moment: readonly 16 | odoo: readonly 17 | openerp: readonly 18 | owl: readonly 19 | 20 | # Styling is handled by Prettier, so we only need to enable AST rules; 21 | # see https://github.com/OCA/maintainer-quality-tools/pull/618#issuecomment-558576890 22 | rules: 23 | accessor-pairs: warn 24 | array-callback-return: warn 25 | callback-return: warn 26 | capitalized-comments: 27 | - warn 28 | - always 29 | - ignoreConsecutiveComments: true 30 | ignoreInlineComments: true 31 | complexity: 32 | - warn 33 | - 15 34 | constructor-super: warn 35 | dot-notation: warn 36 | eqeqeq: warn 37 | global-require: warn 38 | handle-callback-err: warn 39 | id-blacklist: warn 40 | id-match: warn 41 | init-declarations: error 42 | max-depth: warn 43 | max-nested-callbacks: warn 44 | max-statements-per-line: warn 45 | no-alert: warn 46 | no-array-constructor: warn 47 | no-caller: warn 48 | no-case-declarations: warn 49 | no-class-assign: warn 50 | no-cond-assign: error 51 | no-const-assign: error 52 | no-constant-condition: warn 53 | no-control-regex: warn 54 | no-debugger: error 55 | no-delete-var: warn 56 | no-div-regex: warn 57 | no-dupe-args: error 58 | no-dupe-class-members: error 59 | no-dupe-keys: error 60 | no-duplicate-case: error 61 | no-duplicate-imports: error 62 | no-else-return: warn 63 | no-empty-character-class: warn 64 | no-empty-function: error 65 | no-empty-pattern: error 66 | no-empty: warn 67 | no-eq-null: error 68 | no-eval: error 69 | no-ex-assign: error 70 | no-extend-native: warn 71 | no-extra-bind: warn 72 | no-extra-boolean-cast: warn 73 | no-extra-label: warn 74 | no-fallthrough: warn 75 | no-func-assign: error 76 | no-global-assign: error 77 | no-implicit-coercion: 78 | - warn 79 | - allow: ["~"] 80 | no-implicit-globals: warn 81 | no-implied-eval: warn 82 | no-inline-comments: warn 83 | no-inner-declarations: warn 84 | no-invalid-regexp: warn 85 | no-irregular-whitespace: warn 86 | no-iterator: warn 87 | no-label-var: warn 88 | no-labels: warn 89 | no-lone-blocks: warn 90 | no-lonely-if: error 91 | no-mixed-requires: error 92 | no-multi-str: warn 93 | no-native-reassign: error 94 | no-negated-condition: warn 95 | no-negated-in-lhs: error 96 | no-new-func: warn 97 | no-new-object: warn 98 | no-new-require: warn 99 | no-new-symbol: warn 100 | no-new-wrappers: warn 101 | no-new: warn 102 | no-obj-calls: warn 103 | no-octal-escape: warn 104 | no-octal: warn 105 | no-param-reassign: warn 106 | no-path-concat: warn 107 | no-process-env: warn 108 | no-process-exit: warn 109 | no-proto: warn 110 | no-prototype-builtins: warn 111 | no-redeclare: warn 112 | no-regex-spaces: warn 113 | no-restricted-globals: warn 114 | no-restricted-imports: warn 115 | no-restricted-modules: warn 116 | no-restricted-syntax: warn 117 | no-return-assign: error 118 | no-script-url: warn 119 | no-self-assign: warn 120 | no-self-compare: warn 121 | no-sequences: warn 122 | no-shadow-restricted-names: warn 123 | no-shadow: warn 124 | no-sparse-arrays: warn 125 | no-sync: warn 126 | no-this-before-super: warn 127 | no-throw-literal: warn 128 | no-undef-init: warn 129 | no-undef: error 130 | no-unmodified-loop-condition: warn 131 | no-unneeded-ternary: error 132 | no-unreachable: error 133 | no-unsafe-finally: error 134 | no-unused-expressions: error 135 | no-unused-labels: error 136 | no-unused-vars: error 137 | no-use-before-define: error 138 | no-useless-call: warn 139 | no-useless-computed-key: warn 140 | no-useless-concat: warn 141 | no-useless-constructor: warn 142 | no-useless-escape: warn 143 | no-useless-rename: warn 144 | no-void: warn 145 | no-with: warn 146 | operator-assignment: [error, always] 147 | prefer-const: warn 148 | radix: warn 149 | require-yield: warn 150 | sort-imports: warn 151 | spaced-comment: [error, always] 152 | strict: [error, function] 153 | use-isnan: error 154 | valid-jsdoc: 155 | - warn 156 | - prefer: 157 | arg: param 158 | argument: param 159 | augments: extends 160 | constructor: class 161 | exception: throws 162 | func: function 163 | method: function 164 | prop: property 165 | return: returns 166 | virtual: abstract 167 | yield: yields 168 | preferType: 169 | array: Array 170 | bool: Boolean 171 | boolean: Boolean 172 | number: Number 173 | object: Object 174 | str: String 175 | string: String 176 | requireParamDescription: false 177 | requireReturn: false 178 | requireReturnDescription: false 179 | requireReturnType: false 180 | valid-typeof: warn 181 | yoda: warn 182 | -------------------------------------------------------------------------------- /mis_builder/models/accounting_none.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Thomas Binsfeld 2 | # Copyright 2016 ACSONE SA/NV () 3 | # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). 4 | """ 5 | Provides the AccountingNone singleton. 6 | 7 | AccountingNone is a null value that dissolves in basic arithmetic operations, 8 | as illustrated in the examples below. In comparisons, AccountingNone behaves 9 | the same as zero. 10 | 11 | >>> 1 + 1 12 | 2 13 | >>> 1 + AccountingNone 14 | 1 15 | >>> AccountingNone + 1 16 | 1 17 | >>> AccountingNone + None 18 | AccountingNone 19 | >>> None + AccountingNone 20 | AccountingNone 21 | >>> +AccountingNone 22 | AccountingNone 23 | >>> -AccountingNone 24 | AccountingNone 25 | >>> -(AccountingNone) 26 | AccountingNone 27 | >>> AccountingNone - 1 28 | -1 29 | >>> 1 - AccountingNone 30 | 1 31 | >>> abs(AccountingNone) 32 | AccountingNone 33 | >>> AccountingNone - None 34 | AccountingNone 35 | >>> None - AccountingNone 36 | AccountingNone 37 | >>> AccountingNone / 2 38 | 0.0 39 | >>> 2 / AccountingNone 40 | Traceback (most recent call last): 41 | ... 42 | ZeroDivisionError 43 | >>> AccountingNone / AccountingNone 44 | AccountingNone 45 | >>> AccountingNone // 2 46 | 0.0 47 | >>> 2 // AccountingNone 48 | Traceback (most recent call last): 49 | ... 50 | ZeroDivisionError 51 | >>> AccountingNone // AccountingNone 52 | AccountingNone 53 | >>> AccountingNone * 2 54 | 0.0 55 | >>> 2 * AccountingNone 56 | 0.0 57 | >>> AccountingNone * AccountingNone 58 | AccountingNone 59 | >>> AccountingNone * None 60 | AccountingNone 61 | >>> None * AccountingNone 62 | AccountingNone 63 | >>> str(AccountingNone) 64 | '' 65 | >>> bool(AccountingNone) 66 | False 67 | >>> AccountingNone > 0 68 | False 69 | >>> AccountingNone < 0 70 | False 71 | >>> AccountingNone < 1 72 | True 73 | >>> AccountingNone > 1 74 | False 75 | >>> 0 < AccountingNone 76 | False 77 | >>> 0 > AccountingNone 78 | False 79 | >>> 1 < AccountingNone 80 | False 81 | >>> 1 > AccountingNone 82 | True 83 | >>> AccountingNone == 0 84 | True 85 | >>> AccountingNone == 0.0 86 | True 87 | >>> AccountingNone == None 88 | True 89 | >>> AccountingNone >= AccountingNone 90 | True 91 | >>> AccountingNone <= AccountingNone 92 | True 93 | >>> round(AccountingNone, 2) 94 | 0.0 95 | >>> float(AccountingNone) 96 | 0.0 97 | >>> int(AccountingNone) 98 | 0 99 | """ 100 | 101 | __all__ = ["AccountingNone"] 102 | 103 | 104 | class AccountingNoneType(object): 105 | def __add__(self, other): 106 | if other is None: 107 | return AccountingNone 108 | return other 109 | 110 | __radd__ = __add__ 111 | 112 | def __sub__(self, other): 113 | if other is None: 114 | return AccountingNone 115 | return -other 116 | 117 | def __rsub__(self, other): 118 | if other is None: 119 | return AccountingNone 120 | return other 121 | 122 | def __iadd__(self, other): 123 | if other is None: 124 | return AccountingNone 125 | return other 126 | 127 | def __isub__(self, other): 128 | if other is None: 129 | return AccountingNone 130 | return -other 131 | 132 | def __abs__(self): 133 | return self 134 | 135 | def __pos__(self): 136 | return self 137 | 138 | def __neg__(self): 139 | return self 140 | 141 | def __div__(self, other): 142 | if other is AccountingNone: 143 | return AccountingNone 144 | return 0.0 145 | 146 | def __rdiv__(self, other): 147 | raise ZeroDivisionError 148 | 149 | def __floordiv__(self, other): 150 | if other is AccountingNone: 151 | return AccountingNone 152 | return 0.0 153 | 154 | def __rfloordiv__(self, other): 155 | raise ZeroDivisionError 156 | 157 | def __truediv__(self, other): 158 | if other is AccountingNone: 159 | return AccountingNone 160 | return 0.0 161 | 162 | def __rtruediv__(self, other): 163 | raise ZeroDivisionError 164 | 165 | def __mul__(self, other): 166 | if other is None or other is AccountingNone: 167 | return AccountingNone 168 | return 0.0 169 | 170 | __rmul__ = __mul__ 171 | 172 | def __repr__(self): 173 | return "AccountingNone" 174 | 175 | def __str__(self): 176 | return "" 177 | 178 | def __nonzero__(self): 179 | return False 180 | 181 | def __bool__(self): 182 | return False 183 | 184 | def __eq__(self, other): 185 | return other == 0 or other is None or other is AccountingNone 186 | 187 | def __lt__(self, other): 188 | return other > 0 189 | 190 | def __gt__(self, other): 191 | return other < 0 192 | 193 | def __le__(self, other): 194 | return other >= 0 195 | 196 | def __ge__(self, other): 197 | return other <= 0 198 | 199 | def __float__(self): 200 | return 0.0 201 | 202 | def __int__(self): 203 | return 0 204 | 205 | def __round__(self, ndigits): 206 | return 0.0 207 | 208 | 209 | AccountingNone = AccountingNoneType() 210 | 211 | 212 | if __name__ == "__main__": # pragma: no cover 213 | import doctest 214 | 215 | doctest.testmod() 216 | -------------------------------------------------------------------------------- /mis_builder_demo/data/mis_report_instance.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | Demo Expenses vs Budget 7 | 8 | 9 | 10 | 11 | 3 M Budget 12 | 13 | mis_budget 14 | 15 | relative 16 | m 17 | -2 18 | 3 19 | 10 20 | 21 | 22 | Committed 23 | 24 | actuals_alt 25 | 26 | relative 27 | m 28 | -2 29 | 3 30 | 20 31 | 32 | 36 | M-2 37 | 38 | actuals 39 | relative 40 | m 41 | -2 42 | 30 43 | 44 | 48 | M-1 49 | 50 | actuals 51 | relative 52 | m 53 | -1 54 | 40 55 | 56 | 57 | M 58 | 59 | actuals 60 | relative 61 | m 62 | 0 63 | 50 64 | 65 | 66 | Available 67 | 68 | sumcol 69 | none 70 | 71 | 75 | 76 | 77 | + 78 | 79 | 83 | 84 | 85 | - 86 | 87 | 91 | 92 | 93 | - 94 | 95 | 99 | 100 | 101 | - 102 | 103 | 107 | 108 | 109 | - 110 | 111 | 112 | -------------------------------------------------------------------------------- /mis_builder_demo/i18n/pt.po: -------------------------------------------------------------------------------- 1 | # Translation of Odoo Server. 2 | # This file contains the translation of the following modules: 3 | # * mis_builder_demo 4 | # 5 | # Translators: 6 | # OCA Transbot , 2018 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: Odoo Server 11.0\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2018-01-13 15:37+0000\n" 12 | "PO-Revision-Date: 2018-01-13 15:37+0000\n" 13 | "Last-Translator: OCA Transbot , 2018\n" 14 | "Language-Team: Portuguese (https://www.transifex.com/oca/teams/23907/pt/)\n" 15 | "Language: pt\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: \n" 19 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 20 | 21 | #. module: mis_builder_demo 22 | #: model:mis.report.instance.period,name:mis_builder_demo.mis_report_instance_expenses_bud 23 | msgid "3 M Budget" 24 | msgstr "" 25 | 26 | #. module: mis_builder_demo 27 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__account_id 28 | msgid "Account" 29 | msgstr "" 30 | 31 | #. module: mis_builder_demo 32 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__analytic_account_id 33 | msgid "Analytic Account" 34 | msgstr "" 35 | 36 | #. module: mis_builder_demo 37 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__analytic_tag_ids 38 | msgid "Analytic Tags" 39 | msgstr "" 40 | 41 | #. module: mis_builder_demo 42 | #: model:mis.report.instance.period,name:mis_builder_demo.mis_report_instance_expenses_avail 43 | msgid "Available" 44 | msgstr "" 45 | 46 | #. module: mis_builder_demo 47 | #: model:mis.report.instance.period,name:mis_builder_demo.mis_report_instance_expenses_commit 48 | msgid "Committed" 49 | msgstr "" 50 | 51 | #. module: mis_builder_demo 52 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__company_id 53 | msgid "Company" 54 | msgstr "Empresa" 55 | 56 | #. module: mis_builder_demo 57 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__credit 58 | msgid "Credit" 59 | msgstr "" 60 | 61 | #. module: mis_builder_demo 62 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__date 63 | msgid "Date" 64 | msgstr "" 65 | 66 | #. module: mis_builder_demo 67 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__debit 68 | msgid "Debit" 69 | msgstr "" 70 | 71 | #. module: mis_builder_demo 72 | #: model:mis.report,name:mis_builder_demo.mis_report_expenses 73 | msgid "Demo Expenses" 74 | msgstr "" 75 | 76 | #. module: mis_builder_demo 77 | #: model:mis.report.instance,name:mis_builder_demo.mis_report_instance_expenses 78 | msgid "Demo Expenses vs Budget" 79 | msgstr "" 80 | 81 | #. module: mis_builder_demo 82 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__display_name 83 | msgid "Display Name" 84 | msgstr "Nome a exibir" 85 | 86 | #. module: mis_builder_demo 87 | #: model:mis.report.kpi,description:mis_builder_demo.mis_report_expenses_kpi_equip 88 | msgid "Equipment" 89 | msgstr "" 90 | 91 | #. module: mis_builder_demo 92 | #: model:mis.report.kpi,description:mis_builder_demo.mis_report_expenses_kpi_exp 93 | msgid "Expenses" 94 | msgstr "" 95 | 96 | #. module: mis_builder_demo 97 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__id 98 | msgid "ID" 99 | msgstr "ID" 100 | 101 | #. module: mis_builder_demo 102 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase____last_update 103 | msgid "Last Modified on" 104 | msgstr "Última Modificação em" 105 | 106 | #. module: mis_builder_demo 107 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__line_type 108 | msgid "Line Type" 109 | msgstr "" 110 | 111 | #. module: mis_builder_demo 112 | #: model:mis.report.instance.period,name:mis_builder_demo.mis_report_instance_expenses_act_mm1 113 | msgid "M-1" 114 | msgstr "" 115 | 116 | #. module: mis_builder_demo 117 | #: model:mis.report.instance.period,name:mis_builder_demo.mis_report_instance_expenses_act_mm2 118 | msgid "M-2" 119 | msgstr "" 120 | 121 | #. module: mis_builder_demo 122 | #: model:ir.model,name:mis_builder_demo.model_mis_committed_purchase 123 | msgid "MIS Commitment" 124 | msgstr "" 125 | 126 | #. module: mis_builder_demo 127 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__name 128 | msgid "Name" 129 | msgstr "Nome" 130 | 131 | #. module: mis_builder_demo 132 | #: model:mis.report.kpi,description:mis_builder_demo.mis_report_expenses_kpi_other 133 | msgid "Other" 134 | msgstr "" 135 | 136 | #. module: mis_builder_demo 137 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__res_id 138 | msgid "Resource ID" 139 | msgstr "" 140 | 141 | #. module: mis_builder_demo 142 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__res_model 143 | msgid "Resource Model Name" 144 | msgstr "" 145 | 146 | #. module: mis_builder_demo 147 | #: model:mis.report.kpi,description:mis_builder_demo.mis_report_expenses_kpi_total 148 | msgid "Total" 149 | msgstr "" 150 | -------------------------------------------------------------------------------- /mis_builder_demo/i18n/nl.po: -------------------------------------------------------------------------------- 1 | # Translation of Odoo Server. 2 | # This file contains the translation of the following modules: 3 | # * mis_builder_demo 4 | # 5 | # Translators: 6 | # Frank Schellenberg , 2018 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: Odoo Server 11.0\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2018-01-13 15:37+0000\n" 12 | "PO-Revision-Date: 2018-01-13 15:37+0000\n" 13 | "Last-Translator: Frank Schellenberg , 2018\n" 14 | "Language-Team: Dutch (https://www.transifex.com/oca/teams/23907/nl/)\n" 15 | "Language: nl\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: \n" 19 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 20 | 21 | #. module: mis_builder_demo 22 | #: model:mis.report.instance.period,name:mis_builder_demo.mis_report_instance_expenses_bud 23 | msgid "3 M Budget" 24 | msgstr "" 25 | 26 | #. module: mis_builder_demo 27 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__account_id 28 | msgid "Account" 29 | msgstr "" 30 | 31 | #. module: mis_builder_demo 32 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__analytic_account_id 33 | msgid "Analytic Account" 34 | msgstr "" 35 | 36 | #. module: mis_builder_demo 37 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__analytic_tag_ids 38 | msgid "Analytic Tags" 39 | msgstr "" 40 | 41 | #. module: mis_builder_demo 42 | #: model:mis.report.instance.period,name:mis_builder_demo.mis_report_instance_expenses_avail 43 | msgid "Available" 44 | msgstr "" 45 | 46 | #. module: mis_builder_demo 47 | #: model:mis.report.instance.period,name:mis_builder_demo.mis_report_instance_expenses_commit 48 | msgid "Committed" 49 | msgstr "" 50 | 51 | #. module: mis_builder_demo 52 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__company_id 53 | msgid "Company" 54 | msgstr "Bedrijf" 55 | 56 | #. module: mis_builder_demo 57 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__credit 58 | msgid "Credit" 59 | msgstr "" 60 | 61 | #. module: mis_builder_demo 62 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__date 63 | msgid "Date" 64 | msgstr "" 65 | 66 | #. module: mis_builder_demo 67 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__debit 68 | msgid "Debit" 69 | msgstr "" 70 | 71 | #. module: mis_builder_demo 72 | #: model:mis.report,name:mis_builder_demo.mis_report_expenses 73 | msgid "Demo Expenses" 74 | msgstr "" 75 | 76 | #. module: mis_builder_demo 77 | #: model:mis.report.instance,name:mis_builder_demo.mis_report_instance_expenses 78 | msgid "Demo Expenses vs Budget" 79 | msgstr "" 80 | 81 | #. module: mis_builder_demo 82 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__display_name 83 | msgid "Display Name" 84 | msgstr "Weergavenaam" 85 | 86 | #. module: mis_builder_demo 87 | #: model:mis.report.kpi,description:mis_builder_demo.mis_report_expenses_kpi_equip 88 | msgid "Equipment" 89 | msgstr "" 90 | 91 | #. module: mis_builder_demo 92 | #: model:mis.report.kpi,description:mis_builder_demo.mis_report_expenses_kpi_exp 93 | msgid "Expenses" 94 | msgstr "" 95 | 96 | #. module: mis_builder_demo 97 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__id 98 | msgid "ID" 99 | msgstr "ID" 100 | 101 | #. module: mis_builder_demo 102 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase____last_update 103 | msgid "Last Modified on" 104 | msgstr "Voor het laatst aangepast op" 105 | 106 | #. module: mis_builder_demo 107 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__line_type 108 | msgid "Line Type" 109 | msgstr "" 110 | 111 | #. module: mis_builder_demo 112 | #: model:mis.report.instance.period,name:mis_builder_demo.mis_report_instance_expenses_act_mm1 113 | msgid "M-1" 114 | msgstr "" 115 | 116 | #. module: mis_builder_demo 117 | #: model:mis.report.instance.period,name:mis_builder_demo.mis_report_instance_expenses_act_mm2 118 | msgid "M-2" 119 | msgstr "" 120 | 121 | #. module: mis_builder_demo 122 | #: model:ir.model,name:mis_builder_demo.model_mis_committed_purchase 123 | msgid "MIS Commitment" 124 | msgstr "" 125 | 126 | #. module: mis_builder_demo 127 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__name 128 | msgid "Name" 129 | msgstr "Naam" 130 | 131 | #. module: mis_builder_demo 132 | #: model:mis.report.kpi,description:mis_builder_demo.mis_report_expenses_kpi_other 133 | msgid "Other" 134 | msgstr "" 135 | 136 | #. module: mis_builder_demo 137 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__res_id 138 | msgid "Resource ID" 139 | msgstr "" 140 | 141 | #. module: mis_builder_demo 142 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__res_model 143 | msgid "Resource Model Name" 144 | msgstr "" 145 | 146 | #. module: mis_builder_demo 147 | #: model:mis.report.kpi,description:mis_builder_demo.mis_report_expenses_kpi_total 148 | msgid "Total" 149 | msgstr "" 150 | -------------------------------------------------------------------------------- /mis_builder/tests/test_analytic_filters.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 ACSONE SA/NV () 2 | # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). 3 | 4 | from odoo.tests.common import TransactionCase 5 | 6 | 7 | class TestAnalyticFilters(TransactionCase): 8 | def setUp(self): 9 | super(TestAnalyticFilters, self).setUp() 10 | self.aag = self.env["account.analytic.group"].search([], limit=1) 11 | 12 | def test_context_with_filters(self): 13 | aaa = self.env["account.analytic.account"].search([], limit=1) 14 | mri = self.env["mis.report.instance"].new() 15 | mri.analytic_account_id = False 16 | mri.analytic_group_id = False 17 | assert mri._context_with_filters().get("mis_report_filters") == {} 18 | mri.analytic_account_id = aaa 19 | mri.analytic_group_id = self.aag 20 | assert mri._context_with_filters().get("mis_report_filters") == { 21 | "analytic_account_id": {"value": aaa.id, "operator": "="}, 22 | "analytic_account_id.group_id": {"value": self.aag.id, "operator": "="}, 23 | } 24 | # test _context_with_filters does nothing is a filter is already 25 | # in the context 26 | mri.with_context(mis_report_filters={"f": 1})._context_with_filters().get( 27 | "mis_report_filters" 28 | ) == {"f": 1} 29 | 30 | def _check_get_filter_domain_from_context( 31 | self, mis_report_filters, expected_domain 32 | ): 33 | domain = ( 34 | self.env["mis.report.instance.period"] 35 | .with_context(mis_report_filters=mis_report_filters) 36 | ._get_filter_domain_from_context() 37 | ) 38 | assert domain == expected_domain 39 | 40 | def _check_get_filter_descriptions_from_context( 41 | self, mis_report_filters, expected_domain 42 | ): 43 | filter_descriptions = ( 44 | self.env["mis.report.instance"] 45 | .with_context(mis_report_filters=mis_report_filters) 46 | .get_filter_descriptions_from_context() 47 | ) 48 | assert filter_descriptions == expected_domain 49 | 50 | def test_get_filter_domain_from_context_1(self): 51 | # no filter, no domain 52 | self._check_get_filter_domain_from_context({}, []) 53 | # the most basic analytic account filter (default operator is =) 54 | self._check_get_filter_domain_from_context( 55 | {"analytic_account_id": {"value": 1}}, [("analytic_account_id", "=", 1)] 56 | ) 57 | self._check_get_filter_domain_from_context( 58 | {"analytic_group_id": {"value": 1}}, [("analytic_group_id", "=", 1)] 59 | ) 60 | # custom operator 61 | self._check_get_filter_domain_from_context( 62 | {"analytic_account_id": {"value": 1, "operator": "!="}}, 63 | [("analytic_account_id", "!=", 1)], 64 | ) 65 | self._check_get_filter_domain_from_context( 66 | {"analytic_group_id": {"value": 1, "operator": "!="}}, 67 | [("analytic_group_id", "!=", 1)], 68 | ) 69 | # any field name works 70 | self._check_get_filter_domain_from_context( 71 | {"some_field": {"value": "x"}}, [("some_field", "=", "x")] 72 | ) 73 | # filter name without value => no domain 74 | self._check_get_filter_domain_from_context({"some_field": None}, []) 75 | # "is not set" filter must work 76 | self._check_get_filter_domain_from_context( 77 | {"analytic_account_id": {"value": False}}, 78 | [("analytic_account_id", "=", False)], 79 | ) 80 | self._check_get_filter_domain_from_context( 81 | {"analytic_group_id": {"value": False}}, 82 | [("analytic_group_id", "=", False)], 83 | ) 84 | # Filter from analytic account filter widget 85 | self._check_get_filter_domain_from_context( 86 | {"analytic_account_id": {"value": 1, "operator": "all"}}, 87 | [("analytic_account_id", "in", [1])], 88 | ) 89 | 90 | # Filter from analytic group filter widget 91 | self._check_get_filter_domain_from_context( 92 | {"analytic_group_id": {"value": 1, "operator": "all"}}, 93 | [("analytic_group_id", "in", [1])], 94 | ) 95 | 96 | # Filter from analytic tags filter widget 97 | self._check_get_filter_domain_from_context( 98 | {"analytic_tag_ids": {"value": [1, 2], "operator": "all"}}, 99 | [("analytic_tag_ids", "in", [1]), ("analytic_tag_ids", "in", [2])], 100 | ) 101 | 102 | def test_get_filter_descriptions_from_context_1(self): 103 | self._check_get_filter_descriptions_from_context( 104 | {"analytic_account_id.group_id": {"value": self.aag.id}}, 105 | ["Analytic Account Group: %s" % self.aag.display_name], 106 | ) 107 | 108 | def test_get_additional_move_line_filter_with_analytic_group(self): 109 | instance_period = self.env["mis.report.instance.period"].new( 110 | { 111 | "analytic_group_id": self.aag.id, 112 | } 113 | ) 114 | domain = instance_period._get_additional_move_line_filter() 115 | assert domain == [("analytic_account_id.group_id", "=", self.aag.id)] 116 | -------------------------------------------------------------------------------- /mis_builder_demo/i18n/de.po: -------------------------------------------------------------------------------- 1 | # Translation of Odoo Server. 2 | # This file contains the translation of the following modules: 3 | # * mis_builder_demo 4 | # 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: Odoo Server 11.0\n" 8 | "Report-Msgid-Bugs-To: \n" 9 | "PO-Revision-Date: 2019-02-14 20:35+0000\n" 10 | "Last-Translator: Thorsten Vocks \n" 11 | "Language-Team: none\n" 12 | "Language: de\n" 13 | "MIME-Version: 1.0\n" 14 | "Content-Type: text/plain; charset=UTF-8\n" 15 | "Content-Transfer-Encoding: \n" 16 | "Plural-Forms: nplurals=2; plural=n != 1;\n" 17 | "X-Generator: Weblate 3.4\n" 18 | 19 | #. module: mis_builder_demo 20 | #: model:mis.report.instance.period,name:mis_builder_demo.mis_report_instance_expenses_bud 21 | #, fuzzy 22 | msgid "3 M Budget" 23 | msgstr "3 M Budget" 24 | 25 | #. module: mis_builder_demo 26 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__account_id 27 | msgid "Account" 28 | msgstr "Konto" 29 | 30 | #. module: mis_builder_demo 31 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__analytic_account_id 32 | msgid "Analytic Account" 33 | msgstr "Kostenstelle" 34 | 35 | #. module: mis_builder_demo 36 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__analytic_tag_ids 37 | #, fuzzy 38 | msgid "Analytic Tags" 39 | msgstr "Kostenstelle" 40 | 41 | #. module: mis_builder_demo 42 | #: model:mis.report.instance.period,name:mis_builder_demo.mis_report_instance_expenses_avail 43 | msgid "Available" 44 | msgstr "Überschuss" 45 | 46 | #. module: mis_builder_demo 47 | #: model:mis.report.instance.period,name:mis_builder_demo.mis_report_instance_expenses_commit 48 | msgid "Committed" 49 | msgstr "Genehmigt" 50 | 51 | #. module: mis_builder_demo 52 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__company_id 53 | msgid "Company" 54 | msgstr "Unternehmen" 55 | 56 | #. module: mis_builder_demo 57 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__credit 58 | msgid "Credit" 59 | msgstr "Haben" 60 | 61 | #. module: mis_builder_demo 62 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__date 63 | msgid "Date" 64 | msgstr "Datum" 65 | 66 | #. module: mis_builder_demo 67 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__debit 68 | msgid "Debit" 69 | msgstr "Soll" 70 | 71 | #. module: mis_builder_demo 72 | #: model:mis.report,name:mis_builder_demo.mis_report_expenses 73 | msgid "Demo Expenses" 74 | msgstr "Beispiel Ausgaben" 75 | 76 | #. module: mis_builder_demo 77 | #: model:mis.report.instance,name:mis_builder_demo.mis_report_instance_expenses 78 | msgid "Demo Expenses vs Budget" 79 | msgstr "Demo Ausgaben vs. Budget" 80 | 81 | #. module: mis_builder_demo 82 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__display_name 83 | msgid "Display Name" 84 | msgstr "Bezeichnung" 85 | 86 | #. module: mis_builder_demo 87 | #: model:mis.report.kpi,description:mis_builder_demo.mis_report_expenses_kpi_equip 88 | msgid "Equipment" 89 | msgstr "" 90 | 91 | #. module: mis_builder_demo 92 | #: model:mis.report.kpi,description:mis_builder_demo.mis_report_expenses_kpi_exp 93 | msgid "Expenses" 94 | msgstr "Ausgaben" 95 | 96 | #. module: mis_builder_demo 97 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__id 98 | msgid "ID" 99 | msgstr "ID" 100 | 101 | #. module: mis_builder_demo 102 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase____last_update 103 | msgid "Last Modified on" 104 | msgstr "Geändert am" 105 | 106 | #. module: mis_builder_demo 107 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__line_type 108 | msgid "Line Type" 109 | msgstr "Position Typ" 110 | 111 | #. module: mis_builder_demo 112 | #: model:mis.report.instance.period,name:mis_builder_demo.mis_report_instance_expenses_act_mm1 113 | msgid "M-1" 114 | msgstr "" 115 | 116 | #. module: mis_builder_demo 117 | #: model:mis.report.instance.period,name:mis_builder_demo.mis_report_instance_expenses_act_mm2 118 | msgid "M-2" 119 | msgstr "" 120 | 121 | #. module: mis_builder_demo 122 | #: model:ir.model,name:mis_builder_demo.model_mis_committed_purchase 123 | #, fuzzy 124 | msgid "MIS Commitment" 125 | msgstr "MIS genehmigte Einkäufe" 126 | 127 | #. module: mis_builder_demo 128 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__name 129 | msgid "Name" 130 | msgstr "Name" 131 | 132 | #. module: mis_builder_demo 133 | #: model:mis.report.kpi,description:mis_builder_demo.mis_report_expenses_kpi_other 134 | msgid "Other" 135 | msgstr "Sonstige" 136 | 137 | #. module: mis_builder_demo 138 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__res_id 139 | msgid "Resource ID" 140 | msgstr "" 141 | 142 | #. module: mis_builder_demo 143 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__res_model 144 | msgid "Resource Model Name" 145 | msgstr "" 146 | 147 | #. module: mis_builder_demo 148 | #: model:mis.report.kpi,description:mis_builder_demo.mis_report_expenses_kpi_total 149 | msgid "Total" 150 | msgstr "Gesamt" 151 | -------------------------------------------------------------------------------- /mis_builder_demo/i18n/ca.po: -------------------------------------------------------------------------------- 1 | # Translation of Odoo Server. 2 | # This file contains the translation of the following modules: 3 | # * mis_builder_demo 4 | # 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: Odoo Server 11.0\n" 8 | "Report-Msgid-Bugs-To: \n" 9 | "PO-Revision-Date: 2018-10-06 11:41+0000\n" 10 | "Last-Translator: Harald Panten \n" 11 | "Language-Team: none\n" 12 | "Language: ca\n" 13 | "MIME-Version: 1.0\n" 14 | "Content-Type: text/plain; charset=UTF-8\n" 15 | "Content-Transfer-Encoding: \n" 16 | "Plural-Forms: nplurals=2; plural=n != 1;\n" 17 | "X-Generator: Weblate 3.1.1\n" 18 | 19 | #. module: mis_builder_demo 20 | #: model:mis.report.instance.period,name:mis_builder_demo.mis_report_instance_expenses_bud 21 | msgid "3 M Budget" 22 | msgstr "3 M Pressupost" 23 | 24 | #. module: mis_builder_demo 25 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__account_id 26 | msgid "Account" 27 | msgstr "Compte" 28 | 29 | #. module: mis_builder_demo 30 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__analytic_account_id 31 | #, fuzzy 32 | msgid "Analytic Account" 33 | msgstr "Compte" 34 | 35 | #. module: mis_builder_demo 36 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__analytic_tag_ids 37 | #, fuzzy 38 | msgid "Analytic Tags" 39 | msgstr "Compte" 40 | 41 | #. module: mis_builder_demo 42 | #: model:mis.report.instance.period,name:mis_builder_demo.mis_report_instance_expenses_avail 43 | msgid "Available" 44 | msgstr "Disponible" 45 | 46 | #. module: mis_builder_demo 47 | #: model:mis.report.instance.period,name:mis_builder_demo.mis_report_instance_expenses_commit 48 | msgid "Committed" 49 | msgstr "Consignat" 50 | 51 | #. module: mis_builder_demo 52 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__company_id 53 | msgid "Company" 54 | msgstr "Companyia" 55 | 56 | #. module: mis_builder_demo 57 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__credit 58 | msgid "Credit" 59 | msgstr "Haver" 60 | 61 | #. module: mis_builder_demo 62 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__date 63 | msgid "Date" 64 | msgstr "Data" 65 | 66 | #. module: mis_builder_demo 67 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__debit 68 | msgid "Debit" 69 | msgstr "Deure" 70 | 71 | #. module: mis_builder_demo 72 | #: model:mis.report,name:mis_builder_demo.mis_report_expenses 73 | msgid "Demo Expenses" 74 | msgstr "Despeses de demostració" 75 | 76 | #. module: mis_builder_demo 77 | #: model:mis.report.instance,name:mis_builder_demo.mis_report_instance_expenses 78 | msgid "Demo Expenses vs Budget" 79 | msgstr "Despeses de demostració vs pressupost" 80 | 81 | #. module: mis_builder_demo 82 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__display_name 83 | msgid "Display Name" 84 | msgstr "Nom mostrat" 85 | 86 | #. module: mis_builder_demo 87 | #: model:mis.report.kpi,description:mis_builder_demo.mis_report_expenses_kpi_equip 88 | msgid "Equipment" 89 | msgstr "Equipament" 90 | 91 | #. module: mis_builder_demo 92 | #: model:mis.report.kpi,description:mis_builder_demo.mis_report_expenses_kpi_exp 93 | msgid "Expenses" 94 | msgstr "Despeses" 95 | 96 | #. module: mis_builder_demo 97 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__id 98 | msgid "ID" 99 | msgstr "ID" 100 | 101 | #. module: mis_builder_demo 102 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase____last_update 103 | msgid "Last Modified on" 104 | msgstr "Última modificació el" 105 | 106 | #. module: mis_builder_demo 107 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__line_type 108 | msgid "Line Type" 109 | msgstr "Tipus de línia" 110 | 111 | #. module: mis_builder_demo 112 | #: model:mis.report.instance.period,name:mis_builder_demo.mis_report_instance_expenses_act_mm1 113 | msgid "M-1" 114 | msgstr "M-1" 115 | 116 | #. module: mis_builder_demo 117 | #: model:mis.report.instance.period,name:mis_builder_demo.mis_report_instance_expenses_act_mm2 118 | msgid "M-2" 119 | msgstr "M-2" 120 | 121 | #. module: mis_builder_demo 122 | #: model:ir.model,name:mis_builder_demo.model_mis_committed_purchase 123 | #, fuzzy 124 | msgid "MIS Commitment" 125 | msgstr "MIS consignat a compra" 126 | 127 | #. module: mis_builder_demo 128 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__name 129 | msgid "Name" 130 | msgstr "Nom" 131 | 132 | #. module: mis_builder_demo 133 | #: model:mis.report.kpi,description:mis_builder_demo.mis_report_expenses_kpi_other 134 | msgid "Other" 135 | msgstr "Altre" 136 | 137 | #. module: mis_builder_demo 138 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__res_id 139 | msgid "Resource ID" 140 | msgstr "" 141 | 142 | #. module: mis_builder_demo 143 | #: model:ir.model.fields,field_description:mis_builder_demo.field_mis_committed_purchase__res_model 144 | msgid "Resource Model Name" 145 | msgstr "" 146 | 147 | #. module: mis_builder_demo 148 | #: model:mis.report.kpi,description:mis_builder_demo.mis_report_expenses_kpi_total 149 | msgid "Total" 150 | msgstr "Total" 151 | --------------------------------------------------------------------------------