[ADD] web_pivot_computed_measure

pull/2755/head
Alexandre Díaz 2020-03-10 03:35:30 +01:00 committed by Carlos Roca
parent b5bc82a184
commit 1cee9f4685
14 changed files with 1300 additions and 0 deletions

View File

@ -0,0 +1,110 @@
==========================
Web Pivot Computed Measure
==========================
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! 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%2Fweb-lightgray.png?logo=github
:target: https://github.com/OCA/web/tree/12.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-12-0/web-12-0-web_pivot_computed_measure
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
:target: https://runbot.odoo-community.org/runbot/162/12.0
:alt: Try me on Runbot
|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)
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 smashing it by providing a detailed and welcomed
`feedback <https://github.com/OCA/web/issues/new?body=module:%20web_pivot_computed_measure%0Aversion:%2012.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
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.
This module is part of the `OCA/web <https://github.com/OCA/web/tree/12.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,21 @@
# 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': "12.0.1.0.0",
'author': "Tecnativa, "
"Odoo Community Association (OCA)",
'license': 'AGPL-3',
'website': 'https://github.com/OCA/web',
'depends': ['web'],
'data': [
'view/assets.xml'
],
'qweb': [
'static/src/xml/web_pivot_computed_measure.xml',
],
'auto_install': False,
'installable': True,
}

View File

@ -0,0 +1,148 @@
# 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"
"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/xml/web_pivot_computed_measure.xml:96
#, python-format
msgid "Add"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/xml/web_pivot_computed_measure.xml:87
#, python-format
msgid "Can be empty"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/xml/web_pivot_computed_measure.xml:47
#, python-format
msgid "Computed Measure"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/xml/web_pivot_computed_measure.xml:24
#, python-format
msgid "Custom"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/xml/web_pivot_computed_measure.xml:18
#, python-format
msgid "Div (m1 / m2)"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/xml/web_pivot_computed_measure.xml:33
#, python-format
msgid "Float"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/xml/web_pivot_computed_measure.xml:90
#, python-format
msgid "Format"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/xml/web_pivot_computed_measure.xml:82
#, python-format
msgid "Formula"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/xml/web_pivot_computed_measure.xml:30
#, python-format
msgid "Integer"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/xml/web_pivot_computed_measure.xml:56
#, python-format
msgid "Measure 1"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/xml/web_pivot_computed_measure.xml:66
#, python-format
msgid "Measure 2"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/xml/web_pivot_computed_measure.xml:15
#, python-format
msgid "Mult (m1 * m2)"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/xml/web_pivot_computed_measure.xml:86
#, python-format
msgid "Name"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/xml/web_pivot_computed_measure.xml:76
#, python-format
msgid "Operation"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/xml/web_pivot_computed_measure.xml:21
#, python-format
msgid "Perc (m1 * 100 / m2)"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/xml/web_pivot_computed_measure.xml:36
#, python-format
msgid "Percentage"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/xml/web_pivot_computed_measure.xml:12
#, python-format
msgid "Sub (m1 - m2)"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/xml/web_pivot_computed_measure.xml:9
#, python-format
msgid "Sum (m1 + m2)"
msgstr ""
#. module: web_pivot_computed_measure
#. openerp-web
#: code:addons/web_pivot_computed_measure/static/src/js/pivot_model.js:224
#, python-format
msgid "This measure is currently used by a 'computed measure'. Please, disable the computed measure first."
msgstr ""

View File

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

View File

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

View File

@ -0,0 +1 @@
#. Add support to define a style for a computed measure (ex. colored)

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,476 @@
<?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 0.15.1: http://docutils.sourceforge.net/" />
<title>Web Pivot Computed Measure</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
See http://docutils.sf.net/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. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external" 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" 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" href="https://github.com/OCA/web/tree/12.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" href="https://translation.odoo-community.org/projects/web-12-0/web-12-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" href="https://runbot.odoo-community.org/runbot/162/12.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-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="id1">Usage</a></li>
<li><a class="reference internal" href="#known-issues-roadmap" id="id2">Known issues / Roadmap</a></li>
<li><a class="reference internal" href="#bug-tracker" id="id3">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id4">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id5">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id6">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="id7">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#id1">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="#id2">Known issues / Roadmap</a></h1>
<ol class="arabic simple">
<li>Add support to define a style for a computed measure (ex. colored)</li>
</ol>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#id3">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 smashing 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:%2012.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="#id4">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#id5">Authors</a></h2>
<ul class="simple">
<li>Tecnativa</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#id6">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>
</ul>
</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#id7">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>This module is part of the <a class="reference external" href="https://github.com/OCA/web/tree/12.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,145 @@
/* Copyright 2020 Tecnativa - Alexandre Díaz
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) */
odoo.define('web_pivot_computed_measure.PivotController', function (require) {
"use strict";
var core = require('web.core');
var config = require('web.config');
var PivotController = require('web.PivotController');
var QWeb = core.qweb;
PivotController.include({
custom_events: {
'add_measure': '_onAddMeasure',
'remove_measure': '_onRemoveMeasure',
},
computed_measures_open: false,
/**
* Add the computed measures to the context. This
* will be used when save a filter.
*
* @override
*/
getContext: function () {
var res = this._super.apply(this, arguments);
var state = this.model.get();
res.pivot_computed_measures = state.computed_measures;
return res;
},
/**
* @override
*/
renderButtons: function($node) {
this._super.apply(this, arguments);
if ($node) {
this._renderComputedMeasures();
}
},
/**
* Handle click event on measures menu to support computed measures sub-menu
*
* @override
*/
_onButtonClick: function (event) {
var $target = $(event.target);
if ($target.parents("div[data-id='__computed__']").length) {
var hideMenu = false;
event.preventDefault();
if ($target.hasClass('dropdown-item') || $target.hasClass('o_submenu_switcher')) {
this.computed_measures_open = !this.computed_measures_open;
this._renderComputedMeasures();
} else if ($target.hasClass('o_add_computed_measure')) {
hideMenu = true;
var field1 = this.$buttons_measures_ex.find('#computed_measure_field_1').val();
var field2 = this.$buttons_measures_ex.find('#computed_measure_field_2').val();
var oper = this.$buttons_measures_ex.find('#computed_measure_operation').val();
if (oper === "custom") {
oper = this.$buttons_measures_ex.find('#computed_measure_operation_custom').val();
}
var name = this.$buttons_measures_ex.find('#computed_measure_name').val();
var format = this.$buttons_measures_ex.find('#computed_measure_format').val();
var uniqueId = (new Date()).getTime();
this.model.createComputedMeasure(uniqueId, field1, field2, oper, name, format)
.then(this.update.bind(this, {}, {reload: false}));
}
if (!hideMenu) {
event.stopPropagation();
}
return;
}
this._super.apply(this, arguments);
},
/**
* Render computed measures menu
*/
_renderComputedMeasures: function() {
if (this.$buttons_measures_ex && this.$buttons_measures_ex.length) {
this.$buttons_measures_ex.remove();
}
var self = this;
var measures = _.sortBy(_.pairs(_.omit(this.measures, '__count')), function (x) { return x[1].string.toLowerCase(); });
this.$buttons_measures_ex = $(QWeb.render('web_pivot_computed_measure.ExtendedMenu', {
isOpen: this.computed_measures_open,
debug: config.debug,
measures: measures,
computed_measures: _.map(_.reject(measures, function(item) { return !item[1].__computed_id; }), function(item) {
item[1].active = _.contains(self.model.data.measures, item[0]);
return item;
}),
}));
this.$buttons_measures_ex.find('#computed_measure_operation').on('change', this._onChangeComputedMeasureOperation.bind(this));
this.$buttons.find('.o_pivot_measures_list').append(this.$buttons_measures_ex);
},
/**
* Custom event to add a new measure
*
* @param {CustomEvent} ev
*/
_onAddMeasure: function(ev) {
this.measures[ev.data.id] = ev.data.def;
this._renderComputedMeasures();
},
/**
* Custom event to remove a measure
*
* @param {CustomEvent} ev
*/
_onRemoveMeasure: function(ev) {
delete this.measures[ev.data.id];
this._renderComputedMeasures();
},
/**
* Set default values related with the selected operation
*
* @param {ChangeEvent} ev
*/
_onChangeComputedMeasureOperation: function(ev) {
var $option = $(ev.target.options[ev.target.selectedIndex]);
if ($(ev.target).val() === "custom") {
this.$buttons_measures_ex.find('#container_computed_measure_operation_custom').removeClass('d-none');
} else {
var format = $option.data('format');
if (format) {
this.$buttons_measures_ex.find('#computed_measure_format').val(format);
}
this.$buttons_measures_ex.find('#container_computed_measure_operation_custom').addClass('d-none');
}
}
});
});

View File

@ -0,0 +1,256 @@
/* Copyright 2020 Tecnativa - Alexandre Díaz
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) */
odoo.define('web_pivot_computed_measure.PivotModel', function (require) {
"use strict";
var core = require('web.core');
var PivotModel = require('web.PivotModel');
var _t = core._t;
PivotModel.include({
_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
*/
createComputedMeasure: function(id, field1, field2, operation, name, format) {
var measure = _.find(this._computed_measures, function(item) {
return item.field1 === field1 && item.field2 === field2 && item.operation === operation;
});
if (measure) {
return $.Deferred(function(d) {
d.resolve();
});
}
var fieldM1 = this.fields[field1];
var fieldM2 = this.fields[field2];
var cmId = '__computed_' + id;
var oper = operation.replace(/m1/g, field1).replace(/m2/g, field2);
var oper_human = operation.replace(
/m1/g,
fieldM1.__computed_id?"("+fieldM1.string+")":fieldM1.string).replace(
/m2/g,
fieldM2.__computed_id?"("+fieldM2.string+")":fieldM2.string);
var 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
*
* @param {Object} cmDef
* @param {List} fields *Optional*
*/
_createVirtualMeasure: function(cmDef, fields) {
var arrFields = fields || this.fields;
// This is a minimal 'fake' field info
arrFields[cmDef.id] = {
type: cmDef.format, // Used to format the value
string: cmDef.name, // Used to print the header name
__computed_id: cmDef.id, // Used to know if is a computed measure field
}
this.trigger_up("add_measure", {
id: cmDef.id,
def: arrFields[cmDef.id],
});
return this._activeMeasures([cmDef.field1, cmDef.field2, cmDef.id]);
},
/**
* @param {List of Strings} fields
*/
_activeMeasures: function(fields) {
var needLoad = false;
var l = fields.length;
for (var x = 0; x < l; ++x) {
var field = fields[x];
if (!this._isMeasureEnabled(field)) {
this.data.measures.push(field);
needLoad = true;
}
}
if (needLoad) {
return this._loadData();
}
return $.Deferred(function(d) {
d.resolve();
})
},
/**
* @param {String} field
*/
_isMeasureEnabled: function(field) {
return _.contains(this.data.measures, field);
},
/**
* Fill the dataPoints with the computed measures values
*
* @override
*/
_mergeData: function (data, comparisonData, groupBys) {
var res = this._super.apply(this, arguments);
var l = groupBys.length; // Cached loop (This is not python! hehe)
for (var index = 0; index < l; ++index) {
if (data.length) {
var l2 = data[index].length;
for (var k = 0; k < l2; ++k) {
var dataPoint = data[index][k];
if (_.isEmpty(dataPoint)) {
break;
}
var l3 = this._computed_measures.length;
for (var x = 0; x < l3; ++x) {
var cm = this._computed_measures[x];
if (!this._isMeasureEnabled(cm.id)) {
continue;
}
dataPoint[cm.id] = py.eval(cm.operation, dataPoint);
}
}
}
}
return res;
},
/**
* Load the computed measures in context. This is used by filters.
*
* @override
*/
load: function (params) {
var self = this;
this._computed_measures = params.context.pivot_computed_measures || params.computed_measures || [];
var toActive = [];
var l = this._computed_measures.length;
for (var x = 0; x < l; ++x) {
var cmDef = this._computed_measures[x];
params.fields[cmDef.id] = {
type: cmDef.format,
string: cmDef.name,
__computed_id: cmDef.id,
}
toActive.push(cmDef.field1, cmDef.field2, cmDef.id);
}
return this._super(params).then(function() {
_.defer(function() {
for (var x = 0; x < l; ++x) {
var cmDef = self._computed_measures[x];
self.trigger_up("add_measure", {
id: cmDef.id,
def: self.fields[cmDef.id],
});
}
});
self._activeMeasures(toActive);
});
},
/**
* Load the computed measures in context. This is used by filters.
*
* @override
*/
reload: function (handle, params) {
if ('context' in params) {
this._computed_measures = params.context.pivot_computed_measures || params.computed_measures || [];
}
var l = this._computed_measures.length;
for (var x = 0; x < l; ++x) {
this._createVirtualMeasure(this._computed_measures[x]);
}
// Clean unused 'fake' fields
var fieldNames = Object.keys(this.fields);
for (var x = 0; x < fieldNames.length; ++x) {
var field = this.fields[fieldNames[x]];
if (field.__computed_id) {
var cm = _.find(this._computed_measures, {id:field.__computed_id});
if (!cm) {
delete this.fields[fieldNames[x]];
this.data.measures = _.without(this.data.measures, fieldNames[x]);
this.trigger_up("remove_measure", {
id: fieldNames[x],
});
}
}
}
return this._super.apply(this, arguments);
},
/**
* Add the computed measures to the state. This is used by filters.
*
* @override
*/
get: function () {
var res = this._super.apply(this, arguments);
res.computed_measures = this._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: function (field) {
if (this._isMeasureEnabled(field)) {
// Measure is disabled
var umeasures = _.filter(this._computed_measures, function(item) {
return item.field1 === field || item.field2 === field;
})
if (umeasures.length && this._isMeasureEnabled(umeasures[0].id)) {
return $.Deferred(function(d) {
d.reject(_t("This measure is currently used by a 'computed measure'. Please, disable the computed measure first."));
});
}
} else {
// Mesaure is enabled
var toEnable = [];
var toAnalize = [field];
while (toAnalize.length) {
var afield = toAnalize.shift();
var fieldDef = this.fields[afield];
if (fieldDef.__computed_id) {
var cm = _.find(this._computed_measures, {id:fieldDef.__computed_id});
toAnalize.push(cm.field1, cm.field2);
var toEnableFields = [];
if (!this.fields[cm.field1].__computed_id) {
toEnableFields.push(cm.field1);
}
if (!this.fields[cm.field2].__computed_id) {
toEnableFields.push(cm.field2);
}
toEnableFields.push(afield);
toEnable.push(toEnableFields);
}
}
if (toEnable.length) {
return this._activeMeasures(_.flatten(toEnable.reverse()));
}
}
return this._super.apply(this, arguments);
},
});
});

View File

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2020 Tecnativa - Alexandre Díaz
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-->
<templates>
<t t-name="web_pivot_computed_measure.ComputedMeasureOperations">
<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">
<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.ExtendedMenu">
<div role="separator" class="dropdown-divider"/>
<t t-foreach="computed_measures" t-as="cm">
<a role="menuitem" href="#" t-attf-class="dropdown-item {{cm[1].active and 'selected' or ''}}" t-data-computed="1" t-att-data-field="cm[0]"><t t-esc="cm[1].string"/></a>
</t>
<div class="o_menu_item" data-id="__computed__">
<a href="#" role="menuitem" class="dropdown-item">
Computed Measure
<span class="o_submenu_switcher" data-id="__computed__">
<span t-att-class="isOpen ? 'fa fa-caret-down' : 'fa fa-caret-right'"></span>
</span>
</a>
<t t-if="isOpen">
<div class="dropdown-item-text">
<label for="computed_measure_field_1">Measure 1</label>
<select class="o_input o_date_field_selector" id="computed_measure_field_1">
<t t-foreach="measures" t-as="measure">
<option t-att-value="measure[0]">
<t t-esc="measure[1].string" />
</option>
</t>
</select>
</div>
<div class="dropdown-item-text">
<label for="computed_measure_field_2">Measure 2</label>
<select class="o_input o_time_range_selector" id="computed_measure_field_2">
<t t-foreach="measures" t-as="measure">
<option t-att-value="measure[0]">
<t t-esc="measure[1].string" />
</option>
</t>
</select>
</div>
<div class="dropdown-item-text">
<label for="computed_measure_operation">Operation</label>
<select class="o_input o_time_range_selector" id="computed_measure_operation">
<t t-call="web_pivot_computed_measure.ComputedMeasureOperations" />
</select>
</div>
<div t-if="debug" class="dropdown-item-text d-none" id="container_computed_measure_operation_custom">
<label for="computed_measure_operation_custom">Formula</label>
<input type="text" id="computed_measure_operation_custom" />
</div>
<div class="dropdown-item-text">
<label for="computed_measure_name">Name</label>
<input placeholder="Can be empty" type="text" id="computed_measure_name" />
</div>
<div class="dropdown-item-text">
<label for="computed_measure_format">Format</label>
<select class="o_input o_time_range_selector" id="computed_measure_format">
<t t-call="web_pivot_computed_measure.ComputedMeasureFormats" />
</select>
</div>
<div class="dropdown-item-text">
<button class="btn btn-primary o_add_computed_measure" type="button">Add</button>
</div>
</t>
</div>
</t>
</templates>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<template id="assets_backend" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<script type="text/javascript" src="/web_pivot_computed_measure/static/src/js/pivot_model.js"></script>
<script type="text/javascript" src="/web_pivot_computed_measure/static/src/js/pivot_controller.js"></script>
</xpath>
</template>
</odoo>