diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js index c7ff956fd7b..496aaee06f0 100644 --- a/addons/web/static/src/js/fields/relational_fields.js +++ b/addons/web/static/src/js/fields/relational_fields.js @@ -777,8 +777,8 @@ var FieldOne2Many = FieldX2Many.extend({ return this._super.apply(this, arguments).then(function () { if (event && event.target === self && self.view.arch.tag === 'tree') { if (event.data.changes[self.name].operation === 'CREATE') { - // todo: get last record if editable = bottom - var newID = self.value.data[0].id; + var index = self.editable === 'top' ? 0 : self.value.data.length - 1; + var newID = self.value.data[index].id; self.renderer.editRecord(newID); } } diff --git a/addons/web/static/src/js/views/basic/basic_model.js b/addons/web/static/src/js/views/basic/basic_model.js index 1f8d5ce94c6..c95325dbb87 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -1058,7 +1058,7 @@ var BasicModel = AbstractModel.extend({ return this.notifyChanges(id, command.data); })); } else { - defs.push(this.addDefaultRecord(list.id)); + defs.push(this.addDefaultRecord(list.id, {position: command.position})); } break; case 'UPDATE': @@ -1715,20 +1715,29 @@ var BasicModel = AbstractModel.extend({ var removedIds = _.difference(list.res_ids, relIds); var addedIds = _.difference(relIds, list.res_ids); var keptIds = _.intersection(list.res_ids, relIds); + // the didChange variable keeps track of the fact that at // least one id was updated var didChange = false; - var command, i, relRecord; - for (i = 0; i < keptIds.length; i++) { - relRecord = _.findWhere(relData, {res_id: keptIds[i]}); - if (!_.isEmpty(relRecord._changes)) { - var changes = this._generateChanges(relRecord); - command = x2ManyCommands.update(relRecord.res_id, changes); - didChange = true; - } else { - command = x2ManyCommands.link_to(keptIds[i]); + var command, relRecord; + for (var i = 0; i < relIds.length; i++) { + if (_.contains(keptIds, relIds[i])) { + // this is an id that already existed + relRecord = _.findWhere(relData, {res_id: relIds[i]}); + if (!_.isEmpty(relRecord._changes)) { + var changes = this._generateChanges(relRecord); + command = x2ManyCommands.update(relRecord.res_id, changes); + didChange = true; + } else { + command = x2ManyCommands.link_to(relIds[i]); + } + commands[fieldName].push(command); + } else if (_.contains(addedIds, relIds[i])) { + // this is a new id + relRecord = _.findWhere(relData, {res_id: relIds[i]}); + relRecord = this.get(relRecord.id, {raw: true}); + commands[fieldName].push(x2ManyCommands.create(_.omit(relRecord.data, 'id'))); } - commands[fieldName].push(command); } if (changesOnly && !didChange && addedIds.length === 0 && removedIds.length === 0) { // in this situation, we have no changed ids, no added @@ -1736,11 +1745,7 @@ var BasicModel = AbstractModel.extend({ // last changes commands[fieldName] = []; } - for (i = 0; i < addedIds.length; i++) { - relRecord = _.findWhere(relData, {res_id: addedIds[i]}); - relRecord = this.get(relRecord.id, {raw: true}); - commands[fieldName].push(x2ManyCommands.create(_.omit(relRecord.data, 'id'))); - } + // add delete commands for (i = 0; i < removedIds.length; i++) { commands[fieldName].push(x2ManyCommands.delete(removedIds[i])); } diff --git a/addons/web/static/test/fields/relational_fields_tests.js b/addons/web/static/test/fields/relational_fields_tests.js index 7852a2b1e63..eba4d57d012 100644 --- a/addons/web/static/test/fields/relational_fields_tests.js +++ b/addons/web/static/test/fields/relational_fields_tests.js @@ -2628,6 +2628,98 @@ QUnit.module('relational_fields', { form.destroy(); }); + QUnit.test('one2many list editable = top', function (assert) { + assert.expect(6); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
', + res_id: 1, + mockRPC: function (route, args) { + if (args.method === 'write') { + var commands = args.args[1].turtles; + assert.strictEqual(commands[0][0], 0, + "first command is a create"); + assert.strictEqual(commands[1][0], 4, + "second command is a link to"); + } + return this._super.apply(this, arguments); + }, + }); + form.$buttons.find('.o_form_button_edit').click(); + + assert.strictEqual(form.$('.o_data_row').length, 1, + "should start with one data row"); + + form.$('.o_form_field_x2many_list_row_add a').click(); + + assert.strictEqual(form.$('.o_data_row').length, 2, + "should have 2 data rows"); + assert.ok(form.$('tr.o_data_row:first:contains(My little Foo Value)').length, 1, + "first row should be the new value"); + assert.ok(form.$('tr.o_data_row:first').hasClass('o_selected_row'), + "first row should be selected"); + + form.$buttons.find('.o_form_button_save').click(); + form.destroy(); + }); + + QUnit.test('one2many list editable = bottom', function (assert) { + assert.expect(6); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '', + res_id: 1, + mockRPC: function (route, args) { + if (args.method === 'write') { + var commands = args.args[1].turtles; + assert.strictEqual(commands[0][0], 4, + "first command is a link to"); + assert.strictEqual(commands[1][0], 0, + "second command is a create"); + } + return this._super.apply(this, arguments); + }, + }); + form.$buttons.find('.o_form_button_edit').click(); + + assert.strictEqual(form.$('.o_data_row').length, 1, + "should start with one data row"); + + form.$('.o_form_field_x2many_list_row_add a').click(); + + assert.strictEqual(form.$('.o_data_row').length, 2, + "should have 2 data rows"); + assert.ok(form.$('tr.o_data_row:eq(1):contains(My little Foo Value)').length, 1, + "second row should be the new value"); + assert.ok(form.$('tr.o_data_row:eq(1)').hasClass('o_selected_row'), + "second row should be selected"); + + form.$buttons.find('.o_form_button_save').click(); + form.destroy(); + }); + QUnit.module('FieldMany2Many'); QUnit.test('many2many kanban: edition', function (assert) { diff --git a/addons/web/static/test/helpers/mock_server.js b/addons/web/static/test/helpers/mock_server.js index 3cff90bfb45..de5e02934f0 100644 --- a/addons/web/static/test/helpers/mock_server.js +++ b/addons/web/static/test/helpers/mock_server.js @@ -896,26 +896,26 @@ var MockServer = Class.extend({ continue; } if (_.contains(['one2many', 'many2many'], field.type)) { + var ids = []; // convert commands _.each(value, function (command) { if (command[0] === 0) { // CREATE var id = self._mockCreate(field.relation, command[2]); - record[field_changed].push(id); + ids.push(id); } else if (command[0] === 1) { // UPDATE self._mockWrite(field.relation, [[command[1]], command[2]]); } else if (command[0] === 2) { // DELETE - record[field_changed] = _.without(record[field_changed], command[1]); } else if (command[0] === 4) { // LINK_TO - // nothing to do, this command is called by a one2many, - // and should do nothing in a non concurrent environment. + ids.push(command[1]); } else if (command[0] === 5) { // DELETE ALL - record[field_changed] = []; + ids = []; } else if (command[0] === 6) { // REPLACE WITH - record[field_changed] = command[2]; + ids = command[2]; } else { console.error('Command ' + JSON.stringify(command) + ' not supported by the MockServer'); } }); + record[field_changed] = ids; } else if (field.type === 'many2one') { if (value) { var relatedRecord = _.findWhere(this.data[field.relation].records, {