diff --git a/addons/stock/static/src/js/inventory_singleton_list_controller.js b/addons/stock/static/src/js/inventory_singleton_list_controller.js new file mode 100644 index 00000000000..30bd5c4b22a --- /dev/null +++ b/addons/stock/static/src/js/inventory_singleton_list_controller.js @@ -0,0 +1,65 @@ +odoo.define('stock.SingletonListController', function (require) { +"use strict"; + +var core = require('web.core'); +var ListController = require('web.ListController'); + +var _t = core._t; + +/** + * The purpose of this override is to avoid to have two or more similar records + * in the list view. + * + * It's used in quant list view, a list editable where when you create a new + * line about a quant who already exists, we want to update the existing one + * instead of create a new one, and then we don't want to have two similar line + * in the list view, so we refresh it. + */ + +var SingletonListController = ListController.extend({ + /** + * @override + */ + _confirmSave: function (id) { + var newRecord = this.model.localData[id]; + var model = newRecord.model; + var res_id = newRecord.res_id; + + var findSimilarRecords = function (record) { + if ((record.groupedBy && record.groupedBy.length > 0) || record.data.length) { + var recordsToReturn = []; + for (var i in record.data) { + var foundRecords = findSimilarRecords(record.data[i]); + recordsToReturn = recordsToReturn.concat(foundRecords || []); + } + return recordsToReturn; + } else { + if (record.res_id === res_id && record.model === model) { + if (record.count === 0){ + return [record]; + } + else if (record.ref && record.ref.indexOf('virtual') !== -1) { + return [record]; + } + } + } + }; + + var handle = this.model.get(this.handle); + var similarRecords = findSimilarRecords(handle); + + if (similarRecords.length > 1) { + var notification = _t("You tried to create a record who already exists."+ + "
This last one has been modified instead."); + this.do_notify(_t("This record already exists."), notification); + return this.reload(); + } + else { + return this._super(id); + } + }, +}); + +return SingletonListController; + +}); diff --git a/addons/stock/static/src/js/inventory_singleton_list_view.js b/addons/stock/static/src/js/inventory_singleton_list_view.js new file mode 100644 index 00000000000..1f6fc30e483 --- /dev/null +++ b/addons/stock/static/src/js/inventory_singleton_list_view.js @@ -0,0 +1,18 @@ +odoo.define('stock.SingletonListView', function (require) { +'use strict'; + +var ListView = require('web.ListView'); +var SingletonListController = require('stock.SingletonListController'); +var viewRegistry = require('web.view_registry'); + +var SingletonListView = ListView.extend({ + config: _.extend({}, ListView.prototype.config, { + Controller: SingletonListController, + }), +}); + +viewRegistry.add('singleton_list', SingletonListView); + +return SingletonListView; + +}); diff --git a/addons/stock/static/tests/singleton_list_tests.js b/addons/stock/static/tests/singleton_list_tests.js new file mode 100644 index 00000000000..21886429b42 --- /dev/null +++ b/addons/stock/static/tests/singleton_list_tests.js @@ -0,0 +1,254 @@ +odoo.define('web.singleton_list_tests', function (require) { +"use strict"; + +var core = require('web.core'); +var SingletonListView = require('stock.SingletonListView'); +var testUtils = require('web.test_utils'); + +var createView = testUtils.createView; + + +QUnit.module('Views', { + beforeEach: function () { + this.data = { + person: { + fields: { + name: {string: "Name", type: "char"}, + age: {string: "Age", type: "integer"}, + job: {string: "Profession", type: "char"}, + }, + records: [ + {id: 1, name: 'Daniel Fortesque', age: 32, job: 'Soldier'}, + {id: 2, name: 'Samuel Oak', age: 64, job: 'Professor'}, + {id: 3, name: 'Leto II Atreides', age: 128, job: 'Emperor'}, + ] + }, + }; + this.mockRPC = function (route, args) { + if (route === '/web/dataset/call_kw/person/create') { + var name = args.args[0].name; + var age = args.args[0].age; + var job = args.args[0].job; + for (var d of this.data.person.records) { + if (d.name === name) { + d.age = age; + d.job = job; + return Promise.resolve(d.id); + } + } + } + return this._super.apply(this, arguments); + }; + } +}, function () { + + QUnit.module('SingletonListView'); + + QUnit.test('Create new record correctly', async function (assert) { + assert.expect(2); + + var list = await createView({ + View: SingletonListView, + model: 'person', + data: this.data, + arch: ''+ + ''+ + ''+ + '', + mockRPC: this.mockRPC, + }); + // Checks we have initially 3 records + assert.containsN(list, '.o_data_row', 3, "should have 3 records"); + + // Creates a new line... + await testUtils.dom.click($('.o_list_button_add')); + // ... and fills fields with new values + var $input = $('.o_selected_row input[name=name]'); + await testUtils.fields.editInput($input, 'Bilou'); + await testUtils.fields.triggerKeydown($input, 'tab'); + + $input = $('.o_selected_row input[name=age]'); + await testUtils.fields.editInput($input, '24'); + await testUtils.fields.triggerKeydown($input, 'enter'); + await testUtils.dom.click($('.o_list_button_save')); + + // Checks new record is in the list + assert.containsN(list, '.o_data_row', 4, "should now have 4 records"); + list.destroy(); + }); + + QUnit.test('Don\'t duplicate record', async function (assert) { + assert.expect(3); + + var list = await createView({ + View: SingletonListView, + model: 'person', + data: this.data, + arch: ''+ + ''+ + ''+ + '', + mockRPC: this.mockRPC, + }); + // Checks we have initially 3 records + assert.containsN(list, '.o_data_row', 3, "should have 3 records"); + + // Creates a new line... + await testUtils.dom.click($('.o_list_button_add')); + // ... and fills fields with already existing value + var $input = $('.o_selected_row input[name=name]'); + var name = 'Samuel Oak'; + await testUtils.fields.editInput($input, name); + await testUtils.fields.triggerKeydown($input, 'tab'); + + $input = $('.o_selected_row input[name=age]'); + var age = '72'; + await testUtils.fields.editInput($input, age); + await testUtils.fields.triggerKeydown($input, 'enter'); + await testUtils.dom.click($('.o_list_button_save')); + + // Checks we have still only 3 records... + assert.containsN(list, '.o_data_row', 3, "should now have 4 records"); + // ... and verify modification was occured. + var nameField = list.$('td[title="' + name + '"]'); + var ageField = nameField.parent().find('.o_list_number'); + assert.strictEqual(ageField.text(), age, "The age field must be updated"); + list.destroy(); + }); + + QUnit.test('Refresh the list only when needed', async function (assert) { + assert.expect(3); + + var refresh_count = 0; + var list = await createView({ + View: SingletonListView, + model: 'person', + data: this.data, + arch: ''+ + ''+ + ''+ + '', + mockRPC: this.mockRPC, + }); + list.realReload = list.reload; + list.reload = function () { + refresh_count++; + return this.realReload(); + }; + // Modify Record + await testUtils.dom.click(list.$('.o_data_row:nth-child(2) > .o_list_number')); + var $input = $('.o_selected_row input[name=age]'); + await testUtils.fields.editInput($input, '70'); + await testUtils.fields.triggerKeydown($input, 'enter'); + await testUtils.dom.click($('.o_list_button_save')); + assert.strictEqual(refresh_count, 0, "don't refresh when edit existing line"); + + // Add existing record + await testUtils.dom.click($('.o_list_button_add')); + $input = $('.o_selected_row input[name=name]'); + await testUtils.fields.editInput($input, 'Leto II Atreides'); + await testUtils.fields.triggerKeydown($input, 'tab'); + $input = $('.o_selected_row input[name=age]'); + await testUtils.fields.editInput($input, '800'); + await testUtils.dom.click($('.o_list_button_save')); + assert.strictEqual(refresh_count, 1, "refresh after tried to create an existing record"); + + // Add new record + await testUtils.dom.click($('.o_list_button_add')); + $input = $('.o_selected_row input[name=name]'); + await testUtils.fields.editInput($input, 'Valentin Cognito'); + await testUtils.fields.triggerKeydown($input, 'tab'); + $input = $('.o_selected_row input[name=age]'); + await testUtils.fields.editInput($input, '37'); + await testUtils.fields.triggerKeydown($input, 'enter'); + await testUtils.dom.click($('.o_list_button_save')); + assert.strictEqual(refresh_count, 1, "don't refresh when create entirely new record"); + + list.destroy(); + }); + + QUnit.test('Work in grouped list', async function (assert) { + assert.expect(6); + + var refresh_count = 0; + var list = await createView({ + View: SingletonListView, + model: 'person', + data: this.data, + arch: ''+ + ''+ + ''+ + ''+ + '', + mockRPC: this.mockRPC, + groupBy: ['job'], + }); + list.realReload = list.reload; + list.reload = function () { + refresh_count++; + return this.realReload(); + }; + // Opens 'Professor' group + await testUtils.dom.click(list.$('.o_group_header:nth-child(2)')); + + // Creates a new record... + await testUtils.dom.click(list.$('.o_add_record_row a')); + var $input = $('.o_selected_row input[name=name]'); + await testUtils.fields.editInput($input, 'Del Tutorial'); + await testUtils.fields.triggerKeydown($input, 'tab'); + $input = $('.o_selected_row input[name=age]'); + await testUtils.fields.editInput($input, '32'); + await testUtils.fields.triggerKeydown($input, 'tab'); + await testUtils.dom.click($('.o_list_button_save')); + // ... then checks the list didn't refresh + assert.strictEqual(refresh_count, 0, + "don't refresh when creating new record"); + + // Creates an existing record in same group... + await testUtils.dom.click(list.$('.o_add_record_row a')); + var $input = $('.o_selected_row input[name=name]'); + await testUtils.fields.editInput($input, 'Samuel Oak'); + await testUtils.dom.click($('.o_list_button_save')); + // ... then checks the list has been refreshed + assert.strictEqual(refresh_count, 1, + "refresh when try to create an existing record"); + + // Creates an existing but not displayed record... + await testUtils.dom.click(list.$('.o_add_record_row a')); + var $input = $('.o_selected_row input[name=name]'); + await testUtils.fields.editInput($input, 'Daniel Fortesque'); + await testUtils.fields.triggerKeydown($input, 'tab'); + $input = $('.o_selected_row input[name=age]'); + await testUtils.fields.editInput($input, '55'); + await testUtils.fields.triggerKeydown($input, 'tab'); + $input = $('.o_selected_row input[name=job]'); + await testUtils.fields.editInput($input, 'Soldier'); + await testUtils.dom.click($('.o_list_button_save')); + // .. then checks the list didn't refresh + assert.strictEqual(refresh_count, 1, + "don't refresh when creating an existing record but this record " + + "isn't present in the view"); + + // Opens 'Soldier' group + await testUtils.dom.click(list.$('.o_group_header:nth-child(1)').first()); + // Checks the record has been correctly updated + var ageCell = $('tr.o_data_row td.o_list_number').first(); + assert.strictEqual(ageCell.text(), "55", + "age of the record must be updated"); + // Edits the freshly created record... + await testUtils.dom.click(list.$('tr.o_data_row td.o_list_number').eq(1)); + $input = $('.o_selected_row input[name=age]'); + await testUtils.fields.editInput($input, '66'); + await testUtils.dom.click($('.o_list_button_save')); + // ... then checks the list and data have been refreshed + assert.strictEqual(refresh_count, 2, + "refresh when try to create an existing record present in the view"); + ageCell = $('tr.o_data_row td.o_list_number').first(); + assert.strictEqual(ageCell.text(), "66", + "age of the record must be updated"); + + list.destroy(); + }); +}); + +}); diff --git a/addons/stock/views/stock_quant_views.xml b/addons/stock/views/stock_quant_views.xml index 38286c6bff3..d14a4a2add8 100644 --- a/addons/stock/views/stock_quant_views.xml +++ b/addons/stock/views/stock_quant_views.xml @@ -75,7 +75,7 @@ + create="1" edit="1" js_class="singleton_list"> + + @@ -12,6 +14,12 @@ +