mirror of
https://github.com/bringout/oca-ocb-hr.git
synced 2026-04-26 12:12:02 +02:00
Initial commit: Hr packages
This commit is contained in:
commit
62531cd146
2820 changed files with 1432848 additions and 0 deletions
|
|
@ -0,0 +1,27 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import '@mail/../tests/helpers/mock_server'; // ensure mail overrides are applied first
|
||||
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { MockServer } from "@web/../tests/helpers/mock_server";
|
||||
|
||||
patch(MockServer.prototype, 'hr_holidays', {
|
||||
/**
|
||||
* Overrides to add out of office to employees.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_mockResPartnerMailPartnerFormat(ids) {
|
||||
const partnerFormats = this._super(...arguments);
|
||||
const partners = this.getRecords(
|
||||
'res.partner',
|
||||
[['id', 'in', ids]],
|
||||
{ active_test: false },
|
||||
);
|
||||
for (const partner of partners) {
|
||||
// Not a real field but ease the testing
|
||||
partnerFormats.get(partner.id).out_of_office_date_end = partner.out_of_office_date_end;
|
||||
}
|
||||
return partnerFormats;
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { insertModelFields } from '@bus/../tests/helpers/model_definitions_helpers';
|
||||
|
||||
insertModelFields('res.partner', {
|
||||
out_of_office_date_end: { type: 'date' },
|
||||
});
|
||||
|
|
@ -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"
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
|
@ -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"
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
|
@ -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"
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
@ -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");
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
odoo.define('hr_holidays.tour_time_off_request_calendar_view', function (require) {
|
||||
'use strict';
|
||||
|
||||
var tour = require('web_tour.tour');
|
||||
|
||||
tour.register('time_off_request_calendar_view', {
|
||||
test: true,
|
||||
url: '/web',
|
||||
},
|
||||
[
|
||||
tour.stepUtils.showAppsMenuItem(),
|
||||
{
|
||||
content: "Open Time Off app",
|
||||
trigger: '.o_app[data-menu-xmlid="hr_holidays.menu_hr_holidays_root"]',
|
||||
},
|
||||
{
|
||||
content: "Click on the first Thursday of the year",
|
||||
trigger: '.fc-day-top.fc-thu',
|
||||
run: () => {
|
||||
const el = document.querySelector('.fc-day-top.fc-thu').firstChild;
|
||||
el.scrollIntoView();
|
||||
|
||||
const fromPosition = el.getBoundingClientRect();
|
||||
fromPosition.x += el.offsetWidth / 2;
|
||||
fromPosition.y += el.offsetHeight / 2;
|
||||
|
||||
el.dispatchEvent(new MouseEvent("mousedown", {
|
||||
bubbles: true,
|
||||
which: 1,
|
||||
button: 0,
|
||||
clientX: fromPosition.x,
|
||||
clientY: fromPosition.y}));
|
||||
el.dispatchEvent(new MouseEvent("mouseup", {
|
||||
bubbles: true,
|
||||
which: 1,
|
||||
button: 0,
|
||||
clientX: fromPosition.x,
|
||||
clientY: fromPosition.y }));
|
||||
}
|
||||
},
|
||||
{
|
||||
content: "Save the leave",
|
||||
trigger: '.btn:contains("Save")',
|
||||
run: 'click',
|
||||
}
|
||||
]);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue