Skip to content
Snippets Groups Projects
Commit ffe768f2 authored by Mohammed Shekha's avatar Mohammed Shekha Committed by Aaron Bohy
Browse files

[IMP] hr: support many2many_avatar_employee widget


closes odoo/odoo#67515

Related: odoo/enterprise#17081
Signed-off-by: default avatarAaron Bohy (aab) <aab@odoo.com>
parent 8962edf4
No related branches found
No related tags found
No related merge requests found
......@@ -52,7 +52,7 @@
'hr/static/src/scss/hr.scss',
'hr/static/src/js/chat.js',
'hr/static/src/js/language.js',
'hr/static/src/js/many2one_avatar_employee.js',
'hr/static/src/js/m2x_avatar_employee.js',
'hr/static/src/js/standalone_m2o_avatar_employee.js',
'hr/static/src/models/employee/employee.js',
'hr/static/src/models/messaging/messaging.js',
......@@ -61,7 +61,7 @@
],
'web.qunit_suite_tests': [
'hr/static/tests/helpers/mock_models.js',
'hr/static/tests/many2one_avatar_employee_tests.js',
'hr/static/tests/m2x_avatar_employee_tests.js',
'hr/static/tests/standalone_m2o_avatar_employee_tests.js',
],
'web.assets_qweb': [
......
/** @odoo-module alias=hr.Many2OneAvatarEmployee **/
import fieldRegistry from 'web.field_registry';
import { Many2OneAvatarUser, KanbanMany2OneAvatarUser } from '@mail/js/m2x_avatar_user';
import { Many2ManyAvatarUser } from '@mail/js/m2x_avatar_user';
// This module defines variants of the Many2OneAvatarUser and Many2ManyAvatarUser
// field widgets, to support fields pointing to 'hr.employee'. It also defines the
// kanban version of the Many2OneAvatarEmployee widget.
//
// Usage:
// <field name="employee_id" widget="many2one_avatar_employee"/>
const M2XAvatarEmployeeMixin = {
supportedModels: ['hr.employee', 'hr.employee.public'],
//----------------------------------------------------------------------
// Private
//----------------------------------------------------------------------
_getEmployeeID() {
return this.value.res_id;
},
//----------------------------------------------------------------------
// Handlers
//----------------------------------------------------------------------
/**
* @override
*/
_onAvatarClicked(ev) {
ev.stopPropagation(); // in list view, prevent from opening the record
const employeeId = this._getEmployeeID(ev);
this._openChat({ employeeId: employeeId });
}
};
export const Many2OneAvatarEmployee = Many2OneAvatarUser.extend(M2XAvatarEmployeeMixin);
export const KanbanMany2OneAvatarEmployee = KanbanMany2OneAvatarUser.extend(M2XAvatarEmployeeMixin);
fieldRegistry.add('many2one_avatar_employee', Many2OneAvatarEmployee);
fieldRegistry.add('kanban.many2one_avatar_employee', KanbanMany2OneAvatarEmployee);
export const Many2ManyAvatarEmployee = Many2ManyAvatarUser.extend(M2XAvatarEmployeeMixin, {
_getEmployeeID(ev) {
return parseInt(ev.target.getAttribute('data-id'), 10);
},
});
fieldRegistry.add('many2many_avatar_employee', Many2ManyAvatarEmployee);
export default {
Many2OneAvatarEmployee,
};
odoo.define('hr.Many2OneAvatarEmployee', function (require) {
"use strict";
// This module defines a variant of the Many2OneAvatarUser field widget,
// to support many2one fields pointing to 'hr.employee'. It also defines the
// kanban version of this widget.
//
// Usage:
// <field name="employee_id" widget="many2one_avatar_employee"/>
const fieldRegistry = require('web.field_registry');
const { Many2OneAvatarUser, KanbanMany2OneAvatarUser } = require('@mail/js/many2one_avatar_user');
const { Component } = owl;
const Many2OneAvatarEmployeeMixin = {
supportedModels: ['hr.employee', 'hr.employee.public'],
//----------------------------------------------------------------------
// Private
//----------------------------------------------------------------------
/**
* @override
*/
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);
const KanbanMany2OneAvatarEmployee = KanbanMany2OneAvatarUser.extend(Many2OneAvatarEmployeeMixin);
fieldRegistry.add('many2one_avatar_employee', Many2OneAvatarEmployee);
fieldRegistry.add('kanban.many2one_avatar_employee', KanbanMany2OneAvatarEmployee);
return {
Many2OneAvatarEmployee,
KanbanMany2OneAvatarEmployee,
};
});
odoo.define('hr.Many2OneAvatarEmployeeTests', function (require) {
odoo.define('hr.M2XAvatarEmployeeTests', function (require) {
"use strict";
const {
......@@ -15,7 +15,7 @@ const { Many2OneAvatarEmployee } = require('hr.Many2OneAvatarEmployee');
const { dom, mock } = require('web.test_utils');
QUnit.module('hr', {}, function () {
QUnit.module('Many2OneAvatarEmployee', {
QUnit.module('M2XAvatarEmployee', {
beforeEach() {
beforeEach(this);
......@@ -26,9 +26,10 @@ QUnit.module('hr', {}, function () {
'foo': {
fields: {
employee_id: { string: "Employee", type: 'many2one', relation: 'hr.employee.public' },
employee_ids: { string: "Employees", type: "many2many", relation: 'hr.employee.public' },
},
records: [
{ id: 1, employee_id: 11 },
{ id: 1, employee_id: 11, employee_ids: [11, 23] },
{ id: 2, employee_id: 7 },
{ id: 3, employee_id: 11 },
{ id: 4, employee_id: 23 },
......@@ -211,5 +212,171 @@ QUnit.module('hr', {}, function () {
form.destroy();
});
QUnit.test('many2many_avatar_employee widget in form view', async function (assert) {
assert.expect(8);
const { widget: form } = await start({
hasChatWindow: true,
hasView: true,
View: FormView,
model: 'foo',
data: this.data,
arch: '<form><field name="employee_ids" widget="many2many_avatar_employee"/></form>',
mockRPC(route, args) {
if (args.method === 'read') {
assert.step(`read ${args.model} ${args.args[0]}`);
}
return this._super(...arguments);
},
res_id: 1,
});
assert.containsN(form, '.o_field_many2manytags.avatar.o_field_widget .badge', 2,
"should have 2 records");
assert.strictEqual(form.$('.o_field_many2manytags.avatar.o_field_widget .badge:first img').data('src'),
'/web/image/hr.employee.public/11/image_128',
"should have correct avatar image");
await dom.click(form.$('.o_field_many2manytags.avatar .badge:first .o_m2m_avatar'));
await dom.click(form.$('.o_field_many2manytags.avatar .badge:nth(1) .o_m2m_avatar'));
assert.verifySteps([
"read foo 1",
'read hr.employee.public 11,23',
"read hr.employee.public 11",
"read hr.employee.public 23",
]);
assert.containsN(
document.body,
'.o_ChatWindowHeader_name',
2,
"should have 2 chat windows"
);
form.destroy();
});
QUnit.test('many2many_avatar_employee widget in list view', async function (assert) {
assert.expect(10);
const { widget: list } = await start({
hasChatWindow: true,
hasView: true,
View: ListView,
model: 'foo',
data: this.data,
arch: '<tree><field name="employee_ids" widget="many2many_avatar_employee"/></tree>',
mockRPC(route, args) {
if (args.method === 'read') {
assert.step(`read ${args.model} ${args.args[0]}`);
}
return this._super(...arguments);
},
});
assert.strictEqual(list.$('.o_data_cell:first .o_tag_badge_text').text().trim(), 'MarioYoshi');
// click on first employee badge
await afterNextRender(() =>
dom.click(list.$('.o_data_cell:nth(0) .o_m2m_avatar:first'))
);
assert.verifySteps(
['read hr.employee.public 11,23', "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(0) .o_m2m_avatar:nth(1)')
));
assert.verifySteps(
['read hr.employee.public 23'],
"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,
"Yoshi",
'chat window should be with clicked employee'
);
list.destroy();
});
QUnit.test('many2many_avatar_employee: click on an employee not associated with a user', async function (assert) {
assert.expect(10);
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({
hasChatWindow: true,
hasView: true,
View: FormView,
model: 'foo',
data: this.data,
arch: '<form><field name="employee_ids" widget="many2many_avatar_employee"/></form>',
mockRPC(route, args) {
if (args.method === 'read') {
assert.step(`read ${args.model} ${args.args[0]}`);
}
return this._super(...arguments);
},
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.containsN(form, '.o_field_many2manytags.avatar.o_field_widget .badge', 2,
"should have 2 records");
assert.strictEqual(form.$('.o_field_many2manytags.avatar.o_field_widget .badge:first img').data('src'),
'/web/image/hr.employee.public/11/image_128',
"should have correct avatar image");
await dom.click(form.$('.o_field_many2manytags.avatar .badge:first .o_m2m_avatar'));
await dom.click(form.$('.o_field_many2manytags.avatar .badge:nth(1) .o_m2m_avatar'));
assert.verifySteps([
'read foo 1',
'read hr.employee.public 11,23',
"read hr.employee.public 11",
"read hr.employee.public 23"
]);
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"
);
assert.containsOnce(document.body, '.o_ChatWindowHeader_name',
"should have 1 chat window");
form.destroy();
});
});
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment