[IMP] hr: support many2many_avatar_employee widget
closes odoo/odoo#67515 Related: odoo/enterprise#17081 Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
committed by
Aaron Bohy
parent
8962edf492
commit
ffe768f28e
@@ -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': [
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/** @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,
|
||||
};
|
||||
@@ -1,43 +0,0 @@
|
||||
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,
|
||||
};
|
||||
});
|
||||
+170
-3
@@ -1,4 +1,4 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user