From 590a088750b60b393048166f0ec322dccda71093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Theys?= Date: Fri, 28 Aug 2020 12:53:38 +0000 Subject: [PATCH] [FIX] mail, *: fix opens and redirects, adapt messaging menu close MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit task-2244279 task-2262862 task-2274993 task-2277537 task-2282238 closes odoo/odoo#56755 X-original-commit: 17611f4adc5e950b0a75a2a6615b1da3c956387f Related: odoo/enterprise#12769 Signed-off-by: Alexandre Kühn (aku) Signed-off-by: Sébastien Theys (seb) --- addons/hr/__manifest__.py | 5 +- addons/hr/static/src/bugfix/bugfix.js | 10 + addons/hr/static/src/bugfix/bugfix.scss | 6 + addons/hr/static/src/bugfix/bugfix.xml | 11 + addons/hr/static/src/bugfix/bugfix_tests.js | 18 + addons/hr/static/src/js/chat.js | 43 +-- .../static/src/js/many2one_avatar_employee.js | 36 +- .../hr/static/src/models/employee/employee.js | 204 +++++++++++ .../static/src/models/messaging/messaging.js | 36 ++ .../hr/static/src/models/partner/partner.js | 63 ++++ addons/hr/static/src/models/user/user.js | 18 + addons/hr/static/tests/helpers/mock_models.js | 34 ++ .../tests/many2one_avatar_employee_tests.js | 168 ++++----- addons/hr/views/hr_templates.xml | 9 + .../messaging_notification_handler.js | 9 +- .../src/components/chat_window/chat_window.js | 27 +- .../components/chat_window/chat_window.xml | 1 - .../chat_window_hidden_menu.js | 4 +- .../chat_window_manager_tests.js | 124 ++++++- .../static/src/components/chatter/chatter.js | 29 +- .../static/src/components/chatter/chatter.xml | 2 - .../src/components/composer/composer.js | 6 +- .../composer_text_input.js | 12 +- .../static/src/components/discuss/discuss.js | 41 ++- .../static/src/components/discuss/discuss.xml | 4 +- .../components/discuss/tests/discuss_tests.js | 28 +- .../discuss_mobile_mailbox_selection.js | 2 +- .../discuss_sidebar_item.js | 2 +- .../src/components/follower/follower.js | 5 +- .../src/components/follower/follower_tests.js | 16 +- .../static/src/components/message/message.js | 66 ++-- .../static/src/components/message/message.xml | 4 +- .../components/message_list/message_list.js | 5 - .../components/message_list/message_list.xml | 1 - .../messaging_menu/messaging_menu.js | 47 +-- .../messaging_menu/messaging_menu.xml | 2 - .../notification_group/notification_group.js | 6 + ...ification_list_notification_group_tests.js | 1 + .../notification_request.js | 4 +- .../thread_needaction_preview.js | 5 +- .../thread_preview/thread_preview.js | 5 +- .../src/components/thread_view/thread_view.js | 2 - .../components/thread_view/thread_view.xml | 1 - .../thread_view/thread_view_tests.js | 4 +- .../static/src/js/many2one_avatar_user.js | 93 +---- .../src/models/chat_window/chat_window.js | 40 ++- .../chat_window_manager.js | 41 ++- .../mail/static/src/models/chatter/chatter.js | 22 +- .../static/src/models/composer/composer.js | 7 - .../mail/static/src/models/discuss/discuss.js | 78 +++-- .../static/src/models/follower/follower.js | 10 + .../static/src/models/messaging/messaging.js | 158 +++++---- .../src/models/messaging/messaging_tests.js | 96 +++++ .../messaging_notification_handler.js | 171 ++++----- .../notification_group/notification_group.js | 96 ++--- .../mail/static/src/models/partner/partner.js | 73 +++- .../mail/static/src/models/thread/thread.js | 330 +++++++++--------- addons/mail/static/src/models/user/user.js | 148 ++++++++ .../notification_alert_tests.js | 2 +- .../tests/many2one_avatar_user_tests.js | 40 --- .../messaging_initializer.js | 3 - .../messaging_initializer_tests.js | 14 +- .../notification_group/notification_group.js | 53 +-- .../notification_group/notification_group.js | 53 +-- 64 files changed, 1714 insertions(+), 940 deletions(-) create mode 100644 addons/hr/static/src/bugfix/bugfix.js create mode 100644 addons/hr/static/src/bugfix/bugfix.scss create mode 100644 addons/hr/static/src/bugfix/bugfix.xml create mode 100644 addons/hr/static/src/bugfix/bugfix_tests.js create mode 100644 addons/hr/static/src/models/employee/employee.js create mode 100644 addons/hr/static/src/models/messaging/messaging.js create mode 100644 addons/hr/static/src/models/partner/partner.js create mode 100644 addons/hr/static/src/models/user/user.js create mode 100644 addons/hr/static/tests/helpers/mock_models.js diff --git a/addons/hr/__manifest__.py b/addons/hr/__manifest__.py index ada77010824..cb2942140ed 100644 --- a/addons/hr/__manifest__.py +++ b/addons/hr/__manifest__.py @@ -46,5 +46,8 @@ 'installable': True, 'application': True, 'auto_install': False, - 'qweb': ['static/src/xml/hr_templates.xml'], + 'qweb': [ + 'static/src/bugfix/bugfix.xml', + 'static/src/xml/hr_templates.xml', + ], } diff --git a/addons/hr/static/src/bugfix/bugfix.js b/addons/hr/static/src/bugfix/bugfix.js new file mode 100644 index 00000000000..0351046fab3 --- /dev/null +++ b/addons/hr/static/src/bugfix/bugfix.js @@ -0,0 +1,10 @@ +/** + * This file allows introducing new JS modules without contaminating other files. + * This is useful when bug fixing requires adding such JS modules in stable + * versions of Odoo. Any module that is defined in this file should be isolated + * in its own file in master. + */ +odoo.define('hr/static/src/bugfix/bugfix.js', function (require) { +'use strict'; + +}); diff --git a/addons/hr/static/src/bugfix/bugfix.scss b/addons/hr/static/src/bugfix/bugfix.scss new file mode 100644 index 00000000000..c4272e527b4 --- /dev/null +++ b/addons/hr/static/src/bugfix/bugfix.scss @@ -0,0 +1,6 @@ +/** +* This file allows introducing new styles without contaminating other files. +* This is useful when bug fixing requires adding new components for instance in +* stable versions of Odoo. Any style that is defined in this file should be isolated +* in its own file in master. +*/ diff --git a/addons/hr/static/src/bugfix/bugfix.xml b/addons/hr/static/src/bugfix/bugfix.xml new file mode 100644 index 00000000000..c17906f75bb --- /dev/null +++ b/addons/hr/static/src/bugfix/bugfix.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/addons/hr/static/src/bugfix/bugfix_tests.js b/addons/hr/static/src/bugfix/bugfix_tests.js new file mode 100644 index 00000000000..8b23d37282d --- /dev/null +++ b/addons/hr/static/src/bugfix/bugfix_tests.js @@ -0,0 +1,18 @@ +odoo.define('hr/static/src/bugfix/bugfix_tests.js', function (require) { +'use strict'; + +/** + * This file allows introducing new QUnit test modules without contaminating + * other test files. This is useful when bug fixing requires adding new + * components for instance in stable versions of Odoo. Any test that is defined + * in this file should be isolated in its own file in master. + */ +QUnit.module('hr', {}, function () { +QUnit.module('bugfix', {}, function () { +QUnit.module('bugfix_tests.js', { + +}); +}); +}); + +}); diff --git a/addons/hr/static/src/js/chat.js b/addons/hr/static/src/js/chat.js index b9985e3929f..0489ccceac0 100644 --- a/addons/hr/static/src/js/chat.js +++ b/addons/hr/static/src/js/chat.js @@ -22,11 +22,7 @@ odoo.define('hr.employee_chat', function (require) { var self = this; return this._super.apply(this, arguments).then(function () { var $chat_button = self.$el.find('.o_employee_chat_btn'); - if (self.state.context.uid === self.state.data.user_id.res_id) { // Hide the button for yourself - $chat_button.hide(); - } else { - $chat_button.off('click').on('click', self._onOpenChat.bind(self)); - } + $chat_button.off('click').on('click', self._onOpenChat.bind(self)); }); }, @@ -38,9 +34,8 @@ odoo.define('hr.employee_chat', function (require) { _onOpenChat: function (ev) { ev.preventDefault(); ev.stopImmediatePropagation(); - this.trigger_up('open_chat', { - partner_id: this.state.data.user_partner_id.res_id - }); + const env = Component.env; + env.messaging.openChat({ employeeId: this.state.data.id }); return true; }, }; @@ -48,23 +43,9 @@ odoo.define('hr.employee_chat', function (require) { // USAGE OF CHAT MIXIN IN FORM VIEWS var EmployeeFormRenderer = FormRenderer.extend(ChatMixin); - var EmployeeFormController = FormController.extend({ - custom_events: _.extend({}, FormController.prototype.custom_events, { - open_chat: '_onOpenChat' - }), - - _onOpenChat: function (ev) { - const env = Component.env; - const partner = env.models['mail.partner'].insert({ - id: ev.data.partner_id, - }); - partner.openChat(); - }, - }); - var EmployeeFormView = FormView.extend({ config: _.extend({}, FormView.prototype.config, { - Controller: EmployeeFormController, + Controller: FormController, Renderer: EmployeeFormRenderer }), }); @@ -80,23 +61,9 @@ odoo.define('hr.employee_chat', function (require) { }), }); - var EmployeeKanbanController = KanbanController.extend({ - custom_events: _.extend({}, KanbanController.prototype.custom_events, { - open_chat: '_onOpenChat' - }), - - _onOpenChat: function (ev) { - const env = Component.env; - const partner = env.models['mail.partner'].insert({ - id: ev.data.partner_id, - }); - partner.openChat(); - }, - }); - var EmployeeKanbanView = KanbanView.extend({ config: _.extend({}, KanbanView.prototype.config, { - Controller: EmployeeKanbanController, + Controller: KanbanController, Renderer: EmployeeKanbanRenderer }), }); diff --git a/addons/hr/static/src/js/many2one_avatar_employee.js b/addons/hr/static/src/js/many2one_avatar_employee.js index 5be29801ea9..0d5a245aaa2 100644 --- a/addons/hr/static/src/js/many2one_avatar_employee.js +++ b/addons/hr/static/src/js/many2one_avatar_employee.js @@ -8,48 +8,26 @@ odoo.define('hr.Many2OneAvatarEmployee', function (require) { // Usage: // - const { _t } = require('web.core'); const fieldRegistry = require('web.field_registry'); const { Many2OneAvatarUser, KanbanMany2OneAvatarUser } = require('mail.Many2OneAvatarUser'); - const session = require('web.session'); + const { Component } = owl; const Many2OneAvatarEmployeeMixin = { - supportedModel: 'hr.employee', - - /** - * Set the field to read on 'hr.employee' to get the partner id. - * - * @override - */ - init() { - this._super(...arguments); - this.partnerField = 'user_partner_id'; - }, + supportedModels: ['hr.employee', 'hr.employee.public'], //---------------------------------------------------------------------- // Private //---------------------------------------------------------------------- /** - * Display a warning if the user clicked on himself, or on an employee - * not associated with any user. - * * @override - * @param {number} [partnerId] the id of the clicked partner */ - _displayWarning(partnerId) { - if (partnerId !== session.partner_id) { - // this is not ourself, so if we get here it means that the - // employee is not associated with any user - this.displayNotification({ - message: _t('You can only chat with employees that have a dedicated user'), - type: 'info', - }); - } else { - this._super(...arguments); - } - }, + async _onAvatarClicked(ev) { + ev.stopPropagation(); // in list view, prevent from opening the record + const env = Component.env; + await env.messaging.openChat({ employeeId: this.value.res_id }); + } }; const Many2OneAvatarEmployee = Many2OneAvatarUser.extend(Many2OneAvatarEmployeeMixin); diff --git a/addons/hr/static/src/models/employee/employee.js b/addons/hr/static/src/models/employee/employee.js new file mode 100644 index 00000000000..0cf877baab6 --- /dev/null +++ b/addons/hr/static/src/models/employee/employee.js @@ -0,0 +1,204 @@ +odoo.define('hr/static/src/models/employee/employee.js', function (require) { +'use strict'; + +const { registerNewModel } = require('mail/static/src/model/model_core.js'); +const { attr, one2one } = require('mail/static/src/model/model_field.js'); + +function factory(dependencies) { + + class Employee extends dependencies['mail.model'] { + + //---------------------------------------------------------------------- + // Public + //---------------------------------------------------------------------- + + /** + * @static + * @param {Object} data + * @returns {Object} + */ + static convertData(data) { + const data2 = {}; + if ('id' in data) { + data2.id = data.id; + } + if ('user_id' in data) { + data2.hasCheckedUser = true; + if (!data.user_id) { + data2.user = [['unlink']]; + } else { + const partnerNameGet = data['user_partner_id']; + const partnerData = { + display_name: partnerNameGet[1], + id: partnerNameGet[0], + }; + const userNameGet = data['user_id']; + const userData = { + id: userNameGet[0], + partner: [['insert', partnerData]], + partnerDisplayName: userNameGet[1], + }; + data2.user = [['insert', userData]]; + } + } + return data2; + } + + /** + * Performs the `read` RPC on the `hr.employee.public`. + * + * @static + * @param {Object} param0 + * @param {Object} param0.context + * @param {string[]} param0.fields + * @param {integer[]} param0.ids + */ + static async performRpcRead({ context, fields, ids }) { + const employeesData = await this.env.services.rpc({ + model: 'hr.employee.public', + method: 'read', + args: [ids], + kwargs: { + context, + fields, + }, + }); + this.env.models['hr.employee'].insert(employeesData.map(employeeData => + this.env.models['hr.employee'].convertData(employeeData) + )); + } + + /** + * Performs the `search_read` RPC on `hr.employee.public`. + * + * @static + * @param {Object} param0 + * @param {Object} param0.context + * @param {Array[]} param0.domain + * @param {string[]} param0.fields + */ + static async performRpcSearchRead({ context, domain, fields }) { + const employeesData = await this.env.services.rpc({ + model: 'hr.employee.public', + method: 'search_read', + kwargs: { + context, + domain, + fields, + }, + }); + this.env.models['hr.employee'].insert(employeesData.map(employeeData => + this.env.models['hr.employee'].convertData(employeeData) + )); + } + + /** + * Checks whether this employee has a related user and partner and links + * them if applicable. + */ + async checkIsUser() { + return this.env.models['hr.employee'].performRpcRead({ + ids: [this.id], + fields: ['user_id', 'user_partner_id'], + context: { active_test: false }, + }); + } + + /** + * Gets the chat between the user of this employee and the current user. + * + * If a chat is not appropriate, a notification is displayed instead. + * + * @returns {mail.thread|undefined} + */ + async getChat() { + if (!this.user && !this.hasCheckedUser) { + await this.async(() => this.checkIsUser()); + } + // prevent chatting with non-users + if (!this.user) { + this.env.services['notification'].notify({ + message: this.env._t("You can only chat with employees that have a dedicated user."), + type: 'info', + }); + return; + } + return this.user.getChat(); + } + + /** + * Opens a chat between the user of this employee and the current user + * and returns it. + * + * If a chat is not appropriate, a notification is displayed instead. + * + * @param {Object} [options] forwarded to @see `mail.thread:open()` + * @returns {mail.thread|undefined} + */ + async openChat(options) { + const chat = await this.async(() => this.getChat()); + if (!chat) { + return; + } + await this.async(() => chat.open(options)); + return chat; + } + + /** + * Opens the most appropriate view that is a profile for this employee. + */ + async openProfile() { + return this.env.messaging.openDocument({ + id: this.id, + model: 'hr.employee.public', + }); + } + + //---------------------------------------------------------------------- + // Private + //---------------------------------------------------------------------- + + /** + * @override + */ + static _createRecordLocalId(data) { + return `${this.modelName}_${data.id}`; + } + + } + + Employee.fields = { + /** + * Whether an attempt was already made to fetch the user corresponding + * to this employee. This prevents doing the same RPC multiple times. + */ + hasCheckedUser: attr({ + default: false, + }), + /** + * Unique identifier for this employee. + */ + id: attr(), + /** + * Partner related to this employee. + */ + partner: one2one('mail.partner', { + inverse: 'employee', + related: 'user.partner', + }), + /** + * User related to this employee. + */ + user: one2one('mail.user', { + inverse: 'employee', + }), + }; + + Employee.modelName = 'hr.employee'; + + return Employee; +} + +registerNewModel('hr.employee', factory); + +}); diff --git a/addons/hr/static/src/models/messaging/messaging.js b/addons/hr/static/src/models/messaging/messaging.js new file mode 100644 index 00000000000..435761a91ef --- /dev/null +++ b/addons/hr/static/src/models/messaging/messaging.js @@ -0,0 +1,36 @@ +odoo.define('hr/static/src/models/messaging/messaging.js', function (require) { +'use strict'; + +const { + registerInstancePatchModel, +} = require('mail/static/src/model/model_core.js'); + +registerInstancePatchModel('mail.messaging', 'hr/static/src/models/messaging/messaging.js', { + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * @override + * @param {integer} [param0.employeeId] + */ + async getChat({ employeeId }) { + if (employeeId) { + const employee = this.env.models['hr.employee'].insert({ id: employeeId }); + return employee.getChat(); + } + return this._super(...arguments); + }, + /** + * @override + */ + async openProfile({ id, model }) { + if (model === 'hr.employee' || model === 'hr.employee.public') { + const employee = this.env.models['hr.employee'].insert({ id }); + return employee.openProfile(); + } + return this._super(...arguments); + }, +}); + +}); diff --git a/addons/hr/static/src/models/partner/partner.js b/addons/hr/static/src/models/partner/partner.js new file mode 100644 index 00000000000..cbbcb987dd2 --- /dev/null +++ b/addons/hr/static/src/models/partner/partner.js @@ -0,0 +1,63 @@ +odoo.define('hr/static/src/models/partner/partner.js', function (require) { +'use strict'; + +const { + registerInstancePatchModel, + registerFieldPatchModel, +} = require('mail/static/src/model/model_core.js'); +const { attr, one2one } = require('mail/static/src/model/model_field.js'); + +registerInstancePatchModel('mail.partner', 'hr/static/src/models/partner/partner.js', { + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * Checks whether this partner has a related employee and links them if + * applicable. + */ + async checkIsEmployee() { + await this.async(() => this.env.models['hr.employee'].performRpcSearchRead({ + context: { active_test: false }, + domain: [['user_partner_id', '=', this.id]], + fields: ['user_id', 'user_partner_id'], + })); + this.update({ hasCheckedEmployee: true }); + }, + /** + * When a partner is an employee, its employee profile contains more useful + * information to know who he is than its partner profile. + * + * @override + */ + async openProfile() { + // limitation of patch, `this._super` becomes unavailable after `await` + const _super = this._super.bind(this, ...arguments); + if (!this.employee && !this.hasCheckedEmployee) { + await this.async(() => this.checkIsEmployee()); + } + if (this.employee) { + return this.employee.openProfile(); + } + return _super(); + }, +}); + +registerFieldPatchModel('mail.partner', 'hr/static/src/models/partner/partner.js', { + /** + * Employee related to this partner. It is computed through + * the inverse relation and should be considered read-only. + */ + employee: one2one('hr.employee', { + inverse: 'partner', + }), + /** + * Whether an attempt was already made to fetch the employee corresponding + * to this partner. This prevents doing the same RPC multiple times. + */ + hasCheckedEmployee: attr({ + default: false, + }), +}); + +}); diff --git a/addons/hr/static/src/models/user/user.js b/addons/hr/static/src/models/user/user.js new file mode 100644 index 00000000000..a0482d50deb --- /dev/null +++ b/addons/hr/static/src/models/user/user.js @@ -0,0 +1,18 @@ +odoo.define('hr/static/src/models/user/user.js', function (require) { +'use strict'; + +const { + registerFieldPatchModel, +} = require('mail/static/src/model/model_core.js'); +const { one2one } = require('mail/static/src/model/model_field.js'); + +registerFieldPatchModel('mail.user', 'hr/static/src/models/user/user.js', { + /** + * Employee related to this user. + */ + employee: one2one('hr.employee', { + inverse: 'user', + }), +}); + +}); diff --git a/addons/hr/static/tests/helpers/mock_models.js b/addons/hr/static/tests/helpers/mock_models.js new file mode 100644 index 00000000000..d4ae7c86035 --- /dev/null +++ b/addons/hr/static/tests/helpers/mock_models.js @@ -0,0 +1,34 @@ +odoo.define('hr/static/tests/helpers/mock_models.js', function (require) { +'use strict'; + +const MockModels = require('mail/static/tests/helpers/mock_models.js'); + +MockModels.patch('hr/static/tests/helpers/mock_models.js', T => + class extends T { + + //---------------------------------------------------------------------- + // Public + //---------------------------------------------------------------------- + + /** + * @override + */ + static generateData() { + const data = super.generateData(...arguments); + Object.assign(data, { + 'hr.employee.public': { + fields: { + display_name: { string: "Name", type: "char" }, + user_id: { string: "User", type: "many2one", relation: 'res.users' }, + user_partner_id: { string: "Partner", type: "many2one", relation: 'res.partner' }, + }, + records: [], + }, + }); + return data; + } + + } +); + +}); diff --git a/addons/hr/static/tests/many2one_avatar_employee_tests.js b/addons/hr/static/tests/many2one_avatar_employee_tests.js index 06ea5a74f07..c1eef47b182 100644 --- a/addons/hr/static/tests/many2one_avatar_employee_tests.js +++ b/addons/hr/static/tests/many2one_avatar_employee_tests.js @@ -1,7 +1,12 @@ odoo.define('hr.Many2OneAvatarEmployeeTests', function (require) { "use strict"; -const { afterEach, beforeEach, start } = require('mail/static/src/utils/test_utils.js'); +const { + afterEach, + afterNextRender, + beforeEach, + start, +} = require('mail/static/src/utils/test_utils.js'); const FormView = require('web.FormView'); const KanbanView = require('web.KanbanView'); @@ -20,7 +25,7 @@ QUnit.module('hr', {}, function () { Object.assign(this.data, { 'foo': { fields: { - employee_id: { string: "Employee", type: 'many2one', relation: 'hr.employee' }, + employee_id: { string: "Employee", type: 'many2one', relation: 'hr.employee.public' }, }, records: [ { id: 1, employee_id: 11 }, @@ -29,26 +34,17 @@ QUnit.module('hr', {}, function () { { id: 4, employee_id: 23 }, ], }, - 'hr.employee': { - fields: { - display_name: { string: "Name", type: "char" }, - user_partner_id: { string: "Partner", type: "many2one", relation: 'res.partner' }, - }, - records: [{ - id: 11, - name: "Mario", - user_partner_id: 11, - }, { - id: 7, - name: "Luigi", - user_partner_id: 12, - }, { - id: 23, - name: "Yoshi", - user_partner_id: 13, - }], - }, }); + this.data['hr.employee.public'].records.push( + { id: 11, name: "Mario", user_id: 11, user_partner_id: 11 }, + { id: 7, name: "Luigi", user_id: 12, user_partner_id: 12 }, + { id: 23, name: "Yoshi", user_id: 13, user_partner_id: 13 } + ); + this.data['res.users'].records.push( + { id: 11, partner_id: 11 }, + { id: 12, partner_id: 12 }, + { id: 13, partner_id: 13 } + ); this.data['res.partner'].records.push( { id: 11, display_name: "Mario" }, { id: 12, display_name: "Luigi" }, @@ -61,9 +57,10 @@ QUnit.module('hr', {}, function () { }); QUnit.test('many2one_avatar_employee widget in list view', async function (assert) { - assert.expect(4); + assert.expect(11); const { widget: list } = await start({ + hasChatWindow: true, hasView: true, View: ListView, model: 'foo', @@ -79,18 +76,59 @@ QUnit.module('hr', {}, function () { assert.strictEqual(list.$('.o_data_cell span').text(), 'MarioLuigiMarioYoshi'); - await dom.click(list.$('.o_data_cell:nth(0) .o_m2o_avatar')); - await dom.click(list.$('.o_data_cell:nth(1) .o_m2o_avatar')); - await dom.click(list.$('.o_data_cell:nth(2) .o_m2o_avatar')); + // click on first employee + await afterNextRender(() => + dom.click(list.$('.o_data_cell:nth(0) .o_m2o_avatar')) + ); + assert.verifySteps( + ['read hr.employee.public 11'], + "first employee should have been read to find its partner" + ); + assert.containsOnce( + document.body, + '.o_ChatWindowHeader_name', + 'should have opened chat window' + ); + assert.strictEqual( + document.querySelector('.o_ChatWindowHeader_name').textContent, + "Mario", + 'chat window should be with clicked employee' + ); + // click on second employee + await afterNextRender(() => + dom.click(list.$('.o_data_cell:nth(1) .o_m2o_avatar') + )); + assert.verifySteps( + ['read hr.employee.public 7'], + "second employee should have been read to find its partner" + ); + assert.containsN( + document.body, + '.o_ChatWindowHeader_name', + 2, + 'should have opened second chat window' + ); + assert.strictEqual( + document.querySelectorAll('.o_ChatWindowHeader_name')[1].textContent, + "Luigi", + 'chat window should be with clicked employee' + ); - assert.verifySteps([ - 'read hr.employee 11', - // 'call service openDMChatWindow 1', - 'read hr.employee 7', - // 'call service openDMChatWindow 2', - // 'call service openDMChatWindow 1', - ]); + // click on third employee (same as first) + await afterNextRender(() => + dom.click(list.$('.o_data_cell:nth(2) .o_m2o_avatar')) + ); + assert.verifySteps( + [], + "employee should not have been read again because we already know its partner" + ); + assert.containsN( + document.body, + '.o_ChatWindowHeader_name', + 2, + "should still have only 2 chat windows because third is the same partner as first" + ); list.destroy(); }); @@ -117,18 +155,19 @@ QUnit.module('hr', {}, function () { assert.strictEqual(kanban.$('.o_kanban_record').text().trim(), ''); assert.containsN(kanban, '.o_m2o_avatar', 4); - assert.strictEqual(kanban.$('.o_m2o_avatar:nth(0)').data('src'), '/web/image/hr.employee/11/image_128'); - assert.strictEqual(kanban.$('.o_m2o_avatar:nth(1)').data('src'), '/web/image/hr.employee/7/image_128'); - assert.strictEqual(kanban.$('.o_m2o_avatar:nth(2)').data('src'), '/web/image/hr.employee/11/image_128'); - assert.strictEqual(kanban.$('.o_m2o_avatar:nth(3)').data('src'), '/web/image/hr.employee/23/image_128'); + assert.strictEqual(kanban.$('.o_m2o_avatar:nth(0)').data('src'), '/web/image/hr.employee.public/11/image_128'); + assert.strictEqual(kanban.$('.o_m2o_avatar:nth(1)').data('src'), '/web/image/hr.employee.public/7/image_128'); + assert.strictEqual(kanban.$('.o_m2o_avatar:nth(2)').data('src'), '/web/image/hr.employee.public/11/image_128'); + assert.strictEqual(kanban.$('.o_m2o_avatar:nth(3)').data('src'), '/web/image/hr.employee.public/23/image_128'); kanban.destroy(); }); QUnit.test('many2one_avatar_employee: click on an employee not associated with a user', async function (assert) { - assert.expect(5); + assert.expect(6); - this.data['hr.employee'].records[0].user_partner_id = false; + this.data['hr.employee.public'].records[0].user_id = false; + this.data['hr.employee.public'].records[0].user_partner_id = false; const { widget: form } = await start({ hasView: true, View: FormView, @@ -156,50 +195,19 @@ QUnit.module('hr', {}, function () { assert.verifySteps([ 'read foo 1', - 'read hr.employee 11', - 'display notification "You can only chat with employees that have a dedicated user"', + 'read hr.employee.public 11', ]); - form.destroy(); - }); - - QUnit.test('many2one_avatar_employee: click on self', async function (assert) { - assert.expect(5); - - this.data.currentPartnerId = 11; - const { widget: form } = await start({ - hasView: true, - View: FormView, - model: 'foo', - data: this.data, - arch: '
', - mockRPC(route, args) { - if (args.method === 'read') { - assert.step(`read ${args.model} ${args.args[0]}`); - } - return this._super(...arguments); - }, - session: { - partner_id: this.data.currentPartnerId, - }, - res_id: 1, - }); - - mock.intercept(form, 'call_service', (ev) => { - if (ev.data.service === 'notification') { - assert.step(`display notification "${ev.data.args[0].message}"`); - } - }, true); - - assert.strictEqual(form.$('.o_field_widget[name=employee_id]').text().trim(), 'Mario'); - - await dom.click(form.$('.o_m2o_avatar')); - - assert.verifySteps([ - 'read foo 1', - 'read hr.employee 11', - 'display notification "You cannot chat with yourself"', - ]); + assert.containsOnce( + document.body, + '.toast .o_notification_content', + "should display a toast notification after failing to open chat" + ); + assert.strictEqual( + document.querySelector('.o_notification_content').textContent, + "You can only chat with employees that have a dedicated user.", + "should display the correct information in the notification" + ); form.destroy(); }); diff --git a/addons/hr/views/hr_templates.xml b/addons/hr/views/hr_templates.xml index d959b96506b..5d4bcd9da3e 100644 --- a/addons/hr/views/hr_templates.xml +++ b/addons/hr/views/hr_templates.xml @@ -4,15 +4,24 @@