diff --git a/.copier-answers.yml b/.copier-answers.yml index 5f43f2c30..85609caa8 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,11 +1,13 @@ # Do NOT update manually; changes here will be overwritten by Copier -_commit: v1.3.6 +_commit: v1.6.1 _src_path: git+https://github.com/OCA/oca-addons-repo-template -ci: Travis +ci: GitHub dependency_installation_mode: PIP generate_requirements_txt: true include_wkhtmltopdf: true odoo_version: 13.0 +org_name: Odoo Community Association (OCA) +org_slug: OCA rebel_module_groups: [] repo_description: 'This repository hosts alternative reporting engines to the ones included on Odoo core (RML, QWeb and Webkit). @@ -19,6 +21,7 @@ repo_description: 'This repository hosts alternative reporting engines to the on utils, checkers, signing tools and so on).' repo_name: OCA alternative reporting engines and reporting utilities for Odoo repo_slug: reporting-engine +repo_website: https://github.com/OCA/reporting-engine travis_apt_packages: - swig - libreoffice diff --git a/.eslintrc.yml b/.eslintrc.yml index d4cc423cc..9429bc688 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -4,7 +4,7 @@ env: # See https://github.com/OCA/odoo-community.org/issues/37#issuecomment-470686449 parserOptions: - ecmaVersion: 2017 + ecmaVersion: 2019 overrides: - files: diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 80ab1a211..aafa13887 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -2,7 +2,12 @@ name: pre-commit on: pull_request: + branches: + - "13.0*" push: + branches: + - "13.0" + - "13.0-ocabot-*" jobs: pre-commit: @@ -11,10 +16,22 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 with: - # The pylint-odoo version we use here does not support python 3.10 - # https://github.com/OCA/oca-addons-repo-template/issues/80 - # We also need to pin to an older version of python for older odoo versions - # where we are not using black > 21. Older black versions won't work with - # Python 3.9.8+, and we can't bump black without reformatting. - python-version: "3.9.7" - - uses: pre-commit/action@v2.0.0 + python-version: "3.8" + - name: Get python version + run: echo "PY=$(python -VV | sha256sum | cut -d' ' -f1)" >> $GITHUB_ENV + - uses: actions/cache@v1 + with: + path: ~/.cache/pre-commit + key: pre-commit|${{ env.PY }}|${{ hashFiles('.pre-commit-config.yaml') }} + - name: Install pre-commit + run: pip install pre-commit + - name: Run pre-commit + run: pre-commit run --all-files --show-diff-on-failure --color=always + - name: Check that all files generated by pre-commit are in git + run: | + newfiles="$(git ls-files --others --exclude-from=.gitignore)" + if [ "$newfiles" != "" ] ; then + echo "Please check-in the following files:" + echo "$newfiles" + exit 1 + fi diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..e7364499e --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,69 @@ +name: tests + +on: + pull_request: + branches: + - "13.0*" + push: + branches: + - "13.0" + - "13.0-ocabot-*" + +jobs: + unreleased-deps: + runs-on: ubuntu-latest + name: Detect unreleased dependencies + steps: + - uses: actions/checkout@v2 + - run: | + for reqfile in requirements.txt test-requirements.txt ; do + if [ -f ${reqfile} ] ; then + result=0 + # reject non-comment lines that contain a / (i.e. URLs, relative paths) + grep "^[^#].*/" ${reqfile} || result=$? + if [ $result -eq 0 ] ; then + echo "Unreleased dependencies found in ${reqfile}." + exit 1 + fi + fi + done + test: + runs-on: ubuntu-latest + container: ${{ matrix.container }} + name: ${{ matrix.name }} + strategy: + fail-fast: false + matrix: + include: + - container: ghcr.io/oca/oca-ci/py3.6-odoo13.0:latest + makepot: "true" + name: test with Odoo + - container: ghcr.io/oca/oca-ci/py3.6-ocb13.0:latest + name: test with OCB + services: + postgres: + image: postgres:9.6 + env: + POSTGRES_USER: odoo + POSTGRES_PASSWORD: odoo + POSTGRES_DB: odoo + ports: + - 5432:5432 + steps: + - uses: actions/checkout@v2 + with: + persist-credentials: false + - name: Install addons and dependencies + run: oca_install_addons + - name: Check licenses + run: manifestoo -d . check-licenses + - name: Check development status + run: manifestoo -d . check-dev-status --default-dev-status=Beta + - name: Initialize test db + run: oca_init_test_database + - name: Run tests + run: oca_run_tests + - uses: codecov/codecov-action@v1 + - name: Update .pot files + run: oca_export_and_push_pot https://x-access-token:${{ secrets.GIT_PUSH_TOKEN }}@github.com/${{ github.repository }} + if: ${{ matrix.makepot == 'true' && github.event_name == 'push' && github.repository_owner == 'OCA' }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bb1338d50..8d46d21c4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,7 +5,7 @@ exclude: | # Files and folders generated by bots, to avoid loops ^setup/|/static/description/index\.html$| # We don't want to mess with tool-generated files - .svg$|/tests/([^/]+/)?cassettes/|^.copier-answers.yml$| + .svg$|/tests/([^/]+/)?cassettes/|^.copier-answers.yml$|^.github/| # Maybe reactivate this when all README files include prettier ignore tags? ^README\.md$| # Library files can have extraneous formatting (even minimized) @@ -49,6 +49,7 @@ repos: rev: 19.10b0 hooks: - id: black + additional_dependencies: ["click<8.1.0"] - repo: https://github.com/pre-commit/mirrors-prettier rev: v1.19.1 hooks: diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 195fde565..000000000 --- a/.travis.yml +++ /dev/null @@ -1,45 +0,0 @@ -language: python -cache: - directories: - - $HOME/.cache/pip - - $HOME/.cache/pre-commit - -python: - - "3.6" - -addons: - postgresql: "9.6" - apt: - packages: - - expect-dev # provides unbuffer utility - - "swig" - - "libreoffice" - -stages: - - test - -jobs: - include: - - stage: test - env: - - TESTS=1 ODOO_REPO="odoo/odoo" MAKEPOT="1" - - stage: test - env: - - TESTS=1 ODOO_REPO="OCA/OCB" -env: - global: - - VERSION="13.0" TESTS="0" LINT_CHECK="0" MAKEPOT="0" - - WKHTMLTOPDF_VERSION="0.12.5" - - MQT_DEP=PIP - -install: - - git clone --depth=1 https://github.com/OCA/maintainer-quality-tools.git - ${HOME}/maintainer-quality-tools - - export PATH=${HOME}/maintainer-quality-tools/travis:${PATH} - - travis_install_nightly - -script: - - travis_run_tests - -after_success: - - travis_after_tests_success diff --git a/README.md b/README.md index 354cd5ec8..af452838d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ -[![Runbot Status](https://runbot.odoo-community.org/runbot/badge/flat/143/13.0.svg)](https://runbot.odoo-community.org/runbot/repo/github-com-oca-reporting-engine-143) -[![Build Status](https://travis-ci.com/OCA/reporting-engine.svg?branch=13.0)](https://travis-ci.com/OCA/reporting-engine) + +[![Runboat](https://img.shields.io/badge/runboat-Try%20me-875A7B.png)](https://runboat.odoo-community.org/builds?repo=OCA/reporting-engine&target_branch=13.0) +[![Pre-commit Status](https://github.com/OCA/reporting-engine/actions/workflows/pre-commit.yml/badge.svg?branch=13.0)](https://github.com/OCA/reporting-engine/actions/workflows/pre-commit.yml?query=branch%3A13.0) +[![Build Status](https://github.com/OCA/reporting-engine/actions/workflows/test.yml/badge.svg?branch=13.0)](https://github.com/OCA/reporting-engine/actions/workflows/test.yml?query=branch%3A13.0) [![codecov](https://codecov.io/gh/OCA/reporting-engine/branch/13.0/graph/badge.svg)](https://codecov.io/gh/OCA/reporting-engine) [![Translation Status](https://translation.odoo-community.org/widgets/reporting-engine-13-0/-/svg-badge.svg)](https://translation.odoo-community.org/engage/reporting-engine-13-0/?utm_source=widget) @@ -26,7 +28,8 @@ addon | version | maintainers | summary [base_comment_template](base_comment_template/) | 13.0.3.0.1 | | Add conditional mako template to any report on models that inherits comment.template. [bi_sql_editor](bi_sql_editor/) | 13.0.1.0.1 | | BI Views builder, based on Materialized or Normal SQL Views [kpi](kpi/) | 13.0.1.0.1 | | Key Performance Indicator -[kpi_dashboard](kpi_dashboard/) | 13.0.1.0.0 | [![etobella](https://github.com/etobella.png?size=30px)](https://github.com/etobella) | Create Dashboards using kpis +[kpi_dashboard](kpi_dashboard/) | 13.0.1.1.0 | [![etobella](https://github.com/etobella.png?size=30px)](https://github.com/etobella) | Create Dashboards using kpis +[report_async](report_async/) | 13.0.1.0.0 | [![kittiu](https://github.com/kittiu.png?size=30px)](https://github.com/kittiu) | Central place to run reports live or async [report_batch](report_batch/) | 13.0.1.0.1 | [![bodedra](https://github.com/bodedra.png?size=30px)](https://github.com/bodedra) | Ability to print multiple QWeb reports in a single batch. [report_context](report_context/) | 13.0.1.0.0 | | Adding context to reports [report_csv](report_csv/) | 13.0.1.0.2 | | Base module to create csv report @@ -41,8 +44,8 @@ addon | version | maintainers | summary [report_qweb_signer](report_qweb_signer/) | 13.0.2.1.0 | | Sign Qweb PDFs usign a PKCS#12 certificate [report_wkhtmltopdf_param](report_wkhtmltopdf_param/) | 13.0.1.0.0 | | Add new parameters for a paper format to be used by wkhtmltopdf command as arguments. [report_xlsx](report_xlsx/) | 13.0.1.0.6 | | Base module to create xlsx report -[report_xlsx_helper](report_xlsx_helper/) | 13.0.1.1.1 | | Report xlsx helpers -[report_xml](report_xml/) | 13.0.1.0.1 | | Allow to generate XML reports +[report_xlsx_helper](report_xlsx_helper/) | 13.0.1.1.3 | | Report xlsx helpers +[report_xml](report_xml/) | 13.0.1.1.0 | | Allow to generate XML reports [//]: # (end addons) @@ -52,12 +55,11 @@ addon | version | maintainers | summary This repository is licensed under [AGPL-3.0](LICENSE). -However, each module can have a totally different license, as long as they adhere to OCA +However, each module can have a totally different license, as long as they adhere to Odoo Community Association (OCA) policy. Consult each module's `__manifest__.py` file, which contains a `license` key that explains its license. ---- - OCA, or the [Odoo Community Association](http://odoo-community.org/), is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. diff --git a/kpi_dashboard/__manifest__.py b/kpi_dashboard/__manifest__.py index 9dc410d2e..94890a22c 100644 --- a/kpi_dashboard/__manifest__.py +++ b/kpi_dashboard/__manifest__.py @@ -5,7 +5,7 @@ "name": "Kpi Dashboard", "summary": """ Create Dashboards using kpis""", - "version": "13.0.1.0.0", + "version": "13.0.1.1.0", "license": "AGPL-3", "author": "Creu Blanca,Odoo Community Association (OCA)", "website": "https://github.com/OCA/reporting-engine", diff --git a/kpi_dashboard/i18n/kpi_dashboard.pot b/kpi_dashboard/i18n/kpi_dashboard.pot index 1784a2513..6e0c17014 100644 --- a/kpi_dashboard/i18n/kpi_dashboard.pot +++ b/kpi_dashboard/i18n/kpi_dashboard.pot @@ -99,6 +99,7 @@ msgid "Computation Method" msgstr "" #. module: kpi_dashboard +#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard_item__compute_on_fly #: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi__compute_on_fly msgid "Compute On Fly" msgstr "" @@ -542,6 +543,11 @@ msgstr "" msgid "Size Y of the widget cannot be bigger than 10" msgstr "" +#. module: kpi_dashboard +#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard_item__special_context +msgid "Special Context" +msgstr "" + #. module: kpi_dashboard #: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi__store_history msgid "Store History" diff --git a/kpi_dashboard/models/kpi_dashboard.py b/kpi_dashboard/models/kpi_dashboard.py index c03073ab8..c4f0ca5a5 100644 --- a/kpi_dashboard/models/kpi_dashboard.py +++ b/kpi_dashboard/models/kpi_dashboard.py @@ -3,6 +3,7 @@ from odoo import _, api, fields, models from odoo.exceptions import ValidationError +from odoo.tools.safe_eval import safe_eval class KpiDashboard(models.Model): @@ -113,9 +114,11 @@ class KpiDashboardItem(models.Model): color = fields.Char() font_color = fields.Char() modify_context = fields.Boolean() + compute_on_fly = fields.Boolean(related="kpi_id.compute_on_fly") modify_context_expression = fields.Char() modify_color = fields.Boolean() modify_color_expression = fields.Char() + special_context = fields.Char() @api.depends("row", "size_y") def _compute_end_row(self): @@ -189,9 +192,17 @@ class KpiDashboardItem(models.Model): } ) if self.kpi_id.compute_on_fly: + kpi = self.kpi_id + if self.special_context: + try: + ctx = safe_eval(self.special_context) + if isinstance(ctx, dict): + kpi = kpi.with_context(**ctx) + except SyntaxError: + pass vals.update( { - "value": self.kpi_id._compute_value(), + "value": kpi._compute_value(), "value_last_update": fields.Datetime.now(), } ) diff --git a/kpi_dashboard/views/kpi_dashboard.xml b/kpi_dashboard/views/kpi_dashboard.xml index faac1a8ec..e2ce37e9a 100644 --- a/kpi_dashboard/views/kpi_dashboard.xml +++ b/kpi_dashboard/views/kpi_dashboard.xml @@ -162,15 +162,16 @@ widget="ace" options="{'mode': 'python'}" /> + +
-
diff --git a/oca_dependencies.txt b/oca_dependencies.txt index 13546285c..49ccd9736 100644 --- a/oca_dependencies.txt +++ b/oca_dependencies.txt @@ -1,2 +1,3 @@ server-tools server-backend +queue diff --git a/report_async/README.rst b/report_async/README.rst new file mode 100644 index 000000000..075627f87 --- /dev/null +++ b/report_async/README.rst @@ -0,0 +1,120 @@ +============ +Report Async +============ + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Freporting--engine-lightgray.png?logo=github + :target: https://github.com/OCA/reporting-engine/tree/13.0/report_async + :alt: OCA/reporting-engine +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/reporting-engine-13-0/reporting-engine-13-0-report_async + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/143/13.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +The new menu "Report Center" is the central place to host your reports in one place. +From here, there are 2 ways to launch the report, + +1. Run Now - run report immediately as per normal. +2. Run Background - put the report execution to queue job. + +By using the queue job, option 2 is great for long running report. +The report file will be saved for later use, with the option to send report +by email as soon as it is ready. + +Notes: + +* Only user with Technical Feature rights can manage the report. +* Every internal user will have right to execute the report allowed for his/her groups. +* The files created are owned and viewable only by the person who run the report. +* Job queue manager can also see all jobs for each reports. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +Menu: Dashboard > Report Center + +As Technical Feature users, you can manage reports for Report Center. + +- **Report:** choose the report (a window action). Although the option show all window actions + it only make sense for window actions that launch reports. +- **Allow Async:** check this, if you want the report to run in background too, suitable for + report that return file as result, i.e., pdf/xlsx/csv/txt. +- **Email Notification:** if checked, once the background process is completed, email with link to download + report will be sent. +- **Groups:** select user groups allowed to use this report. If left blank, all user can use. + +As normal user, you can run your reports from Report Center + +- **Run Now button:** to run report immediately as per normal. +- **Run Background button:** to run report asynchronously. Fall back to run now, if not report that produce file. +- **Job Status:** show status of the latest run job. If job fail, exception error will also shown +- **Files:** show all files being produced by the job as run by the user. +- **Jobs:** show all jobs triggered by this report as run by the user. Only job queue manager have access to this button. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Ecosoft + +Contributors +~~~~~~~~~~~~ + +* Kitti U. +* Saran Lim. + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-kittiu| image:: https://github.com/kittiu.png?size=40px + :target: https://github.com/kittiu + :alt: kittiu + +Current `maintainer `__: + +|maintainer-kittiu| + +This module is part of the `OCA/reporting-engine `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/report_async/__init__.py b/report_async/__init__.py new file mode 100644 index 000000000..d5a964721 --- /dev/null +++ b/report_async/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html) + +from . import models +from . import wizard diff --git a/report_async/__manifest__.py b/report_async/__manifest__.py new file mode 100644 index 000000000..8fafc099e --- /dev/null +++ b/report_async/__manifest__.py @@ -0,0 +1,23 @@ +# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html) +{ + "name": "Report Async", + "summary": "Central place to run reports live or async", + "version": "13.0.1.0.0", + "author": "Ecosoft, Odoo Community Association (OCA)", + "license": "AGPL-3", + "website": "https://github.com/OCA/reporting-engine", + "category": "Generic Modules", + "depends": ["queue_job"], + "data": [ + "security/ir.model.access.csv", + "security/ir_rule.xml", + "data/mail_template.xml", + "views/report_async.xml", + "wizard/print_report_wizard.xml", + ], + "demo": ["demo/report_async_demo.xml"], + "installable": True, + "maintainers": ["kittiu"], + "development_status": "Beta", +} diff --git a/report_async/data/mail_template.xml b/report_async/data/mail_template.xml new file mode 100644 index 000000000..77411dc14 --- /dev/null +++ b/report_async/data/mail_template.xml @@ -0,0 +1,73 @@ + + + + + Report Async: New Report Available + + Your report is available, ${object.name} + ${object.company_id.partner_id.email_formatted|safe} + ${user.partner_id.id} + + + + + +
+ + + + + + +
+ + + + +
+ % set base_url = object.env['ir.config_parameter'].sudo().get_param('web.base.url') + % set download_url = '%s/web/content/ir.attachment/%s/datas/%s?download=true' % (base_url, object.id, object.name, ) +
+ Dear ${object.create_uid.partner_id.name or ''}, +

+ Your requested report, ${object.name}, is available for + download + . +

+ Have a nice day!
+ --
${object.company_id.name} +
+
+
+
+
+ + +
+
+
diff --git a/report_async/demo/report_async_demo.xml b/report_async/demo/report_async_demo.xml new file mode 100644 index 000000000..1a60a3874 --- /dev/null +++ b/report_async/demo/report_async_demo.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/report_async/i18n/report_async.pot b/report_async/i18n/report_async.pot new file mode 100644 index 000000000..eba48aaa2 --- /dev/null +++ b/report_async/i18n/report_async.pot @@ -0,0 +1,327 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * report_async +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 13.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: report_async +#: model:mail.template,body_html:report_async.async_report_delivery +msgid "" +"\n" +" \n" +" \n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" % set base_url = object.env['ir.config_parameter'].sudo().get_param('web.base.url')\n" +" % set download_url = '%s/web/content/ir.attachment/%s/datas/%s?download=true' % (base_url, object.id, object.name, )\n" +"
\n" +" Dear ${object.create_uid.partner_id.name or ''},\n" +"

\n" +" Your requested report, ${object.name}, is available for \n" +" download\n" +" .\n" +"

\n" +" Have a nice day!
\n" +" --
${object.company_id.name}\n" +"
\n" +"
\n" +"
\n" +"
\n" +" " +msgstr "" + +#. module: report_async +#: model:ir.model.fields,field_description:report_async.field_report_async__allow_async +msgid "Allow Async" +msgstr "" + +#. module: report_async +#: code:addons/report_async/models/report_async.py:0 +#, python-format +msgid "Background process not allowed." +msgstr "" + +#. module: report_async +#: model_terms:ir.ui.view,arch_db:report_async.print_report_wizard +msgid "Cancel" +msgstr "" + +#. module: report_async +#: model:ir.model.fields,field_description:report_async.field_print_report_wizard__create_uid +#: model:ir.model.fields,field_description:report_async.field_report_async__create_uid +msgid "Created by" +msgstr "" + +#. module: report_async +#: model:ir.model.fields,field_description:report_async.field_print_report_wizard__create_date +#: model:ir.model.fields,field_description:report_async.field_report_async__create_date +msgid "Created on" +msgstr "" + +#. module: report_async +#: model:ir.model.fields,field_description:report_async.field_print_report_wizard__display_name +#: model:ir.model.fields,field_description:report_async.field_report_async__display_name +msgid "Display Name" +msgstr "" + +#. module: report_async +#: model:ir.model.fields,field_description:report_async.field_print_report_wizard__reference +msgid "Document" +msgstr "" + +#. module: report_async +#: model:ir.model.fields.selection,name:report_async.selection__report_async__job_status__done +msgid "Done" +msgstr "" + +#. module: report_async +#: model:ir.model.fields,field_description:report_async.field_report_async__email_notify +msgid "Email Notification" +msgstr "" + +#. module: report_async +#: model:ir.model.fields.selection,name:report_async.selection__report_async__job_status__enqueued +msgid "Enqueued" +msgstr "" + +#. module: report_async +#: model_terms:ir.ui.view,arch_db:report_async.print_report_wizard +msgid "Execute" +msgstr "" + +#. module: report_async +#: model:ir.model.fields.selection,name:report_async.selection__report_async__job_status__failed +msgid "Failed" +msgstr "" + +#. module: report_async +#: model:ir.model.fields,field_description:report_async.field_report_async__file_ids +msgid "File" +msgstr "" + +#. module: report_async +#: model_terms:ir.ui.view,arch_db:report_async.view_report_async_form +#: model_terms:ir.ui.view,arch_db:report_async.view_report_async_tree +msgid "Files" +msgstr "" + +#. module: report_async +#: model:ir.model.fields,field_description:report_async.field_report_async__group_ids +msgid "Groups" +msgstr "" + +#. module: report_async +#: model:ir.model.fields,field_description:report_async.field_print_report_wizard__id +#: model:ir.model.fields,field_description:report_async.field_report_async__id +msgid "ID" +msgstr "" + +#. module: report_async +#: model:ir.model.fields,field_description:report_async.field_report_async__job_ids +msgid "Job" +msgstr "" + +#. module: report_async +#: model:ir.model.fields,field_description:report_async.field_report_async__job_info +msgid "Job Info" +msgstr "" + +#. module: report_async +#: model:ir.model.fields,field_description:report_async.field_report_async__job_status +msgid "Job Status" +msgstr "" + +#. module: report_async +#: model_terms:ir.ui.view,arch_db:report_async.view_report_async_form +msgid "Jobs" +msgstr "" + +#. module: report_async +#: model:ir.model.fields,field_description:report_async.field_print_report_wizard____last_update +#: model:ir.model.fields,field_description:report_async.field_report_async____last_update +msgid "Last Modified on" +msgstr "" + +#. module: report_async +#: model_terms:ir.ui.view,arch_db:report_async.view_report_async_form +msgid "Last Run Job Error" +msgstr "" + +#. module: report_async +#: model:ir.model.fields,field_description:report_async.field_print_report_wizard__write_uid +#: model:ir.model.fields,field_description:report_async.field_report_async__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: report_async +#: model:ir.model.fields,field_description:report_async.field_print_report_wizard__write_date +#: model:ir.model.fields,field_description:report_async.field_report_async__write_date +msgid "Last Updated on" +msgstr "" + +#. module: report_async +#: model:ir.model.fields,help:report_async.field_report_async__job_info +msgid "Latest Job Error Message" +msgstr "" + +#. module: report_async +#: model:ir.model.fields,help:report_async.field_report_async__job_status +msgid "Latest Job Status" +msgstr "" + +#. module: report_async +#: model:ir.model.fields,help:report_async.field_report_async__file_ids +msgid "List all files created by this report background process" +msgstr "" + +#. module: report_async +#: model:ir.model.fields,help:report_async.field_report_async__job_ids +msgid "List all jobs related to this running report" +msgstr "" + +#. module: report_async +#: model:ir.model.fields,field_description:report_async.field_report_async__name +msgid "Name" +msgstr "" + +#. module: report_async +#: model_terms:ir.actions.act_window,help:report_async.action_view_files +msgid "No files found" +msgstr "" + +#. module: report_async +#: model:ir.model.fields,help:report_async.field_report_async__group_ids +msgid "" +"Only user in selected groups can use this report.If left blank, everyone can" +" use" +msgstr "" + +#. module: report_async +#: model:ir.model.fields.selection,name:report_async.selection__report_async__job_status__pending +msgid "Pending" +msgstr "" + +#. module: report_async +#: model:ir.actions.act_window,name:report_async.action_print_report_wizard +msgid "Print Document" +msgstr "" + +#. module: report_async +#: model:ir.model,name:report_async.model_print_report_wizard +msgid "Print Report Wizard" +msgstr "" + +#. module: report_async +#: model:ir.model,name:report_async.model_ir_actions_report +msgid "Report Action" +msgstr "" + +#. module: report_async +#: model:ir.model,name:report_async.model_report_async +msgid "Report Async" +msgstr "" + +#. module: report_async +#: model:ir.actions.act_window,name:report_async.action_report_async +#: model:ir.ui.menu,name:report_async.menu_report_async +#: model_terms:ir.ui.view,arch_db:report_async.view_report_async_search +#: model_terms:ir.ui.view,arch_db:report_async.view_report_async_tree +msgid "Report Center" +msgstr "" + +#. module: report_async +#: model:ir.actions.act_window,name:report_async.action_view_files +msgid "Report Files" +msgstr "" + +#. module: report_async +#: model:ir.model.fields,field_description:report_async.field_print_report_wizard__action_report_id +msgid "Report Template" +msgstr "" + +#. module: report_async +#: model:ir.model.fields,field_description:report_async.field_report_async__action_id +msgid "Reports" +msgstr "" + +#. module: report_async +#: model_terms:ir.ui.view,arch_db:report_async.view_report_async_form +#: model_terms:ir.ui.view,arch_db:report_async.view_report_async_tree +msgid "Run Background" +msgstr "" + +#. module: report_async +#: model_terms:ir.ui.view,arch_db:report_async.view_report_async_form +#: model_terms:ir.ui.view,arch_db:report_async.view_report_async_tree +msgid "Run Now" +msgstr "" + +#. module: report_async +#: model_terms:ir.actions.act_window,help:report_async.action_report_async +msgid "Run reports asyncronously" +msgstr "" + +#. module: report_async +#: model:ir.model.fields,help:report_async.field_report_async__email_notify +msgid "Send email with link to report, when it is ready" +msgstr "" + +#. module: report_async +#: model:ir.model.fields.selection,name:report_async.selection__report_async__job_status__started +msgid "Started" +msgstr "" + +#. module: report_async +#: model_terms:ir.ui.view,arch_db:report_async.view_report_async_form +msgid "" +"The last running job was failed.\n" +" Please contact your system administrator." +msgstr "" + +#. module: report_async +#: model_terms:ir.ui.view,arch_db:report_async.view_report_async_form +msgid "" +"The last running job was succeed.\n" +" You can check the result in Files" +msgstr "" + +#. module: report_async +#: model_terms:ir.ui.view,arch_db:report_async.view_report_async_form +msgid "" +"The report will be running by \n" +" job, and will be available at\n" +" Files" +msgstr "" + +#. module: report_async +#: model:ir.model.fields,help:report_async.field_report_async__allow_async +msgid "" +"This is not automatic field, please check if you want to allow this report " +"in background process" +msgstr "" + +#. module: report_async +#: model:mail.template,subject:report_async.async_report_delivery +msgid "Your report is available, ${object.name}" +msgstr "" diff --git a/report_async/models/__init__.py b/report_async/models/__init__.py new file mode 100644 index 000000000..2e6988867 --- /dev/null +++ b/report_async/models/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html) + +from . import report_async +from . import ir_report diff --git a/report_async/models/ir_report.py b/report_async/models/ir_report.py new file mode 100644 index 000000000..03a122cda --- /dev/null +++ b/report_async/models/ir_report.py @@ -0,0 +1,23 @@ +# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html) + +from odoo import models + +# Define all supported report_type +REPORT_TYPES = ["qweb-pdf", "qweb-text", "qweb-xml", "csv", "excel", "xlsx"] + + +class Report(models.Model): + _inherit = "ir.actions.report" + + def report_action(self, docids, data=None, config=True): + res = super(Report, self).report_action(docids, data=data, config=config) + if res["context"].get("async_process", False): + rpt_async_id = res["context"]["active_id"] + report_async = self.env["report.async"].browse(rpt_async_id) + if res["report_type"] in REPORT_TYPES: + report_async.with_delay().run_report( + res["context"].get("active_ids", []), data, self.id, self._uid + ) + return {} + return res diff --git a/report_async/models/report_async.py b/report_async/models/report_async.py new file mode 100644 index 000000000..63480cf9f --- /dev/null +++ b/report_async/models/report_async.py @@ -0,0 +1,170 @@ +# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html) + +import base64 + +from odoo import _, api, fields, models +from odoo.exceptions import UserError +from odoo.tools.safe_eval import safe_eval + +from odoo.addons.queue_job.job import job + +# Define all supported report_type +REPORT_TYPES_FUNC = { + "qweb-pdf": "render_qweb_pdf", + "qweb-text": "render_qweb_text", + "qweb-xml": "render_qweb_xml", + "csv": "render_csv", + "excel": "render_excel", + "xlsx": "render_xlsx", +} + + +class ReportAsync(models.Model): + _name = "report.async" + _description = "Report Async" + + action_id = fields.Many2one( + comodel_name="ir.actions.act_window", string="Reports", required=True, + ) + allow_async = fields.Boolean( + string="Allow Async", + default=False, + help="This is not automatic field, please check if you want to allow " + "this report in background process", + ) + name = fields.Char(string="Name", related="action_id.display_name",) + email_notify = fields.Boolean( + string="Email Notification", + help="Send email with link to report, when it is ready", + ) + group_ids = fields.Many2many( + string="Groups", + comodel_name="res.groups", + help="Only user in selected groups can use this report." + "If left blank, everyone can use", + ) + job_ids = fields.Many2many( + comodel_name="queue.job", + compute="_compute_job", + help="List all jobs related to this running report", + ) + job_status = fields.Selection( + selection=[ + ("pending", "Pending"), + ("enqueued", "Enqueued"), + ("started", "Started"), + ("done", "Done"), + ("failed", "Failed"), + ], + compute="_compute_job", + help="Latest Job Status", + ) + job_info = fields.Text(compute="_compute_job", help="Latest Job Error Message",) + file_ids = fields.Many2many( + comodel_name="ir.attachment", + compute="_compute_file", + help="List all files created by this report background process", + ) + + def _compute_job(self): + for rec in self: + rec.job_ids = ( + self.sudo() + .env["queue.job"] + .search( + [ + ("func_string", "like", "report.async(%s,)" % rec.id), + ("user_id", "=", self._uid), + ], + order="id desc", + ) + ) + rec.job_status = rec.job_ids[0].sudo().state if rec.job_ids else False + rec.job_info = rec.job_ids[0].sudo().exc_info if rec.job_ids else False + + def _compute_file(self): + files = self.env["ir.attachment"].search( + [ + ("res_model", "=", "report.async"), + ("res_id", "in", self.ids), + ("create_uid", "=", self._uid), + ], + order="id desc", + ) + for rec in self: + rec.file_ids = files.filtered(lambda l: l.res_id == rec.id) + + def run_now(self): + self.ensure_one() + action = self.env.ref(self.action_id.xml_id) + result = action.read()[0] + ctx = safe_eval(result.get("context", {})) + ctx.update({"async_process": False}) + result["context"] = ctx + return result + + def run_async(self): + self.ensure_one() + if not self.allow_async: + raise UserError(_("Background process not allowed.")) + action = self.env.ref(self.action_id.xml_id) + result = action.read()[0] + ctx = safe_eval(result.get("context", {})) + ctx.update({"async_process": True}) + result["context"] = ctx + return result + + def view_files(self): + self.ensure_one() + action = self.env.ref("report_async.action_view_files") + result = action.read()[0] + result["domain"] = [("id", "in", self.file_ids.ids)] + return result + + def view_jobs(self): + self.ensure_one() + action = self.env.ref("queue_job.action_queue_job") + result = action.read()[0] + result["domain"] = [("id", "in", self.job_ids.ids)] + result["context"] = {} + return result + + @api.model + @job + def run_report(self, docids, data, report_id, user_id): + report = self.env["ir.actions.report"].browse(report_id) + func = REPORT_TYPES_FUNC[report.report_type] + # Run report + out_file, file_ext = getattr(report, func)(docids, data) + out_file = base64.b64encode(out_file) + out_name = "{}.{}".format(report.name, file_ext) + # Save report to attachment + attachment = ( + self.env["ir.attachment"] + .sudo() + .create( + { + "name": out_name, + "datas": out_file, + "type": "binary", + "res_model": "report.async", + "res_id": self.id, + } + ) + ) + self._cr.execute( + """ + UPDATE ir_attachment SET create_uid = %s, write_uid = %s + WHERE id = %s""", + (self._uid, self._uid, attachment.id), + ) + # Send email + if self.email_notify: + self._send_email(attachment) + + def _send_email(self, attachment): + template = self.env.ref("report_async.async_report_delivery") + template.send_mail( + attachment.id, notif_layout="mail.mail_notification_light", force_send=False + ) diff --git a/report_async/readme/CONTRIBUTORS.rst b/report_async/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..b8832b9e7 --- /dev/null +++ b/report_async/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* Kitti U. +* Saran Lim. diff --git a/report_async/readme/DESCRIPTION.rst b/report_async/readme/DESCRIPTION.rst new file mode 100644 index 000000000..b0c0f140a --- /dev/null +++ b/report_async/readme/DESCRIPTION.rst @@ -0,0 +1,16 @@ +The new menu "Report Center" is the central place to host your reports in one place. +From here, there are 2 ways to launch the report, + +1. Run Now - run report immediately as per normal. +2. Run Background - put the report execution to queue job. + +By using the queue job, option 2 is great for long running report. +The report file will be saved for later use, with the option to send report +by email as soon as it is ready. + +Notes: + +* Only user with Technical Feature rights can manage the report. +* Every internal user will have right to execute the report allowed for his/her groups. +* The files created are owned and viewable only by the person who run the report. +* Job queue manager can also see all jobs for each reports. diff --git a/report_async/readme/USAGE.rst b/report_async/readme/USAGE.rst new file mode 100644 index 000000000..9668d579d --- /dev/null +++ b/report_async/readme/USAGE.rst @@ -0,0 +1,19 @@ +Menu: Dashboard > Report Center + +As Technical Feature users, you can manage reports for Report Center. + +- **Report:** choose the report (a window action). Although the option show all window actions + it only make sense for window actions that launch reports. +- **Allow Async:** check this, if you want the report to run in background too, suitable for + report that return file as result, i.e., pdf/xlsx/csv/txt. +- **Email Notification:** if checked, once the background process is completed, email with link to download + report will be sent. +- **Groups:** select user groups allowed to use this report. If left blank, all user can use. + +As normal user, you can run your reports from Report Center + +- **Run Now button:** to run report immediately as per normal. +- **Run Background button:** to run report asynchronously. Fall back to run now, if not report that produce file. +- **Job Status:** show status of the latest run job. If job fail, exception error will also shown +- **Files:** show all files being produced by the job as run by the user. +- **Jobs:** show all jobs triggered by this report as run by the user. Only job queue manager have access to this button. diff --git a/report_async/security/ir.model.access.csv b/report_async/security/ir.model.access.csv new file mode 100644 index 000000000..ba490c037 --- /dev/null +++ b/report_async/security/ir.model.access.csv @@ -0,0 +1,3 @@ +"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" +access_report_async,report.async.user,model_report_async,base.group_user,1,0,0,0 +access_report_async_sudo,report.async.sudo,model_report_async,base.group_no_one,1,1,1,1 diff --git a/report_async/security/ir_rule.xml b/report_async/security/ir_rule.xml new file mode 100644 index 000000000..b551c92cb --- /dev/null +++ b/report_async/security/ir_rule.xml @@ -0,0 +1,25 @@ + + + + Report Async by Groups + + + + + + + ['|', ('group_ids', '=', False), ('group_ids', 'in', [g.id for g in user.groups_id])] + + + Report Async by Groups + + + + + + + [(1,'=', 1)] + + diff --git a/report_async/static/description/icon.png b/report_async/static/description/icon.png new file mode 100644 index 000000000..3a0328b51 Binary files /dev/null and b/report_async/static/description/icon.png differ diff --git a/report_async/static/description/index.html b/report_async/static/description/index.html new file mode 100644 index 000000000..f31d1515c --- /dev/null +++ b/report_async/static/description/index.html @@ -0,0 +1,460 @@ + + + + + + +Report Async + + + +
+

Report Async

+ + +

Beta License: AGPL-3 OCA/reporting-engine Translate me on Weblate Try me on Runbot

+

The new menu “Report Center” is the central place to host your reports in one place. +From here, there are 2 ways to launch the report,

+
    +
  1. Run Now - run report immediately as per normal.
  2. +
  3. Run Background - put the report execution to queue job.
  4. +
+

By using the queue job, option 2 is great for long running report. +The report file will be saved for later use, with the option to send report +by email as soon as it is ready.

+

Notes:

+
    +
  • Only user with Technical Feature rights can manage the report.
  • +
  • Every internal user will have right to execute the report allowed for his/her groups.
  • +
  • The files created are owned and viewable only by the person who run the report.
  • +
  • Job queue manager can also see all jobs for each reports.
  • +
+

Table of contents

+ +
+

Usage

+

Menu: Dashboard > Report Center

+

As Technical Feature users, you can manage reports for Report Center.

+
    +
  • Report: choose the report (a window action). Although the option show all window actions +it only make sense for window actions that launch reports.
  • +
  • Allow Async: check this, if you want the report to run in background too, suitable for +report that return file as result, i.e., pdf/xlsx/csv/txt.
  • +
  • Email Notification: if checked, once the background process is completed, email with link to download +report will be sent.
  • +
  • Groups: select user groups allowed to use this report. If left blank, all user can use.
  • +
+

As normal user, you can run your reports from Report Center

+
    +
  • Run Now button: to run report immediately as per normal.
  • +
  • Run Background button: to run report asynchronously. Fall back to run now, if not report that produce file.
  • +
  • Job Status: show status of the latest run job. If job fail, exception error will also shown
  • +
  • Files: show all files being produced by the job as run by the user.
  • +
  • Jobs: show all jobs triggered by this report as run by the user. Only job queue manager have access to this button.
  • +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Ecosoft
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

kittiu

+

This module is part of the OCA/reporting-engine project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/report_async/tests/__init__.py b/report_async/tests/__init__.py new file mode 100644 index 000000000..86bbfd13b --- /dev/null +++ b/report_async/tests/__init__.py @@ -0,0 +1 @@ +from . import test_report_async diff --git a/report_async/tests/test_report_async.py b/report_async/tests/test_report_async.py new file mode 100644 index 000000000..a1f1e8bd0 --- /dev/null +++ b/report_async/tests/test_report_async.py @@ -0,0 +1,55 @@ +# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html) +from odoo.exceptions import UserError +from odoo.tests import common +from odoo.tests.common import Form + + +class TestJobChannel(common.TransactionCase): + def setUp(self): + super(TestJobChannel, self).setUp() + self.print_doc = self.env.ref("report_async." "report_async_print_document") + self.test_rec = self.env.ref("base.module_mail") + self.test_rpt = self.env.ref("base.ir_module_reference_print") + + def _print_wizard(self, res): + obj = self.env[res["res_model"]] + ctx = { + "active_model": self.print_doc._name, + "active_id": self.print_doc.id, + } + ctx.update(res["context"]) + with Form(obj.with_context(ctx)) as form: + form.reference = "{},{}".format(self.test_rec._name, self.test_rec.id) + form.action_report_id = self.test_rpt + print_wizard = form.save() + return print_wizard + + def test_1_run_now(self): + """Run now will return report action as normal""" + res = self.print_doc.run_now() + report_action = self._print_wizard(res).print_report() + self.assertEquals(report_action["type"], "ir.actions.report") + + def test_2_run_async(self): + """Run background will return nothing, job started""" + with self.assertRaises(UserError): + self.print_doc.run_async() + self.print_doc.write({"allow_async": True, "email_notify": True}) + res = self.print_doc.run_async() + print_wizard = self._print_wizard(res) + report_action = print_wizard.print_report() + self.assertEquals(report_action, {}) # Do not run report yet + self.assertEquals(self.print_doc.job_status, "pending") # Job started + # Test produce file (as queue will not run in test mode) + docids = [print_wizard.reference.id] + data = None + report_id = self.test_rpt.id + user_id = self.env.user.id + self.print_doc.run_report(docids, data, report_id, user_id) + # Check name of the newly producted file + # Note: on env with test-enable, always fall back to render_qweb_html + self.assertIn(self.test_rpt.name, self.print_doc.file_ids[0].name) + # View fileds/jobs + self.print_doc.view_files() + self.print_doc.view_jobs() diff --git a/report_async/views/report_async.xml b/report_async/views/report_async.xml new file mode 100644 index 000000000..5b833029f --- /dev/null +++ b/report_async/views/report_async.xml @@ -0,0 +1,177 @@ + + + + report.async.tree + report.async + + + +