diff --git a/addons/account/static/tests/tours/account_dashboard_setup_bar_tests.js b/addons/account/static/tests/tours/account_dashboard_setup_bar_tests.js index faec6e7b949..a212e1761ef 100644 --- a/addons/account/static/tests/tours/account_dashboard_setup_bar_tests.js +++ b/addons/account/static/tests/tours/account_dashboard_setup_bar_tests.js @@ -9,7 +9,7 @@ odoo.define('account.dashboard.setup.tour', function (require) { tour.register('account_render_report', { test: true, url: '/web', - }, [tour.STEPS.SHOW_APPS_MENU_ITEM, + }, [tour.stepUtils.showAppsMenuItem(), { id: 'account_menu_click', trigger: '.o_app[data-menu-xmlid="account.menu_finance"]', diff --git a/addons/account/static/tests/tours/tax_group_tests.js b/addons/account/static/tests/tours/tax_group_tests.js index 7d3ffd9ce85..08cb218f741 100644 --- a/addons/account/static/tests/tours/tax_group_tests.js +++ b/addons/account/static/tests/tours/tax_group_tests.js @@ -8,7 +8,7 @@ odoo.define('account.tax.group.tour.tests', function (require) { tour.register('account_tax_group', { test: true, url: "/web", - }, [tour.STEPS.SHOW_APPS_MENU_ITEM, + }, [tour.stepUtils.showAppsMenuItem(), { content: "Go to Invoicing", trigger: '.o_app[data-menu-xmlid="account.menu_finance"]', diff --git a/addons/crm/static/src/js/tours/crm.js b/addons/crm/static/src/js/tours/crm.js index 57f7b1d0df6..f0a5813de14 100644 --- a/addons/crm/static/src/js/tours/crm.js +++ b/addons/crm/static/src/js/tours/crm.js @@ -8,7 +8,7 @@ var _t = core._t; tour.register('crm_tour', { url: "/web", -}, [tour.STEPS.SHOW_APPS_MENU_ITEM, { +}, [tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="crm.crm_menu_root"]', content: _t("Ready to boost your sales? Your Pipeline can be found here, under the CRM app."), position: 'right', @@ -71,7 +71,7 @@ tour.register('crm_tour', { extra_trigger: '.o_lead_opportunity_form', content: _t("Use the breadcrumbs to go back to your sales pipeline."), position: "bottom" -}, tour.STEPS.TOGGLE_HOME_MENU, tour.STEPS.SHOW_APPS_MENU_ITEM, { +}, tour.stepUtils.toggleHomeMenu(), tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="base.menu_administration"]', content: _t("Configuration options are available in the Settings app."), position: "bottom", @@ -85,7 +85,7 @@ tour.register('crm_tour', { trigger: "div#invite_users .o_user_emails", content: _t("Invite coworkers via email.
Enter one email per line."), position: "right" -}, tour.STEPS.TOGGLE_HOME_MENU, tour.STEPS.SHOW_APPS_MENU_ITEM, { +}, tour.stepUtils.toggleHomeMenu(), tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="crm.crm_menu_root"]', content: _t("Good job! You completed the tour of the CRM app."), position: 'right', diff --git a/addons/event_sale/static/tests/tours/event_configurator_ui.js b/addons/event_sale/static/tests/tours/event_configurator_ui.js index b34187d796f..1170a142a09 100644 --- a/addons/event_sale/static/tests/tours/event_configurator_ui.js +++ b/addons/event_sale/static/tests/tours/event_configurator_ui.js @@ -6,7 +6,7 @@ var tour = require('web_tour.tour'); tour.register('event_configurator_tour', { url: "/web", test: true, -}, [tour.STEPS.SHOW_APPS_MENU_ITEM, { +}, [tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]', edition: 'community' }, { diff --git a/addons/hr_expense/static/src/js/tours/hr_expense.js b/addons/hr_expense/static/src/js/tours/hr_expense.js index eddc613b1ce..0422d6870db 100644 --- a/addons/hr_expense/static/src/js/tours/hr_expense.js +++ b/addons/hr_expense/static/src/js/tours/hr_expense.js @@ -8,7 +8,7 @@ var _t = core._t; tour.register('hr_expense_tour' , { url: "/web" -}, [tour.STEPS.SHOW_APPS_MENU_ITEM, { +}, [tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="hr_expense.menu_hr_expense_root"]', content: _t("Want to manage your expenses? It starts here."), position: 'right', diff --git a/addons/hr_recruitment/static/src/js/tours/hr_recruitment.js b/addons/hr_recruitment/static/src/js/tours/hr_recruitment.js index 0f814426e8b..1939b5baf6c 100644 --- a/addons/hr_recruitment/static/src/js/tours/hr_recruitment.js +++ b/addons/hr_recruitment/static/src/js/tours/hr_recruitment.js @@ -6,7 +6,7 @@ var tour = require('web_tour.tour'); var _t = core._t; -tour.register('hr_recruitment_tour', [tour.STEPS.SHOW_APPS_MENU_ITEM, { +tour.register('hr_recruitment_tour', [tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="hr_recruitment.menu_hr_recruitment_root"]', content: _t('Want to start recruiting like a pro? Start here.'), position: 'right', diff --git a/addons/point_of_sale/static/src/js/tours/point_of_sale.js b/addons/point_of_sale/static/src/js/tours/point_of_sale.js index 5fc69341602..161b7516082 100644 --- a/addons/point_of_sale/static/src/js/tours/point_of_sale.js +++ b/addons/point_of_sale/static/src/js/tours/point_of_sale.js @@ -9,7 +9,7 @@ var _t = core._t; tour.register('point_of_sale_tour', { url: "/web", rainbowMan: false, -}, [tour.STEPS.SHOW_APPS_MENU_ITEM, { +}, [tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="point_of_sale.menu_point_root"]', content: _t("Ready to launch your point of sale?"), width: 215, diff --git a/addons/project/static/src/js/tours/project.js b/addons/project/static/src/js/tours/project.js index 951390455b9..407fa9cdb4b 100644 --- a/addons/project/static/src/js/tours/project.js +++ b/addons/project/static/src/js/tours/project.js @@ -8,7 +8,7 @@ var _t = core._t; tour.register('project_tour', { url: "/web", -}, [tour.STEPS.SHOW_APPS_MENU_ITEM, { +}, [tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="project.menu_main_pm"]', content: _t('Want a better way to manage your projects? It starts here.'), position: 'right', diff --git a/addons/purchase_product_matrix/static/tests/tours/purchase_product_matrix_tour.js b/addons/purchase_product_matrix/static/tests/tours/purchase_product_matrix_tour.js index 9148f888b53..7ffd79137b7 100644 --- a/addons/purchase_product_matrix/static/tests/tours/purchase_product_matrix_tour.js +++ b/addons/purchase_product_matrix/static/tests/tours/purchase_product_matrix_tour.js @@ -6,7 +6,7 @@ var tour = require('web_tour.tour'); tour.register('purchase_matrix_tour', { url: "/web", test: true, -}, [tour.STEPS.SHOW_APPS_MENU_ITEM, { +}, [tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="purchase.menu_purchase_root"]', }, { trigger: ".o_list_button_add", diff --git a/addons/sale/static/src/js/tours/sale.js b/addons/sale/static/src/js/tours/sale.js index c1177ccc4d0..4853c905b8b 100644 --- a/addons/sale/static/src/js/tours/sale.js +++ b/addons/sale/static/src/js/tours/sale.js @@ -8,7 +8,7 @@ var _t = core._t; tour.register('sale_tour', { url: "/web", -}, [tour.STEPS.SHOW_APPS_MENU_ITEM, { +}, [tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]', content: _t('Open Sales app to send your first quotation in a few clicks.'), position: 'right', diff --git a/addons/sale_product_configurator/static/tests/tours/product_configurator_advanced_ui.js b/addons/sale_product_configurator/static/tests/tours/product_configurator_advanced_ui.js index 0f351b8be5e..16251f5612f 100644 --- a/addons/sale_product_configurator/static/tests/tours/product_configurator_advanced_ui.js +++ b/addons/sale_product_configurator/static/tests/tours/product_configurator_advanced_ui.js @@ -8,7 +8,7 @@ var optionVariantImage; tour.register('sale_product_configurator_advanced_tour', { url: "/web", test: true, -}, [tour.STEPS.SHOW_APPS_MENU_ITEM, { +}, [tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]', // Note: The module sale_management is mandatory edition: 'community' }, { diff --git a/addons/sale_product_configurator/static/tests/tours/product_configurator_edition_ui.js b/addons/sale_product_configurator/static/tests/tours/product_configurator_edition_ui.js index 907cc86382a..14cf07663ee 100644 --- a/addons/sale_product_configurator/static/tests/tours/product_configurator_edition_ui.js +++ b/addons/sale_product_configurator/static/tests/tours/product_configurator_edition_ui.js @@ -6,7 +6,7 @@ var tour = require('web_tour.tour'); tour.register('sale_product_configurator_edition_tour', { url: "/web", test: true, -}, [tour.STEPS.SHOW_APPS_MENU_ITEM, { +}, [tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]', edition: 'community' }, { diff --git a/addons/sale_product_configurator/static/tests/tours/product_configurator_optional_products_ui.js b/addons/sale_product_configurator/static/tests/tours/product_configurator_optional_products_ui.js index c68fc9debec..c566ce5b1da 100644 --- a/addons/sale_product_configurator/static/tests/tours/product_configurator_optional_products_ui.js +++ b/addons/sale_product_configurator/static/tests/tours/product_configurator_optional_products_ui.js @@ -6,7 +6,7 @@ var tour = require('web_tour.tour'); tour.register('sale_product_configurator_optional_products_tour', { url: "/web", test: true, -}, [tour.STEPS.SHOW_APPS_MENU_ITEM, { +}, [tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]', edition: 'community' }, { diff --git a/addons/sale_product_configurator/static/tests/tours/product_configurator_pricelist_ui.js b/addons/sale_product_configurator/static/tests/tours/product_configurator_pricelist_ui.js index ff761c0b540..60fc965b39b 100644 --- a/addons/sale_product_configurator/static/tests/tours/product_configurator_pricelist_ui.js +++ b/addons/sale_product_configurator/static/tests/tours/product_configurator_pricelist_ui.js @@ -8,7 +8,7 @@ tour.register('sale_product_configurator_pricelist_tour', { test: true, }, [ -tour.STEPS.SHOW_APPS_MENU_ITEM, +tour.stepUtils.showAppsMenuItem(), { content: "navigate to the sale app", trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]', diff --git a/addons/sale_product_configurator/static/tests/tours/product_configurator_single_custom_attribute_ui.js b/addons/sale_product_configurator/static/tests/tours/product_configurator_single_custom_attribute_ui.js index 4c1b68da988..8b1db607579 100644 --- a/addons/sale_product_configurator/static/tests/tours/product_configurator_single_custom_attribute_ui.js +++ b/addons/sale_product_configurator/static/tests/tours/product_configurator_single_custom_attribute_ui.js @@ -6,7 +6,7 @@ var tour = require('web_tour.tour'); tour.register('sale_product_configurator_single_custom_attribute_tour', { url: "/web", test: true, -}, [tour.STEPS.SHOW_APPS_MENU_ITEM, { +}, [tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]', edition: 'community' }, { diff --git a/addons/sale_product_configurator/static/tests/tours/product_configurator_ui.js b/addons/sale_product_configurator/static/tests/tours/product_configurator_ui.js index d1857944ae7..fad1fbd3309 100644 --- a/addons/sale_product_configurator/static/tests/tours/product_configurator_ui.js +++ b/addons/sale_product_configurator/static/tests/tours/product_configurator_ui.js @@ -9,7 +9,7 @@ var tour = require('web_tour.tour'); tour.register('sale_product_configurator_tour', { url: "/web", test: true, -}, [tour.STEPS.SHOW_APPS_MENU_ITEM, { +}, [tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]', edition: 'community' }, { diff --git a/addons/sale_product_matrix/static/tests/tours/sale_product_matrix_tour.js b/addons/sale_product_matrix/static/tests/tours/sale_product_matrix_tour.js index deb3c8bb2ba..f5b3ff51c68 100644 --- a/addons/sale_product_matrix/static/tests/tours/sale_product_matrix_tour.js +++ b/addons/sale_product_matrix/static/tests/tours/sale_product_matrix_tour.js @@ -6,7 +6,7 @@ var tour = require('web_tour.tour'); tour.register('sale_matrix_tour', { url: "/web", test: true, -}, [tour.STEPS.SHOW_APPS_MENU_ITEM, { +}, [tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]', }, { trigger: ".o_list_button_add", diff --git a/addons/web_tour/static/src/js/tip.js b/addons/web_tour/static/src/js/tip.js index 3bc950ba8f8..d9446341de7 100644 --- a/addons/web_tour/static/src/js/tip.js +++ b/addons/web_tour/static/src/js/tip.js @@ -135,6 +135,7 @@ var Tip = Widget.extend({ p = $location.css("position"); } while ( $location.hasClass('dropdown-menu') || + $location.hasClass('o_notebook_headers') || ( (o === "visible" || o === "hidden") && p !== "fixed" && @@ -319,6 +320,10 @@ Tip.getConsumeEventType = function ($element) { if ($element.hasClass("o_field_date_range")) { return "apply.daterangepicker input"; } + if (config.device.isMobile && + $element.closest('.o_field_widget').is('.o_field_many2one, .o_field_many2many')) { + return "click"; + } return "input"; } else if ($element.hasClass('ui-draggable-handle')) { return "drag"; diff --git a/addons/web_tour/static/src/js/tour_manager.js b/addons/web_tour/static/src/js/tour_manager.js index 969f2dd66a0..647d0fe2563 100644 --- a/addons/web_tour/static/src/js/tour_manager.js +++ b/addons/web_tour/static/src/js/tour_manager.js @@ -2,9 +2,11 @@ odoo.define('web_tour.TourManager', function(require) { "use strict"; var core = require('web.core'); +var config = require('web.config'); var local_storage = require('web.local_storage'); var mixins = require('web.mixins'); var utils = require('web_tour.utils'); +var TourStepUtils = require('web_tour.TourStepUtils'); var RainbowMan = require('web.RainbowMan'); var RunningTourActionHelper = require('web_tour.RunningTourActionHelper'); var ServicesMixin = require('web.ServicesMixin'); @@ -116,7 +118,8 @@ return core.Class.extend(mixins.EventDispatcherMixin, ServicesMixin, { return tour.wait_for.then((function () { tour.current_step = parseInt(local_storage.getItem(get_step_key(name))) || 0; tour.steps = _.filter(tour.steps, (function (step) { - return !step.edition || step.edition === this.edition; + return (!step.edition || step.edition === this.edition) && + (step.mobile === undefined || step.mobile === config.device.isMobile); }).bind(this)); if (tour_is_consumed || tour.current_step >= tour.steps.length) { @@ -425,30 +428,6 @@ return core.Class.extend(mixins.EventDispatcherMixin, ServicesMixin, { } } }, - - /** - * Tour predefined steps - */ - STEPS: { - SHOW_APPS_MENU_ITEM: { - edition: 'community', - trigger: '.o_menu_apps a', - auto: true, - position: "bottom", - }, - - TOGGLE_HOME_MENU: { - edition: "enterprise", - trigger: ".o_main_navbar .o_menu_toggle", - content: _t('Click on the Home icon to navigate across apps.'), - position: "bottom", - }, - - WEBSITE_NEW_PAGE: { - trigger: "#new-content-menu > a", - auto: true, - position: "bottom", - }, - }, + stepUtils: new TourStepUtils(this) }); }); diff --git a/addons/web_tour/static/src/js/tour_service.js b/addons/web_tour/static/src/js/tour_service.js index 87cf69b7d83..af4c8db0dc5 100644 --- a/addons/web_tour/static/src/js/tour_service.js +++ b/addons/web_tour/static/src/js/tour_service.js @@ -7,9 +7,6 @@ var rpc = require('web.rpc'); var session = require('web.session'); var TourManager = require('web_tour.TourManager'); -if (config.device.isMobile) { - return Promise.reject(); -} /** * @namespace * @property {Object} active_tooltips diff --git a/addons/web_tour/static/src/js/tour_step_utils.js b/addons/web_tour/static/src/js/tour_step_utils.js new file mode 100644 index 00000000000..cd92f6fe019 --- /dev/null +++ b/addons/web_tour/static/src/js/tour_step_utils.js @@ -0,0 +1,160 @@ +odoo.define('web_tour.TourStepUtils', function (require) { +'use strict'; + +const core = require('web.core'); +const _t = core._t; + +return core.Class.extend({ + _getHelpMessage: (functionName, ...args) => `Generated by function tour utils ${functionName}(${args.join(', ')})`, + + addDebugHelp: helpMessage => step => { + if (typeof step.debugHelp === 'string') { + step.debugHelp = step.debugHelp + '\n' + helpMessage; + } else { + step.debugHelp = helpMessage; + } + return step; + }, + + editionEnterpriseModifier(step) { + step.edition = 'enterprise'; + return step; + }, + + mobileModifier(step) { + step.mobile = true; + return step; + }, + + showAppsMenuItem() { + return { + edition: 'community', + trigger: '.o_menu_apps a', + auto: true, + position: 'bottom', + }; + }, + + toggleHomeMenu() { + return { + edition: 'enterprise', + trigger: '.o_main_navbar .o_menu_toggle', + content: _t('Click on the Home icon to navigate across apps.'), + position: 'bottom', + }; + }, + + autoExpandMoreButtons(extra_trigger) { + return { + trigger: '.oe_button_box', + extra_trigger: extra_trigger, + auto: true, + run: actions => { + const $more = $('.oe_button_box .o_button_more'); + if ($more.length) { + actions.click($more); + } + }, + }; + }, + + goBackBreadcrumbsMobile(description, ...extraTrigger) { + return extraTrigger.map(element => ({ + mobile: true, + trigger: '.breadcrumb-item:not(.d-none):first', + extra_trigger: element, + content: description, + position: 'bottom', + debugHelp: this._getHelpMessage('goBackBreadcrumbsMobile', description, ...extraTrigger), + })); + }, + + goToAppSteps(dataMenuXmlid, description) { + return [ + this.showAppsMenuItem(), + { + trigger: `.o_app[data-menu-xmlid="${dataMenuXmlid}"]`, + content: description, + position: 'right', + edition: 'community', + }, + { + trigger: `.o_app[data-menu-xmlid="${dataMenuXmlid}"]`, + content: description, + position: 'bottom', + edition: 'enterprise', + }, + ].map(this.addDebugHelp(this._getHelpMessage('goToApp', dataMenuXmlid, description))); + }, + + openBuggerMenu(extraTrigger) { + return { + mobile: true, + trigger: '.o_mobile_menu_toggle', + extra_trigger: extraTrigger, + content: _t('Open bugger menu.'), + position: 'bottom', + debugHelp: this._getHelpMessage('openBuggerMenu', extraTrigger), + }; + }, + + statusbarButtonsSteps(innerTextButton, description, extraTrigger) { + return [ + { + mobile: true, + trigger: '.o_statusbar_buttons .btn.dropdown-toggle:contains(Action)', + extra_trigger: extraTrigger, + content: _t('Open Action Dropdown Menu.'), + position: 'bottom', + }, { + trigger: `.o_statusbar_buttons button:enabled:contains('${innerTextButton}')`, + content: description, + position: 'bottom', + }, + ].map(this.addDebugHelp(this._getHelpMessage('statusbarButtonsSteps', innerTextButton, description, extraTrigger))); + }, + + simulateEnterKeyboardInSearchModal() { + return { + mobile: true, + trigger: '.o_searchview_input', + extra_trigger: '.modal:not(.o_inactive_modal) .dropdown-menu.o_searchview_autocomplete', + position: 'bottom', + run: action => { + const keyboardEventUp = new KeyboardEvent('keyup', { + bubbles: true, + cancelable: true, + key: 'Enter', + code: 'Enter', + which: 13, + keyCode: 13 + }); + action.tip_widget.$anchor[0].dispatchEvent(keyboardEventUp); + }, + debugHelp: this._getHelpMessage('simulateEnterKeyboardInSearchModal'), + }; + }, + + mobileKanbanSearchMany2X(modalTitle, valueSearched) { + return [ + { + mobile: true, + trigger: '.o_enable_searchview', + extra_trigger: `.modal:not(.o_inactive_modal) .modal-title:contains('${modalTitle}')`, + position: 'bottom', + }, { + mobile: true, + trigger: '.o_searchview_input', + position: 'bottom', + run: `text ${valueSearched}`, + }, + this.simulateEnterKeyboardInSearchModal(), + { + mobile: true, + trigger: `.o_kanban_record .o_kanban_record_title :contains('${valueSearched}')`, + position: 'bottom', + }, + ].map(this.addDebugHelp(this._getHelpMessage('mobileKanbanSearchMany2X', modalTitle, valueSearched))); + }, +}); +}); diff --git a/addons/web_tour/views/tour_templates.xml b/addons/web_tour/views/tour_templates.xml index 816405a39d5..09376487f73 100644 --- a/addons/web_tour/views/tour_templates.xml +++ b/addons/web_tour/views/tour_templates.xml @@ -10,6 +10,7 @@ + diff --git a/addons/website/static/tests/tours/dashboard_tour.js b/addons/website/static/tests/tours/dashboard_tour.js index b02747c6caa..de1a5f10473 100644 --- a/addons/website/static/tests/tours/dashboard_tour.js +++ b/addons/website/static/tests/tours/dashboard_tour.js @@ -6,7 +6,7 @@ var tour = require("web_tour.tour"); tour.register("backend_dashboard", { test: true, url: "/web", -}, [tour.STEPS.SHOW_APPS_MENU_ITEM, +}, [tour.stepUtils.showAppsMenuItem(), { trigger: 'a[data-menu-xmlid="website.menu_website_configuration"]', }, { diff --git a/addons/website_blog/static/src/js/tours/website_blog.js b/addons/website_blog/static/src/js/tours/website_blog.js index c9a798b2abf..db7510ceb9f 100644 --- a/addons/website_blog/static/src/js/tours/website_blog.js +++ b/addons/website_blog/static/src/js/tours/website_blog.js @@ -8,7 +8,11 @@ odoo.define("website_blog.tour", function (require) { tour.register("blog", { url: "/", - }, [tour.STEPS.WEBSITE_NEW_PAGE, { + }, [{ + trigger: '#new-content-menu > a', + auto: true, + position: 'bottom', + }, { trigger: "a[data-action=new_blog_post]", content: _t("Select this menu item to create a new blog post."), position: "bottom", diff --git a/addons/website_event/static/src/js/tours/website_event.js b/addons/website_event/static/src/js/tours/website_event.js index 8e693c9ef8e..3f704012527 100644 --- a/addons/website_event/static/src/js/tours/website_event.js +++ b/addons/website_event/static/src/js/tours/website_event.js @@ -8,7 +8,11 @@ odoo.define("website_event.tour", function (require) { tour.register("event", { url: "/", - }, [tour.STEPS.WEBSITE_NEW_PAGE, { + }, [{ + trigger: '#new-content-menu > a', + auto: true, + position: 'bottom', + }, { trigger: "a[data-action=new_event]", content: _t("Click here to create a new event."), position: "bottom", diff --git a/odoo/addons/test_main_flows/static/tests/tours/main_flow.js b/odoo/addons/test_main_flows/static/tests/tours/main_flow.js index e32a70ace08..4f079715b2e 100644 --- a/odoo/addons/test_main_flows/static/tests/tours/main_flow.js +++ b/odoo/addons/test_main_flows/static/tests/tours/main_flow.js @@ -1,48 +1,39 @@ -odoo.define('test_main_flows.tour', function(require) { +odoo.define('test_main_flows.tour', function (require) { "use strict"; -var core = require('web.core'); -var tour = require('web_tour.tour'); - -var _t = core._t; +const {_t} = require('web.core'); +const tour = require('web_tour.tour'); tour.register('main_flow_tour', { test: true, url: "/web", -}, [tour.STEPS.SHOW_APPS_MENU_ITEM, { - trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]', - content: _t('Organize your sales activities with the Sales app.'), - position: 'right', - edition: 'community' -}, { - trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]', - content: _t('Organize your sales activities with the Sales app.'), - position: 'bottom', - edition: 'enterprise' -}, { +}, [ +...tour.stepUtils.goToAppSteps('sale.sale_menu_root', _t('Organize your sales activities with the Sales app.')), +tour.stepUtils.openBuggerMenu("li.breadcrumb-item.active:contains('Quotations')"), +{ // Add Stockable product trigger: ".o_menu_sections a:contains('Products')", extra_trigger: '.o_main_navbar', - content: _t("Let\'s create products."), + content: _t("Let's create products."), position: "bottom", }, { trigger: ".o_menu_sections a:has(span:contains('Products'))", - content: _t("Let\'s create products."), + content: _t("Let's create products."), position: "bottom" }, { trigger: '.o-kanban-button-new', - extra_trigger: '.o_kanban_view', - content: _t('Let\'s create your first product.'), + extra_trigger: "li.breadcrumb-item.active:contains('Products')", + content: _t("Let's create your first product."), position: 'bottom', }, { trigger: 'input[name=name]', extra_trigger: '.o_form_sheet', - content: _t('Let\'s enter the name.'), + content: _t("Let's enter the name."), position: 'left', run: 'text the_flow.product', }, { - trigger: "select[name=type]", - content: _t('Let\'s enter the product type'), + trigger: "select[name=type]", + content: _t("Let's enter the product type"), position: 'left', run: 'text "product"', }, { @@ -66,11 +57,12 @@ tour.register('main_flow_tour', { content: _t('Go to main tab'), position: 'top', }, { + mobile: false, trigger: ".o_field_widget[name=taxes_id] input", content: _t("Focus on customer taxes field."), run: function (actions) { actions.click(); - var $e = $('.ui-menu-item:not(.o_m2o_dropdown_option) > a.ui-state-active'); + const $e = $('.ui-menu-item:not(.o_m2o_dropdown_option) > a.ui-state-active'); if ($e.length) { actions.click($e); } else { @@ -79,20 +71,11 @@ tour.register('main_flow_tour', { }, }, { trigger: '.o_form_button_save', - content: _t('Save this product and the modifications you\'ve made to it.'), + content: _t("Save this product and the modifications you've made to it."), position: 'bottom', -}, { - trigger: ".oe_button_box", - extra_trigger: '.o_form_readonly', - auto: true, - run: function (actions) { - // auto expand "More" buttons - var $more = $(".oe_button_box .o_button_more"); - if ($more.length) { - actions.click($more); - } - }, -},{ +}, +tour.stepUtils.autoExpandMoreButtons('.o_form_readonly'), +{ trigger: ".oe_button_box .oe_stat_button:has(div[name=bom_count])", extra_trigger: '.o_form_readonly', content: _t('See Bill of material'), @@ -103,20 +86,41 @@ tour.register('main_flow_tour', { position: "bottom", }, { // Add first component + // FIXME in mobile replace list by kanban + form trigger: ".o_field_x2many_list_row_add > a", extra_trigger: ".o_form_editable", content: _t("Click here to add some lines."), position: "bottom", }, { + mobile: false, trigger: ".o_selected_row .o_required_modifier[name=product_id] input", content: _t("Select a product, or create a new one on the fly."), position: "right", run: "text the_flow.component1", }, { + mobile: false, trigger: ".ui-menu-item > a:contains('the_flow.component1')", auto: true, +}, { + mobile: true, + trigger: ".o_selected_row .o_required_modifier[name=product_id] input", + content: _t("Click here to open kanban search mobile."), + position: "bottom", +}, { + mobile: true, + trigger: ".modal-dialog .btn:contains('Create')", + extra_trigger: ".modal-dialog", + content: _t("Click here to add new line."), + position: "left", +}, { + mobile: true, + trigger: '.modal-body .o_form_editable .o_field_widget.o_input[name="name"]', + content: _t("Select a product, or create a new one on the fly."), + position: "right", + run: "text the_flow.component1", }, { // Edit first component + mobile: false, trigger: ".o_selected_row .o_external_button", content: _t("Click here to edit your component"), position: "right", @@ -133,52 +137,127 @@ tour.register('main_flow_tour', { content: _t('Go to purchase tab'), position: 'top', }, { - trigger: ".o_field_widget[name=seller_ids] .o_field_x2many_list_row_add > a", - content: _t('Let\'s enter the cost price'), + mobile: false, + trigger: ".o_field_widget[name=seller_ids] .o_field_x2many_list_row_add > a", + content: _t("Let's enter the cost price"), position: 'right', }, { - trigger: ".o_field_widget[name=name] input", + mobile: false, + trigger: ".o_field_widget[name=name] input", extra_trigger: ".modal-dialog", content: _t('Select a seller'), position: 'top', run: "text the_flow.vendor", }, { + mobile: false, trigger: ".ui-menu-item > a:contains('the_flow.vendor')", in_modal: false, }, { - trigger: ".o_field_widget[name=price]", + mobile: true, + trigger: ".o_field_widget[name=seller_ids] .o-kanban-button-new", + content: _t("Let's select a vendor"), + position: 'bottom', +}, { + mobile: true, + trigger: ".o_form_editable .o_field_many2one[name=name] input", + extra_trigger: ".modal:not(.o_inactive_modal) .o_form_editable div:contains('Vendor')", + content: _t("Select a vendor, or create a new one on the fly."), + position: "bottom", +}, { + mobile: true, + trigger: ".modal-footer .btn:contains('Create')", + extra_trigger: ".modal:not(.o_inactive_modal) .modal-title:contains('Vendor')", + content: _t("Select a vendor, or create a new one on the fly."), + position: "right", +}, { + mobile: true, + trigger: ".modal-dialog .o_field_widget[name=name] input", + extra_trigger: ".modal:not(.o_inactive_modal) .modal-dialog .o_field_radio.o_field_widget[name=company_type]", + content: _t('Select a seller'), + position: 'top', + run: "text the_flow.vendor", +}, { + mobile: true, + trigger: '.btn:has(:contains(Save))', + extra_trigger: ".modal:not(.o_inactive_modal) .modal-title:contains('Vendor')", + content: _t("Save this product and the modifications you've made to it."), + position: 'right', +}, { + trigger: ".o_field_widget[name=price]", content: _t('Set the cost price'), position: 'right', run: "text 1", }, { + mobile: true, + trigger: ".modal-footer .btn-primary:contains('Save & Close')", + extra_trigger: ".modal:not(.o_inactive_modal) .modal-title:contains('Vendor')", + content: _t('Save & Close'), + position: 'right', +}, { + mobile: true, + trigger: ".modal-footer .btn-primary:contains('Save')", + extra_trigger: ".modal:not(.o_inactive_modal) .modal-title:contains('Component')", + content: _t('Save'), + position: 'right', +}, { + mobile: true, + trigger: '.o_field_widget[name=code]', + extra_trigger: ".o_field_widget[name=product_id] > .o_external_button", // Wait name_create + // click somewhere else to exit cell focus +}, { + mobile: false, trigger: 'label:contains("Vendor Taxes")', extra_trigger: ".o_field_widget[name=name] > .o_external_button", // Wait name_create // click somewhere else to exit cell focus -}, -{ +}, { + mobile: false, trigger: ".modal-footer .btn-primary", content: _t('Save'), position: 'bottom', -}, - { +}, { // Add second component + mobile: false, trigger: ".o_field_x2many_list_row_add > a", extra_trigger: "body:not(:has(table.o_list_table div.o_field_widget[name='product_id'] input))", content: _t("Click here to add some lines."), position: "bottom", -}, -{ +}, { + mobile: true, + trigger: ".o_field_x2many_list_row_add > a", + extra_trigger: ".o_form_editable", + content: _t("Click here to add some lines."), + position: "bottom", +}, { + mobile: true, + trigger: ".o_selected_row .o_required_modifier[name=product_id] input", + content: _t("Click here to open kanban search mobile."), + position: "bottom", +}, { + mobile: true, + trigger: ".modal-dialog .btn:contains('Create')", + extra_trigger: ".modal-dialog", + content: _t("Click here to add new line."), + position: "left", +}, { + mobile: true, + trigger: ".modal-body .o_form_editable .o_field_widget.o_input[name=\"name\"]", + content: _t("Select a product, or create a new one on the fly."), + position: "right", + run: "text the_flow.component2", +}, { + mobile: false, trigger: ".o_selected_row .o_required_modifier[name=product_id] input", extra_trigger: '.o_field_widget[name=bom_line_ids] .o_data_row:nth(1).o_selected_row', content: _t("Select a product, or create a new one on the fly."), position: "right", run: "text the_flow.component2", -}, -{ +}, { + mobile: false, trigger: ".ui-menu-item > a:contains('the_flow.component2')", auto: true, }, { // Edit second component + mobile: false, trigger: ".o_selected_row .o_external_button", content: _t("Click here to edit your component"), position: "right", @@ -187,28 +266,63 @@ tour.register('main_flow_tour', { content: _t('Go to purchase tab'), position: 'top', }, { - trigger: ".o_field_widget[name=seller_ids] .o_field_x2many_list_row_add > a", - content: _t('Let\'s enter the cost price'), + mobile: true, + trigger: ".o_field_widget[name=seller_ids] .o-kanban-button-new", + content: _t("Let's select a vendor"), + position: 'bottom', +}, { + mobile: true, + trigger: '.o_form_editable .o_field_many2one[name="name"] input', + extra_trigger: ".modal:not(.o_inactive_modal) .o_form_editable div:contains('Vendor')", + content: _t("Select a vendor, or create a new one on the fly."), + position: "bottom", +}, +...tour.stepUtils.mobileKanbanSearchMany2X('Vendor', 'the_flow.vendor'), +{ + mobile: false, + trigger: ".o_field_widget[name=seller_ids] .o_field_x2many_list_row_add > a", + content: _t("Let's enter the cost price"), position: 'right', }, { - trigger: ".o_field_widget[name=name] input", + mobile: false, + trigger: ".o_field_widget[name=name] input", extra_trigger: ".modal-dialog", content: _t('Select a seller'), position: 'top', run: "text the_flow.vendor", }, { + mobile: false, trigger: ".ui-menu-item > a:contains('the_flow.vendor')", auto: true, in_modal: false, }, { - trigger: ".o_field_widget[name=price]", + trigger: ".o_field_widget[name=price]", content: _t('Set the cost price'), position: 'right', run: "text 1", }, { + mobile: true, + trigger: ".modal-footer .btn-primary:contains('Save & Close')", + extra_trigger: ".modal:not(.o_inactive_modal) .modal-title:contains('Vendor')", + content: _t('Save & Close'), + position: 'right', +}, { + mobile: true, + trigger: ".modal-footer .btn-primary:contains('Save')", + extra_trigger: ".modal:not(.o_inactive_modal) .modal-title:contains('Component')", + content: _t('Save'), + position: 'right', +}, { + mobile: true, + trigger: '.o_field_widget[name=code]', + extra_trigger: ".o_field_widget[name=product_id] > .o_external_button", // Wait name_create + // click somewhere else to exit cell focus +}, { + mobile: false, trigger: 'label:contains("Vendor Taxes")', // click somewhere else to exit cell focus -},{ +}, { + mobile: false, trigger: ".modal-footer .btn-primary", extra_trigger: ".o_field_widget[name=seller_ids] .o_data_row td:nth-child(2):contains('the_flow.vendor')", content: _t('Save'), @@ -219,19 +333,27 @@ tour.register('main_flow_tour', { content: _t('Save the bom.'), position: 'bottom', }, { + mobile: false, trigger: ".breadcrumb-item:first", content: _t("Use the breadcrumbs to go back to products."), position: "bottom" -}, { +}, +...tour.stepUtils.goBackBreadcrumbsMobile( + _t("Use the breadcrumbs to go back to products."), + undefined, + ".breadcrumb-item.active:contains('Bill of Materials')", + ".breadcrumb-item.active:contains('the_flow.product')" + ), +{ // Add service product trigger: '.o-kanban-button-new', extra_trigger: '.o_kanban_view', - content: _t('Let\'s create your second product.'), + content: _t("Let's create your second product."), position: 'bottom', }, { trigger: 'input[name=name]', extra_trigger: '.o_form_sheet', - content: _t('Let\'s enter the name.'), + content: _t("Let's enter the name."), position: 'left', run: 'text the_flow.service', }, { @@ -240,11 +362,12 @@ tour.register('main_flow_tour', { position: 'left', run: 'text "service"', }, { + mobile: false, trigger: ".o_field_widget[name=taxes_id] input", content: _t("Focus on customer taxes field."), run: function (actions) { actions.click(); - var $e = $('.ui-menu-item:not(.o_m2o_dropdown_option) > a.ui-state-active'); + const $e = $('.ui-menu-item:not(.o_m2o_dropdown_option) > a.ui-state-active'); if ($e.length) { actions.click($e); } else { @@ -264,17 +387,43 @@ tour.register('main_flow_tour', { content: _t('Change track service'), position: 'left', }, { + mobile: false, trigger: '.o_field_widget[name=project_id] input', content: _t('Choose project'), position: 'left', run: 'text the_flow.project', }, { + mobile: true, + trigger: '.o_field_widget[name=project_id] input', + content: _t('Choose project'), + position: 'left', +}, { + mobile: true, + trigger: ".modal-dialog .btn:contains('Create')", + extra_trigger: ".modal-dialog", + content: _t("Click here to add new line."), + position: "left", +}, { + mobile: true, + trigger: 'input[name=name]', + extra_trigger: ".modal:not(.o_inactive_modal) .modal-title:contains('Project')", + content: _t('Let\'s enter the name.'), + position: 'left', + run: 'text the_flow.project', +}, { + mobile: false, trigger: ".ui-menu-item > a:contains('the_flow.project')", auto: true, +}, { + mobile: true, + trigger: ".modal-footer .btn-primary:contains('Save')", + extra_trigger: ".modal:not(.o_inactive_modal) .modal-title:contains('Project')", + content: _t('Save'), + position: 'right', }, { trigger: '.o_form_button_save', extra_trigger: '.o_field_widget[name=project_id] > .o_external_button', // Wait name_create - content: _t('Save this product and the modifications you\'ve made to it.'), + content: _t("Save this product and the modifications you've made to it."), position: 'bottom', }, { // Create an opportunity @@ -282,17 +431,9 @@ tour.register('main_flow_tour', { trigger: '.o_menu_toggle', content: _t('Go back to the home menu'), position: 'bottom', -}, tour.STEPS.SHOW_APPS_MENU_ITEM, { - trigger: '.o_app[data-menu-xmlid="crm.crm_menu_root"]', - content: _t('Organize your sales activities with the CRM app.'), - position: 'right', - edition: 'community' -}, { - trigger: '.o_app[data-menu-xmlid="crm.crm_menu_root"]', - content: _t('Organize your sales activities with the CRM app.'), - position: 'bottom', - edition: 'enterprise' -}, { +}, +...tour.stepUtils.goToAppSteps('crm.crm_menu_root', _t('Organize your sales activities with the CRM app.')), +{ trigger: ".o-kanban-button-new", extra_trigger: '.o_opportunity_kanban', content: _t("Click here to create your first opportunity and add it to your pipeline."), @@ -303,74 +444,140 @@ tour.register('main_flow_tour', { position: "right", run: "text the_flow.opportunity", }, { + mobile: false, trigger: ".o_kanban_quick_create .o_field_widget[name=partner_id] input", content: _t("Write the name of your customer to create one on the fly, or select an existing one."), position: "left", run: "text the_flow.customer", }, { + mobile: true, + trigger: ".o_kanban_quick_create .o_field_widget[name=partner_id] input", + content: _t("Write the name of your customer to create one on the fly, or select an existing one."), + position: "left", +}, { + mobile: true, + trigger: ".modal-dialog .btn:contains('Create')", + extra_trigger: ".modal-dialog", + content: _t("Click here to add new line."), + position: "left", +}, { + mobile: true, + trigger: '.o_field_widget[name=name] input', + extra_trigger: ".modal:not(.o_inactive_modal) .modal-title:contains('Customer')", + content: _t('Let\'s enter the name.'), + position: 'left', + run: 'text the_flow.customer', +}, { + mobile: false, trigger: ".ui-menu-item > a:contains('the_flow.customer')", auto: true, +}, { + mobile: true, + trigger: ".modal-footer .btn-primary:contains('Save')", + extra_trigger: ".modal:not(.o_inactive_modal) .modal-title:contains('Customer')", + content: _t('Save'), + position: 'right', }, { trigger: ".o_kanban_quick_create .o_kanban_add", extra_trigger: ".o_kanban_quick_create .o_field_widget[name=partner_id] > .o_external_button", // Wait name_create content: _t("Click here to add your opportunity."), position: "right", }, { + mobile: false, trigger: ".o_kanban_group:first .o_kanban_record:has(span:contains('the_flow.opportunity'))", content: _t("Drag & drop opportunities between columns as you progress in your sales cycle."), position: "right", run: "drag_and_drop .o_opportunity_kanban .o_kanban_group:eq(2) ", }, { + mobile: false, trigger: ".o_kanban_group:eq(2) > .o_kanban_record:has(span:contains('the_flow.opportunity'))", content: _t("Click on an opportunity to zoom to it."), position: "bottom", }, { -// Create a quotation - trigger: ".o_statusbar_buttons > button:enabled:contains('New Quotation')", - content: _t('

Create a quotation

'), - position: "right" + mobile: true, + trigger: ".o_kanban_group:first .o_kanban_record:has(span:contains('the_flow.opportunity'))", + content: _t("Open the_flow.opportunity"), + position: "bottom", }, { + mobile: true, + trigger: ".o_statusbar_status .btn.dropdown-toggle:contains(New)", + content: _t("Change status from New to proposition."), + position: "bottom", +}, { + mobile: true, + trigger: ".o_statusbar_status .btn:contains(Proposition)", + content: _t("Change status from New to proposition."), + position: "bottom", +}, +// Create a quotation +...tour.stepUtils.statusbarButtonsSteps('New Quotation', _t('

Create a quotation

')), +{ + mobile: false, trigger: ".o_field_widget[name=order_line] .o_field_x2many_list_row_add > a", content: _t("Click here to add some lines to your quotations."), position: "bottom", +}, { + mobile: true, + trigger: ".o_field_widget[name=order_line] .btn:contains(Add)", + content: _t("Click here to add some lines to your quotations."), + position: "bottom", }, { /** * We need both triggers because the "sale_product_configurator" module replaces the * "product_id" field with a "product_template_id" field. * This selector will still only ever select one element. */ + mobile: false, trigger: ".o_field_widget[name=product_id] input, .o_field_widget[name=product_template_id] input", content: _t("Select a product, or create a new one on the fly. The product will define the default sales price (that you can change), taxes and description automatically."), position: "right", run: function (actions) { actions.text("the_flow.product", this.$anchor); // fake keydown to trigger search - var keyDownEvent = jQuery.Event("keydown"); + const keyDownEvent = $.Event("keydown"); keyDownEvent.which = 42; this.$anchor.trigger(keyDownEvent); - var $descriptionElement = $('.o_form_editable textarea[name="name"]'); + const $descriptionElement = $('.o_form_editable textarea[name="name"]'); // when description changes, we know the product has been loaded - $descriptionElement.change(function () { - if ($(this).val().indexOf('the_flow.product') !== -1){ + $descriptionElement.on('change', function () { + if ($(this).val().indexOf('the_flow.product') !== -1) { $(this).addClass('product_loading_success'); } }); }, }, { + mobile: false, trigger: ".ui-menu-item > a:contains('the_flow.product')", }, { + mobile: false, trigger: '.o_form_editable textarea[name="name"].product_loading_success', run: function () {} // wait for product loading }, { + mobile: true, + trigger: ".o_field_widget[name=product_id] input", + extra_trigger: ".modal:not(.o_inactive_modal) .modal-title:contains('Order Lines')", + content: _t("Select a product, or create a new one on the fly. The product will define the default sales price (that you can change), taxes and description automatically."), + position: "right", +}, +...tour.stepUtils.mobileKanbanSearchMany2X('Product', 'the_flow.product'), +{ + mobile: false, trigger: ".o_field_widget[name=order_line] .o_field_x2many_list_row_add > a", content: _t("Click here to add some lines to your quotations."), position: "bottom", +}, { + mobile: true, + trigger: ".modal-footer .btn-primary:contains('Save & New')", + extra_trigger: ".modal:not(.o_inactive_modal) .modal-title:contains('Order Lines')", + content: _t('Save & New'), + position: 'right', }, { /** * We need both triggers because the "sale_product_configurator" module replaces the * "product_id" field with a "product_template_id" field. * This selector will still only ever select one element. */ + mobile: false, trigger: ".o_field_widget[name=product_id] input, .o_field_widget[name=product_template_id] input", extra_trigger: '.o_field_widget[name=order_line] .o_data_row:nth(1).o_selected_row', content: _t("Select a product"), @@ -378,48 +585,60 @@ tour.register('main_flow_tour', { run: function (actions) { actions.text("the_flow.service", this.$anchor); // fake keydown to trigger search - var keyDownEvent = jQuery.Event("keydown"); + const keyDownEvent = $.Event("keydown"); keyDownEvent.which = 42; this.$anchor.trigger(keyDownEvent); - var $descriptionElement = $('.o_form_editable textarea[name="name"]'); + const $descriptionElement = $('.o_form_editable textarea[name="name"]'); // when description changes, we know the product has been loaded - $descriptionElement.change(function () { - if ($(this).val().indexOf('the_flow.service') !== -1){ + $descriptionElement.on('change', function () { + if ($(this).val().indexOf('the_flow.service') !== -1) { $(this).addClass('product_service_loading_success'); } }); }, }, { + mobile: false, trigger: ".ui-menu-item > a:contains('the_flow.service')", }, { + mobile: false, trigger: '.o_form_editable textarea[name="name"].product_service_loading_success', run: function () {} // wait for product loading }, { + mobile: false, trigger: 'label:contains("Untaxed Amount")', // click somewhere else to exit cell focus }, { - trigger: ".o_statusbar_buttons > button:enabled:contains('Send by Email')", - extra_trigger: ".o_statusbar_status .btn-primary:contains('Quotation')", - content: _t("Try to send it to email"), - position: "bottom", -}, { + mobile: true, + trigger: ".o_field_widget[name=product_id] input", + extra_trigger: '.o_field_x2many_kanban[name=order_line] .oe_kanban_card:contains(the_flow.product)', + content: _t("Select a product, or create a new one on the fly. The product will define the default sales price (that you can change), taxes and description automatically."), + position: "right", +}, +...tour.stepUtils.mobileKanbanSearchMany2X('Product', 'the_flow.service'), +{ + mobile: true, + trigger: ".modal-footer .btn-primary:contains('Save & Close')", + extra_trigger: ".modal:not(.o_inactive_modal) .modal-title:contains('Order Lines')", + content: _t('Save & Close'), + position: 'right', +}, +...tour.stepUtils.statusbarButtonsSteps('Send by Email', _t("Try to send it to email"), ".o_statusbar_status .btn-primary:contains('Quotation')"), +{ trigger: ".o_field_widget[name=email]", content: _t("Enter an email address"), position: "right", run: "text test@the_flow.com", -},{ +}, { trigger: ".modal-footer .btn-primary", content: _t("Save your changes"), position: "bottom", -}, { +}, { trigger: ".modal-footer .btn-primary span:contains('Send')", content: _t("Try to send it to email"), position: "bottom", -}, { - trigger: ".o_statusbar_buttons > button:enabled:contains('Confirm')", - content: _t("

Confirm this quotation

"), - position: "bottom" -}, { +}, +...tour.stepUtils.statusbarButtonsSteps('Confirm', _t("

Confirm this quotation

")), +{ trigger: ".o_form_button_save", extra_trigger: ".o_statusbar_status .btn-primary:contains('Sales Order')", content: _t("

Save this quotation

"), @@ -430,18 +649,12 @@ tour.register('main_flow_tour', { trigger: '.o_menu_toggle', content: _t('Go back to the home menu'), position: 'bottom', -}, tour.STEPS.SHOW_APPS_MENU_ITEM, { - trigger: '.o_app:contains("Inventory")', - content: _t('Go to Inventory'), - position: 'right', - edition: 'community' -}, { - trigger: '.o_app > div:contains("Inventory")', - content: _t('Go to Inventory'), - position: 'bottom', - edition: 'enterprise' -}, { +}, +...tour.stepUtils.goToAppSteps('stock.menu_stock_root', _t('Go to Inventory')), +tour.stepUtils.openBuggerMenu("li.breadcrumb-item.active:contains('Inventory Overview')"), +{ trigger: ".o_menu_sections a:contains('Master Data')", + extra_trigger: '.o_main_navbar', content: _t("Go to Master Data"), position: "bottom" }, { @@ -449,18 +662,33 @@ tour.register('main_flow_tour', { content: _t("Reordering Rules"), position: "bottom" }, { + mobile: false, trigger: ".o_list_button_add", content: _t("Let's create a new reordering rule"), position: "right", }, { + mobile: true, + trigger: ".o-kanban-button-new", + content: _t("Let's create a new reordering rule"), + position: "right", +}, { + mobile: false, trigger: ".o_field_widget[name=product_id] input", content: _t("Write the name of your product."), position: "top", run: "text the_flow.component2", }, { + mobile: false, trigger: ".ui-menu-item > a:contains('the_flow.component2')", auto: true, }, { + mobile: true, + trigger: ".o_field_widget[name=product_id] input", + content: _t("Write the name of your product."), + position: "top", +}, +...tour.stepUtils.mobileKanbanSearchMany2X('Product', 'the_flow.component2'), +{ trigger: ".o_field_widget[name=product_min_qty]", content: _t("Set the minimum product quantity"), position: "right", @@ -474,12 +702,14 @@ tour.register('main_flow_tour', { trigger: ".o_form_button_save", content: _t("

Save this reordering rule

"), position: "bottom" -}, { +}, +tour.stepUtils.openBuggerMenu("li.breadcrumb-item.active:contains('OP/')"), +{ // Run the schedulers trigger: ".o_menu_sections a:contains('Operations')", content: _t("Go to Run Schedulers"), position: "bottom" -},{ +}, { trigger: ".o_menu_sections a[data-menu-xmlid='stock.menu_procurement_compute']", content: _t("Click on schedulers"), position: "bottom" @@ -494,33 +724,23 @@ tour.register('main_flow_tour', { trigger: '.o_menu_toggle', content: _t('Go back to the home menu'), position: 'bottom', -}, tour.STEPS.SHOW_APPS_MENU_ITEM, { - trigger: '.o_app:contains("Purchase")', - content: _t('Go to Purchase'), - position: 'right', - edition: 'community' -}, { - trigger: '.o_app > div:contains("Purchase")', - content: _t('Go to Purchase'), - position: 'bottom', - edition: 'enterprise' -}, { +}, +...tour.stepUtils.goToAppSteps('purchase.menu_purchase_root', _t('Go to Purchase')), +{ + mobile: false, trigger: '.o_data_row:has(.o_data_cell:contains("the_flow.vendor"))', content: _t('Select the generated request for quotation'), position: 'bottom', }, { - trigger: ".o_statusbar_buttons > button:enabled:contains('Confirm Order')", - content: _t("Confirm quotation"), - position: "bottom", -}, { - trigger: ".o_statusbar_buttons > button:enabled:contains('Receive Products')", - content: _t("Receive Product"), - position: "bottom", -}, { - trigger: ".o_statusbar_buttons > button:enabled:contains('Validate')", - content: _t("Validate"), - position: "bottom", -}, { + mobile: true, + trigger: '.o_kanban_record:has(.o_kanban_record_title:contains("the_flow.vendor"))', + content: _t('Select the generated request for quotation'), + position: 'bottom', +}, +...tour.stepUtils.statusbarButtonsSteps('Confirm Order', _t("Confirm quotation")), +...tour.stepUtils.statusbarButtonsSteps('Receive Products', _t("Receive Product"), ".o_statusbar_status .btn.dropdown-toggle:contains('Purchase Order')"), +...tour.stepUtils.statusbarButtonsSteps('Validate', _t("Validate"), ".o_statusbar_status .btn.dropdown-toggle:contains('Ready')"), +{ trigger: ".modal-footer .btn-primary", extra_trigger: ".modal-dialog", content: _t("Apply"), @@ -529,19 +749,11 @@ tour.register('main_flow_tour', { trigger: ".o_back_button a, .breadcrumb-item:not('.active'):last", content: _t('go back to the purchase order'), position: 'bottom', - }, { - trigger: ".o_statusbar_buttons > button:enabled:contains('Create Bill')", - content: _t('go to Vendor Bills'), - position: 'bottom', -}, { - trigger: ".o_statusbar_buttons > button:enabled:contains('Post')", - content: _t("Try to send it to email"), - position: "bottom", -}, { - trigger: ".o_statusbar_buttons > button:enabled:contains('Register Payment')", - content: _t("Register Payment"), - position: "bottom", -}, { +}, +...tour.stepUtils.statusbarButtonsSteps('Create Bill', _t('go to Vendor Bills'), ".o_statusbar_status .btn.dropdown-toggle:contains('Purchase Order')"), +...tour.stepUtils.statusbarButtonsSteps('Post', _t("Try to send it to email"), ".o_statusbar_status .btn.dropdown-toggle:contains('Draft')"), +...tour.stepUtils.statusbarButtonsSteps('Register Payment', _t("Register Payment"), ".o_statusbar_status .btn.dropdown-toggle:contains('Posted')"), +{ trigger: ".modal-footer .btn-primary", content: _t("Validate"), position: "bottom", @@ -550,60 +762,65 @@ tour.register('main_flow_tour', { trigger: '.o_menu_toggle', content: _t('Go back to the home menu'), position: 'bottom', -}, tour.STEPS.SHOW_APPS_MENU_ITEM, { - trigger: '.o_app:contains("Manufacturing")', - content: _t('Go to Manufacturing'), - position: 'right', - edition: 'community' -}, { - trigger: '.o_app > div:contains("Manufacturing")', - content: _t('Go to Manufacturing'), - position: 'bottom', - edition: 'enterprise' -}, { +}, +...tour.stepUtils.goToAppSteps('mrp.menu_mrp_root', _t('Go to Manufacturing')), +tour.stepUtils.openBuggerMenu("li.breadcrumb-item.active:contains('Manufacturing Orders'), li.breadcrumb-item.active:contains('Work Centers Overview')"), +{ + mobile: false, trigger: ".o_menu_sections a[data-menu-xmlid='mrp.menu_mrp_manufacturing']", content: _t('Click on Operations menuitem'), position: 'bottom', }, { + mobile: false, trigger: ".o_menu_sections a[data-menu-xmlid='mrp.menu_mrp_production_action']", content: _t('Open manufacturing orders'), position: 'bottom', }, { + mobile: false, trigger: '.o_data_row:has(.o_data_cell:contains("the_flow.product")):first', content: _t('Select the generated manufacturing order'), position: 'bottom', }, { - trigger: ".o_statusbar_buttons > button[name='action_assign']:enabled", - content: _t("Check availability"), + mobile: true, + trigger: ".o_menu_sections a:contains('Operations')", + extra_trigger: '.o_main_navbar', + content: _t("Let's go to manufacturing order."), position: "bottom", }, { - trigger: ".o_statusbar_buttons > button:enabled:contains('Produce')", - content: _t("Produce"), - position: "bottom", + mobile: true, + trigger: ".o_menu_sections a:has(span:contains('Manufacturing Orders'))", + content: _t("Let's go to manufacturing order."), + position: "bottom" }, { - trigger: ".modal-footer .btn-primary:nth-child(3)", + mobile: true, + trigger: '.o_kanban_record:has(.o_kanban_record_title:contains("the_flow.product")):first', + extra_trigger: "li.breadcrumb-item.active:contains('Manufacturing Orders')", + content: _t('Select the generated manufacturing order'), + position: 'bottom', +}, +...tour.stepUtils.statusbarButtonsSteps('Check availability', _t("Check availability")), +...tour.stepUtils.statusbarButtonsSteps('Produce', _t("Produce"), "body.o_web_client:not(.oe_wait)"), +{ + mobile: false, + trigger: ".modal-footer .btn-primary:nth-child(3)", content: _t('Record Production'), position: 'bottom', }, { - trigger: ".o_statusbar_buttons > button:enabled:contains('Mark as Done')", - content: _t("Mark as Done"), - position: "bottom", -}, { + mobile: true, + trigger: '.modal-footer .btn-primary[name="do_produce"]:not(.o_invisible_modifier)', + content: _t('Record Production'), + position: 'bottom', +}, +...tour.stepUtils.statusbarButtonsSteps('Mark as Done', _t("Mark as Done"), ".o_statusbar_status .btn.dropdown-toggle:contains('To Close')"), +{ edition: "enterprise", trigger: '.o_menu_toggle', content: _t('Go back to the home menu'), position: 'bottom', -}, tour.STEPS.SHOW_APPS_MENU_ITEM, { - trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]', - content: _t('Organize your sales activities with the Sales app.'), - position: 'bottom', - edition: 'community' -}, { - trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]', - content: _t('Organize your sales activities with the Sales app.'), - position: 'bottom', - edition: 'enterprise' -}, { +}, +...tour.stepUtils.goToAppSteps('sale.sale_menu_root', _t('Organize your sales activities with the Sales app.')), +tour.stepUtils.openBuggerMenu("li.breadcrumb-item.active:contains('Quotations')"), +{ trigger: ".o_menu_sections a[data-menu-xmlid='sale.sale_order_menu']", content: _t("Go to Sales menu"), position: "bottom" @@ -612,19 +829,37 @@ tour.register('main_flow_tour', { content: _t("Go to the sales orders"), position: "bottom" }, { + mobile: false, trigger: ".o_data_row:first", extra_trigger: '.o_control_panel .breadcrumb:contains("Sales Orders")', content: _t("Go to the last sale order"), position: "right" }, { + mobile: true, + trigger: ".o_kanban_record:first", + extra_trigger: '.o_control_panel .breadcrumb:contains("Sales Orders")', + content: _t("Go to the last sale order"), + position: "bottom" +}, +tour.stepUtils.mobileModifier(tour.stepUtils.autoExpandMoreButtons('.o_control_panel .breadcrumb:contains("S0")')), +{ + mobile: false, trigger: '.oe_button_box .oe_stat_button:has(div[name=tasks_count])', content: _t('See Tasks'), position: 'right', +}, { + mobile: true, + trigger: '.oe_button_box .oe_stat_button:has(div[name=tasks_count])', + extra_trigger: '.o_control_panel .breadcrumb:contains("S0")', + content: _t('See Tasks'), + position: 'bottom', }, { trigger: '.o_field_widget[name=project_id]', content: _t('See Project'), position: 'right', -}, { +}, +tour.stepUtils.mobileModifier(tour.stepUtils.autoExpandMoreButtons('.o_control_panel .breadcrumb:contains("the_flow.project")')), +{ trigger: '.oe_button_box .oe_stat_button:has(span:contains("Timesheets"))', extra_trigger: '.o_form_readonly', content: _t('See Timesheets'), @@ -634,6 +869,7 @@ tour.register('main_flow_tour', { content: _t('Add a Timesheet'), position: 'bottom', }, { + // FIXME replace list by kanban + form trigger: '.o_selected_row input[name=name]', content: _t('Set description'), position: 'bottom', @@ -644,8 +880,15 @@ tour.register('main_flow_tour', { position: 'bottom', run: 'click', }, { + mobile: false, trigger: ".ui-menu-item > a", auto: true, +}, { + mobile: true, + trigger: ".o_kanban_record .o_kanban_record_title :contains('the_flow.service')", + extra_trigger: ".modal:not(.o_inactive_modal) .modal-title:contains('Task')", + content: _t("Select the the_flow.vendor"), + position: "bottom", }, { trigger: '.o_selected_row input[name=unit_amount]', content: _t('Set time'), @@ -655,28 +898,29 @@ tour.register('main_flow_tour', { trigger: '.o_list_button_save', content: _t('Save'), position: 'bottom', -}, { +}, +...tour.stepUtils.goBackBreadcrumbsMobile( + _t('Back to the sale order'), + undefined, + ".breadcrumb-item.active:contains('the_flow.project')", + ".breadcrumb-item.active:contains('the_flow.service')" + ), +{ + mobile: false, trigger: '.breadcrumb-item:nth-child(2) a', extra_trigger: '.o_list_button_add', // Waiting save content: _t('Back to the sale order'), position: 'bottom', -}, { - trigger: ".o_statusbar_buttons > button:enabled:contains('Create Invoice')", - content: _t("Validate"), - position: "bottom", -}, { +}, +...tour.stepUtils.statusbarButtonsSteps('Create Invoice', _t("Validate"), ".o_field_widget[name=order_line]"), +{ trigger: ".modal-footer .btn-primary", content: _t("Create and View Invoices"), position: "bottom", -}, { - trigger: ".o_statusbar_buttons > button:enabled:contains('Post')", - content: _t("Validate"), - position: "bottom", -}, { - trigger: ".o_statusbar_buttons > button:enabled:contains('Register Payment')", - content: _t("Register Payment"), - position: "bottom", -}, { +}, +...tour.stepUtils.statusbarButtonsSteps('Post', _t("Validate"), ".breadcrumb-item.active:contains('Draft Invoice')"), +...tour.stepUtils.statusbarButtonsSteps('Register Payment', _t("Register Payment"), ".breadcrumb-item.active:contains('INV/')"), +{ trigger: ".modal-footer .btn-primary", content: _t("Validate"), position: "bottom", @@ -718,18 +962,19 @@ tour.register('main_flow_tour', { }, { edition: "enterprise", trigger: 'input[name=name]', - content: _t('Let\'s enter the reference.'), + content: _t("Let's enter the reference."), position: 'left', run: 'text the_flow.statement', }, { edition: "enterprise", - trigger: ".o_field_widget[name=balance_end_real] input", - content: _t('Let\'s calculate the ending balance.'), + trigger: ".o_field_widget[name=balance_end_real] input", + content: _t("Let's calculate the ending balance."), position: 'right', run: 'text 9010.85', // + 12.65 }, { + // FIXME convert list in kanban + form edition: "enterprise", - trigger: ".o_field_widget[name=line_ids] .o_field_x2many_list_row_add > a", + trigger: ".o_field_widget[name=line_ids] .o_field_x2many_list_row_add > a", content: _t("Click here to add some lines."), position: "bottom", }, { @@ -739,20 +984,34 @@ tour.register('main_flow_tour', { position: "bottom", run: "text 11.00", }, { + mobile: false, edition: "enterprise", trigger: ".o_selected_row .o_field_widget[name=partner_id] input", content: _t("Write the name of your customer."), position: "bottom", run: "text the_flow.customer", }, { + mobile: false, edition: "enterprise", trigger: ".ui-menu-item > a:contains('the_flow.customer')", in_modal: false, auto: true, +}, { + mobile: true, + edition: "enterprise", + trigger: ".o_selected_row .o_field_widget[name=partner_id] input", + content: _t("Write the name of your customer."), + position: "bottom", +}, { + mobile: true, + trigger: ".o_kanban_record .o_kanban_record_title :contains('the_flow.customer')", + extra_trigger: ".modal:not(.o_inactive_modal) .modal-title:contains('Partner')", + content: _t('Save'), + position: 'right', }, { edition: "enterprise", trigger: ".o_selected_row .o_field_widget[name=name]", - content: _t('Let\'s enter a name.'), + content: _t("Let's enter a name."), position: "bottom", run: "text the_flow.statement.line", }, { @@ -760,12 +1019,9 @@ tour.register('main_flow_tour', { trigger: '.o_form_button_save', content: _t('Save.'), position: 'bottom', -}, { - edition: "enterprise", - trigger: ".o_statusbar_buttons > button:enabled:contains('Reconcile')", - content: _t('

Reconcile

'), - position: "bottom", -}, { +}, +...tour.stepUtils.statusbarButtonsSteps('Reconcile', _t('

Reconcile

'), ".breadcrumb-item.active:contains('the_flow.statement')").map(tour.stepUtils.editionEnterpriseModifier), +{ edition: "enterprise", trigger: "button.o_reconcile, button.o_validate", content: _t('

Click on Reconcile

'), diff --git a/odoo/addons/test_main_flows/tests/test_flow.py b/odoo/addons/test_main_flows/tests/test_flow.py index 2f359d4cccc..d450c335253 100644 --- a/odoo/addons/test_main_flows/tests/test_flow.py +++ b/odoo/addons/test_main_flows/tests/test_flow.py @@ -1,12 +1,12 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. +import odoo import odoo.tests +import unittest +class BaseTestUi(odoo.tests.HttpCase): -@odoo.tests.tagged('post_install', '-at_install') -class TestUi(odoo.tests.HttpCase): - - def test_01_main_flow_tour(self): + def main_flow_tour(self): # Define minimal accounting data to run without CoA a_expense = self.env['account.account'].create({ 'code': 'X2120', @@ -68,3 +68,21 @@ class TestUi(odoo.tests.HttpCase): }) self.start_tour("/web", 'main_flow_tour', login="admin", timeout=180) + +@odoo.tests.tagged('post_install', '-at_install') +class TestUi(BaseTestUi): + + def test_01_main_flow_tour(self): + self.main_flow_tour() + +@odoo.tests.tagged('post_install', '-at_install') +class TestUiMobile(BaseTestUi): + + browser_size = '375x667' + + def test_01_main_flow_tour_mobile(self): + + if odoo.release.version_info[-1] == 'e': + self.main_flow_tour() + else: + raise unittest.SkipTest("Mobile testing not needed in community")