From f896afcc8a54eae3efcdf034e3fd256942f7fa78 Mon Sep 17 00:00:00 2001 From: Yannick Tivisse Date: Tue, 2 Jul 2019 10:41:19 +0200 Subject: [PATCH] [FIX] web: Call 'toggle_active' instead of 'write' Purpose ======= Currently when clicking on 'Archive' or 'Unarchive' on the list view, we write directly on the record {'active': False}, instead of calling toggle_active. Missing that way implementation of numerous business cases. Example - Archive a record --> archive its next activities - (Un)Archive a user --> (Un)Archive its partner - (Un)Archive a product variant --> (Un)Archive its template if there is only one variant - Archive an employee --> Open a wizard to choose an exit plan + describe the departure reason - Archive a route --> Archive its procurement rules - ... Specification ============= - Call toggle_active instead of write when calling toggleActive in the web client. - On the other hand, use actionArchive and actionUnarchive to apply the toggle action on the related records only. --- .../static/src/js/views/basic/basic_model.js | 54 +++++++++++++++++- .../src/js/views/form/form_controller.js | 12 +++- .../src/js/views/kanban/kanban_controller.js | 3 +- .../src/js/views/list/list_controller.js | 12 +++- .../static/tests/views/kanban_model_tests.js | 9 ++- addons/web/static/tests/views/kanban_tests.js | 56 +++++++++++++++++++ addons/web/static/tests/views/list_tests.js | 6 +- odoo/models.py | 15 +++++ 8 files changed, 154 insertions(+), 13 deletions(-) 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 60c2f6a3314..311b0ee6947 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -1176,7 +1176,7 @@ var BasicModel = AbstractModel.extend({ * @param {string} parentID id of the parent resource to reload * @returns {Promise} resolves to the parent id */ - toggleActive: function (recordIDs, value, parentID) { + toggleActive: function (recordIDs, parentID) { var self = this; var parent = this.localData[parentID]; var resIDs = _.map(recordIDs, function (recordID) { @@ -1184,8 +1184,56 @@ var BasicModel = AbstractModel.extend({ }); return this._rpc({ model: parent.model, - method: 'write', - args: [resIDs, { active: value }], + method: 'toggle_active', + args: [resIDs], + }) + .then(function () { + // optionally clear the DataManager's cache + self._invalidateCache(parent); + return self.reload(parentID); + }); + }, + /** + * Archive the given records + * + * @param {Array} recordIDs local ids of the records to (un)archive + * @param {string} parentID id of the parent resource to reload + * @returns {Promise} resolves to the parent id + */ + actionArchive: function (recordIDs, parentID) { + var self = this; + var parent = this.localData[parentID]; + var resIDs = _.map(recordIDs, function (recordID) { + return self.localData[recordID].res_id; + }); + return this._rpc({ + model: parent.model, + method: 'action_archive', + args: [resIDs], + }) + .then(function () { + // optionally clear the DataManager's cache + self._invalidateCache(parent); + return self.reload(parentID); + }); + }, + /** + * Unarchive the given records + * + * @param {Array} recordIDs local ids of the records to (un)archive + * @param {string} parentID id of the parent resource to reload + * @returns {Promise} resolves to the parent id + */ + actionUnarchive: function (recordIDs, parentID) { + var self = this; + var parent = this.localData[parentID]; + var resIDs = _.map(recordIDs, function (recordID) { + return self.localData[recordID].res_id; + }); + return this._rpc({ + model: parent.model, + method: 'action_unarchive', + args: [resIDs], }) .then(function () { // optionally clear the DataManager's cache diff --git a/addons/web/static/src/js/views/form/form_controller.js b/addons/web/static/src/js/views/form/form_controller.js index 67d1a6aa130..b3a65cc2bde 100644 --- a/addons/web/static/src/js/views/form/form_controller.js +++ b/addons/web/static/src/js/views/form/form_controller.js @@ -293,9 +293,15 @@ var FormController = BasicController.extend({ if (ids.length === 0) { return Promise.resolve(); } - return this.model - .toggleActive(ids, !archive, this.handle) - .then(this.update.bind(this, {}, {reload: false})); + if (archive) { + return this.model + .actionArchive(ids, this.handle) + .then(this.update.bind(this, {}, {reload: false})); + } else { + return this.model + .actionUnarchive(ids, this.handle) + .then(this.update.bind(this, {}, {reload: false})); + } }, /** diff --git a/addons/web/static/src/js/views/kanban/kanban_controller.js b/addons/web/static/src/js/views/kanban/kanban_controller.js index 4ea823ed062..bf39906b8e5 100644 --- a/addons/web/static/src/js/views/kanban/kanban_controller.js +++ b/addons/web/static/src/js/views/kanban/kanban_controller.js @@ -569,12 +569,11 @@ var KanbanController = BasicController.extend({ */ _onToggleActiveRecords: function (ev) { var self = this; - var active = !ev.data.archive; var column = ev.target; var recordIds = _.pluck(column.records, 'db_id'); if (recordIds.length) { this.model - .toggleActive(recordIds, active, column.db_id) + .toggleActive(recordIds, column.db_id) .then(function (dbID) { var data = self.model.get(dbID); self.renderer.updateColumn(dbID, data); diff --git a/addons/web/static/src/js/views/list/list_controller.js b/addons/web/static/src/js/views/list/list_controller.js index ffeef454716..bbc1a2cd6ba 100644 --- a/addons/web/static/src/js/views/list/list_controller.js +++ b/addons/web/static/src/js/views/list/list_controller.js @@ -265,9 +265,15 @@ var ListController = BasicController.extend({ if (ids.length === 0) { return Promise.resolve(); } - return this.model - .toggleActive(ids, !archive, this.handle) - .then(this.update.bind(this, {}, {reload: false})); + if (archive) { + return this.model + .actionArchive(ids, this.handle) + .then(this.update.bind(this, {}, {reload: false})); + } else { + return this.model + .actionUnarchive(ids, this.handle) + .then(this.update.bind(this, {}, {reload: false})); + } }, /** * Assign on the buttons create additionnal behavior to facilitate the work of the users doing input only using the keyboard diff --git a/addons/web/static/tests/views/kanban_model_tests.js b/addons/web/static/tests/views/kanban_model_tests.js index 39b601d9a51..81deca04968 100644 --- a/addons/web/static/tests/views/kanban_model_tests.js +++ b/addons/web/static/tests/views/kanban_model_tests.js @@ -135,6 +135,13 @@ QUnit.module('Views', { var model = createModel({ Model: KanbanModel, data: this.data, + mockRPC: function (route, args) { + if (route === '/web/dataset/call_kw/partner/action_archive') { + this.data.partner.records[0].active = false; + return Promise.resolve(); + } + return this._super.apply(this, arguments); + }, }); var params = _.extend(this.params, { @@ -151,7 +158,7 @@ QUnit.module('Views', { // archive the column 'xphone' var recordIDs = _.pluck(xphoneGroup.data, 'id'); - await model.toggleActive(recordIDs, false, xphoneGroup.id); + await model.actionArchive(recordIDs, xphoneGroup.id); state = model.get(resultID); xphoneGroup = _.findWhere(state.data, {res_id: 37}); assert.strictEqual(xphoneGroup.count, 0, 'xphone group has no record anymore'); diff --git a/addons/web/static/tests/views/kanban_tests.js b/addons/web/static/tests/views/kanban_tests.js index 7284caeae21..d3ff979de8b 100644 --- a/addons/web/static/tests/views/kanban_tests.js +++ b/addons/web/static/tests/views/kanban_tests.js @@ -176,6 +176,20 @@ QUnit.module('Views', { '
' + '', groupBy: ['bar'], + mockRPC: function (route, args) { + if (route === '/web/dataset/call_kw/partner/toggle_active') { + var partnerIDS = args.args[0]; + var records = this.data.partner.records + _.each(partnerIDS, function(partnerID) { + _.find(records, function (record) { + return record.id === partnerID; + }).active = false; + }) + this.data.partner.records[0].active; + return Promise.resolve(); + } + return this._super.apply(this, arguments); + }, }); // check archive/restore all actions in kanban header's config dropdown @@ -219,6 +233,20 @@ QUnit.module('Views', { '
' + '', groupBy: ['bar'], + mockRPC: function (route, args) { + if (route === '/web/dataset/call_kw/partner/toggle_active') { + var partnerIDS = args.args[0]; + var records = this.data.partner.records + _.each(partnerIDS, function(partnerID) { + _.find(records, function (record) { + return record.id === partnerID; + }).active = false; + }) + this.data.partner.records[0].active; + return Promise.resolve(); + } + return this._super.apply(this, arguments); + }, }); // check archive/restore all actions in kanban header's config dropdown @@ -4747,6 +4775,20 @@ QUnit.module('Views', { '' + '', groupBy: ['bar'], + mockRPC: function (route, args) { + if (route === '/web/dataset/call_kw/partner/toggle_active') { + var partnerIDS = args.args[0]; + var records = this.data.partner.records + _.each(partnerIDS, function(partnerID) { + _.find(records, function (record) { + return record.id === partnerID; + }).active = false; + }) + this.data.partner.records[0].active; + return Promise.resolve(); + } + return this._super.apply(this, arguments); + }, }); assert.strictEqual(kanban.$('.o_kanban_group:eq(1) .o_kanban_counter_side').text(), "36", @@ -4790,6 +4832,20 @@ QUnit.module('Views', { '' + '', groupBy: ['bar'], + mockRPC: function (route, args) { + if (route === '/web/dataset/call_kw/partner/toggle_active') { + var partnerIDS = args.args[0]; + var records = this.data.partner.records + _.each(partnerIDS, function(partnerID) { + _.find(records, function (record) { + return record.id === partnerID; + }).active = false; + }) + this.data.partner.records[0].active; + return Promise.resolve(); + } + return this._super.apply(this, arguments); + }, }); assert.deepEqual(kanban.exportState().resIds, [1, 2, 3, 4]); diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js index 797aaea016b..2e6969bab48 100644 --- a/addons/web/static/tests/views/list_tests.js +++ b/addons/web/static/tests/views/list_tests.js @@ -1473,6 +1473,10 @@ QUnit.module('Views', { arch: '', mockRPC: function (route) { assert.step(route); + if (route === '/web/dataset/call_kw/foo/action_archive') { + this.data.foo.records[0].active = false; + return Promise.resolve(); + } return this._super.apply(this, arguments); }, }); @@ -1496,7 +1500,7 @@ QUnit.module('Views', { assert.strictEqual($('.modal').length, 1, 'a confirm modal should be displayed'); await testUtils.dom.click($('.modal-footer .btn-primary')); assert.containsN(list, 'tbody td.o_list_record_selector', 3, "should have 3 records"); - assert.verifySteps(['/web/dataset/call_kw/foo/write', '/web/dataset/search_read']); + assert.verifySteps(['/web/dataset/call_kw/foo/action_archive', '/web/dataset/search_read']); list.destroy(); }); diff --git a/odoo/models.py b/odoo/models.py index e23120f36d9..0ab45f403d1 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -4671,6 +4671,21 @@ Fields: for record in self: record.active = not record.active + def action_archive(self): + """ + Set active=False on a recordset, by calling toggle_active to take the + corresponding actions according to the model + """ + return self.filtered(lambda record: record.active).toggle_active() + + @api.multi + def action_unarchive(self): + """ + Set active=True on a recordset, by calling toggle_active to take the + corresponding actions according to the model + """ + return self.filtered(lambda record: not record.active).toggle_active() + def _register_hook(self): """ stuff to do right after the registry is built """ pass