diff --git a/addons/hr_timesheet/__manifest__.py b/addons/hr_timesheet/__manifest__.py index d74677ca892..29ffcbd066d 100644 --- a/addons/hr_timesheet/__manifest__.py +++ b/addons/hr_timesheet/__manifest__.py @@ -47,7 +47,7 @@ up a management by affair. 'uninstall_hook': '_uninstall_hook', 'assets': { 'web.assets_backend': [ - 'hr_timesheet/static/src/scss/timesheets_task_form.scss', + 'hr_timesheet/static/src/**/*.scss', 'hr_timesheet/static/src/**/*.js', ('remove', 'hr_timesheet/static/src/js/*_legacy.js') ], @@ -58,7 +58,7 @@ up a management by affair. 'hr_timesheet/static/tests/**/*', ], 'web.assets_qweb': [ - 'hr_timesheet/static/src/xml/**/*', + 'hr_timesheet/static/src/**/*.xml', ], }, 'license': 'LGPL-3', diff --git a/addons/hr_timesheet/static/src/app_store_widget/app_store_widget.js b/addons/hr_timesheet/static/src/app_store_widget/app_store_widget.js new file mode 100644 index 00000000000..5355a228432 --- /dev/null +++ b/addons/hr_timesheet/static/src/app_store_widget/app_store_widget.js @@ -0,0 +1,61 @@ +/** @odoo-module */ + +import { registry } from "@web/core/registry"; +import { _t } from "@web/core/l10n/translation"; +import { Dialog } from "@web/core/dialog/dialog"; +import { useService } from "@web/core/utils/hooks"; +import { isAndroid, isIOS } from "@web/core/browser/feature_detection"; + +const { Component } = owl; + +const configs = { + apple: { + alt: "Apple App Store", + className: "o_config_app_store", + src: "project/static/src/img/app_store.png", + storeUrl: "https://apps.apple.com/be/app/awesome-timesheet/id1078657549", + }, + google: { + alt: "Google Play Store", + className: "o_config_play_store", + src: "project/static/src/img/play_store.png", + storeUrl: "https://play.google.com/store/apps/details?id=com.odoo.OdooTimesheets", + }, +}; + +class AppStoreWidget extends Component { + setup() { + this.dialog = useService("dialog"); + this.config = configs[this.props.node.attrs.type]; + } + + openQRDialog() { + if ( + (this.props.node.attrs.type === "apple" && isIOS()) || + (this.props.node.attrs.type === "google" && isAndroid()) + ) { + this.env.services.action.doAction({ + type: "ir.actions.act_url", + url: this.config.storeUrl, + }); + } else { + this.dialog.add(AppStoreQRDialog, { url: this.config.storeUrl }); + } + } +} +AppStoreWidget.template = "hr_timesheet.AppStoreWidget"; + +class AppStoreQRDialog extends Component { + setup() { + this.title = _t("Download our app"); + this.qrCodeUrl = `/report/barcode/?barcode_type=QR&value=${this.props.url}&width=256&height=256&humanreadable=1`; + } + + viewApp() { + window.open(this.props.url, "_blank"); + } +} +AppStoreQRDialog.components = { Dialog }; +AppStoreQRDialog.template = "hr_timesheet.AppStoreQRDialog"; + +registry.category("view_widgets").add("hr_timesheet.app_store_widget", AppStoreWidget); diff --git a/addons/hr_timesheet/static/src/app_store_widget/app_store_widget.scss b/addons/hr_timesheet/static/src/app_store_widget/app_store_widget.scss new file mode 100644 index 00000000000..5c480670577 --- /dev/null +++ b/addons/hr_timesheet/static/src/app_store_widget/app_store_widget.scss @@ -0,0 +1,3 @@ +.o_widget.o_widget_hr_timesheet\.app_store_widget { + display: contents; +} diff --git a/addons/hr_timesheet/static/src/app_store_widget/app_store_widget.xml b/addons/hr_timesheet/static/src/app_store_widget/app_store_widget.xml new file mode 100644 index 00000000000..a0d11f14471 --- /dev/null +++ b/addons/hr_timesheet/static/src/app_store_widget/app_store_widget.xml @@ -0,0 +1,23 @@ + + + + + + + + + +
+

Scan this QR code to get the Awesome Timesheet app:

+
+
+ +
+ + + + +
+
+ +
diff --git a/addons/hr_timesheet/static/src/js/timesheet_config_form_view.js b/addons/hr_timesheet/static/src/js/timesheet_config_form_view.js index dfa49acdaa1..250bb0910b9 100644 --- a/addons/hr_timesheet/static/src/js/timesheet_config_form_view.js +++ b/addons/hr_timesheet/static/src/js/timesheet_config_form_view.js @@ -4,8 +4,6 @@ odoo.define('hr_timesheet.res.config.form', function (require) { const core = require('web.core'); const config = require('web.config'); const Dialog = require('web.Dialog'); - const viewRegistry = require('web.view_registry'); - const BaseSetting = require('base.settings'); const QWeb = core.qweb; const _t = core._t; @@ -59,16 +57,6 @@ odoo.define('hr_timesheet.res.config.form', function (require) { }; - var TimesheetConfigFormRenderer = BaseSetting.Renderer.extend(TimesheetConfigQRCodeMixin); - const BaseSettingView = viewRegistry.get('base_settings'); - var TimesheetConfigFormView = BaseSettingView.extend({ - config: _.extend({}, BaseSettingView.prototype.config, { - Renderer : TimesheetConfigFormRenderer, - }), - }); - - viewRegistry.add('hr_timesheet_config_form', TimesheetConfigFormView); - - return {TimesheetConfigQRCodeDialog, TimesheetConfigQRCodeMixin, TimesheetConfigFormRenderer, TimesheetConfigFormView}; + return {TimesheetConfigQRCodeDialog, TimesheetConfigQRCodeMixin}; }); diff --git a/addons/hr_timesheet/views/res_config_settings_views.xml b/addons/hr_timesheet/views/res_config_settings_views.xml index cccb9c3cd91..5db4003cba3 100644 --- a/addons/hr_timesheet/views/res_config_settings_views.xml +++ b/addons/hr_timesheet/views/res_config_settings_views.xml @@ -7,9 +7,6 @@ - - hr_timesheet_config_form -

Time Encoding

@@ -61,10 +58,10 @@
- Apple App Store +
- Google Play Store +
diff --git a/addons/point_of_sale/static/src/backend/web_overrides/res_config_settings.js b/addons/point_of_sale/static/src/backend/web_overrides/res_config_settings.js deleted file mode 100644 index 3c7983ebadd..00000000000 --- a/addons/point_of_sale/static/src/backend/web_overrides/res_config_settings.js +++ /dev/null @@ -1,46 +0,0 @@ -odoo.define('point_of_sale.res_config_settings', function (require) { - 'use strict'; - - const ResConfigSettings = require('base.settings'); - - ResConfigSettings.Renderer.include({ - _searchModule: function (module) { - const isModuleVisible = this._super.apply(this, arguments); - if (isModuleVisible && module.key === 'point_of_sale') { - // Find all field elements (.o_field_widget) from shown (:not(.o_hidden)) setting boxes. - // Then filter those with name attributes that starts with 'pos_'. - const posConfigFields = module.settingView - .find('.o_setting_box:not(.o_hidden) div.o_field_widget') - .filter(function (_, el) { - const name = $(el).attr('name'); - return name ? name.startsWith('pos_') : false; - }); - - // Show the pos_config_id field if there are shown 'pos_*' fields. - // But the search header has ugly bottom margin when the pos_config_id field is shown, - // so we remove the bottom margin when the field is shown. - const posSearchHeader = module.settingView.find('.settingSearchHeader'); - if (posConfigFields.length > 0) { - module.settingView.find('div#pos_header .o_setting_box').removeClass('o_hidden'); - posSearchHeader.addClass('mb-0'); - } else { - posSearchHeader.removeClass('mb-0'); - } - } - return isModuleVisible; - }, - }); - - ResConfigSettings.Controller.include({ - _startRenderer() { - return this._super.apply(this, arguments).then(result => { - // Force tooltip to elements with `pos-data-bs-toggle="tooltip"` attribute. - // We made it specific to ensure we don't interfere with other data-bs-toggle="tooltip" in the form. - this.renderer.$('[pos-data-bs-toggle="tooltip"]').tooltip(); - return result; - }) - } - }) - - return ResConfigSettings; -}); diff --git a/addons/web/__manifest__.py b/addons/web/__manifest__.py index aa03aa457ec..c8e340d56fc 100644 --- a/addons/web/__manifest__.py +++ b/addons/web/__manifest__.py @@ -92,7 +92,6 @@ This module provides the core of the Odoo Web Client. ('include', 'web._assets_bootstrap'), 'base/static/src/css/modules.css', - 'base/static/src/js/res_config_settings.js', 'web/static/src/core/utils/transitions.scss', 'web/static/src/core/**/*', @@ -526,7 +525,6 @@ This module provides the core of the Odoo Web Client. ("include", "web.assets_backend_legacy_lazy"), ], 'web.qunit_suite_tests': [ - 'base/static/tests/base_settings_tests.js', 'web/static/tests/env_tests.js', 'web/static/tests/core/**/*.js', 'web/static/tests/search/**/*.js', diff --git a/addons/web/static/src/legacy/xml/base.xml b/addons/web/static/src/legacy/xml/base.xml index 4eadc50e243..9c9be3dfa23 100644 --- a/addons/web/static/src/legacy/xml/base.xml +++ b/addons/web/static/src/legacy/xml/base.xml @@ -1543,21 +1543,6 @@ - - -
-
@@ -224,7 +224,7 @@ Sitemap.xml: Help search engine crawlers to find out what pages are present and which have recently changed, and to crawl your site accordingly. This file is automatically generated by Odoo.
-
diff --git a/odoo/addons/base/static/src/js/res_config_settings.js b/odoo/addons/base/static/src/js/res_config_settings.js deleted file mode 100644 index 5de8b7521a8..00000000000 --- a/odoo/addons/base/static/src/js/res_config_settings.js +++ /dev/null @@ -1,478 +0,0 @@ -odoo.define('base.settings', function (require) { -"use strict"; - -var BasicModel = require('web.BasicModel'); -var core = require('web.core'); -var FormView = require('web.FormView'); -var FormController = require('web.FormController'); -var FormRenderer = require('web.FormRenderer'); -var view_registry = require('web.view_registry'); -const Dialog = require('web.Dialog'); - -var QWeb = core.qweb; -var _t = core._t; - -var BaseSettingRenderer = FormRenderer.extend({ - events: _.extend({}, FormRenderer.prototype.events, { - 'click .tab': '_onSettingTabClick', - 'keyup .searchInput': '_onKeyUpSearch', - }), - - init: function () { - this._super.apply(this, arguments); - this.activeView = false; - this.activeTab = false; - }, - - /** - * @override - * overridden to show a message, informing user that there are changes - */ - confirmChange: function () { - var self = this; - return this._super.apply(this, arguments).then(function () { - if (!self.$(".o_dirty_warning").length) { - self.$('.o_statusbar_buttons') - .append($('', {text: _t("Unsaved changes"), class: 'text-muted ms-2 o_dirty_warning'})) - } - }); - }, - /** - * @override - */ - on_attach_callback: function () { - this._super.apply(this, arguments); - // set default focus on searchInput - this.searchInput.focus(); - }, - - /** - * @override - */ - displayTranslationAlert: function () { - // Translation alerts are disabled for res.config.settings: - // those are designed to warn user to translate field he just changed, but - // * in res.config.settings almost all fields marked as changed (because - // it's not a usual record and all values are set via default_get) - // * page is reloaded after saving, so those alerts would be visible - // only for short time after clicking Save - }, - /** - * initialize modules list. - * remove module that restricted in groups - * data contains - * { - * key: moduel key - * string: moduel string - * imgurl: icon url - * } - * - * @private - */ - _initModules: function () { - var self = this; - this.modules = []; - _.each(this.$('.app_settings_block'), function (settingView, index) { - var group = !$(settingView).hasClass('o_invisible_modifier'); - var isNotApp = $(settingView).hasClass('o_not_app'); - if(group && !isNotApp) { - var data = $(settingView).data(); - data.string = $(settingView).attr('string') || data.string; - self.modules.push({ - key: data.key, - string: data.string, - imgurl: self._getAppIconUrl(data.key), - }); - } else { - $(settingView).remove(); - } - }); - }, - /** - * initialize searchtext variable - * initialize jQuery search input element - * - * @private - */ - _initSearch: function () { - this.searchInput = this.$('.searchInput'); - if (this.searchText) { - this.searchInput.val(this.searchText); - this._onKeyUpSearch(); - } else { - this.searchText = ""; - } - }, - /** - * find current app index in modules - * - */ - _currentAppIndex: function () { - var self = this; - var index = _.findIndex(this.modules, function (module) { - return module.key === self.activeSettingTab; - }); - return index; - }, - /** - * - * @private - * @param {string} module - * @returns {string} icon url - */ - _getAppIconUrl: function (module) { - return module === "general_settings" ? "/base/static/description/settings.png" : "/"+module+"/static/description/icon.png"; - }, - /** - * - * @private - * @param {string} imgurl - * @param {string} string(moduel name) - * @returns {object} - */ - _getSearchHeader: function (imgurl, string) { - return $(QWeb.render('BaseSetting.SearchHeader', { - imgurl: imgurl, - string: string - })); - }, - /** - * move to selected setting - * - * @private - * @param {int} index - */ - _moveToTab: function (index) { - this.currentIndex = !index || index === -1 ? 0 : (index === this.modules.length ? index - 1 : index); - if (this.currentIndex !== -1) { - if (this.activeView) { - this.activeView.addClass("o_hidden"); - } - if (this.activeTab) { - this.activeTab.removeClass("selected"); - } - var view = this.modules[this.currentIndex].settingView; - var tab = this.$(".tab[data-key='" + this.modules[this.currentIndex].key + "']"); - view.removeClass("o_hidden"); - this.activeView = view; - this.activeTab = tab; - tab.addClass("selected"); - } - }, - - _onSettingTabClick: function (event) { - this.searchInput.focus(); - if (this.searchText.length > 0) { - this.searchInput.val(''); - this.searchText = ""; - this._searchSetting(); - } - var settingKey = this.$(event.currentTarget).data('key'); - this._moveToTab(_.findIndex(this.modules, function (m) { - return m.key === settingKey; - })); - }, - - _onKeyUpSearch: function (event) { - this.searchText = this.searchInput.val(); - this.activeTab.removeClass('selected'); - this._searchSetting(); - }, - /** - * reset setting view - * - * @private - */ - _resetSearch: function () { - this.searchInput.val(""); - _.each(this.modules, function (module) { - module.settingView.addClass('o_hidden'); - module.settingView.find('h2, .o_setting_box, .o_setting_tip').removeClass('o_hidden'); - module.settingView.find('.settingSearchHeader').addClass('o_hidden'); - module.settingView.find('.o_settings_container').addClass('mt16').removeClass('mb-0'); - }); - this.activeTab.removeClass('o_hidden').addClass('selected'); - this.activeView.removeClass('o_hidden'); - }, - - _render: function () { - var self = this; - return this._super.apply(this, arguments).then(function() { - self._initModules(); - self._renderLeftPanel(); - self._initSearch(); - }); - }, - - _renderLeftPanel: function () { - var self = this; - _.each(this.modules, function (module) { - module.settingView = self.$('.app_settings_block[data-key="' + module.key + '"]'); - module.settingView.addClass("o_hidden"); - module.settingView.prepend(self._getSearchHeader(module.imgurl, module.string)); - }); - this._renderTabs(); - this._moveToTab(this.currentIndex || this._currentAppIndex()); - }, - - _renderTabs: function () { - var tabs = $(QWeb.render('BaseSetting.Tabs', {tabItems : this.modules})); - tabs.appendTo(this.$(".settings_tab")); - }, - /** - * search setting in DOM - * - * @private - */ - _searchSetting: function () { - this.count = 0; - _.each(this.modules, module => this._searchModule(module)); - this.count === _.size(this.modules) ? this.$('.notFound').removeClass('o_hidden') : this.$('.notFound').addClass('o_hidden'); - if (this.searchText.length === 0) { - this._resetSearch(); - } - }, - - /** - * @param {*} module - * @returns whether the module is visible or not - */ - _searchModule: function (module) { - var self = this; - let inVisibleCount = 0; - let isModuleVisible = false; - module.settingView.find('h2, .o_setting_box, .o_setting_tip').addClass('o_hidden'); - module.settingView.find('.settingSearchHeader').addClass('o_hidden'); - module.settingView.find('.o_settings_container').removeClass('mt16').addClass('mb-0'); - - const upperCasedSearchText = self.searchText.toUpperCase(); - const [matches, others] = _.partition(module.settingView.find(".o_form_label"), - (e) => e.textContent.toUpperCase().includes(upperCasedSearchText)); - if (matches.length) { - for (let result of matches) { - const settingBox = $(result).closest('.o_setting_box'); - if (!settingBox.hasClass('o_invisible_modifier')) { - settingBox.removeClass('o_hidden'); - self._wordHighlighter(result, upperCasedSearchText); - } else { - inVisibleCount++; - } - } - isModuleVisible = inVisibleCount !== matches.length - if (isModuleVisible) { - module.settingView.find('.settingSearchHeader').removeClass('o_hidden'); - module.settingView.removeClass('o_hidden'); - } - } else { - ++self.count; - } - others.filter(e => e.firstElementChild).forEach(e => self._removeHighlight(e)); - return isModuleVisible; - }, - /** - * highlight search word - * - * @private - * @param {HTMLElement} node - * @param {string} upperCasedSearchText - */ - _wordHighlighter: function (node, upperCasedSearchText) { - const text = node.textContent; - const startIndex = text.toUpperCase().indexOf(upperCasedSearchText); - const endIndex = startIndex + upperCasedSearchText.length; - $(node).empty().append( - document.createTextNode(text.substring(0, startIndex)), - $('').text(text.substring(startIndex, endIndex)), - document.createTextNode(text.substring(endIndex)) - ); - }, - - /** - * @param {HTMLElement} node - * @private - */ - _removeHighlight: function(node) { - node.textContent = node.textContent; - }, -}); - -var BaseSettingController = FormController.extend({ - custom_events: _.extend({}, FormController.prototype.custom_events, { - button_clicked: '_onButtonClicked', - }), - init: function () { - this._super.apply(this, arguments); - this.disableAutofocus = true; - this.renderer.activeSettingTab = this.initialState.context.module; - // discardingDef is used to ensure that we don't ask twice the user if - // he wants to discard changes, when 'canBeDiscarded' is called several - // times "in parallel" - this.discardingDef = null; - }, - /** - * Settings view should always be in edit mode, so we have to override - * default behaviour - * - * @override - */ - willRestore: function () { - this.mode = 'edit'; - }, - /** - * @override - * @returns {Promise} - */ - canBeRemoved: function () { - return this.discardChanges(undefined, { - noAbandon: true, - readonlyIfRealDiscard: true, - }); - }, - /** - * @override - * @param {string} recordId - * @returns {Promise} - */ - canBeDiscarded: function (recordId) { - if (this.discardingDef) { - return this.discardingDef; - } - if (!this.isDirty(recordId)) { - return Promise.resolve(false); - } - const message = _t('Would you like to save your changes?'); - this.discardingDef = new Promise((resolve, reject) => { - const reset = () => { - // enable buttons if user first save which fails because of required field missed - // and then cancel confirmation dialog - this._enableButtons(); - this.discardingDef = null; - }; - const cancel = () => { - reject(); - reset(); - }; - const dialog = Dialog.confirm(this, message, { - title: _t('Unsaved changes'), - buttons: [{ - text: _t('Save'), - classes: 'btn-primary', - click: async () => { - this._disableButtons(); - try { - // _onButtonClicked always saves the record even if - // it's discarded. Here we need to save before - // triggering the changes on the server. - await this.saveRecord(recordId, { - stayInEdit: true, - }); - const record = this.model.get(recordId); - this.trigger_up('execute_action', { - action_data: { - context: record.getContext({ - additionalContext: {}, - }), - name: "execute", - type: "object", - }, - env: { - context: record.getContext(), - currentID: record.data.id, - model: record.model, - resIDs: record.res_ids, - }, - on_success() { - resolve(false); - dialog.close(); - }, - on_fail() { - cancel(); - dialog.close(); - }, - }); - } catch (_e) { - cancel(); - dialog.close(); - } - }, - }, { - text: _t('Stay Here'), - close: true, - click: cancel, - }, { - text: _t('Discard'), - close: true, - click: () => { - resolve(true); - reset(); - }, - }], - }); - dialog.on('closed', this.discardingDef, cancel); - }); - return this.discardingDef; - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * @private - */ - _onButtonClicked: function (ev) { - var self = this; - if (ev.data.attrs.name !== 'execute' && ev.data.attrs.name !== 'cancel') { - var recordID = ev.data.recordID; - var _super = this._super; - var args = arguments; - this._discardChanges(recordID, { noAbandon: true }).then(function () { - _super.apply(self, args); - }); - } else { - this._super.apply(this, arguments); - } - }, - /** - * @override - * @private - */ - _onBeforeUnload: function () { - // We should not save when leaving Odoo in the settings - }, - -}); - -const BaseSettingsModel = BasicModel.extend({ - save(recordID, options) { - const savePoint = options && options.savePoint; - return this._super.apply(this, arguments).then(result => { - if (!savePoint && this.localData[recordID].model === 'res.config.settings') { - // we remove here the res_id, because the record should still be - // considered new. We want the web client to always perform a - // onchange to fetch the settings anew. - delete this.localData[recordID].res_id; - } - return result; - }); - }, -}); - -var BaseSettingView = FormView.extend({ - jsLibs: [], - - config: _.extend({}, FormView.prototype.config, { - Model: BaseSettingsModel, - Renderer: BaseSettingRenderer, - Controller: BaseSettingController, - }), -}); - -view_registry.add('base_settings', BaseSettingView); - -return { - Model: BaseSettingsModel, - Renderer: BaseSettingRenderer, - Controller: BaseSettingController, -}; -}); diff --git a/odoo/addons/base/static/tests/base_settings_tests.js b/odoo/addons/base/static/tests/base_settings_tests.js deleted file mode 100644 index 1016f8c8420..00000000000 --- a/odoo/addons/base/static/tests/base_settings_tests.js +++ /dev/null @@ -1,708 +0,0 @@ -odoo.define('base.settings_tests', function (require) { -"use strict"; - -var testUtils = require('web.test_utils'); -var view_registry = require('web.view_registry'); - -var createView = testUtils.createView; -var BaseSettingsView = view_registry.get('base_settings'); - -const { getFixture, legacyExtraNextTick } = require("@web/../tests/helpers/utils"); -const { createWebClient, doAction } = require("@web/../tests/webclient/helpers"); - -let serverData; -let target; -QUnit.module('base_settings_tests', { - beforeEach: function () { - this.data = { - 'res.config.settings': { - fields: { - foo: {string: "Foo", type: "boolean"}, - bar: {string: "Bar", type: "boolean"}, - tasks: {string: "one2many field", type: "one2many", relation: 'task'}, - baz: { - string: "Baz", - type: "selection", - selection: [[1, "treads"], [2, "treats"]], - default: 1, - }, - }, - }, - 'task': { - fields: {} - } - }; - serverData = { models: this.data }; - target = getFixture(); - } -}, function () { - - QUnit.module('BaseSetting'); - - QUnit.test('change setting on nav bar click in base settings', async function (assert) { - assert.expect(5); - - var form = await createView({ - View: BaseSettingsView, - model: 'res.config.settings', - data: this.data, - arch: '
' + - '
' + - ' ' + - '
' + - '
' + - '
' + - '
' + - '
'+ - '
' + - '
No Record Found
' + - '
' + - '
'+ - '
'+ - '
' + - ''+ - '
'+ - '
'+ - '
' + - '
'+ - '
'+ - '
' + - ''+ - '
'+ - '
'+ - 'Foo'+ - '
'+ - 'this is foo'+ - '
'+ - '
' + - '
'+ - '
' + - '
' + - '
' + - '
' + - '', - }); - - assert.hasAttrValue(form.$('.selected'), 'data-key',"crm","crm setting selected"); - assert.isVisible(form.$(".settings .app_settings_block"), "res.config.settings settings show"); - await testUtils.fields.editAndTrigger(form.$('.searchInput'), 'b', 'keyup'); - assert.strictEqual(form.$('.highlighter').html(), "B", "b word highlighted"); - await testUtils.fields.editAndTrigger(form.$('.searchInput'), 'bx', 'keyup'); - assert.isVisible(form.$('.notFound'), "record not found message shown"); - form.$('.searchInput').val('f').trigger('keyup'); - assert.strictEqual(form.$('span.o_form_label .highlighter').html(), "F", "F word highlighted"); - form.destroy(); - }); - - QUnit.test('unhighlight section not matching anymore', async function(assert) { - assert.expect(7); - - const form = await createView({ - View: BaseSettingsView, - model: 'res.config.settings', - data: this.data, - arch: ` -
-
- -
-
-
-
-
-
-
No Record Found
-
-
-
-
-
-
-
-
-
-
- ` - }); - assert.hasAttrValue(form.$('.selected'), 'data-key',"crm","crm setting selected"); - assert.isVisible(form.$(".settings .app_settings_block"), "project settings show"); - - await testUtils.fields.editAndTrigger(form.$('.searchInput'), 'trea', 'keyup'); - assert.containsN(form, '.highlighter', 2, 'should have 2 options highlighted'); - assert.equal(form.$('.highlighter:eq(0)').parent().text(), 'treads'); - assert.equal(form.$('.highlighter:eq(1)').parent().text(), 'treats'); - - await testUtils.fields.editAndTrigger(form.$('.searchInput'), 'tread', 'keyup'); - assert.containsN(form, '.highlighter', 1, 'should have only one highlighted'); - assert.equal(form.$('.highlighter').parent().text(), 'treads'); - - form.destroy(); - }); - - QUnit.test('hide / show setting tips properly', async function (assert) { - assert.expect(3); - - const form = await createView({ - View: BaseSettingsView, - model: 'res.config.settings', - data: this.data, - arch: ` -
-
- -
-
-
-
-
No Record Found
-
-

Setting Header

-

Settings will appear below

-
-
-
- ` - }); - - assert.containsOnce(form, '.o_setting_tip:not(.o_hidden)', 'Tip should not be hidden initially'); - - await testUtils.fields.editAndTrigger(form.$('.searchInput'), 'Setting', 'keyup'); - assert.containsOnce(form, '.o_setting_tip.o_hidden', 'Tip should be hidden when user searches in settings'); - - await testUtils.fields.editAndTrigger(form.$('.searchInput'), '', 'keyup'); - assert.containsOnce(form, '.o_setting_tip:not(.o_hidden)', 'Tip should be displayed again'); - - form.destroy(); - }); - - QUnit.test( - "settings views does not read existing id when coming back in breadcrumbs", - async function (assert) { - serverData.actions = { - 1: { - id: 1, - name: "Settings view", - res_model: "res.config.settings", - type: "ir.actions.act_window", - views: [[1, "form"]], - }, - 4: { - id: 4, - name: "Other action", - res_model: "task", - type: "ir.actions.act_window", - views: [[2, "list"]], - }, - }; - - serverData.views = { - "res.config.settings,1,form": - `
-
-
-
`, - "task,2,list": '', - "res.config.settings,false,search": "", - "task,false,search": "", - }; - - const mockRPC = (route, args) => { - if (args.method) { - assert.step(args.method); - } - }; - - const webClient = await createWebClient({ serverData, mockRPC }); - - await doAction(webClient, 1); - await testUtils.dom.click($(target).find('button[name="4"]')); - await legacyExtraNextTick(); - await testUtils.dom.click($(".o_control_panel .breadcrumb-item a")); - await legacyExtraNextTick(); - assert.hasClass($(target).find(".o_form_view"), "o_form_editable"); - assert.verifySteps([ - "get_views", // initial setting action - "onchange", // this is a setting view => create new record - "create", // when we click on action button => save - "read", // with save, we have a reload... (not necessary actually) - "get_views", // for other action in breadcrumb, - "web_search_read", // same - "onchange", // when we come back, we want to restart from scratch - ]); - } - ); - - QUnit.test( - "clicking on any button in setting should show discard warning if setting form is dirty", - async function (assert) { - assert.expect(11); - - serverData.actions = { - 1: { - id: 1, - name: "Settings view", - res_model: "res.config.settings", - type: "ir.actions.act_window", - views: [[1, "form"]], - }, - 4: { - id: 4, - name: "Other action", - res_model: "task", - type: "ir.actions.act_window", - views: [[2, "list"]], - }, - }; - - serverData.views = { - "res.config.settings,1,form": - `
-
-
-
-
-
-
- -
-
- Foo -
- this is foo -
-
-
-
-
-
`, - "task,2,list": '', - "res.config.settings,false,search": "", - "task,false,search": "", - }; - - const mockRPC = (route, args) => { - if (route === "/web/dataset/call_button") { - if (args.method === "execute") { - assert.ok("execute method called"); - return true; - } - if (args.method === "cancel") { - assert.ok("cancel method called"); - return true; - } - } - }; - - const webClient = await createWebClient({ serverData, mockRPC }); - - await doAction(webClient, 1); - assert.containsNone( - target, - ".o_field_boolean input:checked", - "checkbox should not be checked" - ); - - await testUtils.dom.click($(target).find("input[type='checkbox']")); - assert.containsOnce( - target, - ".o_field_boolean input:checked", - "checkbox should be checked" - ); - - await testUtils.dom.click($(target).find('button[name="4"]')); - await legacyExtraNextTick(); - assert.containsOnce(document.body, ".modal", "should open a warning dialog"); - - await testUtils.dom.click($(".modal button:contains(Discard)")); - await legacyExtraNextTick(); - assert.containsOnce(target, ".o_list_view", "should be open list view"); - - await testUtils.dom.click($(".o_control_panel .breadcrumb-item a")); - await legacyExtraNextTick(); - assert.containsNone( - target, - ".o_field_boolean input:checked", - "checkbox should not be checked" - ); - - await testUtils.dom.click($(target).find("input[type='checkbox']")); - await testUtils.dom.click($(target).find('button[name="4"]')); - await legacyExtraNextTick(); - assert.containsOnce(document.body, ".modal", "should open a warning dialog"); - - await testUtils.dom.click($(".modal button:contains(Stay Here)")); - await legacyExtraNextTick(); - assert.containsOnce(target, ".o_form_view", "should be remain on form view"); - - await testUtils.dom.click($(target).find("button[name='execute']")); - await legacyExtraNextTick(); - assert.containsNone( - document.body, - ".modal", - "should not open a warning dialog" - ); - - await testUtils.dom.click($(target).find("input[type='checkbox']")); - await testUtils.dom.click($(target).find("button[name='cancel']")); - await legacyExtraNextTick(); - assert.containsNone( - document.body, - ".modal", - "should not open a warning dialog" - ); - } - ); - - QUnit.test('settings view does not display other settings after reload', async function (assert) { - assert.expect(2); - - var form = await createView({ - View: BaseSettingsView, - model: 'res.config.settings', - data: this.data, - arch: '
' + - '
' + - ' ' + - '
' + - '
' + - '
' + - '
' + - '
'+ - '
' + - '
No Record Found
' + - '
' + - 'crm tab' + - '
' + - '
' + - 'other app tab' + - '
' + - '
' + - '
' + - '', - }); - - assert.strictEqual(form.$('.app_settings_block').text().replace(/\s/g,''), 'CRMcrmtab'); - await form.reload(); - assert.strictEqual(form.$('.app_settings_block').text().replace(/\s/g,''), 'CRMcrmtab'); - form.destroy(); - }); - - QUnit.test('settings view shows a message if there are changes', async function (assert) { - assert.expect(5); - - var form = await createView({ - View: BaseSettingsView, - model: 'res.config.settings', - data: this.data, - arch: '
' + - '
' + - '
' + - '
' + - '
' + - '
' + - '
No Record Found
' + - '
' + - 'Make Changes' + - '
' + - '
' + - '
' + - '', - }); - - testUtils.mock.intercept(form, "field_changed", function (event) { - assert.ok(true,"field changed"); - }, true); - - assert.containsNone(form, '.o_field_boolean input:checked', "checkbox should not be checked"); - assert.containsNone(form, ".o_dirty_warning", "warning message should not be shown"); - await testUtils.dom.click(form.$("input[type='checkbox']")); - assert.containsOnce(form, '.o_field_boolean input:checked' ,"checkbox should be checked"); - assert.containsOnce(form, ".o_dirty_warning", "warning message should be shown"); - form.destroy(); - }); - - QUnit.test('settings view shows a message if there are changes even if the save failed', async function (assert) { - assert.expect(3); - var self = this; - self.alreadySavedOnce = false; - - var form = await createView({ - View: BaseSettingsView, - model: 'res.config.settings', - data: this.data, - mockRPC: function (route, args) { - if (args.method === "create" && !self.alreadySavedOnce) { - self.alreadySavedOnce = true; - //fail on first create - return Promise.reject({}); - } - return this._super.apply(this, arguments); - }, - arch: '
' + - '
' + - '
' + - '
' + - '
' + - '
' + - '
No Record Found
' + - '
' + - 'Make Changes' + - '
' + - '
' + - '
' + - '', - }); - - - await testUtils.dom.click(form.$("input[type='checkbox']")); - assert.containsOnce(form, ".o_dirty_warning", "warning message should be shown"); - await testUtils.form.clickSave(form); - assert.containsOnce(form, ".o_dirty_warning", "warning message should be shown"); - await testUtils.form.clickSave(form); - assert.containsNone(form, ".o_dirty_warning", "warning message should be shown"); - - form.destroy(); - }); - - QUnit.test( - "execute action from settings view with several actions in the breadcrumb", - async function (assert) { - // This commit fixes a race condition, that's why we artificially slow down a read rpc - assert.expect(4); - - serverData.actions = { - 1: { - id: 1, - name: "First action", - res_model: "task", - type: "ir.actions.act_window", - views: [[1, "list"]], - }, - 2: { - id: 2, - name: "Settings view", - res_model: "res.config.settings", - type: "ir.actions.act_window", - views: [[2, "form"]], - }, - 3: { - id: 3, - name: "Other action", - res_model: "task", - type: "ir.actions.act_window", - views: [[3, "list"]], - }, - }; - - serverData.views = { - "task,1,list": '', - "res.config.settings,2,form": ` -
-
-
-
`, - "task,3,list": '', - "res.config.settings,false,search": "", - "task,false,search": "", - }; - - let loadViewsDef; - const mockRPC = async (route, args) => { - if (args.method === "read") { - await loadViewsDef; // slow down reload of settings view - } - }; - - const webClient = await createWebClient({ serverData, mockRPC }); - await doAction(webClient, 1); - assert.strictEqual($(target).find(".breadcrumb").text(), "First action"); - - await doAction(webClient, 2); - assert.strictEqual( - $(target).find(".breadcrumb").text(), - "First actionNew" - ); - - loadViewsDef = testUtils.makeTestPromise(); - await testUtils.dom.click($(target).find('button[name="3"]')); - await legacyExtraNextTick(); - assert.strictEqual( - $(target).find(".breadcrumb").text(), - "First actionNew" - ); - - loadViewsDef.resolve(); - await testUtils.nextTick(); - await legacyExtraNextTick(); - assert.strictEqual( - $(target).find(".breadcrumb").text(), - "First actionNewOther action" - ); - } - ); - - QUnit.test('settings can contain one2many fields', async function (assert) { - assert.expect(2); - - const form = await createView({ - View: BaseSettingsView, - model: 'res.config.settings', - data: this.data, - arch: ` -
-
-
-
-
-
-
No Record Found
-
- - - - -
-
-
- `, - }); - - await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - await testUtils.fields.editInput($('.modal-body input[name=display_name]'), 'Added Task'); - await testUtils.dom.click($('.modal-dialog footer button:first-child')); - - assert.strictEqual(form.$('table.o_list_table:eq(0) tr.o_data_row td.o_data_cell:eq(0)').text(), - 'Added Task', - 'The one2many relation item should have been added'); - - await testUtils.form.clickSave(form); - - assert.strictEqual(form.$('table.o_list_table:eq(0) tr.o_data_row td.o_data_cell:eq(0)').text(), - 'Added Task', - 'The one2many relation item should still be present'); - - form.destroy(); - }); - - QUnit.test( - 'call "call_button/execute" when clicking on a button in dirty settings', - async function (assert) { - assert.expect(7); - - serverData.actions = { - 1: { - id: 1, - name: "Settings view", - res_model: "res.config.settings", - type: "ir.actions.act_window", - views: [[1, "form"]], - }, - 4: { - id: 4, - name: "Other Action", - res_model: "task", - type: "ir.actions.act_window", - views: [[false, "list"]], - } - }; - - serverData.views = { - "res.config.settings,1,form": ` -
-
-
-
-
- -
-
- Foo -
- this is foo -
-
-
-
-
-
- `, - "res.config.settings,false,search": "", - "task,false,list": "", - "task,false,search": "", - }; - - const mockRPC = (route, args) => { - if (route === "/web/dataset/call_button" && args.method === "execute") { - assert.step("execute"); - return true; - } else if (args.method === "create") { - assert.step("create"); - } - }; - - const webClient = await createWebClient({ serverData, mockRPC }); - - await doAction(webClient, 1); - assert.containsNone( - target, - ".o_field_boolean input:checked", - "checkbox should not be checked" - ); - - await testUtils.dom.click($(target).find('input[type="checkbox"]')); - assert.containsOnce( - target, - ".o_field_boolean input:checked", - "checkbox should be checked" - ); - - await testUtils.dom.click($(target).find('button[name="4"]')); - assert.containsOnce(document.body, ".modal", "should open a warning dialog"); - - await testUtils.dom.click($(".modal-footer .btn-primary")); - assert.verifySteps([ - "create", // saveRecord from modal - "execute", // execute_action - "create", // saveRecord from FormController._onButtonClicked - ]); - } - ); - -}); -}); diff --git a/odoo/addons/base/views/res_config_settings_views.xml b/odoo/addons/base/views/res_config_settings_views.xml index d7d552f0f44..6d0f1adbd6e 100644 --- a/odoo/addons/base/views/res_config_settings_views.xml +++ b/odoo/addons/base/views/res_config_settings_views.xml @@ -6,36 +6,8 @@ res.config.settings
-
-
- - -
-
-
-
-
-
-
-
-
-

- No setting found -

- Try searching for another keyword -

-
-
-
-
+