[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
This commit is contained in:
Aaron Bohy
2023-07-24 20:17:55 +02:00
parent 0e2d8eeabe
commit 0df1f4d439
3 changed files with 169 additions and 2 deletions
@@ -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();
@@ -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;
}
@@ -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: `
<form>
<group>
<field name="int_field"/>
<field name="p">
<tree>
<field name="foo"/>
</tree>
<form>
<field name="turtles">
<tree editable="bottom">
<field name="turtle_foo"/>
<field name="partner_ids" widget="many2many_tags"/>
</tree>
</field>
</form>
</field>
</group>
</form>`,
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);
});
});