Initial commit: Hr packages
|
After Width: | Height: | Size: 6.5 KiB |
|
|
@ -0,0 +1,23 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="70" height="70" viewBox="0 0 70 70">
|
||||
<defs>
|
||||
<path id="icon-a" d="M4,5.35309892e-14 C36.4160122,9.87060235e-15 58.0836068,-3.97961823e-14 65,5.07020818e-14 C69,6.733808e-14 70,1 70,5 C70,43.0488877 70,62.4235458 70,65 C70,69 69,70 65,70 C61,70 9,70 4,70 C1,70 7.10542736e-15,69 7.10542736e-15,65 C7.25721566e-15,62.4676575 3.83358709e-14,41.8005206 3.60818146e-14,5 C-1.13686838e-13,1 1,5.75716207e-14 4,5.35309892e-14 Z"/>
|
||||
<linearGradient id="icon-c" x1="100%" x2="0%" y1="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#B06161"/>
|
||||
<stop offset="45.785%" stop-color="#984E4E"/>
|
||||
<stop offset="100%" stop-color="#7C3838"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<mask id="icon-b" fill="#fff">
|
||||
<use xlink:href="#icon-a"/>
|
||||
</mask>
|
||||
<g mask="url(#icon-b)">
|
||||
<rect width="70" height="70" fill="url(#icon-c)"/>
|
||||
<path fill="#FFF" fill-opacity=".383" d="M4,1.8 L65,1.8 C67.6666667,1.8 69.3333333,1.13333333 70,-0.2 C70,2.46666667 70,3.46666667 70,2.8 L1.10547097e-14,2.8 C-1.65952376e-14,3.46666667 -2.9161925e-14,2.46666667 -2.66453526e-14,-0.2 C0.666666667,1.13333333 2,1.8 4,1.8 Z" transform="matrix(1 0 0 -1 0 2.8)"/>
|
||||
<path fill="#393939" d="M4,59 C2,59 -7.10542736e-15,58.8521303 0,54.8596491 L0,35.1929825 L29.9218256,0 L35,0 L35,11.3859649 L49,13.4561404 L54,35.1929825 L37.7206839,59 L4,59 Z" opacity=".324" transform="translate(0 11)"/>
|
||||
<path fill="#000" fill-opacity=".383" d="M4,4 L65,4 C67.6666667,4 69.3333333,3 70,1 C70,3.66666667 70,5 70,5 L1.77635684e-15,5 C1.77635684e-15,5 1.77635684e-15,3.66666667 1.77635684e-15,1 C0.666666667,3 2,4 4,4 Z" transform="translate(0 65)"/>
|
||||
<path fill="#000" d="M37.9166667,30.1041667 L37.9166667,42.0625 C37.9166667,42.3116313 37.8365885,42.516276 37.6764323,42.6764323 C37.516276,42.8365885 37.3116313,42.9166667 37.0625,42.9166667 L28.5208333,42.9166667 C28.2717019,42.9166667 28.0670573,42.8365885 27.906901,42.6764323 C27.7467448,42.516276 27.6666667,42.3116313 27.6666667,42.0625 L27.6666667,40.3541667 C27.6666667,40.1050353 27.7467448,39.9003906 27.906901,39.7402344 C28.0670573,39.5800781 28.2717019,39.5 28.5208333,39.5 L34.5,39.5 L34.5,30.1041667 C34.5,29.8550353 34.5800781,29.6503906 34.7402344,29.4902344 C34.9003906,29.3300781 35.1050353,29.25 35.3541667,29.25 L37.0625,29.25 C37.3116313,29.25 37.516276,29.3300781 37.6764323,29.4902344 C37.8365885,29.6503906 37.9166667,29.8550353 37.9166667,30.1041667 Z M49.0208333,39.5 C49.0208333,36.8663189 48.3713095,34.4372824 47.0722656,32.2128906 C45.7732215,29.9884988 44.0115027,28.2267792 41.7871094,26.9277344 C39.5627158,25.6286895 37.1336811,24.9791667 34.5,24.9791667 C31.8663189,24.9791667 29.4372824,25.6286895 27.2128906,26.9277344 C24.9884988,28.2267792 23.2267792,29.9884988 21.9277344,32.2128906 C20.6286895,34.4372824 19.9791667,36.8663189 19.9791667,39.5 C19.9791667,42.1336811 20.6286895,44.5627158 21.9277344,46.7871094 C23.2267792,49.0115027 24.9884988,50.7732215 27.2128906,52.0722656 C29.4372824,53.3713095 31.8663189,54.0208333 34.5,54.0208333 C37.1336811,54.0208333 39.5627158,53.3713095 41.7871094,52.0722656 C44.0115027,50.7732215 45.7732215,49.0115027 47.0722656,46.7871094 C48.3713095,44.5627158 49.0208333,42.1336811 49.0208333,39.5 Z M50.0369141,26.0953981 L52.6900045,24.0961511 C53.1310787,23.7637779 53.7580818,23.8518974 54.090455,24.2929716 L55.8959001,26.6888781 C56.2282733,27.1299524 56.1401538,27.7569554 55.6990796,28.0893287 L52.8223311,30.2571141 C54.2741095,33.1090334 55,36.1899942 55,39.5 C55,43.219185 54.0835491,46.649198 52.250651,49.7900391 C50.4177527,52.9308798 47.9308798,55.4177527 44.7900391,57.250651 C41.649198,59.0835491 38.219185,60 34.5,60 C30.7808149,60 27.3508035,59.0835491 24.2099609,57.250651 C21.0691184,55.4177527 18.5822488,52.9308798 16.749349,49.7900391 C14.9164495,46.649198 14,43.219185 14,39.5 C14,35.7808149 14.9164495,32.3508035 16.749349,29.2099609 C18.5822488,26.0691184 21.0691184,23.5822488 24.2099609,21.749349 C26.6343486,20.3345506 29.2310275,19.4657874 32,19.143059 L32,17 L30,17 C29.4477153,17 29,16.5522847 29,16 L29,14 C29,13.4477153 29.4477153,13 30,13 L40,13 C40.5522847,13 41,13.4477153 41,14 L41,16 C41,16.5522847 40.5522847,17 40,17 L38,17 L38,19.2845984 C40.3963771,19.684578 42.6597235,20.5061615 44.7900391,21.749349 C46.8093002,22.9277288 48.5582591,24.3764115 50.0369141,26.0953981 Z" opacity=".3"/>
|
||||
<path fill="#FFF" d="M37.9166667,28.1041667 L37.9166667,40.0625 C37.9166667,40.3116313 37.8365885,40.516276 37.6764323,40.6764323 C37.516276,40.8365885 37.3116313,40.9166667 37.0625,40.9166667 L28.5208333,40.9166667 C28.2717019,40.9166667 28.0670573,40.8365885 27.906901,40.6764323 C27.7467448,40.516276 27.6666667,40.3116313 27.6666667,40.0625 L27.6666667,38.3541667 C27.6666667,38.1050353 27.7467448,37.9003906 27.906901,37.7402344 C28.0670573,37.5800781 28.2717019,37.5 28.5208333,37.5 L34.5,37.5 L34.5,28.1041667 C34.5,27.8550353 34.5800781,27.6503906 34.7402344,27.4902344 C34.9003906,27.3300781 35.1050353,27.25 35.3541667,27.25 L37.0625,27.25 C37.3116313,27.25 37.516276,27.3300781 37.6764323,27.4902344 C37.8365885,27.6503906 37.9166667,27.8550353 37.9166667,28.1041667 Z M49.0208333,37.5 C49.0208333,34.8663189 48.3713095,32.4372824 47.0722656,30.2128906 C45.7732215,27.9884988 44.0115027,26.2267792 41.7871094,24.9277344 C39.5627158,23.6286895 37.1336811,22.9791667 34.5,22.9791667 C31.8663189,22.9791667 29.4372824,23.6286895 27.2128906,24.9277344 C24.9884988,26.2267792 23.2267792,27.9884988 21.9277344,30.2128906 C20.6286895,32.4372824 19.9791667,34.8663189 19.9791667,37.5 C19.9791667,40.1336811 20.6286895,42.5627158 21.9277344,44.7871094 C23.2267792,47.0115027 24.9884988,48.7732215 27.2128906,50.0722656 C29.4372824,51.3713095 31.8663189,52.0208333 34.5,52.0208333 C37.1336811,52.0208333 39.5627158,51.3713095 41.7871094,50.0722656 C44.0115027,48.7732215 45.7732215,47.0115027 47.0722656,44.7871094 C48.3713095,42.5627158 49.0208333,40.1336811 49.0208333,37.5 Z M50.0369141,24.0953981 L52.6900045,22.0961511 C53.1310787,21.7637779 53.7580818,21.8518974 54.090455,22.2929716 L55.8959001,24.6888781 C56.2282733,25.1299524 56.1401538,25.7569554 55.6990796,26.0893287 L52.8223311,28.2571141 C54.2741095,31.1090334 55,34.1899942 55,37.5 C55,41.219185 54.0835491,44.649198 52.250651,47.7900391 C50.4177527,50.9308798 47.9308798,53.4177527 44.7900391,55.250651 C41.649198,57.0835491 38.219185,58 34.5,58 C30.7808149,58 27.3508035,57.0835491 24.2099609,55.250651 C21.0691184,53.4177527 18.5822488,50.9308798 16.749349,47.7900391 C14.9164495,44.649198 14,41.219185 14,37.5 C14,33.7808149 14.9164495,30.3508035 16.749349,27.2099609 C18.5822488,24.0691184 21.0691184,21.5822488 24.2099609,19.749349 C26.6343486,18.3345506 29.2310275,17.4657874 32,17.143059 L32,15 L30,15 C29.4477153,15 29,14.5522847 29,14 L29,12 C29,11.4477153 29.4477153,11 30,11 L40,11 C40.5522847,11 41,11.4477153 41,12 L41,14 C41,14.5522847 40.5522847,15 40,15 L38,15 L38,17.2845984 C40.3963771,17.684578 42.6597235,18.5061615 44.7900391,19.749349 C46.8093002,20.9277288 48.5582591,22.3764115 50.0369141,24.0953981 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 9 KiB |
|
|
@ -0,0 +1,51 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { Many2OneField } from "@web/views/fields/many2one/many2one_field";
|
||||
|
||||
|
||||
class TaskWithHours extends Many2OneField {
|
||||
|
||||
get canCreate() {
|
||||
return Boolean(this.context.default_project_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
get displayName() {
|
||||
const displayName = super.displayName;
|
||||
return displayName ? displayName.split('\u00A0')[0] : displayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
get context() {
|
||||
return {...super.context, hr_timesheet_display_remaining_hours: true};
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
get Many2XAutocompleteProps() {
|
||||
const props = super.Many2XAutocompleteProps;
|
||||
if (!this.canCreate) {
|
||||
props.quickCreate = null;
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
computeActiveActions(props) {
|
||||
super.computeActiveActions(props);
|
||||
const activeActions = this.state.activeActions;
|
||||
activeActions.create = activeActions.create && this.canCreate;
|
||||
activeActions.createEdit = activeActions.create;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
registry.category("fields").add("task_with_hours", TaskWithHours);
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { session } from "@web/session";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { FloatFactorField } from "@web/views/fields/float_factor/float_factor_field";
|
||||
import { FloatToggleField } from "@web/views/fields/float_toggle/float_toggle_field";
|
||||
import { FloatTimeField } from "@web/views/fields/float_time/float_time_field";
|
||||
import { standardFieldProps } from "@web/views/fields/standard_field_props";
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
|
||||
export class TimesheetUOM extends Component {
|
||||
|
||||
setup() {
|
||||
this.companyService = useService("company");
|
||||
}
|
||||
|
||||
get timesheetUOMId() {
|
||||
return this.companyService.currentCompany.timesheet_uom_id;
|
||||
}
|
||||
|
||||
get timesheetWidget() {
|
||||
let timesheet_widget = "float_factor";
|
||||
if (this.timesheetUOMId in session.uom_ids) {
|
||||
timesheet_widget = session.uom_ids[this.timesheetUOMId].timesheet_widget;
|
||||
}
|
||||
return timesheet_widget;
|
||||
}
|
||||
|
||||
get timesheetComponent() {
|
||||
return registry.category("fields").get(this.timesheetWidget, FloatFactorField);
|
||||
}
|
||||
|
||||
get timesheetComponentProps() {
|
||||
const factorDependantComponents = ["float_toggle", "float_factor"];
|
||||
return factorDependantComponents.includes(this.timesheetWidget) ? this.FactorCompanyDependentProps : this.props;
|
||||
}
|
||||
|
||||
get FactorCompanyDependentProps() {
|
||||
const factor = this.companyService.currentCompany.timesheet_uom_factor || this.props.factor;
|
||||
return { ...this.props, factor };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TimesheetUOM.props = {
|
||||
...standardFieldProps,
|
||||
};
|
||||
|
||||
TimesheetUOM.template = "hr_timesheet.TimesheetUOM";
|
||||
|
||||
TimesheetUOM.components = { FloatFactorField, FloatToggleField, FloatTimeField };
|
||||
|
||||
registry.category("fields").add("timesheet_uom", TimesheetUOM);
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<templates>
|
||||
|
||||
<t t-name="hr_timesheet.TimesheetUOM" owl="1">
|
||||
<t t-component="timesheetComponent" t-props="timesheetComponentProps"/>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
import { TimesheetUOM } from "../timesheet_uom/timesheet_uom";
|
||||
|
||||
|
||||
export class TimesheetUOMNoToggle extends TimesheetUOM {
|
||||
|
||||
get timesheetWidget() {
|
||||
const timesheetWidget = super.timesheetWidget;
|
||||
return timesheetWidget !== "float_toggle" ? timesheetWidget : "float_factor";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// As FloatToggleField won't be used by TimesheetUOMNoToggle, we remove it from the components that we get from TimesheetUOM.
|
||||
delete TimesheetUOMNoToggle.components.FloatToggleField;
|
||||
|
||||
registry.category("fields").add("timesheet_uom_no_toggle", TimesheetUOMNoToggle);
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
/** @odoo-module alias=hr_timesheet.task_with_hours **/
|
||||
|
||||
import field_registry from 'web.field_registry';
|
||||
import TimesheetFieldMany2One from 'hr_timesheet.TimesheetFieldMany2one';
|
||||
|
||||
const TaskWithHours = TimesheetFieldMany2One.extend({
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
this.additionalContext.hr_timesheet_display_remaining_hours = true;
|
||||
// By default, we keep the no_quick_create value or we set to false.
|
||||
this.nodeOptions.no_quick_create = this.nodeOptions.no_quick_create || false;
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_getDisplayNameWithoutHours: function (value) {
|
||||
return value && value.split('\u00A0')[0];
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_onInputClick: function () {
|
||||
const context = Object.assign(
|
||||
this.record.getContext(this.recordParams),
|
||||
this.additionalContext
|
||||
);
|
||||
// We don't want to quick create if no project is set in the timesheet
|
||||
const canCreate = 'default_project_id' in context && context.default_project_id;
|
||||
this.nodeOptions.no_quick_create =
|
||||
this.nodeOptions.no_quick_create || !canCreate;
|
||||
this.can_create = this.can_create && canCreate;
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_renderEdit: function (){
|
||||
this.m2o_value = this._getDisplayNameWithoutHours(this.m2o_value);
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
||||
field_registry.add('task_with_hours', TaskWithHours);
|
||||
|
||||
export default TaskWithHours;
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
/** @odoo-module alias=hr_timesheet.TimesheetFieldMany2one **/
|
||||
|
||||
import FieldRegistry from 'web.field_registry';
|
||||
import { FieldMany2One } from 'web.relational_fields';
|
||||
|
||||
import { SelectCreateDialog } from "@web/views/view_dialogs/select_create_dialog";
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
const TimesheetFieldMany2one = FieldMany2One.extend({
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_searchCreatePopup(view, ids, context, dynamicFilters) {
|
||||
const options = this._getSearchCreatePopupOptions(view, ids, context, dynamicFilters);
|
||||
Component.env.services.dialog.add(SelectCreateDialog, {
|
||||
title: options.title,
|
||||
resModel: options.res_model,
|
||||
multiSelect: false,
|
||||
domain: options.domain,
|
||||
context: options.context,
|
||||
noCreate: options.no_create,
|
||||
onSelected: (resId) => {
|
||||
return this.reinitialize(resId);
|
||||
},
|
||||
onClose: () => {
|
||||
this.activate();
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
FieldRegistry.add('timesheet_field_many2one', TimesheetFieldMany2one);
|
||||
|
||||
export default TimesheetFieldMany2one;
|
||||
|
|
@ -0,0 +1,196 @@
|
|||
odoo.define('hr_timesheet.timesheet_uom', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { registry } = require("@web/core/registry");
|
||||
const basicFields = require('web.basic_fields');
|
||||
const fieldUtils = require('web.field_utils');
|
||||
|
||||
const fieldRegistry = require('web.field_registry');
|
||||
|
||||
// We need the field registry to be populated, as we bind the
|
||||
// timesheet_uom widget on existing field widgets.
|
||||
require('web._field_registry');
|
||||
|
||||
const session = require('web.session');
|
||||
|
||||
const TimesheetUOMMultiCompanyMixin = {
|
||||
init: function(parent, name, record, options) {
|
||||
this._super(parent, name, record, options);
|
||||
const currentCompanyId = session.user_context.allowed_company_ids[0];
|
||||
const currentCompany = session.user_companies.allowed_companies[currentCompanyId];
|
||||
this.currentCompanyTimesheetUOMFactor = currentCompany.timesheet_uom_factor || 1;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Extend the float factor widget to set default value for timesheet
|
||||
* use case. The 'factor' is forced to be the UoM timesheet
|
||||
* conversion from the session info.
|
||||
**/
|
||||
const FieldTimesheetFactor = basicFields.FieldFloatFactor.extend(TimesheetUOMMultiCompanyMixin).extend({
|
||||
formatType: 'float_factor',
|
||||
/**
|
||||
* Override init to tweak options depending on the session info
|
||||
*
|
||||
* @constructor
|
||||
* @override
|
||||
*/
|
||||
init: function(parent, name, record, options) {
|
||||
this._super(parent, name, record, options);
|
||||
|
||||
// force factor in format and parse options
|
||||
this.nodeOptions.factor = this.currentCompanyTimesheetUOMFactor;
|
||||
this.parseOptions.factor = this.currentCompanyTimesheetUOMFactor;
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Extend the float toggle widget to set default value for timesheet
|
||||
* use case. The 'range' is different from the default one of the
|
||||
* native widget, and the 'factor' is forced to be the UoM timesheet
|
||||
* conversion.
|
||||
**/
|
||||
const FieldTimesheetToggle = basicFields.FieldFloatToggle.extend(TimesheetUOMMultiCompanyMixin).extend({
|
||||
formatType: 'float_factor',
|
||||
/**
|
||||
* Override init to tweak options depending on the session info
|
||||
*
|
||||
* @constructor
|
||||
* @override
|
||||
*/
|
||||
init: function(parent, name, record, options) {
|
||||
options = options || {};
|
||||
var fieldsInfo = record.fieldsInfo[options.viewType || 'default'];
|
||||
var attrs = options.attrs || (fieldsInfo && fieldsInfo[name]) || {};
|
||||
|
||||
var hasRange = _.contains(_.keys(attrs.options || {}), 'range');
|
||||
|
||||
this._super(parent, name, record, options);
|
||||
|
||||
// Set the timesheet widget options: the range can be customized
|
||||
// by setting the option on the field in the view. The factor
|
||||
// is forced to be the UoM conversion factor.
|
||||
if (!hasRange) {
|
||||
this.nodeOptions.range = [0.00, 1.00, 0.50];
|
||||
}
|
||||
this.nodeOptions.factor = this.currentCompanyTimesheetUOMFactor;
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Extend float time widget
|
||||
*/
|
||||
const FieldTimesheetTime = basicFields.FieldFloatTime.extend(TimesheetUOMMultiCompanyMixin).extend({
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
this.nodeOptions.factor = this.currentCompanyTimesheetUOMFactor;
|
||||
this.parseOptions.factor = this.currentCompanyTimesheetUOMFactor;
|
||||
}
|
||||
});
|
||||
|
||||
const timesheetUomService = {
|
||||
dependencies: ["legacy_session"],
|
||||
start() {
|
||||
const timesheetUomInfo = {
|
||||
widget: null,
|
||||
factor: 1,
|
||||
};
|
||||
if (session.user_context &&
|
||||
session.user_context.allowed_company_ids &&
|
||||
session.user_context.allowed_company_ids.length) {
|
||||
const currentCompanyId = session.user_context.allowed_company_ids[0];
|
||||
const currentCompany = session.user_companies.allowed_companies[currentCompanyId];
|
||||
const currentCompanyTimesheetUOMId = currentCompany.timesheet_uom_id || false;
|
||||
timesheetUomInfo.factor = currentCompany.timesheet_uom_factor || 1;
|
||||
if (currentCompanyTimesheetUOMId) {
|
||||
timesheetUomInfo.widget = session.uom_ids[currentCompanyTimesheetUOMId].timesheet_widget;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Binding depending on Company Preference
|
||||
*
|
||||
* determine wich widget will be the timesheet one.
|
||||
* Simply match the 'timesheet_uom' widget key with the correct
|
||||
* implementation (float_time, float_toggle, ...). The default
|
||||
* value will be 'float_factor'.
|
||||
**/
|
||||
const widgetName = timesheetUomInfo.widget || 'float_factor';
|
||||
|
||||
let FieldTimesheetUom = null;
|
||||
|
||||
if (widgetName === 'float_toggle') {
|
||||
FieldTimesheetUom = FieldTimesheetToggle;
|
||||
} else if (widgetName === 'float_time') {
|
||||
FieldTimesheetUom = FieldTimesheetTime;
|
||||
} else {
|
||||
FieldTimesheetUom = (
|
||||
fieldRegistry.get(widgetName) &&
|
||||
fieldRegistry.get(widgetName).extend({ })
|
||||
) || FieldTimesheetFactor;
|
||||
}
|
||||
fieldRegistry.add('timesheet_uom', FieldTimesheetUom);
|
||||
|
||||
// widget timesheet_uom_no_toggle is the same as timesheet_uom but without toggle.
|
||||
// We can modify easly huge amount of days.
|
||||
let FieldTimesheetUomWithoutToggle = null;
|
||||
if (widgetName === 'float_toggle') {
|
||||
FieldTimesheetUomWithoutToggle = FieldTimesheetFactor;
|
||||
} else {
|
||||
FieldTimesheetUomWithoutToggle = FieldTimesheetTime;
|
||||
}
|
||||
fieldRegistry.add('timesheet_uom_no_toggle', FieldTimesheetUomWithoutToggle);
|
||||
|
||||
|
||||
// bind the formatter and parser method, and tweak the options
|
||||
const _tweak_options = (options) => {
|
||||
if (!_.contains(options, 'factor')) {
|
||||
options.factor = timesheetUomInfo.factor;
|
||||
}
|
||||
return options;
|
||||
};
|
||||
|
||||
fieldUtils.format.timesheet_uom = function(value, field, options) {
|
||||
options = _tweak_options(options || { });
|
||||
const formatter = fieldUtils.format[FieldTimesheetUom.prototype.formatType];
|
||||
return formatter(value, field, options);
|
||||
};
|
||||
|
||||
fieldUtils.parse.timesheet_uom = function(value, field, options) {
|
||||
options = _tweak_options(options || { });
|
||||
const parser = fieldUtils.parse[FieldTimesheetUom.prototype.formatType];
|
||||
return parser(value, field, options);
|
||||
};
|
||||
|
||||
fieldUtils.format.timesheet_uom_no_toggle = function(value, field, options) {
|
||||
options = _tweak_options(options || { });
|
||||
const formatter = fieldUtils.format[FieldTimesheetUom.prototype.formatType];
|
||||
return formatter(value, field, options);
|
||||
};
|
||||
|
||||
fieldUtils.parse.timesheet_uom_no_toggle = function(value, field, options) {
|
||||
options = _tweak_options(options || { });
|
||||
const parser = fieldUtils.parse[FieldTimesheetUom.prototype.formatType];
|
||||
return parser(value, field, options);
|
||||
};
|
||||
if (!registry.category("formatters").contains("timesheet_uom")) {
|
||||
registry.category("formatters").add("timesheet_uom", fieldUtils.format.timesheet_uom);
|
||||
}
|
||||
if (!registry.category("formatters").contains("timesheet_uom_no_toggle")) {
|
||||
registry.category("formatters").add("timesheet_uom_no_toggle", fieldUtils.format.timesheet_uom_no_toggle);
|
||||
}
|
||||
return timesheetUomInfo;
|
||||
},
|
||||
};
|
||||
registry.category("services").add("timesheet_uom", timesheetUomService);
|
||||
|
||||
return {
|
||||
FieldTimesheetFactor,
|
||||
FieldTimesheetTime,
|
||||
FieldTimesheetToggle,
|
||||
timesheetUomService,
|
||||
};
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
.o_project_kanban .oe_kanban_align.badge {
|
||||
color: inherit;
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { GraphModel } from "@web/views/graph/graph_model";
|
||||
|
||||
const FIELDS = [
|
||||
'unit_amount', 'effective_hours', 'planned_hours', 'remaining_hours', 'total_hours_spent', 'subtask_effective_hours',
|
||||
'overtime', 'number_hours', 'difference', 'hours_effective', 'hours_planned', 'timesheet_unit_amount'
|
||||
];
|
||||
|
||||
export class hrTimesheetGraphModel extends GraphModel {
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
setup(params, services) {
|
||||
super.setup(...arguments);
|
||||
this.companyService = services.company;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Override processDataPoints to take into account the analytic line uom.
|
||||
* @override
|
||||
*/
|
||||
_getProcessedDataPoints() {
|
||||
const currentCompany = this.companyService.currentCompany;
|
||||
const factor = currentCompany.timesheet_uom_factor || 1;
|
||||
if (factor !== 1 && FIELDS.includes(this.metaData.measure)) {
|
||||
// recalculate the Duration values according to the timesheet_uom_factor
|
||||
for (const dataPt of this.dataPoints) {
|
||||
dataPt.value *= factor;
|
||||
}
|
||||
}
|
||||
return super._getProcessedDataPoints(...arguments);
|
||||
}
|
||||
}
|
||||
hrTimesheetGraphModel.services = [...GraphModel.services, "company"];
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { projectGraphView } from "@project/js/project_graph_view";
|
||||
import { hrTimesheetGraphModel } from "./timesheet_graph_model";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
const viewRegistry = registry.category("views");
|
||||
|
||||
export const hrTimesheetGraphView = {
|
||||
...projectGraphView,
|
||||
Model: hrTimesheetGraphModel,
|
||||
};
|
||||
|
||||
viewRegistry.add("hr_timesheet_graphview", hrTimesheetGraphView);
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { session } from "@web/session";
|
||||
import { companyService } from "@web/webclient/company_service";
|
||||
|
||||
import { patchWithCleanup } from "@web/../tests/helpers/utils";
|
||||
import { setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
|
||||
|
||||
export const getServerData = () => JSON.parse(JSON.stringify({
|
||||
models: {
|
||||
'account.analytic.line': {
|
||||
fields: {
|
||||
project_id: { string: "Project", type: "many2one", relation: "project.project" },
|
||||
task_id: { string: "Task", type: "many2one", relation: "project.task" },
|
||||
unit_amount: { string: "Unit Amount", type: "integer" },
|
||||
},
|
||||
records: [
|
||||
{ id: 1, project_id: 1, task_id: 3, unit_amount: 1 },
|
||||
{ id: 2, project_id: 1, task_id: false, unit_amount: 1 },
|
||||
{ id: 3, project_id: false, task_id: false, unit_amount: 1 },
|
||||
],
|
||||
},
|
||||
'project.project': {
|
||||
fields: {
|
||||
name: { string: "Name", type: "string" },
|
||||
},
|
||||
records: [
|
||||
{ id: 1, name: "Project 1" },
|
||||
],
|
||||
},
|
||||
'project.task': {
|
||||
fields: {
|
||||
name: { string: "Name", type: "string" },
|
||||
project_id: { string: "Project", type: "many2one", relation: "project.project" },
|
||||
},
|
||||
records: [
|
||||
{ id: 1, name: "Task 1\u00A0AdditionalInfo", project_id: 1 },
|
||||
{ id: 2, name: "Task 2\u00A0AdditionalInfo", project_id: 1 },
|
||||
{ id: 3, name: "Task 3\u00A0AdditionalInfo", project_id: 1 },
|
||||
],
|
||||
},
|
||||
},
|
||||
views: {
|
||||
"account.analytic.line,false,form": `
|
||||
<form>
|
||||
<field name="project_id"/>
|
||||
<field name="task_id"/>
|
||||
<field name="unit_amount"/>
|
||||
</form>
|
||||
`,
|
||||
"account.analytic.line,false,list": `
|
||||
<tree editable="bottom">
|
||||
<field name="project_id"/>
|
||||
<field name="task_id"/>
|
||||
<field name="unit_amount"/>
|
||||
</tree>
|
||||
`,
|
||||
},
|
||||
}));
|
||||
|
||||
export function updateArch(serverData, fieldNameWidgetNameMapping = {}, fieldNameContextMapping = {}) {
|
||||
for (const viewKey in serverData.views) {
|
||||
for (const [fieldName, widgetName] of Object.entries(fieldNameWidgetNameMapping)) {
|
||||
serverData.views[viewKey] = serverData.views[viewKey].replace(
|
||||
`name="${fieldName}"`,
|
||||
`name="${fieldName}" widget="${widgetName}"`
|
||||
);
|
||||
}
|
||||
for (const [fieldName, context] of Object.entries(fieldNameContextMapping)) {
|
||||
serverData.views[viewKey] = serverData.views[viewKey].replace(
|
||||
`name="${fieldName}"`,
|
||||
`name="${fieldName}" context="${context}"`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function addFieldsInArch(serverData, fields, beforeField) {
|
||||
let fieldsArch = "";
|
||||
for (const field of fields) {
|
||||
fieldsArch += `<field name="${field}"/>
|
||||
`;
|
||||
}
|
||||
for (const viewKey in serverData.views) {
|
||||
serverData.views[viewKey] = serverData.views[viewKey].replace(
|
||||
`<field name="${beforeField}"`,
|
||||
`${fieldsArch}<field name="${beforeField}"`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function setupTestEnv() {
|
||||
setupViewRegistries();
|
||||
|
||||
patchWithCleanup(session, {
|
||||
user_companies: {
|
||||
current_company: 1,
|
||||
allowed_companies: {
|
||||
1: {
|
||||
id: 1,
|
||||
name: 'Company',
|
||||
timesheet_uom_id: 1,
|
||||
timesheet_uom_factor: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
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',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const serviceRegistry = registry.category("services");
|
||||
serviceRegistry.add("company", companyService, { force: true });
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
});
|
||||
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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");
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import { makeView } from "@web/../tests/views/helpers";
|
||||
import { click, clickDropdown, editInput, getFixture } from "@web/../tests/helpers/utils";
|
||||
|
||||
import { getServerData, updateArch, setupTestEnv } from "./hr_timesheet_common_tests";
|
||||
|
||||
|
||||
QUnit.module("hr_timesheet", (hooks) => {
|
||||
let target;
|
||||
let serverData;
|
||||
hooks.beforeEach(async function (assert) {
|
||||
setupTestEnv();
|
||||
serverData = getServerData();
|
||||
updateArch(
|
||||
serverData,
|
||||
{ task_id: "task_with_hours" },
|
||||
{ task_id: "{ 'default_project_id': project_id }" });
|
||||
target = getFixture();
|
||||
});
|
||||
|
||||
QUnit.module("task_with_hours");
|
||||
|
||||
async function _testCreateAndEdit(target, visible, assert) {
|
||||
await click(target, ".o_list_many2one[name=task_id]");
|
||||
await click(target, ".o_list_many2one[name=task_id] input");
|
||||
await editInput(target, ".o_list_many2one[name=task_id] input", "NonExistingTask");
|
||||
await click(target, ".o_list_many2one[name=task_id] input");
|
||||
await clickDropdown(target, "task_id");
|
||||
const testFunction = visible ? assert.containsOnce : assert.containsNone;
|
||||
testFunction(target, '.o_list_many2one[name=task_id] .dropdown ul li:contains("Create and edit...")');
|
||||
}
|
||||
|
||||
QUnit.test("quick create is enabled when project_id is set", async function (assert) {
|
||||
await makeView({
|
||||
serverData,
|
||||
type: "list",
|
||||
resModel: "account.analytic.line",
|
||||
});
|
||||
const secondRow = target.querySelector(".o_list_table .o_data_row:nth-of-type(2)");
|
||||
await _testCreateAndEdit(secondRow, true, assert);
|
||||
});
|
||||
|
||||
QUnit.test("quick create is no enabled when project_id is not set", async function (assert) {
|
||||
await makeView({
|
||||
serverData,
|
||||
type: "list",
|
||||
resModel: "account.analytic.line",
|
||||
});
|
||||
const thirdRow = target.querySelector(".o_list_table .o_data_row:nth-of-type(3)");
|
||||
await _testCreateAndEdit(thirdRow, false, assert);
|
||||
});
|
||||
|
||||
QUnit.test("the text of the task includes hours in the drop down but not in the line", async function (assert) {
|
||||
await makeView({
|
||||
serverData,
|
||||
type: "list",
|
||||
resModel: "account.analytic.line",
|
||||
});
|
||||
const firstRow = target.querySelector(".o_list_table .o_data_row:first-of-type");
|
||||
assert.containsNone(firstRow, '.o_list_many2one[name=task_id]:contains("AdditionalInfo")');
|
||||
await click(firstRow, ".o_list_many2one[name=task_id]");
|
||||
await clickDropdown(firstRow, "task_id");
|
||||
assert.containsN(firstRow, '.o_list_many2one[name=task_id] .dropdown ul li:contains("AdditionalInfo")', 3);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { companyService } from "@web/webclient/company_service";
|
||||
import { getGraphRenderer } from "@web/../tests/views/graph_view_tests";
|
||||
import { makeView } from "@web/../tests/views/helpers";
|
||||
import { patchWithCleanup } from "@web/../tests/helpers/utils";
|
||||
import { session } from "@web/session";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { setupControlPanelServiceRegistry } from "@web/../tests/search/helpers";
|
||||
|
||||
const serviceRegistry = registry.category("services");
|
||||
|
||||
QUnit.module('hr_timesheet', function (hooks) {
|
||||
let serverData;
|
||||
hooks.beforeEach(() => {
|
||||
serverData = {
|
||||
models: {
|
||||
'account.analytic.line': {
|
||||
fields: {
|
||||
unit_amount: { string: "Unit Amount", type: "float", group_operator: "sum", store: true },
|
||||
},
|
||||
records: [
|
||||
{ id: 1, unit_amount: 8 }
|
||||
],
|
||||
},
|
||||
},
|
||||
views: {
|
||||
// unit_amount is used as group_by and measure
|
||||
"account.analytic.line,false,graph": `
|
||||
<graph>
|
||||
<field name="unit_amount"/>
|
||||
<field name="unit_amount" type="measure"/>
|
||||
</graph>
|
||||
`,
|
||||
}
|
||||
}
|
||||
setupControlPanelServiceRegistry();
|
||||
serviceRegistry.add("company", companyService, { force: true });
|
||||
});
|
||||
|
||||
QUnit.module("hr_timesheet_graphview");
|
||||
|
||||
QUnit.test('the timesheet graph view data are not multiplied by a factor that is company related (factor = 1)', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
patchWithCleanup(session.user_companies.allowed_companies[1], {
|
||||
timesheet_uom_factor: 1,
|
||||
});
|
||||
|
||||
const graph = await makeView({
|
||||
serverData,
|
||||
resModel: "account.analytic.line",
|
||||
type: "hr_timesheet_graphview",
|
||||
});
|
||||
|
||||
const renderedData = getGraphRenderer(graph).chart.data.datasets[0].data;
|
||||
assert.deepEqual(renderedData, [8], 'The timesheet graph view is taking the timesheet_uom_factor into account (factor === 1)');
|
||||
});
|
||||
|
||||
QUnit.test('the timesheet graph view data are multiplied by a factor that is company related (factor !== 1)', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
patchWithCleanup(session.user_companies.allowed_companies[1], {
|
||||
timesheet_uom_factor: 0.125,
|
||||
});
|
||||
|
||||
const graph = await makeView({
|
||||
serverData,
|
||||
resModel: "account.analytic.line",
|
||||
type: "hr_timesheet_graphview",
|
||||
});
|
||||
|
||||
const renderedData = getGraphRenderer(graph).chart.data.datasets[0].data;
|
||||
assert.deepEqual(renderedData, [1], 'The timesheet graph view is taking the timesheet_uom_factor into account (factor !== 1)');
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import { session } from "@web/session";
|
||||
|
||||
import { makeView } from "@web/../tests/views/helpers";
|
||||
import { getFixture, patchWithCleanup } from "@web/../tests/helpers/utils";
|
||||
|
||||
import { getServerData, updateArch, setupTestEnv } from "./hr_timesheet_common_tests";
|
||||
|
||||
|
||||
QUnit.module("hr_timesheet", (hooks) => {
|
||||
let target;
|
||||
let serverData;
|
||||
hooks.beforeEach(async function (assert) {
|
||||
setupTestEnv();
|
||||
serverData = getServerData();
|
||||
updateArch(serverData, { unit_amount: "timesheet_uom_no_toggle" });
|
||||
target = getFixture();
|
||||
});
|
||||
|
||||
QUnit.module("timesheet_uom_no_toggle");
|
||||
|
||||
QUnit.test("FloatTimeField is used when current company uom uses float_time widget", async function (assert) {
|
||||
await makeView({
|
||||
serverData,
|
||||
type: "form",
|
||||
resModel: "account.analytic.line",
|
||||
resId: 1,
|
||||
});
|
||||
const unitAmountInput = target.querySelector('div[name="unit_amount"] input');
|
||||
assert.equal(unitAmountInput.value, "01:00", "unit_amount is displayed as time");
|
||||
});
|
||||
|
||||
QUnit.test("FloatTimeField is not dependent of timesheet_uom_factor of the current company when current company uom uses float_time widget", async function (assert) {
|
||||
patchWithCleanup(session.user_companies.allowed_companies[1], { timesheet_uom_factor: 2 });
|
||||
await makeView({
|
||||
serverData,
|
||||
type: "form",
|
||||
resModel: "account.analytic.line",
|
||||
resId: 1,
|
||||
});
|
||||
const unitAmountInput = target.querySelector('div[name="unit_amount"] input');
|
||||
assert.equal(unitAmountInput.value, "01:00", "timesheet_uom_factor is not taken into account");
|
||||
});
|
||||
|
||||
QUnit.test("FloatToggleField is not used when current company uom uses float_toggle widget, FloatFactorField is used instead", async function (assert) {
|
||||
patchWithCleanup(session.user_companies.allowed_companies[1], { timesheet_uom_id: 2 });
|
||||
await makeView({
|
||||
serverData,
|
||||
type: "form",
|
||||
resModel: "account.analytic.line",
|
||||
resId: 1,
|
||||
});
|
||||
assert.containsNone(target, 'div[name="unit_amount"] .o_field_float_toggle', "unit_amount is not displayed as float toggle");
|
||||
const unitAmountInput = target.querySelector('div[name="unit_amount"] input');
|
||||
assert.containsOnce(target, 'div[name="unit_amount"] input[inputmode="decimal"]', "unit_amount is displayed as float");
|
||||
assert.equal(unitAmountInput.value, "1.00", "unit_amount is not displayed as float and not as time");
|
||||
});
|
||||
|
||||
QUnit.test("FloatFactorField is used when the current_company uom is not part of the session uom", async function (assert) {
|
||||
patchWithCleanup(session.user_companies.allowed_companies[1], { timesheet_uom_id: 'dummy' });
|
||||
await makeView({
|
||||
serverData,
|
||||
type: "form",
|
||||
resModel: "account.analytic.line",
|
||||
resId: 1,
|
||||
});
|
||||
const unitAmountInput = target.querySelector('div[name="unit_amount"] input');
|
||||
assert.containsOnce(target, 'div[name="unit_amount"] input[inputmode="decimal"]', "unit_amount is displayed as float");
|
||||
assert.equal(unitAmountInput.value, "1.00", "unit_amount is not displayed as float and not as time");
|
||||
assert.containsNone(target, 'div[name="unit_amount"].o_field_float_toggle', "unit_amount is not displayed as float toggle");
|
||||
});
|
||||
|
||||
QUnit.test("FloatFactorField is dependent of timesheet_uom_factor of the current company when current company uom uses float_toggle widget", async function (assert) {
|
||||
patchWithCleanup(session.user_companies.allowed_companies[1], { timesheet_uom_id: 'dummy', timesheet_uom_factor: 2 });
|
||||
await makeView({
|
||||
serverData,
|
||||
type: "form",
|
||||
resModel: "account.analytic.line",
|
||||
resId: 1,
|
||||
});
|
||||
const unitAmountInput = target.querySelector('div[name="unit_amount"] input');
|
||||
assert.equal(unitAmountInput.value, "2.00", "timesheet_uom_factor is taken into account");
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import { session } from "@web/session";
|
||||
|
||||
import { makeView } from "@web/../tests/views/helpers";
|
||||
import { getFixture, patchWithCleanup } from "@web/../tests/helpers/utils";
|
||||
|
||||
import { getServerData, updateArch, setupTestEnv } from "./hr_timesheet_common_tests";
|
||||
|
||||
|
||||
QUnit.module("hr_timesheet", (hooks) => {
|
||||
let target;
|
||||
let serverData;
|
||||
hooks.beforeEach(async function (assert) {
|
||||
setupTestEnv();
|
||||
serverData = getServerData();
|
||||
updateArch(serverData, { unit_amount: "timesheet_uom" });
|
||||
target = getFixture();
|
||||
});
|
||||
|
||||
QUnit.module("timesheet_uom");
|
||||
|
||||
QUnit.test("FloatTimeField is used when current company uom uses float_time widget", async function (assert) {
|
||||
await makeView({
|
||||
serverData,
|
||||
type: "form",
|
||||
resModel: "account.analytic.line",
|
||||
resId: 1,
|
||||
});
|
||||
const unitAmountInput = target.querySelector('div[name="unit_amount"] input');
|
||||
assert.equal(unitAmountInput.value, "01:00", "unit_amount is displayed as time");
|
||||
});
|
||||
|
||||
QUnit.test("FloatTimeField is not dependent of timesheet_uom_factor of the current company when current company uom uses float_time widget", async function (assert) {
|
||||
patchWithCleanup(session.user_companies.allowed_companies[1], { timesheet_uom_factor: 2 });
|
||||
await makeView({
|
||||
serverData,
|
||||
type: "form",
|
||||
resModel: "account.analytic.line",
|
||||
resId: 1,
|
||||
});
|
||||
const unitAmountInput = target.querySelector('div[name="unit_amount"] input');
|
||||
assert.equal(unitAmountInput.value, "01:00", "timesheet_uom_factor is not taken into account");
|
||||
});
|
||||
|
||||
QUnit.test("FloatToggleField is used when current company uom uses float_toggle widget", async function (assert) {
|
||||
patchWithCleanup(session.user_companies.allowed_companies[1], { timesheet_uom_id: 2 });
|
||||
await makeView({
|
||||
serverData,
|
||||
type: "form",
|
||||
resModel: "account.analytic.line",
|
||||
resId: 1,
|
||||
});
|
||||
assert.containsOnce(target, 'div[name="unit_amount"] .o_field_float_toggle', "unit_amount is displayed as float toggle");
|
||||
});
|
||||
|
||||
QUnit.test("FloatToggleField is dependent of timesheet_uom_factor of the current company when current company uom uses float_toggle widget", async function (assert) {
|
||||
patchWithCleanup(session.user_companies.allowed_companies[1], { timesheet_uom_id: 2, timesheet_uom_factor: 2 });
|
||||
await makeView({
|
||||
serverData,
|
||||
type: "form",
|
||||
resModel: "account.analytic.line",
|
||||
resId: 1,
|
||||
});
|
||||
assert.containsOnce(target, 'div[name="unit_amount"] .o_field_float_toggle:contains("2.00")', "timesheet_uom_factor is taken into account");
|
||||
});
|
||||
|
||||
QUnit.test("FloatFactorField is used when the current_company uom is not part of the session uom", async function (assert) {
|
||||
patchWithCleanup(session.user_companies.allowed_companies[1], { timesheet_uom_id: 'dummy' });
|
||||
await makeView({
|
||||
serverData,
|
||||
type: "form",
|
||||
resModel: "account.analytic.line",
|
||||
resId: 1,
|
||||
});
|
||||
const unitAmountInput = target.querySelector('div[name="unit_amount"] input');
|
||||
assert.containsOnce(target, 'div[name="unit_amount"] input[inputmode="decimal"]', "unit_amount is displayed as float");
|
||||
assert.equal(unitAmountInput.value, "1.00", "unit_amount is not displayed as float and not as time");
|
||||
assert.containsNone(target, 'div[name="unit_amount"].o_field_float_toggle', "unit_amount is not displayed as float toggle");
|
||||
});
|
||||
|
||||
QUnit.test("FloatFactorField is dependent of timesheet_uom_factor of the current company when current company uom uses float_toggle widget", async function (assert) {
|
||||
patchWithCleanup(session.user_companies.allowed_companies[1], { timesheet_uom_id: 'dummy', timesheet_uom_factor: 2 });
|
||||
await makeView({
|
||||
serverData,
|
||||
type: "form",
|
||||
resModel: "account.analytic.line",
|
||||
resId: 1,
|
||||
});
|
||||
const unitAmountInput = target.querySelector('div[name="unit_amount"] input');
|
||||
assert.equal(unitAmountInput.value, "2.00", "timesheet_uom_factor is taken into account");
|
||||
});
|
||||
|
||||
});
|
||||