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,196 @@
odoo.define("hr_timesheet.timesheet_uom_tests_env", function (require) {
"use strict";
const session = require('web.session');
const { createView } = require("web.test_utils");
const ListView = require('web.ListView');
const { timesheetUomService } = require('hr_timesheet.timesheet_uom');
const { makeTestEnv } = require("@web/../tests/helpers/mock_env");
const { registry } = require("@web/core/registry");
/**
* Sets the timesheet related widgets testing environment up.
*/
function SetupTimesheetUOMWidgetsTestEnvironment () {
this.allowedCompanies = {
1: {
id: 1,
name: 'Company 1',
timesheet_uom_id: 1,
timesheet_uom_factor: 1,
},
2: {
id: 2,
name: 'Company 2',
timesheet_uom_id: 2,
timesheet_uom_factor: 0.125,
},
3: {
id: 3,
name: 'Company 3',
timesheet_uom_id: 2,
timesheet_uom_factor: 0.125,
},
};
this.uomIds = {
1: {
id: 1,
name: 'hour',
rounding: 0.01,
timesheet_widget: 'float_time',
},
2: {
id: 2,
name: 'day',
rounding: 0.01,
timesheet_widget: 'float_toggle',
},
};
this.singleCompanyHourUOMUser = {
allowed_company_ids: [this.allowedCompanies[1].id],
};
this.singleCompanyDayUOMUser = {
allowed_company_ids: [this.allowedCompanies[2].id],
};
this.multiCompanyHourUOMUser = {
allowed_company_ids: [
this.allowedCompanies[1].id,
this.allowedCompanies[3].id,
],
};
this.multiCompanyDayUOMUser = {
allowed_company_ids: [
this.allowedCompanies[3].id,
this.allowedCompanies[1].id,
],
};
this.session = {
uid: 0, // In order to avoid bbqState and cookies to be taken into account in AbstractWebClient.
user_companies: {
current_company: 1,
allowed_companies: this.allowedCompanies,
},
user_context: this.singleCompanyHourUOMUser,
uom_ids: this.uomIds,
};
this.data = {
'account.analytic.line': {
fields: {
project_id: {
string: "Project",
type: "many2one",
relation: "project.project",
},
task_id: {
string:"Task",
type: "many2one",
relation: "project.task",
},
date: {
string: "Date",
type: "date",
},
unit_amount: {
string: "Unit Amount",
type: "float",
},
},
records: [
{
id: 1,
project_id: 1,
task_id: 1,
date: "2021-01-12",
unit_amount: 8,
},
],
},
'project.project': {
fields: {
name: {
string: "Project Name",
type: "char",
},
},
records: [
{
id: 1,
display_name: "P1",
},
],
},
'project.task': {
fields: {
name: {
string: "Task Name",
type: "char",
},
project_id: {
string: "Project",
type: "many2one",
relation: "project.project",
},
},
records: [
{
id: 1,
display_name: "T1",
project_id: 1,
},
],
},
};
this.patchSessionAndStartServices = async function (sessionToApply, doNotUseEnvSession = false) {
/*
Adds the timesheet_uom to the fieldRegistry by setting the session and
starting the timesheet_uom service which registers the widget in the registry.
*/
session.user_companies = Object.assign(
{ },
!doNotUseEnvSession && this.session.user_companies || { },
sessionToApply && sessionToApply.user_companies);
if (Object.keys(session.user_companies).length === 0) {
delete session.user_companies;
}
session.user_context = Object.assign(
{ },
!doNotUseEnvSession && this.session.user_context || { },
sessionToApply && sessionToApply.user_context);
session.uom_ids = Object.assign(
{ },
!doNotUseEnvSession && this.session.uom_ids || { },
sessionToApply && sessionToApply.uom_ids);
if (!doNotUseEnvSession && 'uid' in this.session) {
session.uid = this.session.uid;
}
if (sessionToApply && 'uid' in sessionToApply) {
session.uid = sessionToApply.uid;
}
const serviceRegistry = registry.category("services");
if (!serviceRegistry.contains("timesheet_uom")) {
// Remove dependency on legacy_session since we're patching the session directly
serviceRegistry.add("timesheet_uom", Object.assign({}, timesheetUomService, { dependencies: [] }));
}
await makeTestEnv(); // Start services
};
this.createView = async function (options) {
const sessionToApply = options && options.session || { };
await this.patchSessionAndStartServices(sessionToApply);
return await createView(Object.assign(
{
View: ListView,
data: this.data,
model: 'account.analytic.line',
arch: `
<tree>
<field name="unit_amount" widget="timesheet_uom"/>
</tree>`,
},
options || { },
));
};
};
return SetupTimesheetUOMWidgetsTestEnvironment;
});

View file

@ -0,0 +1,140 @@
odoo.define("hr_timesheet.timesheet_uom_tests", function (require) {
"use strict";
const session = require('web.session');
const SetupTimesheetUOMWidgetsTestEnvironment = require('hr_timesheet.timesheet_uom_tests_env');
const fieldUtils = require('web.field_utils');
const TimesheetUOM = require('hr_timesheet.timesheet_uom');
QUnit.module('Timesheet UOM Widgets', function (hooks) {
let env;
let sessionUserCompaniesBackup;
let sessionUserContextBackup;
let sessionUOMIdsBackup;
let sessionUIDBackup;
hooks.beforeEach(async function (assert) {
env = new SetupTimesheetUOMWidgetsTestEnvironment();
// Backups session parts that this testing module will alter in order to restore it at the end.
sessionUserCompaniesBackup = session.user_companies || false;
sessionUserContextBackup = session.user_context || false;
sessionUOMIdsBackup = session.uom_ids || false;
sessionUIDBackup = session.uid || false;
});
hooks.afterEach(async function (assert) {
// Restores the session
const sessionToApply = Object.assign(
{ },
sessionUserCompaniesBackup && {
user_companies: sessionUserCompaniesBackup,
} || { },
sessionUserContextBackup && {
user_context: sessionUserContextBackup,
} || { },
sessionUOMIdsBackup && {
uom_ids: sessionUOMIdsBackup,
} || { },
sessionUIDBackup && {
uid: sessionUIDBackup,
} || { });
await env.patchSessionAndStartServices(sessionToApply, true);
});
QUnit.module('timesheet_uom', function (hooks) {
QUnit.module('fieldRegistry', function (hooks) {
let FieldTimesheetTimeBackup;
let FieldTimesheetToggleBackup;
hooks.beforeEach(function (assert) {
// Backups the FieldTimesheetTime widget as it will be altered in this testing module
// in order to to ease testing.
FieldTimesheetTimeBackup = TimesheetUOM.FieldTimesheetTime;
TimesheetUOM.FieldTimesheetTime.include({
_render: function () {
const $widgetIdentification = $('<div>').addClass('i_am_a_timesheet_time_widget');
this.$el.append($widgetIdentification);
},
});
FieldTimesheetToggleBackup = TimesheetUOM.FieldTimesheetToggle;
TimesheetUOM.FieldTimesheetToggle.include({
_render: function () {
const $widgetIdentification = $('<div>').addClass('i_am_a_timesheet_toggle_widget');
this.$el.append($widgetIdentification);
},
});
});
hooks.afterEach(async function (hooks) {
// Restores the widgets and trigger reload in FieldRegistry.
TimesheetUOM.FieldTimesheetTime = FieldTimesheetTimeBackup;
TimesheetUOM.FieldTimesheetToggle = FieldTimesheetToggleBackup;
await env.patchSessionAndStartServices({ }, true);
});
QUnit.test('the timesheet_uom widget added to the fieldRegistry is company related', async function (assert) {
assert.expect(2);
let view = await env.createView();
assert.ok(view.$('.i_am_a_timesheet_time_widget').length, 'FieldTimesheetTime is rendered when company uom is hour');
view.destroy();
let option = {
session: {
user_context: env.singleCompanyDayUOMUser,
},
};
view = await env.createView(option);
assert.ok(view.$('.i_am_a_timesheet_toggle_widget').length, 'FieldTimesheetToggle is rendered when company uom is day');
view.destroy();
});
QUnit.test('the timesheet_uom widget added to the fieldRegistry in a multi company environment is the current company', async function (assert) {
assert.expect(2);
let option = {
session: {
user_context: env.multiCompanyHourUOMUser,
},
};
let view = await env.createView(option);
assert.ok(view.$('.i_am_a_timesheet_time_widget').length, 'FieldTimesheetTime is rendered when current company uom is hour');
view.destroy();
option = {
session: {
user_context: env.multiCompanyDayUOMUser,
},
};
view = await env.createView(option);
assert.ok(view.$('.i_am_a_timesheet_toggle_widget').length, 'FieldTimesheetToggle is rendered when current company uom is day');
view.destroy();
});
});
QUnit.module('timesheet_uom_factor', function (hooks) {
QUnit.test('the timesheet_uom_factor usage in formatters and parsers is company related', async function (assert) {
assert.expect(4);
await env.patchSessionAndStartServices();
assert.strictEqual(fieldUtils.format.timesheet_uom(1), '01:00', 'The format is taking the timesheet_uom_factor into account');
assert.strictEqual(fieldUtils.parse.timesheet_uom('01:00'), 1, 'The parsing is taking the timesheet_uom_factor into account');
const sessionToApply = {
user_context: env.singleCompanyDayUOMUser,
};
await env.patchSessionAndStartServices(sessionToApply);
assert.strictEqual(fieldUtils.format.timesheet_uom(8), '1.00', 'The format is taking the timesheet_uom_factor into account');
assert.strictEqual(fieldUtils.parse.timesheet_uom('1.00'), 8, 'The parsing is taking the timesheet_uom_factor into account');
});
QUnit.test('the timesheet_uom_factor taken into account in a multi company environment is the current company', async function (assert) {
assert.expect(4);
let sessionToApply = {
user_context: env.multiCompanyHourUOMUser,
};
await env.patchSessionAndStartServices(sessionToApply);
assert.strictEqual(fieldUtils.format.timesheet_uom(1), '01:00', 'The format is taking the timesheet_uom_factor into account');
assert.strictEqual(fieldUtils.parse.timesheet_uom('01:00'), 1, 'The parsing is taking the timesheet_uom_factor into account');
sessionToApply.user_context = env.singleCompanyDayUOMUser;
await env.patchSessionAndStartServices(sessionToApply);
assert.strictEqual(fieldUtils.format.timesheet_uom(8), '1.00', 'The format is taking the timesheet_uom_factor into account');
assert.strictEqual(fieldUtils.parse.timesheet_uom('1.00'), 8, 'The parsing is taking the timesheet_uom_factor into account');
});
});
});
});
});

View file

@ -0,0 +1,125 @@
/** @odoo-module */
import { registry } from "@web/core/registry";
import { session } from "@web/session";
import { companyService } from "@web/webclient/company_service";
import { uiService } from "@web/core/ui/ui_service";
import { makeView, setupViewRegistries} from "@web/../tests/views/helpers";
import { click, getFixture, patchWithCleanup } from "@web/../tests/helpers/utils";
const serviceRegistry = registry.category("services");
QUnit.module("Timesheet UOM Widgets", (hooks) => {
let serverData;
let target;
hooks.beforeEach(async function (assert) {
setupViewRegistries();
target = getFixture();
serverData = {
models: {
'account.analytic.line': {
fields: {
unit_amount: { string: "Unit Amount", type: "float" },
},
records: [
{ id: 1, unit_amount: 8 }
],
},
},
views: {
"account.analytic.line,false,list": `
<tree>
<field name="unit_amount" widget="timesheet_uom"/>
</tree>
`,
},
};
serviceRegistry.add("ui", uiService);
serviceRegistry.add("company", companyService, { force: true });
patchWithCleanup(session, {
user_companies: {
current_company: 1,
allowed_companies: {
1: {
id: 1,
name: 'Company',
timesheet_uom_id: 2,
timesheet_uom_factor: 0.125,
},
},
},
user_context: {
allowed_company_ids: [1],
},
uom_ids: {
1: {
id: 1,
name: 'hour',
rounding: 0.01,
timesheet_widget: 'float_time',
},
2: {
id: 2,
name: 'day',
rounding: 0.01,
timesheet_widget: 'float_toggle',
},
}
});
});
QUnit.module("TimesheetFloatToggleField");
QUnit.test("factor is applied in TimesheetFloatToggleField", async function (assert) {
await makeView({
serverData,
type: "list",
resModel: "account.analytic.line",
});
assert.containsOnce(target, 'div[name="unit_amount"]:contains("1")', "TimesheetFloatToggleField should take `timesheet_uom_factor` into account");
});
QUnit.test("ranges are working properly in TimesheetFloatToggleField", async function (assert) {
serverData.models["account.analytic.line"].records[0].unit_amount = 1;
serverData.views["account.analytic.line,false,list"] = serverData.views["account.analytic.line,false,list"].replace('<tree', '<tree editable="bottom"')
await makeView({
serverData,
type: "list",
resModel: "account.analytic.line",
});
// Enter edit mode
await click(target, 'div[name="unit_amount"]');
await click(target, 'div[name="unit_amount"] .o_field_float_toggle');
assert.containsOnce(target, 'div[name="unit_amount"]:contains("0.00")', "ranges are working properly in TimesheetFloatToggleField");
await click(target, 'div[name="unit_amount"] .o_field_float_toggle');
assert.containsOnce(target, 'div[name="unit_amount"]:contains("0.50")', "ranges are working properly in TimesheetFloatToggleField");
await click(target, 'div[name="unit_amount"] .o_field_float_toggle');
assert.containsOnce(target, 'div[name="unit_amount"]:contains("1.00")', "ranges are working properly in TimesheetFloatToggleField");
});
QUnit.module("TimesheetFloatTimeField");
QUnit.test("factor is applied in TimesheetFloatTimeField", async function (assert) {
patchWithCleanup(session.user_companies.allowed_companies[1], {timesheet_uom_id: 1});
await makeView({
serverData,
type: "list",
resModel: "account.analytic.line",
});
assert.containsOnce(target, 'div[name="unit_amount"]:contains("08:00")', "TimesheetFloatTimeField should not take `timesheet_uom_factor` into account");
});
QUnit.module("TimesheetFloatFactorField");
QUnit.test("factor is applied in TimesheetFloatFactorField", async function (assert) {
patchWithCleanup(session.uom_ids[2], {timesheet_widget: 'float_factor'});
await makeView({
serverData,
type: "list",
resModel: "account.analytic.line",
});
assert.containsOnce(target, 'div[name="unit_amount"]:contains("1")', "TimesheetFloatFactorField should take `timesheet_uom_factor` into account");
});
});