Merge PR #2755 into 16.0

Signed-off-by pedrobaeza
pull/2758/head
OCA-git-bot 2024-02-28 12:42:28 +00:00
commit 65fa9a6dd5
28 changed files with 1983 additions and 0 deletions

View File

@ -0,0 +1 @@
../../../../web_pivot_computed_measure

View File

@ -0,0 +1,6 @@
import setuptools
setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)

View File

@ -0,0 +1 @@
odoo-test-helper

View File

@ -0,0 +1,124 @@
==========================
Web Pivot Computed Measure
==========================
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:4cef60dd775467b756ccbacb8c215bba0bc74fd32e1917c8b15c3be9de6302a1
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |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%2Fweb-lightgray.png?logo=github
:target: https://github.com/OCA/web/tree/16.0/web_pivot_computed_measure
:alt: OCA/web
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/web-16-0/web-16-0-web_pivot_computed_measure
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/web&target_branch=16.0
:alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5|
Adds support for computed measures on the pivot view.
**Table of contents**
.. contents::
:local:
Usage
=====
Go to pivot view and click on the "Measures" menu, you will see
a new option called 'Computed Measure'.
You have the follow options to create a 'computed measure':
- Measure 1: Used in 'operation formula' as 'm1'
- Measure 2: Used in 'operation formula' as 'm2'
- Operation: The formula
- Sum: m1 + m2
- Sub: m1 - m2
- Mult: m1 * m2
- Div: m1 / m2 (Format: Float)
- Perc m1 / m2 (Format: Percentage)
- Custom: Special option only visible in debug mode to write a custom formula.
- Name: The name of the new measure (emtpy = auto-generated)
- Format: How will the value be printed
- Integer
- Float
- Percentage (value * 100)
- Formula*: Custom operation formula
These formula is evaluated using 'PY.eval'
These computed measures can be mixed (You can reuse it to make new computed measures) and saved as favorites.
Notice that "measures/computed measures" involved in an active 'computed measure'
can't be deactivated until you have deactivate the 'computed measure'.
Known issues / Roadmap
======================
#. Add support to define a style for a computed measure (ex. colored)
#. Use t-model to data-binding instead of jquery selectors
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/web/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/web/issues/new?body=module:%20web_pivot_computed_measure%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
~~~~~~~
* Tecnativa
Contributors
~~~~~~~~~~~~
* `Tecnativa <https://www.tecnativa.com/>`_:
* Alexandre D. Díaz
* Pedro M. Baeza
* Ernesto Tejeda
* Carlos Roca
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-CarlosRoca13| image:: https://github.com/CarlosRoca13.png?size=40px
:target: https://github.com/CarlosRoca13
:alt: CarlosRoca13
Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:
|maintainer-CarlosRoca13|
This module is part of the `OCA/web <https://github.com/OCA/web/tree/16.0/web_pivot_computed_measure>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@ -0,0 +1,25 @@
# Copyright 2020 Tecnativa - Alexandre Díaz
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
{
"name": "Web Pivot Computed Measure",
"category": "web",
"version": "16.0.1.0.0",
"author": "Tecnativa, Odoo Community Association (OCA)",
"license": "AGPL-3",
"website": "https://github.com/OCA/web",
"depends": ["web"],
"auto_install": False,
"installable": True,
"maintainers": ["CarlosRoca13"],
"assets": {
"web.assets_backend": [
"/web_pivot_computed_measure/static/src/**/*.esm.js",
"/web_pivot_computed_measure/static/src/**/*.scss",
("remove", "/web_pivot_computed_measure/static/src/test/*.esm.js"),
"/web_pivot_computed_measure/static/src/**/*.xml",
],
"web.assets_tests": [
"/web_pivot_computed_measure/static/src/test/test.esm.js",
],
},
}

View File

@ -0,0 +1,161 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_pivot_computed_measure
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 13.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2021-07-05 12:48+0000\n"
"Last-Translator: jabelchi <jabelchi@gmail.com>\n"
"Language-Team: none\n"
"Language: ca\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.3.2\n"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/view.xml:0
#, python-format
msgid "!measure.startsWith('__computed_')"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Add"
msgstr "Afegir"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Can be empty"
msgstr "Pot estar buit"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Computed Measure"
msgstr "Mesura calculada"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Custom"
msgstr "Personalitat"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Div (m1 / m2)"
msgstr "Div (m1 / m2)"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Float"
msgstr "Comma flotant"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Format"
msgstr "Format"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Formula"
msgstr "Fórmula"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Integer"
msgstr "Enter"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Measure 1"
msgstr "Mesura 1"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Measure 2"
msgstr "Mesura 2"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Mult (m1 * m2)"
msgstr "Mult (m1 * m2)"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Name"
msgstr "Nom"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Operation"
msgstr "Operació"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Perc (m1 * 100 / m2)"
msgstr "Perc (m1 * 100 / m2)"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Percentage"
msgstr "Percentatge"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Sub (m1 - m2)"
msgstr "Resta (m1 - m2)"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Sum (m1 + m2)"
msgstr "Suma (m1 + m2)"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/pivot/pivot_model.esm.js:0
#, python-format
msgid ""
"This measure is currently used by a 'computed measure'. Please, disable the "
"computed measure first."
msgstr ""
"Aquesta mesura s'usa actualment per una \"mesura calculada\". Si us plau, "
"inhabiliteu primer la mesura calculada."

View File

@ -0,0 +1,162 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_pivot_computed_measure
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 12.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-11-22 12:42+0000\n"
"PO-Revision-Date: 2023-11-19 19:33+0000\n"
"Last-Translator: Ivorra78 <informatica@totmaterial.es>\n"
"Language-Team: none\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.17\n"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/view.xml:0
#, python-format
msgid "!measure.startsWith('__computed_')"
msgstr "!measure.startsWith('__computed_')"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Add"
msgstr "Añadir"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Can be empty"
msgstr "Puede estar vacío"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Computed Measure"
msgstr "Medida computada"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Custom"
msgstr "Customizado"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Div (m1 / m2)"
msgstr "Div (m1 / m2)"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Float"
msgstr "Flotador"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Format"
msgstr "Formato"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Formula"
msgstr "Fórmula"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Integer"
msgstr "Entero"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Measure 1"
msgstr "Medida 1"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Measure 2"
msgstr "Medida 2"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Mult (m1 * m2)"
msgstr "Mult (m1 * m2)"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Name"
msgstr "Nombre"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Operation"
msgstr "Operación"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Perc (m1 * 100 / m2)"
msgstr "Perc (m1 * 100 / m2)"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Percentage"
msgstr "Porcentaje"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Sub (m1 - m2)"
msgstr "Sub (m1 - m2)"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Sum (m1 + m2)"
msgstr "Sum (m1 + m2)"
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/pivot/pivot_model.esm.js:0
#, python-format
msgid ""
"This measure is currently used by a 'computed measure'. Please, disable the "
"computed measure first."
msgstr ""
"Esta medida está utilizada por una 'medida computada'. Por favor, desabilita "
"la medida computada primero."

View File

@ -0,0 +1,156 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_pivot_computed_measure
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 15.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: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/view.xml:0
#, python-format
msgid "!measure.startsWith('__computed_')"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Add"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Can be empty"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Computed Measure"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Custom"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Div (m1 / m2)"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Float"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Format"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Formula"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Integer"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Measure 1"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Measure 2"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Mult (m1 * m2)"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Name"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Operation"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Perc (m1 * 100 / m2)"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Percentage"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Sub (m1 - m2)"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/dropdown_item_custom_measure/dropdown_item_custom_measure.xml:0
#, python-format
msgid "Sum (m1 + m2)"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/pivot/pivot_model.esm.js:0
#, python-format
msgid ""
"This measure is currently used by a 'computed measure'. Please, disable the "
"computed measure first."
msgstr ""

View File

@ -0,0 +1,6 @@
* `Tecnativa <https://www.tecnativa.com/>`_:
* Alexandre D. Díaz
* Pedro M. Baeza
* Ernesto Tejeda
* Carlos Roca

View File

@ -0,0 +1 @@
Adds support for computed measures on the pivot view.

View File

@ -0,0 +1,2 @@
#. Add support to define a style for a computed measure (ex. colored)
#. Use t-model to data-binding instead of jquery selectors

View File

@ -0,0 +1,25 @@
Go to pivot view and click on the "Measures" menu, you will see
a new option called 'Computed Measure'.
You have the follow options to create a 'computed measure':
- Measure 1: Used in 'operation formula' as 'm1'
- Measure 2: Used in 'operation formula' as 'm2'
- Operation: The formula
- Sum: m1 + m2
- Sub: m1 - m2
- Mult: m1 * m2
- Div: m1 / m2 (Format: Float)
- Perc m1 / m2 (Format: Percentage)
- Custom: Special option only visible in debug mode to write a custom formula.
- Name: The name of the new measure (emtpy = auto-generated)
- Format: How will the value be printed
- Integer
- Float
- Percentage (value * 100)
- Formula*: Custom operation formula
These formula is evaluated using 'PY.eval'
These computed measures can be mixed (You can reuse it to make new computed measures) and saved as favorites.
Notice that "measures/computed measures" involved in an active 'computed measure'
can't be deactivated until you have deactivate the 'computed measure'.

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@ -0,0 +1,483 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>Web Pivot Computed Measure</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="web-pivot-computed-measure">
<h1 class="title">Web Pivot Computed Measure</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:4cef60dd775467b756ccbacb8c215bba0bc74fd32e1917c8b15c3be9de6302a1
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/web/tree/16.0/web_pivot_computed_measure"><img alt="OCA/web" src="https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/web-16-0/web-16-0-web_pivot_computed_measure"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/web&amp;target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>Adds support for computed measures on the pivot view.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#usage" id="toc-entry-1">Usage</a></li>
<li><a class="reference internal" href="#known-issues-roadmap" id="toc-entry-2">Known issues / Roadmap</a></li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-3">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-4">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-5">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-6">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-7">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#toc-entry-1">Usage</a></h1>
<p>Go to pivot view and click on the “Measures” menu, you will see
a new option called Computed Measure.</p>
<dl class="docutils">
<dt>You have the follow options to create a computed measure:</dt>
<dd><ul class="first last simple">
<li>Measure 1: Used in operation formula as m1</li>
<li>Measure 2: Used in operation formula as m2</li>
<li><dl class="first docutils">
<dt>Operation: The formula</dt>
<dd><ul class="first last">
<li>Sum: m1 + m2</li>
<li>Sub: m1 - m2</li>
<li>Mult: m1 * m2</li>
<li>Div: m1 / m2 (Format: Float)</li>
<li>Perc m1 / m2 (Format: Percentage)</li>
<li>Custom: Special option only visible in debug mode to write a custom formula.</li>
</ul>
</dd>
</dl>
</li>
<li>Name: The name of the new measure (emtpy = auto-generated)</li>
<li><dl class="first docutils">
<dt>Format: How will the value be printed</dt>
<dd><ul class="first last">
<li>Integer</li>
<li>Float</li>
<li>Percentage (value * 100)</li>
</ul>
</dd>
</dl>
</li>
<li><dl class="first docutils">
<dt>Formula*: Custom operation formula</dt>
<dd>These formula is evaluated using PY.eval</dd>
</dl>
</li>
</ul>
</dd>
</dl>
<p>These computed measures can be mixed (You can reuse it to make new computed measures) and saved as favorites.</p>
<p>Notice that “measures/computed measures” involved in an active computed measure
cant be deactivated until you have deactivate the computed measure.</p>
</div>
<div class="section" id="known-issues-roadmap">
<h1><a class="toc-backref" href="#toc-entry-2">Known issues / Roadmap</a></h1>
<ol class="arabic simple">
<li>Add support to define a style for a computed measure (ex. colored)</li>
<li>Use t-model to data-binding instead of jquery selectors</li>
</ol>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#toc-entry-3">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/web/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/web/issues/new?body=module:%20web_pivot_computed_measure%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#toc-entry-4">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#toc-entry-5">Authors</a></h2>
<ul class="simple">
<li>Tecnativa</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#toc-entry-6">Contributors</a></h2>
<ul class="simple">
<li><a class="reference external" href="https://www.tecnativa.com/">Tecnativa</a>:<ul>
<li>Alexandre D. Díaz</li>
<li>Pedro M. Baeza</li>
<li>Ernesto Tejeda</li>
<li>Carlos Roca</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-7">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<p>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.</p>
<p>Current <a class="reference external" href="https://odoo-community.org/page/maintainer-role">maintainer</a>:</p>
<p><a class="reference external image-reference" href="https://github.com/CarlosRoca13"><img alt="CarlosRoca13" src="https://github.com/CarlosRoca13.png?size=40px" /></a></p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/web/tree/16.0/web_pivot_computed_measure">OCA/web</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,49 @@
/** @odoo-module **/
/* Copyright 2022 Tecnativa - Carlos Roca
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) */
import {Component, useState} from "@odoo/owl";
/**
* @extends Component
*/
export class DropdownItemCustomMeasure extends Component {
setup() {
this.isOpen = useState({value: false});
}
onClickComputedMeasure() {
this.isOpen.value = !this.isOpen.value;
}
addMeasure(ev) {
const $target = $(ev.target).closest("#add_computed_measure_wrapper");
const id = new Date().getTime();
const field1 = $target.find("#computed_measure_field_1").val();
const field2 = $target.find("#computed_measure_field_2").val();
let operation = $target.find("#computed_measure_operation").val();
if (operation === "custom") {
operation = $target.find("#computed_measure_operation_custom").val();
}
const name = $target.find("#computed_measure_name").val();
const format = $target.find("#computed_measure_format").val();
this.props.model.addComputedMeasure(
id,
field1,
field2,
operation,
name,
format
);
// Click on measures button to close the modal and recompute the measures added
$(ev.target).closest(".dropdown").find(".dropdown-toggle").trigger("click");
}
}
DropdownItemCustomMeasure.template =
"web_pivot_computed_measure.DropdownItemCustomMeasure";
DropdownItemCustomMeasure.props = {
measures: Object,
// Set as model because this module can be extended to be used on views that
// uses Measures like the graph view.
model: Object,
};

View File

@ -0,0 +1,9 @@
#add_computed_measure_wrapper {
padding: 0 20px;
min-width: 300px;
white-space: nowrap;
.d-table-cell {
vertical-align: middle;
padding: 3px 0;
}
}

View File

@ -0,0 +1,160 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="web_pivot_computed_measure.ComputedMeasureOperations" owl="1">
<option name="sum" value="m1+m2">
Sum (m1 + m2)
</option>
<option name="sub" value="m1-m2">
Sub (m1 - m2)
</option>
<option name="mult" value="m1*m2">
Mult (m1 * m2)
</option>
<option name="div" data-format="float" value="m1/m2">
Div (m1 / m2)
</option>
<option name="perc" data-format="percentage" value="m1/m2">
Perc (m1 * 100 / m2)
</option>
<option t-if="debug" name="custom" value="custom">
Custom
</option>
</t>
<t t-name="web_pivot_computed_measure.ComputedMeasureFormats" owl="1">
<option name="int" value="integer">
Integer
</option>
<option name="float" value="float" selected="selected">
Float
</option>
<option name="percentage" value="percentage">
Percentage
</option>
</t>
<t t-name="web_pivot_computed_measure.DropdownItemCustomMeasure" owl="1">
<div class="o_menu_item dropdown-item" data-id="__computed__">
<a href="#" role="menuitem" t-on-click="onClickComputedMeasure">
Computed Measure
<span class="o_submenu_switcher" data-id="__computed__">
<span
t-att-class="isOpen.value ? 'fa fa-caret-down' : 'fa fa-caret-right'"
/>
</span>
</a>
<t t-if="isOpen.value">
<div id="add_computed_measure_wrapper" class="d-table">
<div class="d-table-row">
<div class="d-table-cell">
<label for="computed_measure_field_1">Measure 1</label>
</div>
<div class="d-table-cell">
<select class="o_input" id="computed_measure_field_1">
<t
t-foreach="props.measures"
t-as="measure"
t-key="measure"
>
<option
t-att-value="measure"
t-if="measure != '__count'"
>
<t t-esc="props.measures[measure].string" />
</option>
</t>
</select>
</div>
</div>
<div class="d-table-row">
<div class="d-table-cell">
<label for="computed_measure_field_2">Measure 2</label>
</div>
<div class="d-table-cell">
<select class="o_input" id="computed_measure_field_2">
<t
t-foreach="props.measures"
t-as="measure"
t-key="measure"
>
<option
t-att-value="measure"
t-if="measure != '__count'"
>
<t t-esc="props.measures[measure].string" />
</option>
</t>
</select>
</div>
</div>
<div class="d-table-row">
<div class="d-table-cell">
<label for="computed_measure_operation">Operation</label>
</div>
<div class="d-table-cell">
<select class="o_input" id="computed_measure_operation">
<t
t-call="web_pivot_computed_measure.ComputedMeasureOperations"
/>
</select>
</div>
</div>
<div
t-if="debug"
class="d-none"
id="container_computed_measure_operation_custom"
>
<div class="d-table-cell">
<label
for="computed_measure_operation_custom"
>Formula</label>
</div>
<div class="d-table-cell">
<input
type="text"
class="o_input"
id="computed_measure_operation_custom"
/>
</div>
</div>
<div class="d-table-row">
<div class="d-table-cell">
<label for="computed_measure_name">Name</label>
</div>
<div class="d-table-cell">
<input
placeholder="Can be empty"
type="text"
class="o_input"
id="computed_measure_name"
/>
</div>
</div>
<div class="d-table-row">
<div class="d-table-cell">
<label for="computed_measure_format">Format</label>
</div>
<div class="d-table-cell">
<select class="o_input" id="computed_measure_format">
<t
t-call="web_pivot_computed_measure.ComputedMeasureFormats"
/>
</select>
</div>
</div>
<div class="d-table-row">
<div class="d-table-cell">
<button
class="btn btn-primary o_add_computed_measure"
type="button"
t-on-click="addMeasure"
>Add</button>
</div>
</div>
</div>
</t>
</div>
</t>
</templates>

View File

@ -0,0 +1,124 @@
/** @odoo-module **/
/* Copyright 2023 Tecnativa - Carlos Roca
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) */
/**
* Function that traverse the text given character by character
*
* @param {String} text
* @returns {Array}
*/
function getTokensFromText(text) {
const symbols = ["+", "-", "*", "/", "(", ")"];
const tokens = [];
let token = "";
for (let i = 0; i < text.length; i++) {
const c = text[i];
if (c === " ") continue;
if (symbols.includes(c)) {
if (token !== "") {
tokens.push(token);
token = "";
}
tokens.push(c);
} else {
token += c;
}
}
if (token !== "") {
tokens.push(token);
}
return tokens;
}
/**
* Function that executes an operation between the last two operands in the operands stack
* and the last operator in the operators stack, and saves the result in the operands stack.
*
* @param {Array} operands
* @param {Array} operators
*/
function executeOperation(operands, operators) {
const b = operands.pop();
const a = operands.pop();
const op = operators.pop();
switch (op) {
case "+":
operands.push(a + b);
break;
case "-":
operands.push(a - b);
break;
case "*":
operands.push(a * b);
break;
case "/":
operands.push(a / b);
break;
}
}
/**
* Function that returns the precedence of an operator
*
* @param {String} op
* @returns {Number}
*/
function precedence(op) {
if (op === "+" || op === "-") {
return 1;
}
if (op === "*" || op === "/") {
return 2;
}
if (op === "(" || op === ")") {
return 0;
}
}
/**
* Helper function that takes a mathematical expression in text form and an object
* of variable values, evaluates the expression, and returns the result.
*
* @param {String} text
* @param {Object} values
* @returns {any}
*/
export function evalOperation(text, values) {
const tokens = getTokensFromText(text);
const operands = [];
const operators = [];
for (const token of tokens) {
if (!isNaN(token)) {
// If the token is a number, convert it to a number and add it to the operands stack
operands.push(Number(token));
} else if (token in values) {
// If the token is a variable, get its value from the object and add it to the operands stack
operands.push(values[token]);
} else if (token === "(") {
// If the token is an open parenthesis, add it to the operators stack
operators.push(token);
} else if (token === ")") {
// If the token is a closing parenthesis, pop and execute operators from the stack until an open parenthesis is found
while (operators.length > 0 && operators[operators.length - 1] !== "(") {
executeOperation(operands, operators);
}
// Pop the open parenthesis from the operators stack
operators.pop();
} else {
// If the token is an operator, pop and execute operators from the stack while they have equal or higher precedence than the token
while (
operators.length > 0 &&
precedence(operators[operators.length - 1]) >= precedence(token)
) {
executeOperation(operands, operators);
}
// Add the token to the operators stack
operators.push(token);
}
}
while (operators.length > 0) {
executeOperation(operands, operators);
}
return operands.pop();
}

View File

@ -0,0 +1,26 @@
/** @odoo-module **/
/* Copyright 2024 Tecnativa - Carlos Roca
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) */
import {PivotController} from "@web/views/pivot/pivot_controller";
import {patch} from "@web/core/utils/patch";
import {DropdownItemCustomMeasure} from "../dropdown_item_custom_measure/dropdown_item_custom_measure.esm";
patch(PivotController.prototype, "web_pivot_computed_measure.PivotController", {
/**
* Add computed_measures to context key to avoid loosing info when saving the
* filter to favorites.
*
* @override
*/
getContext() {
var res = this._super(...arguments);
res.pivot_computed_measures = this.model._computed_measures;
return res;
},
});
PivotController.components = {
...PivotController.components,
DropdownItemCustomMeasure,
};

View File

@ -0,0 +1,296 @@
/** @odoo-module **/
/* Copyright 2020 Tecnativa - Alexandre Díaz
* Copyright 2022 Tecnativa - Carlos Roca
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) */
import {PivotModel} from "@web/views/pivot/pivot_model";
import {patch} from "@web/core/utils/patch";
import {computeReportMeasures} from "@web/views/utils";
import {evalOperation} from "../helpers/utils.esm";
patch(PivotModel.prototype, "web_pivot_computed_measure.PivotModel", {
/**
* Add _computed_measures to avoid recompute them until page is recharged
*
* @override
*/
setup() {
this._super(...arguments);
this._computed_measures = [];
},
/**
* Create a new computed measure
*
* @param {String} id
* @param {String} field1
* @param {String} field2
* @param {String} operation
* @param {String} name
* @param {String} format
* @returns a promise
*/
addComputedMeasure(id, field1, field2, operation, name, format) {
const measure = _.find(this._computed_measures, (item) => {
return (
item.field1 === field1 &&
item.field2 === field2 &&
item.operation === operation
);
});
if (measure) {
return Promise.resolve();
}
const fieldM1 = this.metaData.fields[field1];
const fieldM2 = this.metaData.fields[field2];
const cmId = "__computed_" + id;
const oper = operation.replace(/m1/g, field1).replace(/m2/g, field2);
const oper_human = operation
.replace(
/m1/g,
fieldM1.__computed_id ? "(" + fieldM1.string + ")" : fieldM1.string
)
.replace(
/m2/g,
fieldM2.__computed_id ? "(" + fieldM2.string + ")" : fieldM2.string
);
const cmTotal = this._computed_measures.push({
field1: field1,
field2: field2,
operation: oper,
name: name || oper_human,
id: cmId,
format: format,
});
return this._createVirtualMeasure(this._computed_measures[cmTotal - 1]);
},
/**
* Create and enable a measure based on a 'fake' field
*
* @private
* @param {Object} cmDef
* @param {List} fields *Optional*
* @returns a promise
*/
_createVirtualMeasure(cmDef, fields) {
this._createVirtualField(cmDef, fields);
// Activate computed field
return this.toggleMeasure(cmDef.id);
},
_createVirtualField(cmDef, fields, config) {
const arrFields = fields || this.metaData.fields;
// This is a minimal 'fake' field info
arrFields[cmDef.id] = {
// Used to format the value
type: cmDef.format,
// Used to print the header name
string: cmDef.name,
// Referenced on payload prop at DropdownItem, used to interact with
// created measures
name: cmDef.id,
// Used to know if is a computed measure field
__computed_id: cmDef.id,
// Operator used for group the measure added.
group_operator: "sum",
};
const metaData = (config && config.metaData) || this.metaData;
metaData.measures[cmDef.id] = arrFields[cmDef.id];
},
/**
* Active the measures related to the 'fake' field
*
* @private
* @param {List of Strings} fields
*/
async _activeMeasures(fields) {
let needLoad = false;
for (const field of fields) {
if (await !this._isMeasureEnabled(field)) {
this.metaData.activeMeasures.push(field);
needLoad = true;
}
}
if (needLoad) {
const config = {metaData: this.metaData, data: this.data};
return this._loadData(config).then(() => {
// Notify changes to renderer for show it on the pivot view
this.notify();
});
}
return Promise.resolve();
},
/**
* Check if the measure is enabled
*
* @private
* @param {String} field
*/
_isMeasureEnabled(field, config) {
const activeMeasures =
(config && config.metaData.activeMeasures) ||
this.metaData.activeMeasures ||
[];
return _.contains(activeMeasures, field);
},
/**
* Helper function to add computed measure fields data into a 'subGroupData'
*
* @private
* @param {Object} subGroupData
*/
_fillComputedMeasuresData(subGroupData, config) {
for (const cm of this._computed_measures) {
if (!this._isMeasureEnabled(cm.id, config)) continue;
if (subGroupData.__count === 0) {
subGroupData[cm.id] = false;
} else {
// eslint-disable-next-line no-undef
subGroupData[cm.id] = evalOperation(cm.operation, subGroupData);
}
}
},
/**
* Fill the groupSubdivisions with the computed measures and their values
*
* @override
*/
_prepareData(group, groupSubdivisions, config) {
for (const groupSubdivision of groupSubdivisions) {
for (const subGroup of groupSubdivision.subGroups) {
this._fillComputedMeasuresData(subGroup, config);
}
}
this._super(...arguments);
},
/**
* _getGroupSubdivision method invokes the read_group method of the
* model via rpc and the passed 'fields' argument is the list of
* measure names that is in this.metaData.activeMeasures, so we remove the
* computed measures form this.metaData.activeMeasures before calling _super
* to prevent any possible exception.
*
* @override
*/
_getGroupSubdivision(group, rowGroupBy, colGroupBy, config) {
const computed_measures = [];
for (let i = 0; i < config.metaData.activeMeasures.length; i++)
if (config.metaData.activeMeasures[i].startsWith("__computed_")) {
computed_measures.push(config.metaData.activeMeasures[i]);
config.metaData.activeMeasures.splice(i, 1);
i--;
}
const res = this._super(...arguments);
$.merge(config.metaData.activeMeasures, computed_measures);
return res;
},
/**
* Adds a rule to deny that measures can be disabled if are being used by a computed measure.
* In the other hand, when enables a measure analyzes it to active all involved measures.
*
* @override
*/
toggleMeasure(fieldName) {
if (this._isMeasureEnabled(fieldName)) {
// Mesaure is enabled
const umeasures = _.filter(this._computed_measures, (item) => {
return item.field1 === fieldName || item.field2 === fieldName;
});
if (umeasures.length && this._isMeasureEnabled(umeasures[0].id)) {
return Promise.reject(
this.env._t(
"This measure is currently used by a 'computed measure'. Please, disable the computed measure first."
)
);
}
} else {
// Measure is disabled
const toEnable = [];
const toAnalyze = [fieldName];
while (toAnalyze.length) {
// Analyze all items involved on computed measures to enable them
const afield = toAnalyze.shift();
const fieldDef = this.metaData.fields[afield];
// Need to check if fieldDef exists to avoid problems with __count
if (fieldDef && fieldDef.__computed_id) {
const cm = _.find(this._computed_measures, {
id: fieldDef.__computed_id,
});
toAnalyze.push(cm.field1, cm.field2);
const toEnableFields = [];
if (!this.metaData.fields[cm.field1].__computed_id) {
toEnableFields.push(cm.field1);
}
if (!this.metaData.fields[cm.field2].__computed_id) {
toEnableFields.push(cm.field2);
}
toEnableFields.push(afield);
toEnable.push(toEnableFields);
}
}
if (toEnable.length) {
this._activeMeasures(
// Transform the array of arrays to a simple array.
// [1, [2, 3]] => [1, 2, 3]
_.flatten(toEnable.reverse())
);
}
}
return this._super(...arguments);
},
/**
* Load the measures added to selected favorite filters
*
* @override
*/
async load(searchParams) {
var _super = this._super.bind(this);
var config = {metaData: this.metaData, data: this.data};
if (!this.metaData.measures) {
const metaData = this._buildMetaData();
metaData.measures = computeReportMeasures(
metaData.fields,
metaData.fieldAttrs,
metaData.activeMeasures,
metaData.additionalMeasures
);
config = {metaData, data: this.data};
}
if ("context" in searchParams) {
this._computed_measures =
searchParams.context.pivot_computed_measures ||
searchParams.computed_measures ||
[];
}
for (const cmDef of this._computed_measures) {
if (this._isMeasureEnabled(cmDef.id, config)) {
continue;
}
await this._createVirtualField(cmDef, undefined, config);
}
const fieldNames = Object.keys(this.metaData.fields);
for (const fieldName of fieldNames) {
const field = this.metaData.fields[fieldName];
if (field.__computed_id) {
const cm = _.find(this._computed_measures, {
id: field.__computed_id,
});
if (!cm) {
delete this.metaData.fields[fieldName];
delete this.metaData.measures[fieldName];
this.metaData.activeMeasures = _.without(
this.metaData.activeMeasures,
fieldName
);
}
}
}
return _super(...arguments);
},
});

View File

@ -0,0 +1,15 @@
/** @odoo-module **/
/* Copyright 2022 Tecnativa - Carlos Roca
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) */
import {PivotRenderer} from "@web/views/pivot/pivot_renderer";
import {patch} from "@web/core/utils/patch";
patch(PivotRenderer.prototype, "web_pivot_computed_measure.PivotRenderer", {
getFormattedValue(cell) {
if (cell.value === Infinity) {
return "-";
}
return this._super(...arguments);
},
});

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-inherit="web.PivotView.Buttons" t-inherit-mode="extension" owl="1">
<xpath expr="//t[@t-call='web.ReportViewMeasures']" position="inside">
<t t-set="add_computed_measures" t-value="true" />
<t t-set="model" t-value="model" />
</xpath>
</t>
</templates>

View File

@ -0,0 +1,67 @@
/** @odoo-module **/
/* Copyright 2022 Tecnativa - Carlos Roca
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) */
import tour from "web_tour.tour";
tour.register(
"web_pivot_computed_measure_tour",
{
url: "/web",
test: true,
},
[
{
trigger: ".o_navbar_apps_menu button",
},
{
trigger: '.o_app[data-menu-xmlid="base.menu_administration"]',
},
{
trigger: 'button[data-menu-xmlid="base.menu_users"]',
},
{
trigger: 'a[data-menu-xmlid="base.menu_action_res_users"]',
},
{
trigger: "button.o_pivot",
},
{
trigger: 'button:contains(" Measures ")',
},
{
trigger: 'a:contains(" Computed Measure ")',
},
{
trigger: "select#computed_measure_field_1",
run: "text user_year_now",
},
{
trigger: "select#computed_measure_field_2",
run: "text user_year_born",
},
{
trigger: "select#computed_measure_operation",
run: "text m1-m2",
},
{
trigger: "select#computed_measure_format",
run: "text integer",
},
{
trigger: "button.o_add_computed_measure",
},
{
trigger: 'th.o_pivot_measure_row:contains("User Year Now")',
extra_trigger: 'div.o_value:contains("2,022")',
},
{
trigger: 'th.o_pivot_measure_row:contains("User Year Born")',
extra_trigger: 'div.o_value:contains("1,998")',
},
{
trigger: 'th.o_pivot_measure_row:contains("User Year Now-User Year Born")',
extra_trigger: 'div.o_value:contains("24")',
},
]
);

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-inherit="web.ReportViewMeasures" t-inherit-mode="extension" owl="1">
<xpath expr="//DropdownItem" position="attributes">
<attribute name="t-if">!measure.startsWith('__computed_')</attribute>
</xpath>
<xpath expr="//t[@t-foreach='measures']" position="after">
<div role="separator" class="dropdown-divider" t-if="add_computed_measures" />
<t t-foreach="measures" t-as="measure" t-key="measure_value.name">
<DropdownItem
class="{ o_menu_item: true, selected: activeMeasures.includes(measure) }"
t-if="add_computed_measures and measure.startsWith('__computed_')"
t-esc="measures[measure].string"
parentClosingMode="'none'"
onSelected="() => this.onMeasureSelected({ measure: measure_value.name })"
/>
</t>
<DropdownItemCustomMeasure
measures="measures"
model="model"
t-if="add_computed_measures"
/>
</xpath>
</t>
</templates>

View File

@ -0,0 +1 @@
from . import test_ui_pivot

View File

@ -0,0 +1,12 @@
# Copyright 2022 Tecnativa - Carlos Roca
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
from odoo import fields, models
class ResUsersFake(models.Model):
_inherit = "res.users"
user_year_born = fields.Integer()
user_year_now = fields.Integer()

View File

@ -0,0 +1,37 @@
# Copyright 2022 Tecnativa - Carlos Roca
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
from odoo_test_helper import FakeModelLoader
from odoo.tests import common, tagged
@tagged("post_install", "-at_install")
class TestUIPivot(common.HttpCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.loader = FakeModelLoader(cls.env, cls.__module__)
cls.loader.backup_registry()
from .res_users_fake import ResUsersFake
cls.loader.update_registry((ResUsersFake,))
cls.env["res.users"].create(
{
"name": "User 1",
"login": "us_1",
# Fake fields
"user_year_born": 1998,
"user_year_now": 2022,
}
)
# Set pivot view to company action
action = cls.env.ref("base.action_res_users")
action.view_mode += ",pivot"
def test_ui(self):
self.start_tour(
"/web",
"web_pivot_computed_measure_tour",
login="admin",
step_delay=100,
)