From 0df1f4d439ac4274f9b5edee383de0c48e7e0247 Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Mon, 26 Jun 2023 14:02:40 +0000 Subject: [PATCH] [FIX] web: forget command for nested x2many in form, not in list Have a form view with an x2many field A displayed as a non editable list view. In A's form view, there's an x2many field B, which isn't in the list. B is itself displayed in the dialog form view as a list, which contains an x2many fields C displayed as many2many_tags. A contains a record. For this record, B contains also a record, which contains two tags for C. Have an onchange on the main record which triggers an update command for the record in A, such that the command also updates the record in B, which deletes (or forgets), i.e. commands 2 or 3, a tag C. Before this commit, if the user opened the x2many record of A after the onchange, it didn't displayed the remaining tag in C. The reason is that we received commands to remove the tag before we actually fetched them (as we don't need them for the list of A). The tags were then fetched when we opened the record in a dialog, but we didn't correctly merge the values read from the server with the commands we received earlier. With this commit, we properly store the commands and apply them after reading the values. Part of task~3179751 Part-of: odoo/odoo#114024 --- .../src/model/relational_model/record.js | 10 +- .../src/model/relational_model/static_list.js | 20 ++- .../views/fields/one2many_field_tests.js | 141 ++++++++++++++++++ 3 files changed, 169 insertions(+), 2 deletions(-) diff --git a/addons/web/static/src/model/relational_model/record.js b/addons/web/static/src/model/relational_model/record.js index 95291065f27..67541910f71 100644 --- a/addons/web/static/src/model/relational_model/record.js +++ b/addons/web/static/src/model/relational_model/record.js @@ -297,7 +297,15 @@ export class Record extends DataPoint { } _applyValues(values) { - Object.assign(this._values, this._parseServerValues(values)); + const newValues = this._parseServerValues(values); + Object.assign(this._values, newValues); + for (const fieldName in newValues) { + if (fieldName in this._changes) { + if (["one2many", "many2many"].includes(this.fields[fieldName].type)) { + this._changes[fieldName] = newValues[fieldName]; + } + } + } Object.assign(this.data, this._values, this._changes); this._setTextValues(Object.assign({}, values, this._changes)); this._setEvalContext(); diff --git a/addons/web/static/src/model/relational_model/static_list.js b/addons/web/static/src/model/relational_model/static_list.js index 748b3a2b9e5..4f35965d826 100644 --- a/addons/web/static/src/model/relational_model/static_list.js +++ b/addons/web/static/src/model/relational_model/static_list.js @@ -262,6 +262,11 @@ export class StaticList extends DataPoint { } } record._applyValues(data); + const commands = this._unknownRecordCommands[record.resId]; + delete this._unknownRecordCommands[record.resId]; + if (commands) { + this._applyCommands(commands); + } record._addSavePoint(); } else { // case 2: the record is a new record @@ -506,7 +511,20 @@ export class StaticList extends DataPoint { // records for the current page) this._unknownRecordCommands[command[1]].push(command); } else { - record._applyChanges(record._parseServerValues(command[2], record.data)); + const changes = {}; + for (const fieldName in command[2]) { + if (["one2many", "many2many"].includes(this.fields[fieldName].type)) { + if (record.activeFields[fieldName].invisible) { + if (!(command[1] in this._unknownRecordCommands)) { + this._unknownRecordCommands[command[1]] = []; + } + this._unknownRecordCommands[command[1]].push(command); + continue; + } + } + changes[fieldName] = command[2][fieldName]; + } + record._applyChanges(record._parseServerValues(changes, record.data)); } break; } diff --git a/addons/web/static/tests/views/fields/one2many_field_tests.js b/addons/web/static/tests/views/fields/one2many_field_tests.js index 99cd995deaf..e24ec35a9ec 100644 --- a/addons/web/static/tests/views/fields/one2many_field_tests.js +++ b/addons/web/static/tests/views/fields/one2many_field_tests.js @@ -13337,4 +13337,145 @@ QUnit.module("Fields", (hooks) => { ); } ); + + QUnit.test("forget command for nested x2manys in form, not in list", async function (assert) { + assert.expect(8); + + serverData.models.partner.records[0].p = [1, 2]; + serverData.models.partner.records[1].turtles = [2]; + serverData.models.partner.onchanges = { + int_field: function (obj) { + obj.p = [ + [ + 1, + 1, + { + foo: "new foo value (1)", + turtles: [ + [ + 1, + 2, + { + turtle_foo: "new turtle foo value (1)", + partner_ids: [[3, 4]], + }, + ], + ], + }, + ], + [ + 1, + 2, + { + foo: "new foo value (2)", + turtles: [ + [ + 1, + 2, + { + turtle_foo: "new turtle foo value (2)", + partner_ids: [[3, 2]], + }, + ], + ], + }, + ], + ]; + }, + }; + await makeView({ + type: "form", + resModel: "partner", + serverData, + arch: ` +
+ + + + + + + + + + + + + + + +
+ `, + mockRPC(route, args) { + if (args.method === "write") { + assert.deepEqual(args.args[1], { + int_field: 16, + p: [ + [ + 1, + 1, + { + foo: "new foo value (1)", + turtles: [ + [ + 1, + 2, + { + turtle_foo: "new turtle foo value (1)", + partner_ids: [[3, 4]], + }, + ], + ], + }, + ], + [ + 1, + 2, + { + foo: "new foo value (2)", + turtles: [ + [ + 1, + 2, + { + turtle_foo: "new turtle foo value (2)", + partner_ids: [[3, 2]], + }, + ], + ], + }, + ], + ], + }); + } + }, + resId: 1, + }); + + assert.strictEqual(target.querySelector("[name=int_field] input").value, "10"); + + // trigger the onchange + await editInput(target, "[name=int_field] input", "16"); + assert.strictEqual(target.querySelector("[name=foo]").innerText, "new foo value (1)"); + assert.strictEqual(target.querySelectorAll("[name=foo]")[1].innerText, "new foo value (2)"); + + // open the second x2many record + await click(target.querySelectorAll(".o_data_row")[1].querySelector("td")); + assert.containsOnce(target.querySelector(".o_dialog"), ".o_data_row"); + assert.strictEqual( + target.querySelector(".o_dialog .o_data_cell[name=turtle_foo]").innerText, + "new turtle foo value (2)" + ); + assert.containsOnce( + target.querySelector(".o_dialog .o_data_cell[name=partner_ids]"), + ".o_tag" + ); + assert.strictEqual( + target.querySelector(".o_dialog .o_data_cell[name=partner_ids] .o_tag").innerText, + "aaa" + ); + + await clickSave(target.querySelector(".o_dialog")); + await clickSave(target); + }); });