Initial commit: Hr packages

This commit is contained in:
Ernad Husremovic 2025-08-29 15:20:50 +02:00
commit 62531cd146
2820 changed files with 1432848 additions and 0 deletions

View file

@ -0,0 +1,114 @@
/** @odoo-module **/
import { UPDATE_BUS_PRESENCE_DELAY } from '@bus/im_status_service';
import { start, startServer } from '@mail/../tests/helpers/test_utils';
QUnit.module('hr_holidays', {}, function () {
QUnit.module('components', {}, function () {
QUnit.module('persona_im_status_icon_tests.js');
QUnit.test('on leave & online', async function (assert) {
assert.expect(2);
const pyEnv = await startServer();
const partnerId = pyEnv['res.partner'].create({ im_status: 'leave_online' });
const mailChannelId = pyEnv['mail.channel'].create({});
pyEnv['mail.message'].create({
author_id: partnerId,
body: 'not empty',
model: 'mail.channel',
res_id: mailChannelId,
});
const { advanceTime, afterNextRender, openDiscuss } = await start({
discuss: {
params: {
default_active_id: mailChannelId,
},
},
hasTimeControl: true,
});
await openDiscuss();
await afterNextRender(() => advanceTime(UPDATE_BUS_PRESENCE_DELAY));
assert.hasClass(
document.querySelector('.o_PersonaImStatusIcon_icon'),
'o-online',
"persona IM status icon should have online status rendering"
);
assert.hasClass(
document.querySelector('.o_PersonaImStatusIcon_icon'),
'fa-plane',
"persona IM status icon should have leave status rendering"
);
});
QUnit.test('on leave & away', async function (assert) {
assert.expect(2);
const pyEnv = await startServer();
const partnerId = pyEnv['res.partner'].create({ im_status: 'leave_away' });
const mailChannelId = pyEnv['mail.channel'].create({});
pyEnv['mail.message'].create({
author_id: partnerId,
body: 'not empty',
model: 'mail.channel',
res_id: mailChannelId,
});
const { advanceTime, afterNextRender, openDiscuss } = await start({
discuss: {
params: {
default_active_id: mailChannelId,
},
},
hasTimeControl: true,
});
await openDiscuss();
await afterNextRender(() => advanceTime(UPDATE_BUS_PRESENCE_DELAY));
assert.hasClass(
document.querySelector('.o_PersonaImStatusIcon_icon'),
'o-away',
"persona IM status icon should have away status rendering"
);
assert.hasClass(
document.querySelector('.o_PersonaImStatusIcon_icon'),
'fa-plane',
"persona IM status icon should have leave status rendering"
);
});
QUnit.test('on leave & offline', async function (assert) {
assert.expect(2);
const pyEnv = await startServer();
const partnerId = pyEnv['res.partner'].create({ im_status: 'leave_offline' });
const mailChannelId = pyEnv['mail.channel'].create({});
pyEnv['mail.message'].create({
author_id: partnerId,
body: 'not empty',
model: 'mail.channel',
res_id: mailChannelId,
});
const { advanceTime, afterNextRender, openDiscuss } = await start({
discuss: {
params: {
default_active_id: mailChannelId,
},
},
hasTimeControl: true,
});
await openDiscuss();
await afterNextRender(() => advanceTime(UPDATE_BUS_PRESENCE_DELAY));
assert.hasClass(
document.querySelector('.o_PersonaImStatusIcon_icon'),
'o-offline',
"persona IM status icon should have offline status rendering"
);
assert.hasClass(
document.querySelector('.o_PersonaImStatusIcon_icon'),
'fa-plane',
"persona IM status icon should have leave status rendering"
);
});
});
});

View file

@ -0,0 +1,103 @@
/** @odoo-module **/
import {
start,
startServer,
} from '@mail/../tests/helpers/test_utils';
QUnit.module('mail', {}, function () {
QUnit.module('components', {}, function () {
QUnit.module('thread_icon_tests.js');
QUnit.test('thread icon of a chat when correspondent is on leave & online', async function (assert) {
assert.expect(2);
const pyEnv = await startServer();
const resPartnerId1 = pyEnv['res.partner'].create({
im_status: 'leave_online',
name: 'Demo',
});
pyEnv['mail.channel'].create({
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: resPartnerId1 }],
],
channel_type: 'chat',
});
const { openDiscuss } = await start();
await openDiscuss();
assert.containsOnce(
document.body,
'.o_ThreadIcon_online',
"thread icon should have online status rendering"
);
assert.hasClass(
document.querySelector('.o_ThreadIcon_online'),
'fa-plane',
"thread icon should have leave status rendering"
);
});
QUnit.test('thread icon of a chat when correspondent is on leave & away', async function (assert) {
assert.expect(2);
const pyEnv = await startServer();
const resPartnerId1 = pyEnv['res.partner'].create({
im_status: 'leave_away',
name: 'Demo',
});
pyEnv['mail.channel'].create({
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: resPartnerId1 }],
],
channel_type: 'chat',
});
const { openDiscuss } = await start();
await openDiscuss();
assert.containsOnce(
document.body,
'.o_ThreadIcon_away',
"thread icon should have away status rendering"
);
assert.hasClass(
document.querySelector('.o_ThreadIcon_away'),
'fa-plane',
"thread icon should have leave status rendering"
);
});
QUnit.test('thread icon of a chat when correspondent is on leave & offline', async function (assert) {
assert.expect(2);
const pyEnv = await startServer();
const resPartnerId1 = pyEnv['res.partner'].create({
im_status: 'leave_offline',
name: 'Demo',
});
pyEnv['mail.channel'].create({
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: resPartnerId1 }],
],
channel_type: 'chat',
});
const { openDiscuss } = await start();
await openDiscuss();
assert.containsOnce(
document.body,
'.o_ThreadIcon_offline',
"thread icon should have offline status rendering"
);
assert.hasClass(
document.querySelector('.o_ThreadIcon_offline'),
'fa-plane',
"thread icon should have leave status rendering"
);
});
});
});

View file

@ -0,0 +1,51 @@
/** @odoo-module **/
import { start, startServer } from '@mail/../tests/helpers/test_utils';
QUnit.module('hr_holidays', {}, function () {
QUnit.module('components', {}, function () {
QUnit.module('thread_view_tests.js');
QUnit.test('out of office message on direct chat with out of office partner', async function (assert) {
assert.expect(2);
// Returning date of the out of office partner, simulates he'll be back in a month
const returningDate = moment.utc().add(1, 'month');
const pyEnv = await startServer();
// Needed partner & user to allow simulation of message reception
const resPartnerId1 = pyEnv['res.partner'].create({
name: "Foreigner partner",
out_of_office_date_end: returningDate.format("YYYY-MM-DD"),
});
const mailChannelId1 = pyEnv['mail.channel'].create({
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: resPartnerId1 }],
],
channel_type: 'chat',
});
const { openDiscuss, messaging } = await start({
discuss: {
params: {
default_active_id: `mail.channel_${mailChannelId1}`,
},
},
});
await openDiscuss();
assert.containsOnce(
document.body,
'.o_ThreadView_outOfOffice',
"should have an out of office alert on thread view"
);
const formattedDate = returningDate.toDate().toLocaleDateString(
messaging.locale.language.replace(/_/g, '-'),
{ day: 'numeric', month: 'short' }
);
assert.ok(
document.querySelector('.o_ThreadView_outOfOffice').textContent.includes(formattedDate),
"out of office message should mention the returning date"
);
});
});
});

View file

@ -0,0 +1,59 @@
/** @odoo-module **/
import { click, clickSave, getFixture } from "@web/../tests/helpers/utils";
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
let serverData;
let target;
QUnit.module("Fields", (hooks) => {
hooks.beforeEach(() => {
serverData = {
models: {
partner: {
fields: {
product_id: { string: "Product", type: "many2one", relation: "product" },
},
records: [{ id: 1, product_id: false }],
},
product: {
fields: {
name: { string: "Product Name", type: "char" },
},
records: [
{ id: 1, display_name: "a" },
{ id: 2, display_name: "b" },
{ id: 3, display_name: "c" },
],
},
},
};
target = getFixture();
setupViewRegistries();
});
QUnit.module("RadioImageField");
QUnit.test("field is correctly renderered", async function (assert) {
await makeView({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: '<form><field name="product_id" widget="hr_holidays_radio_image"/></form>',
});
assert.containsOnce(target, ".o_field_widget.o_field_hr_holidays_radio_image");
assert.containsN(target, ".o_radio_input", 3);
assert.containsNone(target, ".o_radio_input:checked");
assert.containsN(target, "img", 3);
await click(target.querySelector("img"));
assert.containsOnce(target, ".o_radio_input:checked");
await clickSave(target);
assert.containsOnce(target, ".o_field_widget.o_field_hr_holidays_radio_image");
assert.containsN(target, ".o_radio_input", 3);
assert.containsN(target, "img", 3);
});
});

View file

@ -0,0 +1,159 @@
/** @odoo-module */
import { selectDropdownItem, editInput, getFixture } from '@web/../tests/helpers/utils';
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
let serverData;
let target;
QUnit.module('leave_stats_widget', (hooks) => {
hooks.beforeEach(() => {
setupViewRegistries();
target = getFixture();
serverData = {
models: {
department: {
fields: {
name: { string: "Name", type: "char" },
},
records: [{id:11, name: "R&D"}],
},
employee: {
fields: {
name: { string: "Name", type: "char" },
department_id: { string: "Department", type: "many2one", relation: 'department' },
},
records: [{
id: 100,
name: "Richard",
department_id: 11,
},{
id: 200,
name: "Jesus",
department_id: 11,
}],
},
'hr.leave.type': {
fields: {
name: { string: "Name", type: "char" }
},
records: [{
id: 55,
name: "Legal Leave",
}]
},
'hr.leave': {
fields: {
employee_id: { string: "Employee", type: "many2one", relation: 'employee' },
department_id: { string: "Department", type: "many2one", relation: 'department' },
date_from: { string: "From", type: "datetime" },
date_to: { string: "To", type: "datetime" },
holiday_status_id: { string: "Leave type", type: "many2one", relation: 'hr.leave.type' },
state: { string: "State", type: "char" },
holiday_type: { string: "Holiday Type", type: "char" },
number_of_days: { string: "State", type: "integer" },
},
records: [{
id: 12,
employee_id: 100,
department_id: 11,
date_from: "2016-10-20 09:00:00",
date_to: "2016-10-25 18:00:00",
holiday_status_id: 55,
state: 'validate',
number_of_days: 5,
holiday_type: 'employee',
},{
id: 13,
employee_id: 100,
department_id: 11,
date_from: "2016-10-02 09:00:00",
date_to: "2016-10-02 18:00:00",
holiday_status_id: 55,
state: 'validate',
number_of_days: 1,
holiday_type: 'employee',
},{
id: 14,
employee_id: 200,
department_id: 11,
date_from: "2016-10-15 09:00:00",
date_to: "2016-10-20 18:00:00",
holiday_status_id: 55,
state: 'validate',
number_of_days: 8,
holiday_type: 'employee',
}]
}
}
};
});
QUnit.test('leave stats renders correctly', async (assert) => {
assert.expect(5);
await makeView({
serverData,
type: "form",
resModel: 'hr.leave',
arch: '<form string="Leave">' +
'<field name="employee_id"/>' +
'<field name="department_id"/>' +
'<field name="date_from"/>' +
'<widget name="hr_leave_stats"/>' +
'</form>',
resId: 12,
mockRPC(route, args) {
if (args.model === 'hr.leave' && args.method === 'search') {
return Promise.resolve(this.data['hr.leave'].records.map(function (record) { return record.id; }));
}
},
});
const $leaveTypeBody = target.querySelector('.o_leave_stats #o_leave_stats_employee');
const $leavesDepartmentBody = target.querySelector('.o_leave_stats #o_leave_stats_department');
assert.containsOnce($leaveTypeBody, 'span:contains(Legal Leave)', "it should have leave type");
assert.containsOnce($leaveTypeBody, 'span:contains(6)', "it should have 6 days");
assert.containsN($leavesDepartmentBody, 'span:contains(Richard)', 2, "it should have 2 leaves for Richard");
assert.containsOnce($leavesDepartmentBody, 'span:contains(Jesus)', "it should have 1 leaves for Jesus");
assert.containsOnce($leavesDepartmentBody, 'div.o_horizontal_separator:contains(R&D)', "it should have R&D title");
});
QUnit.test('leave stats reload when employee/department changes', async (assert) => {
assert.expect(3);
await makeView({
serverData,
type: "form",
resModel: 'hr.leave',
mode: 'edit',
arch: '<form string="Leave">' +
'<field name="employee_id"/>' +
'<field name="department_id"/>' +
'<field name="date_from"/>' +
'<widget name="hr_leave_stats"/>' +
'</form>',
mockRPC(route, args) {
if (args.model === 'hr.leave' && args.method === 'search_read') {
assert.ok(_.some(args.kwargs.domain, _.matcher(['department_id', '=', 11])), "It should load department's leaves data");
}
if (args.model === 'hr.leave' && args.method === 'read_group') {
assert.ok(_.some(args.kwargs.domain, _.matcher(['employee_id', '=', 200])), "It should load employee's leaves data");
}
},
});
// Set date => shouldn't load data yet (no employee nor department defined)
await editInput(
target,
"div[name='date_from'] .o_datepicker_input",
"2016-10-12 09:00:00"
);
// Set employee => should load employee's date
await selectDropdownItem(target, "employee_id", "Jesus");
// Set department => should load department's data
await selectDropdownItem(target, "department_id", "R&D");
});
});