Merge PR #1592 into 12.0

Signed-off-by pedrobaeza
This commit is contained in:
OCA-git-bot
2020-08-19 07:19:40 +00:00
10 changed files with 441 additions and 0 deletions

View File

@@ -0,0 +1 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

View File

@@ -0,0 +1,22 @@
# Copyright 2014-2015 GRAP
# Copyright 2016 LasLabs Inc.
# Copyright 2020 Brainbean Apps (https://brainbeanapps.com)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
{
'name': 'Web Widget - Formulas in Float Fields',
'summary': 'Allow use of simple formulas in float fields',
'version': '12.0.1.0.0',
'category': 'Web',
'author':
'GRAP, LasLabs, Brainbean Apps, Odoo Community Association (OCA)',
'website': 'https://github.com/OCA/web/',
'license': 'AGPL-3',
'depends': [
'web',
],
'data': [
'templates/assets.xml',
],
'installable': True,
}

View File

@@ -0,0 +1,14 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 10.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"

View File

@@ -0,0 +1,3 @@
* Sylvain Le Gal (https://twitter.com/legalsylvain)
* Oleg Bulkin <o.bulkin@gmail.com>
* Alexey Pelykh <alexey.pelykh@brainbeanapps.com>

View File

@@ -0,0 +1,11 @@
This module allows the use of simple math formulas in corresponding fields:
``=45 + 4/3 - 5 * (2 + 1)``
Features:
* ``+`` (addition)
* ``-`` (subtraction)
* ``*`` (multiplication)
* ``/`` (division)
* ``%`` (modulus)
* ``(`` and ``)`` parentheses

View File

@@ -0,0 +1 @@
This module is not needed for v13, as this feature is bundled with Odoo v13.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -0,0 +1,200 @@
/**
* Copyright 2014-2015 GRAP
* Copyright 2016 LasLabs Inc.
* Copyright 2020 Brainbean Apps (https://brainbeanapps.com)
* License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
*/
odoo.define('web_widget_float_formula', function(require) {
"use strict";
var field_utils = require('web.field_utils');
var pyUtils = require('web.py_utils');
var NumericField = require('web.basic_fields').NumericField;
var FieldMonetary = require('web.basic_fields').FieldMonetary;
var FormulaFieldMixin = {
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* Unaltered formula that user has entered.
*
* @private
*/
_formula: '',
/**
* Value of the field that was concealed during formula reveal.
*
* @private
*/
_concealedValue: '',
/**
* Returns formula prefix character
*
* @private
*/
_getFormulaPrefix: function () {
return '=';
},
/**
* Process formula if one is detected.
*
* @override
* @private
* @param {any} value
* @param {Object} [options]
*/
_setValue: function (value, options) {
this._formula = '';
if (!!value && this._isFormula(value)) {
try {
var evaluated_value = this._evaluateFormula(value);
this._formula = value;
value = this._formatValue(evaluated_value);
this.$input.val(value);
} catch (err) {
this._formula = '';
} finally {
this._concealedValue = '';
}
}
return this._super(value, options);
},
/**
* Checks if provided value is a formula.
*
* @private
* @param {any} value
*/
_isFormula: function(value) {
value = value.toString().replace(/\s+/gm, '');
return value.startsWith(this._getFormulaPrefix())
|| this._getOperatorsRegExp().test(value);
},
/**
* Returns regular expression that matches all supported operators
*
* @private
*/
_getOperatorsRegExp: function () {
return /((?:\+)|(?:\-)|(?:\*)|(?:\/)|(?:\()|(?:\))|(?:\%))/;
},
/**
* Evaluate formula.
*
* @private
* @param {any} formula
*/
_evaluateFormula: function(formula) {
return pyUtils.py_eval(this._preparseFormula(formula));
},
/**
* Pre-parses and sanitizes formula
*
* @private
* @param {string} formula
*/
_preparseFormula: function(formula) {
formula = formula.toString().replace(/\s+/gm, '');
var prefix = this._getFormulaPrefix();
if (formula.startsWith(prefix)) {
formula = formula.substring(prefix.length);
}
var operatorsRegExp = this._getOperatorsRegExp();
return formula.split(operatorsRegExp).reduce((tokens, token) => {
if (token === '') {
return tokens;
}
if (!operatorsRegExp.test(token)) {
token = field_utils.parse.float(token);
}
tokens.push(token);
return tokens;
}, []).join('');
},
/**
* Reveals formula
*
* @private
*/
_revealFormula: function () {
if (!!this._formula) {
this._concealedValue = this.$input.val();
this.$input.val(this._formula);
}
},
/**
* Conceals formula
*
* @private
*/
_concealFormula: function () {
var value = this.$input.val();
if (!!value && this._isFormula(value)) {
if (value !== this._formula) {
this.commitChanges();
} else if (!!this._concealedValue) {
this.$input.val(this._concealedValue);
this._concealedValue = '';
}
}
},
/**
* Handles 'focus' event
*
* @private
* @param {FocusEvent} event
*/
_onFocusFormulaField: function(event) {
if (this.$input === undefined || this.mode !== 'edit') {
return;
}
this._revealFormula();
},
/**
* Handles 'blur' event
*
* @private
* @param {FocusEvent} event
*/
_onBlurFormulaField: function(event) {
if (this.$input === undefined || this.mode !== 'edit') {
return;
}
this._concealFormula();
},
};
NumericField.include({
...FormulaFieldMixin,
events: _.extend({}, NumericField.prototype.events, {
'focus': '_onFocusFormulaField',
'blur': '_onBlurFormulaField',
}),
});
FieldMonetary.include({
...FormulaFieldMixin,
events: _.extend({}, FieldMonetary.prototype.events, {
'focusin': '_onFocusFormulaField',
'focusout': '_onBlurFormulaField',
}),
});
return {
FormulaFieldMixin: FormulaFieldMixin,
};
});

View File

@@ -0,0 +1,169 @@
/**
* Copyright 2016 LasLabs Inc.
* Copyright 2020 Brainbean Apps (https://brainbeanapps.com)
* License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
*/
odoo.define('web_widget_float_formula.test_web_widget_float_formula', function (require) {
"use strict";
var FormView = require('web.FormView');
var testUtils = require('web.test_utils');
QUnit.module('web_widget_float_formula', {}, function () {
QUnit.test('float field', async function (assert) {
assert.expect(5);
const form = await testUtils.createAsyncView({
View: FormView,
model: 'demo_entry',
data: {
demo_entry: {
fields: {
test_field: {string: 'Test Field', type: 'float'},
},
records: [{id: 1, test_field: 0.0}],
},
},
res_id: 1,
arch:
'<form>' +
'<field name="test_field"/>' +
'</form>',
viewOptions: {
mode: 'edit',
},
});
var test_field = form.$('.o_field_widget[name="test_field"]');
testUtils.fields.editInput(test_field, '0.0 + 40.0 + 2.0');
assert.strictEqual(test_field.val(), '42.00');
test_field.triggerHandler('focus');
assert.strictEqual(test_field.val(), '0.0 + 40.0 + 2.0');
test_field.triggerHandler('blur');
assert.strictEqual(test_field.val(), '42.00');
testUtils.fields.editInput(test_field, '=(1.5+8.0/2.0-(15+5)*0.1)');
assert.strictEqual(test_field.val(), '3.50');
testUtils.fields.editInput(test_field, 'bubblegum');
assert.strictEqual(test_field.val(), 'bubblegum');
form.destroy();
});
QUnit.test('integer field', async function (assert) {
assert.expect(5);
const form = await testUtils.createAsyncView({
View: FormView,
model: 'demo_entry',
data: {
demo_entry: {
fields: {
test_field: {string: 'Test Field', type: 'integer'},
},
records: [{id: 1, test_field: 0}],
},
},
res_id: 1,
arch:
'<form>' +
'<field name="test_field"/>' +
'</form>',
viewOptions: {
mode: 'edit',
},
});
var test_field = form.$('.o_field_widget[name="test_field"]');
testUtils.fields.editInput(test_field, '0 + 40 + 2');
assert.strictEqual(test_field.val(), '42');
test_field.triggerHandler('focus');
assert.strictEqual(test_field.val(), '0 + 40 + 2');
test_field.triggerHandler('blur');
assert.strictEqual(test_field.val(), '42');
testUtils.fields.editInput(test_field, '=(1+8/2-(15+5)*0.1)');
assert.strictEqual(test_field.val(), '3');
testUtils.fields.editInput(test_field, 'bubblegum');
assert.strictEqual(test_field.val(), 'bubblegum');
form.destroy();
});
QUnit.test('monetary field', async function (assert) {
assert.expect(5);
const form = await testUtils.createAsyncView({
View: FormView,
model: 'demo_entry',
data: {
demo_entry: {
fields: {
test_field: {string: 'Test Field', type: 'monetary'},
currency_id: {string: 'Currency', type: 'many2one', relation: 'currency', searchable: true},
},
records: [{id: 1, test_field: 0.0, currency_id: 1}],
},
currency: {
fields: {
symbol: {string: 'Currency Sumbol', type: 'char', searchable: true},
position: {string: 'Currency Position', type: 'char', searchable: true},
},
records: [{
id: 1,
display_name: '$',
symbol: '$',
position: 'before',
}]
},
},
res_id: 1,
arch:
'<form>' +
'<field name="test_field" widget="monetary"/>' +
'<field name="currency_id" invisible="1"/>' +
'</form>',
viewOptions: {
mode: 'edit',
},
session: {
currencies: {
1: {
id: 1,
display_name: '$',
symbol: '$',
position: 'before',
},
},
},
});
var test_field = form.$('.o_field_widget[name="test_field"]');
var test_field_input = form.$('.o_field_widget[name="test_field"] input');
testUtils.fields.editInput(test_field_input, '0.0 + 40.0 + 2.0');
assert.strictEqual(test_field_input.val(), '42.00');
test_field.triggerHandler('focusin');
assert.strictEqual(test_field_input.val(), '0.0 + 40.0 + 2.0');
test_field.triggerHandler('focusout');
assert.strictEqual(test_field_input.val(), '42.00');
testUtils.fields.editInput(test_field_input, '=(1.5+8.0/2.0-(15+5)*0.1)');
assert.strictEqual(test_field_input.val(), '3.50');
testUtils.fields.editInput(test_field_input, 'bubblegum');
assert.strictEqual(test_field_input.val(), 'bubblegum');
form.destroy();
});
});
});

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2014-2015 GRAP
Copyright 2016 LasLabs Inc.
Copyright 2020 Brainbean Apps (https://brainbeanapps.com)
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
-->
<odoo>
<template id="assets_backend" name="web_widget_float_formula Assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<script type="text/javascript" src="/web_widget_float_formula/static/src/js/web_widget_float_formula.js"/>
</xpath>
</template>
<template id="qunit_suite" name="web_widget_float_formula Test Assets" inherit_id="web.qunit_suite">
<xpath expr="//t[@t-set='head']" position="inside">
<script type="text/javascript" src="/web_widget_float_formula/static/tests/js/test_web_widget_float_formula.js"/>
</xpath>
</template>
</odoo>