From 6af8b7f95e70e4f55cb72ec1f76c4c3752cda4c9 Mon Sep 17 00:00:00 2001 From: "Lucas Perais (lpe)" Date: Thu, 7 Apr 2022 13:31:19 +0000 Subject: [PATCH] [FIX] web: basic_model: applyOnChange with 2 consecutive one2many The problem is generally due to the use of `var`, this one https://github.com/odoo-dev/odoo/blob/58a046d2146e884d467cde827f89fa603ed98a3b/addons/web/static/src/js/views/basic/basic_model.js#L1780 followed by an asynchronous call to a function using it We can therefore summarize the part of the `_applyOnChange` function that interests us by ```js for () { if (field. type === 'one2many' || field. type === 'many2many') { var list; ... //using list ... promiseFunction(list).then(function(){ //using list }) } } ``` In the case where we have two one2many which follow each other, this scenario occurs: - `list` is initially the first o2m - the `promiseFunction` is called with `list` (first o2m) - before the promise is resolved the loop continues - `list` is `Promise.then()` the second o2m - the first call to `promiseFunction` with `list` (first o2m) is done - we enter in `Promise.then()` - `list` is currently worth the value of the second o2m because `list` is a `var` - the second call to `promiseFunction` with `list` is finished - we enter the `Promise.then()` - `list` is currently worth the value of the second o2m because `list` is a `var` Result: `Promise.then()` is called twice with the first o2m and never with the second We can see what happened with this code example: ```js const array = ["a", "b"]; const asyncRead = async(value) => { console.log("async function arg", value) setTimeout(()=>{ return Promise. resolve(value); }, 100) } const forFunction = () => { for (let i = 0; i < array.length; i++){ var list = array[i]; asyncRead(list).then(function (){ console.log("then async function arg", list) }) } } forFunction() /* async function arg a async function arg b then async function arg b then async function arg b */ ``` While if: We use `let` by replacing `var list = array[i];` with `let list = array[i];` OR We force the use of `Promise.resolve()` argument by replacing `.then(function (){}` with `.then(function (list){}` We will get: ```js /* async function arg a async function arg b then async function arg a then async function arg b */ ``` opw-2694855 closes odoo/odoo#89160 X-original-commit: 99cb2f52e109ccf94ac8bd735cd776e84ef04260 Signed-off-by: Nicolas Lempereur (nle) --- .../src/legacy/js/views/basic/basic_model.js | 2 +- .../legacy/fields/relational_fields_tests.js | 50 +++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/addons/web/static/src/legacy/js/views/basic/basic_model.js b/addons/web/static/src/legacy/js/views/basic/basic_model.js index dc6856fa50c..9ff6bdcdc18 100644 --- a/addons/web/static/src/legacy/js/views/basic/basic_model.js +++ b/addons/web/static/src/legacy/js/views/basic/basic_model.js @@ -1909,7 +1909,7 @@ var BasicModel = AbstractModel.extend({ }); } }); - var def = self._readUngroupedList(list).then(function () { + var def = self._readUngroupedList(list).then(function (list) { var x2ManysDef = self._fetchX2ManysBatched(list); var referencesDef = self._fetchReferencesBatched(list); return Promise.all([x2ManysDef, referencesDef]); diff --git a/addons/web/static/tests/legacy/fields/relational_fields_tests.js b/addons/web/static/tests/legacy/fields/relational_fields_tests.js index a7473396140..c2da4a2dec1 100644 --- a/addons/web/static/tests/legacy/fields/relational_fields_tests.js +++ b/addons/web/static/tests/legacy/fields/relational_fields_tests.js @@ -4178,6 +4178,56 @@ QUnit.module('relational_fields', { form.destroy(); }); + QUnit.test('Check onchange with two consecutive many2one', async function (assert) { + assert.expect(2); + this.data.product.fields.product_partner_ids = { string: "User", type: 'one2many', relation: 'partner' }; + this.data.product.records[0].product_partner_ids = [1]; + this.data.product.records[1].product_partner_ids = [2]; + this.data.turtle.fields.product_ids = { string: "Product", type: "one2many", relation: 'product' }; + this.data.turtle.fields.user_ids = { string: "Product", type: "one2many", relation: 'user' }; + this.data.turtle.onchanges = { + turtle_trululu: function (record) { + record.product_ids = [37]; + record.user_ids = [17, 19]; + }, + }; + var form = await createView({ + View: FormView, + model: 'turtle', + data: this.data, + arch: + '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '', + res_id: 1, + }); + + await testUtils.form.clickEdit(form); + await testUtils.fields.many2one.clickOpenDropdown("turtle_trululu"); + await testUtils.fields.many2one.searchAndClickItem('turtle_trululu', {search: 'first record'}); + + const getElementTextContent = name => [...document.querySelectorAll(`.o_field_many2manytags[name="${name}"] .badge.o_tag_color_0 > span`)] + .map(x=>x.textContent); + assert.deepEqual( + getElementTextContent('product_partner_ids'), + ['first record'], + "should have the correct value in the many2many tag widget"); + assert.deepEqual( + getElementTextContent('partner_ids'), + ['first record', 'second record'], + "should have the correct values in the many2many tag widget"); + form.destroy(); + }); }); }); });