[FIX] web: onchange: send correct nested x2manys values

Commit [1] recently fixed an issue with nested x2many fields and
onchanges: in some cases, all field values weren't sent to the
server as they should.

However, there is a small issue with this fix. We didn't correctly
apply the default value to option `changesOnly`: when not given,
it was considered false, whereas in this particular function it
should have been true.

It caused an issue in the following scenario:

Have a form view with an x2many field (say A) displayed as a list.
In the list, there is another x2many field (say B), and (whatever
its type) a field C with an onchange. When the user changes C, the
onchange is performed, and we send to the server the value of all
fields. In particular, in the row, we send the value for the
many2one field pointing to the main record (the inverse field of
the x2many relation). The value for that field is basically the
whole record, containing itself field A. For field A, the value is
a list of commands, and for the updated record, it is a command 1.
Before this commit, in the values sent for this command, field B
was the empty array, even if it wasn't empty.

This issue was reproducible in account.move, on an existing record
having already a line in invoice_line_ids (this is field A). In
that line, tax_ids (this is field B) must have a tax which is
included in the price. When changing the quantity of the product,
the subtotal wasn't correctly computed.

[1] https://github.com/odoo/odoo/commit/a8b43d02066b2299ac2f4b88056c37725e9ce6cd
opw~2489755

closes odoo/odoo#69227

X-original-commit: 3f54ca3e29d1537f66c6a3b56494dc065d9230e9
Signed-off-by: Lucas Perais (lpe) <lpe@odoo.com>
Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
Aaron Bohy
2021-04-14 09:01:34 +00:00
parent fef5c6eff7
commit facd78d5f3
2 changed files with 53 additions and 4 deletions
@@ -3241,14 +3241,15 @@ var BasicModel = AbstractModel.extend({
options = options || {};
var viewType = options.viewType || record.viewType;
var changes;
if ('changesOnly' in options && !options.changesOnly) {
const changesOnly = 'changesOnly' in options ? !!options.changesOnly : true;
if (!changesOnly) {
changes = _.extend({}, record.data, record._changes);
} else {
changes = _.extend({}, record._changes);
}
var withReadonly = options.withReadonly || false;
var commands = this._generateX2ManyCommands(record, {
changesOnly: 'changesOnly' in options ? options.changesOnly : true,
changesOnly: changesOnly,
withReadonly: withReadonly,
});
for (var fieldName in record.fields) {
@@ -3265,7 +3266,7 @@ var BasicModel = AbstractModel.extend({
var type = record.fields[fieldName].type;
var value;
if (type === 'one2many' || type === 'many2many') {
if (!options.changesOnly || (commands[fieldName] && commands[fieldName].length)) { // replace localId by commands
if (!changesOnly || (commands[fieldName] && commands[fieldName].length)) { // replace localId by commands
changes[fieldName] = commands[fieldName];
} else { // no command -> no change for that field
delete changes[fieldName];
@@ -1633,7 +1633,6 @@ QUnit.module('fields', {}, function () {
'</form>' +
'</field>' +
'</form>',
debug: 1,
});
@@ -4668,6 +4667,55 @@ QUnit.module('fields', {}, function () {
form.destroy();
});
QUnit.test('parent data is properly sent on an onchange rpc (existing x2many record)', async function (assert) {
assert.expect(4);
this.data.partner.onchanges = {
display_name: function () {},
};
this.data.partner.records[0].p = [1];
this.data.partner.records[0].turtles = [2];
const form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: `
<form>
<field name="foo"/>
<field name="p">
<tree editable="top">
<field name="display_name"/>
<field name="turtles" widget="many2many_tags"/>
</tree>
</field>
</form>`,
res_id: 1,
mockRPC(route, args) {
if (args.method === 'onchange') {
const fieldValues = args.args[1];
assert.strictEqual(fieldValues.trululu.foo, "yop");
// we only send fields that changed inside the reverse many2one
assert.deepEqual(fieldValues.trululu.p, [
[1, 1, { display_name: 'new val' }],
]);
}
return this._super(...arguments);
},
viewOptions: {
mode: 'edit',
},
});
assert.containsOnce(form, '.o_data_row');
await testUtils.dom.click(form.$('.o_data_row .o_data_cell:first'));
assert.containsOnce(form, '.o_data_row.o_selected_row');
await testUtils.fields.editInput(form.$('.o_selected_row .o_field_widget[name=display_name]'), "new val");
form.destroy();
});
QUnit.test('parent data is properly sent on an onchange rpc, new record', async function (assert) {
assert.expect(4);