mirror of
https://github.com/OCA/web.git
synced 2025-02-22 13:21:25 +02:00
1
web_widget_float_formula/__init__.py
Normal file
1
web_widget_float_formula/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
22
web_widget_float_formula/__manifest__.py
Normal file
22
web_widget_float_formula/__manifest__.py
Normal 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,
|
||||
}
|
||||
14
web_widget_float_formula/i18n/web_widget_float_formula.pot
Normal file
14
web_widget_float_formula/i18n/web_widget_float_formula.pot
Normal 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"
|
||||
|
||||
3
web_widget_float_formula/readme/CONTRIBUTORS.rst
Normal file
3
web_widget_float_formula/readme/CONTRIBUTORS.rst
Normal file
@@ -0,0 +1,3 @@
|
||||
* Sylvain Le Gal (https://twitter.com/legalsylvain)
|
||||
* Oleg Bulkin <o.bulkin@gmail.com>
|
||||
* Alexey Pelykh <alexey.pelykh@brainbeanapps.com>
|
||||
11
web_widget_float_formula/readme/DESCRIPTION.rst
Normal file
11
web_widget_float_formula/readme/DESCRIPTION.rst
Normal 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
|
||||
1
web_widget_float_formula/readme/ROADMAP.rst
Normal file
1
web_widget_float_formula/readme/ROADMAP.rst
Normal file
@@ -0,0 +1 @@
|
||||
This module is not needed for v13, as this feature is bundled with Odoo v13.
|
||||
BIN
web_widget_float_formula/static/description/icon.png
Normal file
BIN
web_widget_float_formula/static/description/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
@@ -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,
|
||||
};
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
20
web_widget_float_formula/templates/assets.xml
Normal file
20
web_widget_float_formula/templates/assets.xml
Normal 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>
|
||||
Reference in New Issue
Block a user