From 3e847fc8f499c96b8f2d072ab19f35e105fd7749 Mon Sep 17 00:00:00 2001 From: "Laurent Stukkens (LTU)" Date: Fri, 19 Feb 2021 16:28:33 +0100 Subject: [PATCH] [FIX] web,hr_timesheet,partner_autocomplete: make multi company timesheet uom work This commit's primary objective is to ensure that the timesheet uom is working in accordance with the company settings in a multi company environment. Prior to this commit: - The timesheet preferences sent to the front end were always those of the default company of the user. - The timesheet related widgets initialisation process (adding the correct ones in the fieldRegistry) was performed before the front end treatment of cids and coockies which prevented applying the front end selected company settings. After this commit: - A dictionnary is used in the session in order to structure the companies info. - The company timesheet preferences are sent to the front end through the company dict. - The uom info is sent to the front end through the session. - The timesheet uom is now managed from the frontend and is now in sync with the settings. - Timesheet widgets are initialised during the AbstractWebClient init and are in sync with the multicompany front end settings task-2168337 Closes: #66551 --- addons/hr_timesheet/models/ir_http.py | 35 +++- .../static/src/js/timesheet_graph.js | 8 +- .../static/src/js/timesheet_uom.js | 161 +++++++++--------- .../static/tests/timesheet_uom_tests.js | 0 .../static/src/js/web_company_autocomplete.js | 2 +- addons/web/models/ir_http.py | 10 +- .../src/js/chrome/abstract_web_client.js | 57 ++++--- .../static/src/js/views/basic/basic_model.js | 4 +- .../src/js/widgets/switch_company_menu.js | 5 +- addons/web/static/src/xml/base.xml | 14 +- .../tests/widgets/company_switcher_tests.js | 42 ++++- 11 files changed, 212 insertions(+), 126 deletions(-) create mode 100644 addons/hr_timesheet/static/tests/timesheet_uom_tests.js diff --git a/addons/hr_timesheet/models/ir_http.py b/addons/hr_timesheet/models/ir_http.py index 51e929b62af..cc687a513c4 100644 --- a/addons/hr_timesheet/models/ir_http.py +++ b/addons/hr_timesheet/models/ir_http.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo import models +from odoo import api, models +from odoo.http import request class Http(models.AbstractModel): @@ -9,13 +10,35 @@ class Http(models.AbstractModel): def session_info(self): """ The widget 'timesheet_uom' needs to know which UoM conversion factor and which javascript - widget to apply, depending on th ecurrent company. + widget to apply, depending on the current company. """ result = super(Http, self).session_info() if self.env.user.has_group('base.group_user'): - company = self.env.company - encoding_uom = company.timesheet_encode_uom_id + company_ids = request.env.user.company_ids - result['timesheet_uom'] = encoding_uom.read(['name', 'rounding', 'timesheet_widget'])[0] - result['timesheet_uom_factor'] = company.project_time_mode_id._compute_quantity(1.0, encoding_uom, round=False) # convert encoding uom into stored uom to get conversion factor + for company in company_ids: + result["user_companies"]["allowed_companies"][company.id].update({ + "timesheet_uom_id": company.timesheet_encode_uom_id.id, + "timesheet_uom_factor": company.project_time_mode_id._compute_quantity( + 1.0, + company.timesheet_encode_uom_id, + round=False + ), + }) + result["uom_ids"] = self.get_timesheet_uoms() return result + + @api.model + def get_timesheet_uoms(self): + company_ids = request.env.user.company_ids + uom_ids = company_ids.mapped('timesheet_encode_uom_id') | \ + company_ids.mapped('project_time_mode_id') + return { + uom.id: + { + 'id': uom.id, + 'name': uom.name, + 'rounding': uom.rounding, + 'timesheet_widget': uom.timesheet_widget, + } for uom in uom_ids + } diff --git a/addons/hr_timesheet/static/src/js/timesheet_graph.js b/addons/hr_timesheet/static/src/js/timesheet_graph.js index 490812de103..ca09fefb9dc 100644 --- a/addons/hr_timesheet/static/src/js/timesheet_graph.js +++ b/addons/hr_timesheet/static/src/js/timesheet_graph.js @@ -12,10 +12,14 @@ odoo.define('hr_timesheet.GraphView', function (require) { _processData: function (originIndex, rawData) { this._super.apply(this, arguments); const session = this.getSession(); - if (this.chart.measure === 'unit_amount' && session.timesheet_uom_factor !== 1) { + const currentCompanyId = session.user_context.allowed_company_ids[0]; + const currentCompany = session.user_companies.allowed_companies[currentCompanyId]; + const currentCompanyTimesheetUOMFactor = currentCompany.timesheet_uom_factor || 1; + + if (this.chart.measure === 'unit_amount' && currentCompanyTimesheetUOMFactor !== 1) { // recalculate the Duration values according to the timesheet_uom_factor this.chart.dataPoints.forEach(function (dataPt) { - dataPt.value *= session.timesheet_uom_factor; + dataPt.value *= currentCompanyTimesheetUOMFactor; }); } }, diff --git a/addons/hr_timesheet/static/src/js/timesheet_uom.js b/addons/hr_timesheet/static/src/js/timesheet_uom.js index 982caa462ab..6b68e3741f7 100644 --- a/addons/hr_timesheet/static/src/js/timesheet_uom.js +++ b/addons/hr_timesheet/static/src/js/timesheet_uom.js @@ -11,16 +11,26 @@ const fieldRegistry = require('web.field_registry'); require('web._field_registry'); const session = require('web.session'); +const AbstractWebClient = require('web.AbstractWebClient'); + +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({ +const FieldTimesheetFactor = basicFields.FieldFloatFactor.extend(TimesheetUOMMultiCompanyMixin).extend({ formatType: 'float_factor', /** - * Override init to tweak options depending on the session_info + * Override init to tweak options depending on the session info * * @constructor * @override @@ -29,10 +39,8 @@ const FieldTimesheetFactor = basicFields.FieldFloatFactor.extend({ this._super(parent, name, record, options); // force factor in format and parse options - if (session.timesheet_uom_factor) { - this.nodeOptions.factor = session.timesheet_uom_factor; - this.parseOptions.factor = session.timesheet_uom_factor; - } + this.nodeOptions.factor = this.currentCompanyTimesheetUOMFactor; + this.parseOptions.factor = this.currentCompanyTimesheetUOMFactor; }, }); @@ -43,10 +51,10 @@ const FieldTimesheetFactor = basicFields.FieldFloatFactor.extend({ * native widget, and the 'factor' is forced to be the UoM timesheet * conversion. **/ -const FieldTimesheetToggle = basicFields.FieldFloatToggle.extend({ +const FieldTimesheetToggle = basicFields.FieldFloatToggle.extend(TimesheetUOMMultiCompanyMixin).extend({ formatType: 'float_factor', /** - * Override init to tweak options depending on the session_info + * Override init to tweak options depending on the session info * * @constructor * @override @@ -66,7 +74,7 @@ const FieldTimesheetToggle = basicFields.FieldFloatToggle.extend({ if (!hasRange) { this.nodeOptions.range = [0.00, 1.00, 0.50]; } - this.nodeOptions.factor = session.timesheet_uom_factor; + this.nodeOptions.factor = this.currentCompanyTimesheetUOMFactor; }, }); @@ -74,91 +82,92 @@ const FieldTimesheetToggle = basicFields.FieldFloatToggle.extend({ /** * Extend float time widget */ -const FieldTimesheetTime = basicFields.FieldFloatTime.extend({ +const FieldTimesheetTime = basicFields.FieldFloatTime.extend(TimesheetUOMMultiCompanyMixin).extend({ init: function () { this._super.apply(this, arguments); - - if (session.timesheet_uom_factor) { - this.nodeOptions.factor = session.timesheet_uom_factor; - this.parseOptions.factor = session.timesheet_uom_factor; - } + this.nodeOptions.factor = this.currentCompanyTimesheetUOMFactor; + this.parseOptions.factor = this.currentCompanyTimesheetUOMFactor; } }); +AbstractWebClient.include({ + init: function () { + this._super(...arguments); + /** + * 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 = this.currentCompanyTimesheetUOM && + this.currentCompanyTimesheetUOM.timesheet_widget || + 'float_factor'; -/** - * 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 = 'timesheet_uom' in session ? - session.timesheet_uom.timesheet_widget : 'float_factor'; + let FieldTimesheetUom = null; -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); -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); + // 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 = function(options) { - if (!_.contains(options, 'factor')) { - options.factor = session.timesheet_uom_factor; - } - return options; -}; + // bind the formatter and parser method, and tweak the options + const _tweak_options = (options) => { + if (!_.contains(options, 'factor')) { + options.factor = this.currentCompanyTimesheetUOMFactor; + } + 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.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.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.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); -}; + 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); + }; + }, +}); return { - FieldTimesheetUom, FieldTimesheetFactor, FieldTimesheetTime, - FieldTimesheetToggle + FieldTimesheetToggle, }; }); diff --git a/addons/hr_timesheet/static/tests/timesheet_uom_tests.js b/addons/hr_timesheet/static/tests/timesheet_uom_tests.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/addons/partner_autocomplete/static/src/js/web_company_autocomplete.js b/addons/partner_autocomplete/static/src/js/web_company_autocomplete.js index dbd413b1de9..53c5029adfe 100644 --- a/addons/partner_autocomplete/static/src/js/web_company_autocomplete.js +++ b/addons/partner_autocomplete/static/src/js/web_company_autocomplete.js @@ -8,7 +8,7 @@ return AbstractWebClient.include({ start: function () { if (session.iap_company_enrich) { - const current_company_id = session.user_companies.current_company[0]; + const current_company_id = session.user_companies.current_company; this._rpc({ model: 'res.company', method: 'iap_enrich_auto', diff --git a/addons/web/models/ir_http.py b/addons/web/models/ir_http.py index 748e469ff28..fd83e877969 100644 --- a/addons/web/models/ir_http.py +++ b/addons/web/models/ir_http.py @@ -60,7 +60,15 @@ class Http(models.AbstractModel): } session_info.update({ # current_company should be default_company - "user_companies": {'current_company': (user.company_id.id, user.company_id.name), 'allowed_companies': [(comp.id, comp.name) for comp in user.company_ids]}, + "user_companies": { + 'current_company': user.company_id.id, + 'allowed_companies': { + comp.id: { + 'id': comp.id, + 'name': comp.name, + } for comp in user.company_ids + }, + }, "currencies": self.get_currencies(), "show_effect": True, "display_switch_company_menu": user.has_group('base.group_multi_company') and len(user.company_ids) > 1, diff --git a/addons/web/static/src/js/chrome/abstract_web_client.js b/addons/web/static/src/js/chrome/abstract_web_client.js index 032b8706bc5..d7f3af00357 100644 --- a/addons/web/static/src/js/chrome/abstract_web_client.js +++ b/addons/web/static/src/js/chrome/abstract_web_client.js @@ -104,6 +104,19 @@ var AbstractWebClient = Widget.extend(KeyboardNavigationMixin, { this.set('title_part', {"zopenerp": "Odoo"}); this.env = env; this.env.bus.on('set_title_part', this, this._onSetTitlePart); + // Only manage the multi company state if a session exists (in order not to interfere during js tests) + if (session.uid && Number.isInteger(session.uid) && session.uid > 0) { + this._manageMultiCompanyState(); + } + if (session.user_context && + session.user_context.allowed_company_ids && + session.user_context.allowed_company_ids.length) { + this.currentCompanyId = session.user_context.allowed_company_ids[0]; + const currentCompany = session.user_companies.allowed_companies[this.currentCompanyId]; + const currentCompanyTimesheetUOMId = currentCompany.timesheet_uom_id || false; + this.currentCompanyTimesheetUOMFactor = currentCompany.timesheet_uom_factor || 1; + this.currentCompanyTimesheetUOM = currentCompanyTimesheetUOMId && session.uom_ids[currentCompanyTimesheetUOMId] || false; + } }, /** * @override @@ -120,28 +133,8 @@ var AbstractWebClient = Widget.extend(KeyboardNavigationMixin, { this.on("change:title_part", this, this._title_changed); this._title_changed(); - var state = $.bbq.getState(); - // If not set on the url, retrieve cids from the local storage - // of from the default company on the user - var current_company_id = session.user_companies.current_company[0] - if (!state.cids) { - state.cids = utils.get_cookie('cids') !== null ? utils.get_cookie('cids') : String(current_company_id); - } - // If a key appears several times in the hash, it is available in the - // bbq state as an array containing all occurrences of that key - const cids = Array.isArray(state.cids) ? state.cids[0] : state.cids; - let stateCompanyIDS = cids.split(',').map(cid => parseInt(cid, 10)); - var userCompanyIDS = _.map(session.user_companies.allowed_companies, function(company) {return company[0]}); - // Check that the user has access to all the companies - if (!_.isEmpty(_.difference(stateCompanyIDS, userCompanyIDS))) { - state.cids = String(current_company_id); - stateCompanyIDS = [current_company_id] - } - // Update the user context with this configuration - session.user_context.allowed_company_ids = stateCompanyIDS; - $.bbq.pushState(state); // Update favicon - $("link[type='image/x-icon']").attr('href', '/web/image/res.company/' + String(stateCompanyIDS[0]) + '/favicon/') + $("link[type='image/x-icon']").attr('href', '/web/image/res.company/' + String(this.currentCompanyId) + '/favicon/'); return session.is_bound .then(function () { @@ -237,6 +230,28 @@ var AbstractWebClient = Widget.extend(KeyboardNavigationMixin, { }); this.$el.children().remove(); }, + _manageMultiCompanyState: function () { + let state = $.bbq.getState(); + // If not set on the url, retrieve cids from the local storage + // or from the default company on the user + const current_company_id = session.user_companies.current_company; + if (!state.cids) { + state.cids = utils.get_cookie('cids') !== null ? utils.get_cookie('cids') : String(current_company_id); + } + // If a key appears several times in the hash, it is available in the + // bbq state as an array containing all occurrences of that key + const cids = Array.isArray(state.cids) ? state.cids[0] : state.cids; + let stateCompanyIDS = cids.split(',').map(cid => parseInt(cid, 10)); + const userCompanyIDS = Object.keys(session.user_companies.allowed_companies).map(id => parseInt(id, 10)); + // Check that the user has access to all the companies + if (!_.isEmpty(_.difference(stateCompanyIDS, userCompanyIDS))) { + state.cids = String(current_company_id); + stateCompanyIDS = [current_company_id]; + } + // Update the user context with this configuration + session.user_context.allowed_company_ids = stateCompanyIDS; + $.bbq.pushState(state); + }, // -------------------------------------------------------------- // Window title handling // -------------------------------------------------------------- diff --git a/addons/web/static/src/js/views/basic/basic_model.js b/addons/web/static/src/js/views/basic/basic_model.js index cfeb1e8c305..2524ff2ab02 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -3710,10 +3710,10 @@ var BasicModel = AbstractModel.extend({ // in general, the actual "company_id" field of the form should be used for m2o domains, not this fallback let current_company_id; if (session.user_context.allowed_company_ids) { - current_company_id = session.user_context.allowed_company_ids[0]; + current_company_id = Object.keys(session.user_context.allowed_company_ids)[0]; } else { current_company_id = session.user_companies ? - session.user_companies.current_company[0] : + session.user_companies.current_company : false; } return Object.assign( diff --git a/addons/web/static/src/js/widgets/switch_company_menu.js b/addons/web/static/src/js/widgets/switch_company_menu.js index 06d33862d57..d8eab231c63 100644 --- a/addons/web/static/src/js/widgets/switch_company_menu.js +++ b/addons/web/static/src/js/widgets/switch_company_menu.js @@ -38,15 +38,12 @@ var SwitchCompanyMenu = Widget.extend({ * @override */ willStart: function () { - var self = this; this.allowed_company_ids = String(session.user_context.allowed_company_ids) .split(',') .map(function (id) {return parseInt(id);}); this.user_companies = session.user_companies.allowed_companies; this.current_company = this.allowed_company_ids[0]; - this.current_company_name = _.find(session.user_companies.allowed_companies, function (company) { - return company[0] === self.current_company; - })[1]; + this.current_company_name = session.user_companies.allowed_companies[this.current_company]['name']; return this._super.apply(this, arguments); }, diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 781eed08395..8dcde9c934e 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -1734,11 +1734,11 @@