diff options
| author | eco-odoo <eco@odoo.com> | 2018-07-20 14:09:27 +0200 |
|---|---|---|
| committer | Yannick Tivisse <yti@odoo.com> | 2018-08-10 16:08:47 +0200 |
| commit | 89e358caa8627c5eff21ee63015dece0997db2b4 (patch) | |
| tree | 8534521fd812ec993c31d6cc0271bdb213d94d00 | |
| parent | 33dee649a891fc5e0385862cfdc0890b000749f2 (diff) | |
[FIX] onboarding: Fix/Update onboarding to v2.0
Specification
=============
- Fix lots of bug due to the BS4 migration
- Improve steps
- Validate some steps (Like the company configuration) in all the
onboarding bars if done in one onboarding bar.
- Only animate the confetti one time if a step is done, instead of
on each view loading
For more information, see: https://www.odoo.com/web#id=1869513&action=333&active_id=965&model=project.task&view_type=form&menu_id=4720
44 files changed, 736 insertions, 1079 deletions
diff --git a/addons/account/__manifest__.py b/addons/account/__manifest__.py index 13d366f8200..1f0aa99d6a4 100644 --- a/addons/account/__manifest__.py +++ b/addons/account/__manifest__.py @@ -86,7 +86,6 @@ You could use this simplified accounting in case you work with an (external) acc "static/src/xml/account_reconciliation.xml", "static/src/xml/account_payment.xml", "static/src/xml/account_report_backend.xml", - "static/src/xml/account_dashboard_setup_bar.xml", ], 'installable': True, 'application': True, diff --git a/addons/account/controllers/onboarding.py b/addons/account/controllers/onboarding.py index 1fd91294489..879fd0a82fc 100644 --- a/addons/account/controllers/onboarding.py +++ b/addons/account/controllers/onboarding.py @@ -10,13 +10,15 @@ class OnboardingController(http.Controller): It can be empty if the user has closed it or if he doesn't have the permission to see it. """ + company = request.env.user.company_id if not request.env.user._is_admin() or \ - request.env.user.company_id.account_invoice_onboarding_closed: + company.account_invoice_onboarding_state == 'closed': return {} return { 'html': request.env.ref('account.account_invoice_onboarding_panel').render({ - 'company': request.env.user.company_id, + 'company': company, + 'state': company.get_and_update_account_invoice_onboarding_state() }) } @@ -25,13 +27,15 @@ class OnboardingController(http.Controller): """ Returns the `banner` for the account dashboard onboarding panel. It can be empty if the user has closed it or if he doesn't have the permission to see it. """ + company = request.env.user.company_id if not request.env.user._is_admin() or \ - request.env.user.company_id.account_dashboard_onboarding_closed: + company.account_dashboard_onboarding_state == 'closed': return {} return { 'html': request.env.ref('account.account_dashboard_onboarding_panel').render({ - 'company': request.env.user.company_id, + 'company': company, + 'state': company.get_and_update_account_dashboard_onboarding_state() }) - }
\ No newline at end of file + } diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 0ba6359b6af..58711dfb92f 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -607,7 +607,7 @@ class AccountInvoice(models.Model): def message_post(self, **kwargs): if self.env.context.get('mark_invoice_as_sent'): self.filtered(lambda inv: not inv.sent).write({'sent': True}) - self.env.user.company_id.account_onboarding_sample_invoice_sent = True + self.env.user.company_id.set_onboarding_step_done('account_onboarding_sample_invoice_state') return super(AccountInvoice, self.with_context(mail_post_autofollow=True)).message_post(**kwargs) @api.model diff --git a/addons/account/models/account_journal_dashboard.py b/addons/account/models/account_journal_dashboard.py index a8e229883d1..da0c1dbb960 100644 --- a/addons/account/models/account_journal_dashboard.py +++ b/addons/account/models/account_journal_dashboard.py @@ -26,7 +26,6 @@ class account_journal(models.Model): kanban_dashboard_graph = fields.Text(compute='_kanban_dashboard_graph') show_on_dashboard = fields.Boolean(string='Show journal on dashboard', help="Whether this journal should be displayed on the dashboard or not", default=True) color = fields.Integer("Color Index", default=0) - account_setup_bank_data_done = fields.Boolean(string='Bank setup marked as done', related='company_id.account_setup_bank_data_done', help="Technical field used in the special view for the setup bar step.") def _graph_title_and_key(self): if self.type in ['sale', 'purchase']: @@ -434,23 +433,10 @@ class account_journal(models.Model): ##################### # Setup Steps Stuff # ##################### - @api.model - def retrieve_account_dashboard_setup_bar(self): - """ Returns the data used by the setup bar on the Accounting app dashboard.""" - company = self.env.user.company_id - return { - 'show_setup_bar': not company.account_setup_bar_closed, - 'company': company.account_setup_company_data_done, - 'bank': company.account_setup_bank_data_done, - 'fiscal_year': company.account_setup_fy_data_done, - 'chart_of_accounts': company.account_setup_coa_done, - 'initial_balance': company.opening_move_posted(), - } - def mark_bank_setup_as_done_action(self): """ Marks the 'bank setup' step as done in the setup bar and in the company.""" - self.company_id.account_setup_bank_data_done = True + self.company_id.set_onboarding_step_done('account_setup_bank_data_state') def unmark_bank_setup_as_done_action(self): """ Marks the 'bank setup' step as not done in the setup bar and in the company.""" - self.company_id.account_setup_bank_data_done = False + self.company_id.account_setup_bank_data_state = 'not_done' diff --git a/addons/account/models/company.py b/addons/account/models/company.py index 7e4ab7c216f..b659d0ad00e 100644 --- a/addons/account/models/company.py +++ b/addons/account/models/company.py @@ -71,33 +71,45 @@ Best Regards,''')) account_opening_journal_id = fields.Many2one(string='Opening Journal', comodel_name='account.journal', related='account_opening_move_id.journal_id', help="Journal where the opening entry of this company's accounting has been posted.") account_opening_date = fields.Date(string='Opening Date', related='account_opening_move_id.date', help="Date at which the opening entry of this company's accounting has been posted.") - #Fields marking the completion of a setup step - account_setup_company_data_done = fields.Boolean(string='Company Setup Marked As Done', help="Technical field holding the status of the company setup step.") - account_setup_bank_data_done = fields.Boolean('Bank Setup Marked As Done', help="Technical field holding the status of the bank setup step.") - account_setup_fy_data_done = fields.Boolean('Financial Year Setup Marked As Done', help="Technical field holding the status of the financial year setup step.") - account_setup_coa_done = fields.Boolean(string='Chart of Account Checked', help="Technical field holding the status of the chart of account setup step.") - account_setup_bar_closed = fields.Boolean(string='Setup Bar Closed', help="Technical field set to True when setup bar has been closed by the user.") - - # account invoice onboarding - account_invoice_onboarding_closed = fields.Boolean( - string="Account invoice onboarding panel closed", - help="Refers to the account invoice onboarding panel closed state.") - account_invoice_onboarding_folded = fields.Boolean( - string="Account invoice onboarding panel folded", - help="Refers to the account invoice onboarding panel folded state.") - - account_onboarding_invoice_layout_done = fields.Boolean("Onboarding invoice layout step done", - compute="_compute_account_onboarding_invoice_layout_done") - account_onboarding_sample_invoice_sent = fields.Boolean( - "Onboarding sample invoice step completed", default=False) + # Fields marking the completion of a setup step + # YTI FIXME : The selection should be factorize as a static list in base, like ONBOARDING_STEP_STATES + account_setup_bank_data_state = fields.Selection([('not_done', "Not done"), ('just_done', "Just done"), ('done', "Done")], string="State of the onboarding bank data step", default='not_done') + account_setup_fy_data_state = fields.Selection([('not_done', "Not done"), ('just_done', "Just done"), ('done', "Done")], string="State of the onboarding fiscal year step", default='not_done') + account_setup_coa_state = fields.Selection([('not_done', "Not done"), ('just_done', "Just done"), ('done', "Done")], string="State of the onboarding charts of account step", default='not_done') + account_onboarding_invoice_layout_state = fields.Selection([('not_done', "Not done"), ('just_done', "Just done"), ('done', "Done")], string="State of the onboarding invoice layout step", default='not_done') + account_onboarding_sample_invoice_state = fields.Selection([('not_done', "Not done"), ('just_done', "Just done"), ('done', "Done")], string="State of the onboarding sample invoice step", default='not_done') + account_onboarding_sale_tax_state = fields.Selection([('not_done', "Not done"), ('just_done', "Just done"), ('done', "Done")], string="State of the onboarding sale tax step", default='not_done') # account dashboard onboarding - account_dashboard_onboarding_closed = fields.Boolean( - string="Account dashboard onboarding panel closed", - help="Refers to the account dashboard onboarding panel closed state.") - account_dashboard_onboarding_folded = fields.Boolean( - string="Account dashboard onboarding panel folded", - help="Refers to the account dashboard onboarding panel folded state.") + account_invoice_onboarding_state = fields.Selection([('not_done', "Not done"), ('just_done', "Just done"), ('done', "Done"), ('closed', "Closed")], string="State of the account invoice onboarding panel", default='not_done') + account_dashboard_onboarding_state = fields.Selection([('not_done', "Not done"), ('just_done', "Just done"), ('done', "Done"), ('closed', "Closed")], string="State of the account dashboard onboarding panel", default='not_done') + + def get_and_update_account_invoice_onboarding_state(self): + """ This method is called on the controller rendering method and ensures that the animations + are displayed only one time. """ + return self.get_and_update_onbarding_state( + 'account_invoice_onboarding_state', + self.get_account_invoice_onboarding_steps_states_names() + ) + + # YTI FIXME: Define only one method that returns {'account': [], 'sale': [], ...} + def get_account_invoice_onboarding_steps_states_names(self): + """ Necessary to add/edit steps from other modules (payment acquirer in this case). """ + return [ + 'base_onboarding_company_state', + 'account_onboarding_invoice_layout_state', + 'account_onboarding_sample_invoice_state', + ] + + def get_and_update_account_dashboard_onboarding_state(self): + """ This method is called on the controller rendering method and ensures that the animations + are displayed only one time. """ + return self.get_and_update_onbarding_state('account_dashboard_onboarding_state', [ + 'base_onboarding_company_state', + 'account_setup_bank_data_state', + 'account_setup_fy_data_state', + 'account_setup_coa_state', + ]) @api.multi def _check_lock_dates(self, vals): @@ -261,20 +273,6 @@ Best Regards,''')) return super(ResCompany, self).write(values) @api.model - def setting_init_company_action(self): - """ Called by the 'Company Data' button of the setup bar.""" - company = self.env.user.company_id - view_id = self.env.ref('account.setup_view_company_form').id - return {'type': 'ir.actions.act_window', - 'name': _('Company Data'), - 'res_model': 'res.company', - 'target': 'new', - 'view_mode': 'form', - 'res_id': company.id, - 'views': [[view_id, 'form']], - } - - @api.model def setting_init_bank_account_action(self): """ Called by the 'Bank Accounts' button of the setup bar.""" view_id = self.env.ref('account.setup_bank_account_wizard').id @@ -308,7 +306,7 @@ Best Regards,''')) def setting_chart_of_accounts_action(self): """ Called by the 'Chart of Accounts' button of the setup bar.""" company = self.env.user.company_id - company.account_setup_coa_done = True + company.set_onboarding_step_done('account_setup_coa_state') # If an opening move has already been posted, we open the tree view showing all the accounts if company.opening_move_posted(): @@ -333,11 +331,6 @@ Best Regards,''')) } @api.model - def setting_hide_setup_bar(self): - """ Called by the cross button of the setup bar, to close it.""" - self.env.user.company_id.account_setup_bar_closed = True - - @api.model def create_op_move_if_non_existant(self): """ Creates an empty opening move in 'draft' state for the current company if there wasn't already one defined. For this, the function needs at least @@ -356,14 +349,6 @@ Best Regards,''')) 'journal_id': default_journal.id, }) - def mark_company_setup_as_done_action(self): - """ Marks the 'company' setup step as completed.""" - self.account_setup_company_data_done = True - - def unmark_company_setup_as_done_action(self): - """ Marks the 'company' setup step as uncompleted.""" - self.account_setup_company_data_done = False - def opening_move_posted(self): """ Returns true if this company has an opening account move and this move is posted.""" return bool(self.account_opening_move_id) and self.account_opening_move_id.state == 'posted' @@ -430,36 +415,15 @@ Best Regards,''')) 'credit': debit_diff, }) - @api.depends('logo', 'account_invoice_onboarding_closed') - def _compute_account_onboarding_invoice_layout_done(self): - """ The invoice onboarding step is marked as done if logo is filled - and different from the default one. """ - for record in self: - record.account_onboarding_invoice_layout_done = \ - record.account_invoice_onboarding_closed or ( - bool(record.logo) and record.logo != record._get_logo()) - - @api.model - def action_toggle_fold_account_invoice_onboarding(self): - """ Toggle the onboarding panel `folded` state. """ - self.env.user.company_id.account_invoice_onboarding_folded =\ - not self.env.user.company_id.account_invoice_onboarding_folded - @api.model def action_close_account_invoice_onboarding(self): - """ Mark the onboarding panel as closed. """ - self.env.user.company_id.account_invoice_onboarding_closed = True - - @api.model - def action_toggle_fold_account_dashboard_onboarding(self): - """ Toggle the dashboard onboarding panel `folded` state. """ - self.env.user.company_id.account_dashboard_onboarding_folded =\ - not self.env.user.company_id.account_dashboard_onboarding_folded + """ Mark the invoice onboarding panel as closed. """ + self.env.user.company_id.account_invoice_onboarding_state = 'closed' @api.model def action_close_account_dashboard_onboarding(self): """ Mark the dashboard onboarding panel as closed. """ - self.env.user.company_id.account_dashboard_onboarding_closed = True + self.env.user.company_id.account_dashboard_onboarding_state = 'closed' @api.model def action_open_account_onboarding_invoice_layout(self): @@ -469,6 +433,13 @@ Best Regards,''')) return action @api.model + def action_open_account_onboarding_sale_tax(self): + """ Onboarding step for the invoice layout. """ + action = self.env.ref('account.action_open_account_onboarding_sale_tax').read()[0] + action['res_id'] = self.env.user.company_id.id + return action + + @api.model def _get_sample_invoice(self): """ Get a sample invoice or create one if it does not exist. """ # use current user as partner @@ -482,13 +453,7 @@ Best Regards,''')) if len(sample_invoice) == 0: # If there are no existing accounts or no journal, fail - account = self.env['account.account'].search([('company_id', '=', company_id)], limit=1) - if len(account) == 0: - action = self.env.ref('account.action_account_config') - msg = _( - "We cannot find a chart of accounts for this company, you should configure it. \n" - "Please go to Account Configuration and select or install a fiscal localization.") - raise RedirectWarning(msg, action.id, _("Go to the configuration panel")) + account = self.env.user.company_id.get_chart_of_accounts_or_fail() journal = self.env['account.journal'].search([('company_id', '=', company_id)], limit=1) if len(journal) == 0: @@ -537,3 +502,24 @@ Best Regards,''')) 'force_email': True, } return action + + @api.multi + def action_save_onboarding_invoice_layout(self): + """ Set the onboarding step as done """ + if bool(self.logo) and self.logo != self._get_logo(): + self.set_onboarding_step_done('account_onboarding_invoice_layout_state') + + @api.multi + def action_save_onboarding_sale_tax(self): + """ Set the onboarding step as done """ + self.set_onboarding_step_done('account_onboarding_sale_tax_state') + + def get_chart_of_accounts_or_fail(self): + account = self.env['account.account'].search([('company_id', '=', self.id)], limit=1) + if len(account) == 0: + action = self.env.ref('account.action_account_config') + msg = _( + "We cannot find a chart of accounts for this company, you should configure it. \n" + "Please go to Account Configuration and select or install a fiscal localization.") + raise RedirectWarning(msg, action.id, _("Go to the configuration panel")) + return account diff --git a/addons/account/models/res_config_settings.py b/addons/account/models/res_config_settings.py index 481e9af3218..d9e08ca9bbf 100644 --- a/addons/account/models/res_config_settings.py +++ b/addons/account/models/res_config_settings.py @@ -74,7 +74,6 @@ class ResConfigSettings(models.TransientModel): module_account_taxcloud = fields.Boolean(string="Account TaxCloud") tax_exigibility = fields.Boolean(string='Cash Basis', related='company_id.tax_exigibility') tax_cash_basis_journal_id = fields.Many2one('account.journal', related='company_id.tax_cash_basis_journal_id', string="Tax Cash Basis Journal") - account_hide_setup_bar = fields.Boolean(string='Hide Setup Bar', related='company_id.account_setup_bar_closed',help="Tick if you wish to hide the setup bar on the dashboard") invoice_reference_type = fields.Selection(string='Communication', related='company_id.invoice_reference_type', help='Default Reference Type on Invoices.') account_bank_reconciliation_start = fields.Date(string="Bank Reconciliation Threshold", diff --git a/addons/account/static/src/js/account_dashboard_setup_bar.js b/addons/account/static/src/js/account_dashboard_setup_bar.js index 17aa1bad174..e69de29bb2d 100644 --- a/addons/account/static/src/js/account_dashboard_setup_bar.js +++ b/addons/account/static/src/js/account_dashboard_setup_bar.js @@ -1,235 +0,0 @@ -odoo.define('account.dashboard_setup_bar', function (require) { -"use strict"; - -var core = require('web.core'); -var field_utils = require('web.field_utils'); -var KanbanView = require('web.KanbanView'); -var KanbanModel = require('web.KanbanModel'); -var KanbanRenderer = require('web.KanbanRenderer'); -var KanbanController = require('web.KanbanController'); -var session = require('web.session'); -var view_registry = require('web.view_registry'); - -var QWeb = core.qweb; -var _t = core._t; -var _lt = core._lt; - -var COMPANY_METHOD_TYPE = "company_object"; - -var AccountSetupBarRenderer = KanbanRenderer.extend({ - events: _.extend({}, KanbanRenderer.prototype.events, { - 'click .account_setup_dashboard_action': 'onActionClicked', - }), - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * Notifies the controller that the target has changed. - * - * @private - * @param {string} target_name the name of the changed target - * @param {string} value the new value - */ - _notifyTargetChange: function (target_name, value) { - this.trigger_up('dashboard_edit_target', { - target_name: target_name, - target_value: value, - }); - }, - /** - * @override - * @private - * @returns {Deferred} - */ - _render: function () { - var self = this; - - return this._super.apply(this, arguments).then(function () { - var values = self.state.dashboardValues; - var account_dashboard = QWeb.render('account.AccountDashboardSetupBar', { - widget: self, - values: values, - }); - self.$el.prepend(account_dashboard); - }); - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * @private - * @param {MouseEvent} - */ - onActionClicked: function(e) { - e.preventDefault(); - var self = this; - var $action = $(e.currentTarget); - var name_attr = $action.attr('name'); - var type_attr = $action.attr('type'); - var action_context = $action.data('context'); - - if(type_attr == COMPANY_METHOD_TYPE) { - self.trigger_up('company_button_action', {rpc_method: name_attr, context: action_context}) - } - }, -}); - -var AccountSetupBarModel = KanbanModel.extend({ - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - /** - * @override - */ - init: function () { - this.dashboardValues = {}; - this._super.apply(this, arguments); - }, - - /** - * @override - */ - get: function (localID) { - var result = this._super.apply(this, arguments); - if (this.dashboardValues[localID]) { - result.dashboardValues = this.dashboardValues[localID]; - } - return result; - }, - - - /** - * @œverride - * @returns {Deferred} - */ - load: function () { - return this._loadDashboard(this._super.apply(this, arguments)); - }, - /** - * @œverride - * @returns {Deferred} - */ - reload: function () { - return this._loadDashboard(this._super.apply(this, arguments)); - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * @abstract - * @returns {Deferred<Object>} resolves to the required dashboard data - */ - _fetchDashboardData: function () { - return $.when(this._rpc({ - model: 'account.journal', - method: 'retrieve_account_dashboard_setup_bar', - args: [], - })); - }, - /** - * @private - * @param {Deferred} super_def a deferred that resolves with a dataPoint id - * @returns {Deferred<string>} resolves to the dataPoint id - */ - _loadDashboard: function (super_def) { - var self = this; - var dashboard_def = this._fetchDashboardData(); - return $.when(super_def, dashboard_def).then(function (id, dashboardValues) { - self.dashboardValues[id] = dashboardValues; - return id; - }); - }, -}); - -var AccountSetupBarController = KanbanController.extend({ - /* The company_button_action action allows the buttons of the setup bar to - * trigger Python code defined in api.model functions in res.company model, - * and to execute the action returned them. - * It uses the 'type' attributes on buttons : if 'company_object', it will - * run Python function 'name' of company model. - */ - custom_events: _.extend({}, KanbanController.prototype.custom_events, { - dashboard_open_action: '_onDashboardOpenAction', - company_button_action: '_triggerCompanyButtonAction', - }), - - start: function () { - this._super(); - var self = this; - core.bus.on('refresh_account_dashboard', this, function (){ - self.trigger_up('reload') - }); - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * @private - * @param {OdooEvent} e - */ - _onDashboardOpenAction: function (e) { - var action_name = e.data.action_name; - var action_context = e.data.action_context; - return this.do_action(action_name, { - additional_context: action_context, - }); - }, - - /** - * Manages the clicks on the setup bar buttons. - **/ - _triggerCompanyButtonAction: function (odooEvent) { - var self = this - if (odooEvent.data.rpc_method !== undefined) { - self._rpc({ - model: 'res.company', - method: odooEvent.data.rpc_method, - args: [], - }) - .then( - function(rslt_action) { - if (rslt_action !== undefined) { - self.do_action(rslt_action, { - action_context: odooEvent.data.context, - on_close: function () { - self.trigger_up('reload'); //Reloads the dashboard to refresh the status of the setup bar. - }, - }); - } - else { //Happens for any button not returning anything, like the cross to close the setup bar, for example. - self.trigger_up('reload'); - } - }); - } - } -}); - -var AccountDashboardView = KanbanView.extend({ - config: _.extend({}, KanbanView.prototype.config, { - Model: AccountSetupBarModel, - Renderer: AccountSetupBarRenderer, - Controller: AccountSetupBarController, - }), - display_name: _lt('Dashboard'), - icon: 'fa-dashboard', - searchview_hidden: false, -}); - -view_registry.add('account_setup_bar', AccountDashboardView); - -return { - Model: AccountSetupBarModel, - Renderer: AccountSetupBarRenderer, - Controller: AccountSetupBarController, -}; - -}); diff --git a/addons/account/static/src/xml/account_dashboard_setup_bar.xml b/addons/account/static/src/xml/account_dashboard_setup_bar.xml deleted file mode 100644 index a3e8600b894..00000000000 --- a/addons/account/static/src/xml/account_dashboard_setup_bar.xml +++ /dev/null @@ -1,37 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<templates> - <!-- This template defines the setup bar, at the top of account module's dashboard --> - <t t-name="account.AccountDashboardSetupBar"> - <div class="o_account_dashboard_header d-none d-md-block o_form_view" t-if="values['show_setup_bar']"> - <div class="o_form_statusbar"> - <div class="o_statusbar_status" data-original-title="" title="" > - <button name="setting_chart_of_accounts_action" type="company_object" t-attf-class="btn o_arrow_button btn-secondary account_setup_dashboard_action #{values['chart_of_accounts'] and 'o_action_done' or ''}" > - <i class="fa fa-check" t-if="values['chart_of_accounts']" role="img" aria-label="Current state" title="Current state" aria-current="step"/> - <b class="o_account_dashboard_index">4</b> - Chart of Accounts - </button> - <button name="setting_init_fiscal_year_action" type="company_object" t-attf-class="btn o_arrow_button btn-secondary account_setup_dashboard_action #{values['fiscal_year'] and 'o_action_done' or ''}"> - <i class="fa fa-check" t-if="values['fiscal_year']" role="img" aria-label="Current state" title="Current state" aria-current="step"/> - <b class="o_account_dashboard_index">3</b> - Fiscal Year - </button> - <button name="setting_init_bank_account_action" type="company_object" t-attf-class="btn o_arrow_button btn-secondary account_setup_dashboard_action #{values['bank'] and 'o_action_done' or ''}"> - <i class="fa fa-check" t-if="values['bank']" role="img" aria-label="Current state" title="Current state" aria-current="step"/> - <b class="o_account_dashboard_index">2</b> - Add a Bank Account - </button> - <button name="setting_init_company_action" type="company_object" t-attf-class="btn o_arrow_button btn-secondary account_setup_dashboard_action #{values['company'] and 'o_action_done' or ''}"> - <i class="fa fa-check" t-if="values['company']" role="img" aria-label="Current state" title="Current state" aria-current="step"/> - <b class="o_account_dashboard_index">1</b> - Company Data - </button> - - <h4 class="text-muted">Configuration Steps</h4> - </div> - <div class="float-right" style="padding: 7px 0;"> - <button aria-label="Hide setup bar" data-dismiss="modal" type="company_object" name="setting_hide_setup_bar" class="account_setup_dashboard_action close">×</button> - </div> - </div> - </div> - </t> -</templates> diff --git a/addons/account/static/tests/account_dashboard_setup_bar_tests.js b/addons/account/static/tests/account_dashboard_setup_bar_tests.js index 4729467a13d..e69de29bb2d 100644 --- a/addons/account/static/tests/account_dashboard_setup_bar_tests.js +++ b/addons/account/static/tests/account_dashboard_setup_bar_tests.js @@ -1,65 +0,0 @@ -odoo.define('account.setup_bar_tests', function (require) { -"use strict"; - -var testUtils = require('web.test_utils'); -var view_registry = require('web.view_registry'); - -var createView = testUtils.createView; - -QUnit.module('Views', {}, function () { - -QUnit.module('Account Dashboard Setup Bar', { - beforeEach: function() { - this.data = { - partner: { - fields: { - foo: {string: "Foo", type: "char"}, - }, - records: [ - {id: 1, foo: "yop"}, - {id: 2, foo: "blip"}, - {id: 3, foo: "gnap"}, - {id: 4, foo: "blip"}, - ] - }, - }; - this.dashboard_data = { - show_setup_bar: true, - company: false, - bank: false, - fiscal_year: false, - chart_of_accounts: false, - initial_balance: false, - }; - } -}); - -QUnit.test('setup bar basic rendering', function(assert) { - assert.expect(2); - - var dashboard_data = this.dashboard_data; - var kanban = createView({ - View: view_registry.get('account_setup_bar'), - model: 'partner', - data: this.data, - arch: '<kanban class="o_kanban_test">' + - '<templates><t t-name="kanban-box">' + - '<div><field name="foo"/></div>' + - '</t></templates>' + - '</kanban>', - mockRPC: function(route, args) { - if (args.method === 'retrieve_account_dashboard_setup_bar') { - assert.ok(true, "should call /retrieve_account_dashboard_setup_bar"); - return $.when(dashboard_data); - } - return this._super(route, args); - }, - }); - - assert.strictEqual(kanban.$('.o_account_dashboard_header').length, 1, "should render the setup bar"); - kanban.destroy(); -}); - -}); - -}); diff --git a/addons/account/views/account.xml b/addons/account/views/account.xml index 70d525890d1..348e6fdeec2 100644 --- a/addons/account/views/account.xml +++ b/addons/account/views/account.xml @@ -22,7 +22,6 @@ <script type="text/javascript" src="/account/static/src/js/reconciliation/tour_reconciliation.js"></script> <script type="text/javascript" src="/account/static/src/js/account_payment_field.js"></script> - <script type="text/javascript" src="/account/static/src/js/account_dashboard_setup_bar.js"></script> <script type="text/javascript" src="/account/static/src/js/section_and_note_fields_backend.js"></script> <script type="text/javascript" src="/account/static/src/js/tour.js"></script> diff --git a/addons/account/views/account_onboarding_templates.xml b/addons/account/views/account_onboarding_templates.xml index 682da061ae0..47150b860ce 100644 --- a/addons/account/views/account_onboarding_templates.xml +++ b/addons/account/views/account_onboarding_templates.xml @@ -4,49 +4,69 @@ <template id="onboarding_invoice_layout_step"> <t t-call="base.onboarding_step"> <t t-set="title">Invoice Layout</t> - <t t-set="description">Create and send professional looking invoices.</t> + <t t-set="description">Customize the look of your invoices.</t> <t t-set="done_icon" t-value="'fa-star'" /> + <t t-set="btn_text">Customize</t> <t t-set="done_text">Looks great!</t> <t t-set="method" t-value="'action_open_account_onboarding_invoice_layout'" /> <t t-set="model" t-value="'res.company'" /> - <t t-set="done" t-value="company.account_onboarding_invoice_layout_done" /> + <t t-set="state" t-value="state.get('account_onboarding_invoice_layout_state')" /> </t> </template> <template id="onboarding_sample_invoice_step"> <t t-call="base.onboarding_step"> <t t-set="title">Sample Invoice</t> - <t t-set="description">Quickly preview what your client sees by sending a sample invoice - to yourself. + <t t-set="description"> + Send an invoice to test the customer portal. </t> - <t t-set="btn_text">I'm ready</t> + <t t-set="btn_text">Send Sample</t> <t t-set="done_text">Sample invoice sent!</t> <t t-set="method" t-value="'action_open_account_onboarding_sample_invoice'" /> <t t-set="model" t-value="'res.company'" /> - <t t-set="done" t-value="company.account_onboarding_sample_invoice_sent" /> + <t t-set="state" t-value="state.get('account_onboarding_sample_invoice_state')" /> + </t> + </template> + <template id="onboarding_bank_account_step"> + <t t-call="base.onboarding_step"> + <t t-set="title">Bank Account</t> + <t t-set="description">Setup your bank account to sync bank feeds.</t> + <t t-set="btn_text">Add a bank</t> + <t t-set="done_text">Step Completed!</t> + <t t-set="method" t-value="'setting_init_bank_account_action'" /> + <t t-set="model" t-value="'res.company'" /> + <t t-set="state" t-value="state.get('account_setup_bank_data_state')" /> </t> </template> <template id="onboarding_fiscal_year_step"> <t t-call="base.onboarding_step"> <t t-set="title">Fiscal Year</t> - <t t-set="description">Set the closing date of your legal reports.</t> - <t t-set="btn_text">I'm ready</t> + <t t-set="description">Define your fiscal years opening & closing dates.</t> + <t t-set="btn_text">Configure</t> <t t-set="done_text">Step Completed!</t> <t t-set="method" t-value="'setting_init_fiscal_year_action'" /> <t t-set="model" t-value="'res.company'" /> - <t t-set="done" t-value="company.account_setup_fy_data_done" /> + <t t-set="state" t-value="state.get('account_setup_fy_data_state')" /> </t> </template> <template id="onboarding_chart_of_account_step"> <t t-call="base.onboarding_step"> <t t-set="title">Chart of Accounts</t> - <t t-set="description">Setup your chart of accounts and enter the initial balances.</t> - <t t-set="btn_text">I'm ready</t> + <t t-set="description">Setup your chart of accounts and record initial balances.</t> + <t t-set="btn_text">Review</t> <t t-set="done_text">Chart of account set.</t> <t t-set="method" t-value="'setting_chart_of_accounts_action'" /> <t t-set="model" t-value="'res.company'" /> - <t t-set="done" t-value="company.account_setup_coa_done" /> + <t t-set="state" t-value="state.get('account_setup_coa_state')" /> </t> </template> + <template id="dashboard_onboarding_company_step" primary="True" + inherit_id="base.onboarding_company_step"> + <xpath expr="//t[@t-set='description']" position="replace"> + <t t-set="description"> + Setup your company's data for reports headers. + </t> + </xpath> + </template> <!-- ONBOARDING PANELS --> <template id="account_invoice_onboarding_panel" name="account.invoice.onboarding.panel"> <t t-call="base.onboarding_container"> @@ -54,9 +74,8 @@ <t t-set="bg_image">/account/static/src/img/account_invoice_onboarding_bg.jpg</t> <t t-set="close_method">action_close_account_invoice_onboarding</t> <t t-set="close_model">res.company</t> - <t t-set="fold_method">action_toggle_fold_account_invoice_onboarding</t> - <t t-set="fold_model">res.company</t> - <t t-set="folded_by_default" t-value="company.account_invoice_onboarding_folded" /> + <t t-set="text_completed">Congratulations! You are all set. You can now issue real invoices to your customers.</t> + <t t-call="base.onboarding_company_step" name="company_step" /> <t t-call="account.onboarding_invoice_layout_step" name="invoice_layout_step" /> <t t-call="account.onboarding_sample_invoice_step" name="sample_invoice_step" /> @@ -68,22 +87,33 @@ <t t-set="bg_image">/account/static/src/img/account_dashboard_onboarding_bg.jpg</t> <t t-set="close_method">action_close_account_dashboard_onboarding</t> <t t-set="close_model">res.company</t> - <t t-set="fold_method">action_toggle_fold_account_dashboard_onboarding</t> - <t t-set="fold_model">res.company</t> - <t t-set="folded_by_default" t-value="company.account_dashboard_onboarding_folded" /> - <t t-call="base.onboarding_company_step" name="company_step" /> + + <t t-call="account.dashboard_onboarding_company_step" name="company_step" /> + <t t-call="account.onboarding_bank_account_step" name="bank_account_step" /> <t t-call="account.onboarding_fiscal_year_step" name="fiscal_year_step" /> <t t-call="account.onboarding_chart_of_account_step" name="chart_of_account_step" /> </t> </template> <!-- INVOICE FORM --> + <record id="account_invoice_onboarding_invoice_layout_form" model="ir.ui.view"> + <field name="name">account.invoice.layout.onboarding.form</field> + <field name="model">res.company</field> + <field name="inherit_id" ref="base.view_company_document_template_form" /> + <field name="mode">primary</field> + <field name="priority">1000</field> + <field name="arch" type="xml"> + <xpath expr="//button[@special='save']" position="replace"> + <button string="Apply" class="btn btn-primary" type="object" name="action_save_onboarding_invoice_layout" /> + </xpath> + </field> + </record> <record id="action_open_account_onboarding_invoice_layout" model="ir.actions.act_window"> - <field name="name">Configure your document template</field> + <field name="name">Configure your document layout</field> <field name="type">ir.actions.act_window</field> <field name="res_model">res.company</field> <field name="view_mode">form</field> <field name="view_type">form</field> - <field name="view_id" ref="base.view_company_document_template_form" /> + <field name="view_id" ref="account_invoice_onboarding_invoice_layout_form" /> <field name="target">new</field> </record> <!-- SAMPLE INVOICE EMAIL --> @@ -115,4 +145,42 @@ <field name="view_id" ref="email_compose_onboarding_sample_invoice" /> <field name="target">new</field> </record> + <!-- SALE TAX --> + <template id="onboarding_sale_tax_step"> + <t t-call="base.onboarding_step"> + <t t-set="title">Taxes</t> + <t t-set="description">Choose a default tax.</t> + <t t-set="btn_text">I'm ready</t> + <t t-set="done_text">Step Completed!</t> + <t t-set="method" t-value="'action_open_account_onboarding_sale_tax'" /> + <t t-set="model" t-value="'res.company'" /> + <t t-set="state" t-value="state.get('account_onboarding_sale_tax_state')" /> + </t> + </template> + <record id="account_invoice_onboarding_sale_tax_form" model="ir.ui.view"> + <field name="name">account.invoice.layout.onboarding.form</field> + <field name="model">res.company</field> + <field name="mode">primary</field> + <field name="priority">1000</field> + <field name="arch" type="xml"> + <form> + <div>Choose a default sales tax for your products.</div> + <label for="account_sale_tax_id" string="Sales tax"/> + <field name="account_sale_tax_id" /> + <footer> + <button string="Apply" class="btn btn-primary" type="object" name="action_save_onboarding_sale_tax" /> + <button string="Cancel" class="btn-secondary" special="cancel" /> + </footer> + </form> + </field> + </record> + <record id="action_open_account_onboarding_sale_tax" model="ir.actions.act_window"> + <field name="name">Sales tax</field> + <field name="type">ir.actions.act_window</field> + <field name="res_model">res.company</field> + <field name="view_mode">form</field> + <field name="view_type">form</field> + <field name="view_id" ref="account_invoice_onboarding_sale_tax_form" /> + <field name="target">new</field> + </record> </odoo> diff --git a/addons/account/views/res_config_settings_views.xml b/addons/account/views/res_config_settings_views.xml index c0df8501892..ffe56968f14 100644 --- a/addons/account/views/res_config_settings_views.xml +++ b/addons/account/views/res_config_settings_views.xml @@ -44,18 +44,6 @@ <t groups="account.group_account_user"> <h2>Setup</h2> <div class="row mt16 o_settings_container" id="setup"> - <div class="col-12 col-lg-6 o_setting_box"> - <div class="o_setting_left_pane"> - <field name="account_hide_setup_bar" widget="upgrade_boolean"/> - </div> - <div class="o_setting_right_pane"> - <label for="account_hide_setup_bar" string='Hide the Configuration Steps bar'/> - <div class="text-muted"> - Deactivate the "Configuration Steps" bar on the dashboard - </div> - </div> - </div> - <div class="col-xs-12 col-md-6 o_setting_box"> <div class="o_setting_left_pane"/> <div class="o_setting_right_pane"> diff --git a/addons/account/wizard/setup_wizards.py b/addons/account/wizard/setup_wizards.py index 407a4b45e27..0c0d9fe43df 100644 --- a/addons/account/wizard/setup_wizards.py +++ b/addons/account/wizard/setup_wizards.py @@ -16,12 +16,6 @@ class FinancialYearOpeningWizard(models.TransientModel): related="company_id.fiscalyear_last_month", required=True, help="The last day of the month will be taken if the chosen day doesn't exist.") - account_setup_fy_data_done = fields.Boolean(string='Financial year setup marked as done', compute="_compute_setup_marked_done") - - @api.depends('company_id.account_setup_fy_data_done') - def _compute_setup_marked_done(self): - for record in self: - record.account_setup_fy_data_done = record.company_id.account_setup_fy_data_done @api.depends('company_id.account_opening_move_id') def _compute_opening_move_posted(self): @@ -41,7 +35,7 @@ class FinancialYearOpeningWizard(models.TransientModel): @api.multi def action_save_onboarding_fiscal_year(self): - self.env.user.company_id.account_setup_fy_data_done = True + self.env.user.company_id.set_onboarding_step_done('account_setup_fy_data_state') class SetupBarBankConfigWizard(models.TransientModel): diff --git a/addons/account/wizard/setup_wizards_view.xml b/addons/account/wizard/setup_wizards_view.xml index fa3c70e0c02..1c9e21c8592 100644 --- a/addons/account/wizard/setup_wizards_view.xml +++ b/addons/account/wizard/setup_wizards_view.xml @@ -2,26 +2,6 @@ <odoo> <data> - - <record id="setup_view_company_form" model="ir.ui.view"> - <field name="name">account.setup.company.form</field> - <field name="model">res.company</field> - <field name="inherit_id" ref="base.view_company_form"/> - <field name="mode">primary</field> - <field name="priority">42000</field> - <field name="arch" type="xml"> - <xpath expr="//form/sheet" position="after"> - <footer> - <field name="account_setup_company_data_done" invisible="True"/> - <button string="Mark as done" type="object" name="mark_company_setup_as_done_action" class="oe_highlight" attrs="{'invisible': [('account_setup_company_data_done','=',True)]}"/> - <button string="Save" type="action" name="account.setup_wizard_close" attrs="{'invisible': [('account_setup_company_data_done','=',True)]}"/> - <button string="Save" type="action" name="account.setup_wizard_close" class="oe_highlight" attrs="{'invisible': [('account_setup_company_data_done','=',False)]}"/> - <button string="Unmark as done" type="object" name="unmark_company_setup_as_done_action" attrs="{'invisible': [('account_setup_company_data_done','=',False)]}"/> - </footer> - </xpath> - </field> - </record> - <record id="setup_financial_year_opening_form" model="ir.ui.view"> <field name="name">account.financial.year.op.setup.wizard.form</field> <field name="model">account.financial.year.op</field> @@ -49,7 +29,7 @@ </div> </div> <footer> - <button name="action_save_onboarding_fiscal_year" string="OK" + <button name="action_save_onboarding_fiscal_year" string="Apply" class="oe_highlight" type="object" /> <button special="cancel" string="Cancel" /> </footer> @@ -110,9 +90,5 @@ </field> </record> - <record id="setup_wizard_close" model="ir.actions.act_window_close"> - <field name="name">Close setup wizards</field> - </record> - </data> </odoo> diff --git a/addons/base_setup/models/res_config_settings.py b/addons/base_setup/models/res_config_settings.py index a2d4c5a4511..3e548b015e2 100644 --- a/addons/base_setup/models/res_config_settings.py +++ b/addons/base_setup/models/res_config_settings.py @@ -39,7 +39,7 @@ class ResConfigSettings(models.TransientModel): group_multi_currency = fields.Boolean(string='Multi-Currencies', implied_group='base.group_multi_currency', help="Allows to work in a multi currency environment") - paperformat_id = fields.Many2one(related="company_id.paperformat_id", string='Paper format') + paperformat_id = fields.Many2one(related="company_id.paperformat_id", string='Paper Format') external_report_layout = fields.Selection(related="company_id.external_report_layout") show_effect = fields.Boolean(string="Show Effect", config_parameter='base_setup.show_effect') diff --git a/addons/base_vat/views/res_company_views.xml b/addons/base_vat/views/res_company_views.xml index bf85b2b7f88..f4af1016a00 100644 --- a/addons/base_vat/views/res_company_views.xml +++ b/addons/base_vat/views/res_company_views.xml @@ -5,9 +5,6 @@ <field name="model">res.company</field> <field name="inherit_id" ref="base.view_company_form"/> <field name="arch" type="xml"> - <field name="parent_id" position="after"> - <field name="vat_check_vies" help="If this checkbox is ticked, you will not be able to save a contact if its VAT number cannot be verified by the European VIES service." /> - </field> <xpath expr="//field[@name='vat']" position="attributes"> <attribute name="string">VAT</attribute> </xpath> diff --git a/addons/payment/models/res_company.py b/addons/payment/models/res_company.py index c2b52486563..277c44f06b1 100644 --- a/addons/payment/models/res_company.py +++ b/addons/payment/models/res_company.py @@ -1,15 +1,30 @@ -from odoo import _, api, fields, models +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models class ResCompany(models.Model): _inherit = 'res.company' - # Step is marked as done if one payment module is installed or - # `custom` payment has been changed or `skip this step` has been clicked - payment_acquirer_onboarding_done = fields.Boolean('Payment acquirer onboarding done', default=False) + payment_acquirer_onboarding_state = fields.Selection([('not_done', "Not done"), ('just_done', "Just done"), ('done', "Done")], string="State of the onboarding payment acquirer step", default='not_done') + # YTI FIXME: Check if it's really needed on the company. Should be enough on the wizard + payment_onboarding_payment_method = fields.Selection([ + ('paypal', "PayPal"), + ('stripe', "Stripe"), + ('manual', "Manual"), + ], string="Selected onboarding payment method") @api.model def action_open_payment_onboarding_payment_acquirer(self): """ Called by onboarding panel above the customer invoice list.""" + # Fail if there are no existing accounts + self.env.user.company_id.get_chart_of_accounts_or_fail() + action = self.env.ref('payment.action_open_payment_onboarding_payment_acquirer_wizard').read()[0] return action + + def get_account_invoice_onboarding_steps_states_names(self): + """ Override. """ + steps = super(ResCompany, self).get_account_invoice_onboarding_steps_states_names() + return steps + ['payment_acquirer_onboarding_state'] diff --git a/addons/payment/views/payment_acquirer_onboarding_templates.xml b/addons/payment/views/payment_acquirer_onboarding_templates.xml index 011344f6c30..3c2d0b96fef 100644 --- a/addons/payment/views/payment_acquirer_onboarding_templates.xml +++ b/addons/payment/views/payment_acquirer_onboarding_templates.xml @@ -4,12 +4,12 @@ <template id="onboarding_payment_acquirer_step"> <t t-call="base.onboarding_step"> <t t-set="title">Payment Method</t> - <t t-set="description">Choose the payment methods to collect payments on your invoices.</t> + <t t-set="description">Configure your payment methods.</t> + <t t-set="btn_text">Set Payments</t> <t t-set="done_text">Payment method set!</t> <t t-set="method" t-value="'action_open_payment_onboarding_payment_acquirer'" /> <t t-set="model" t-value="'res.company'" /> - <t t-set="id" t-value="company.id" /> - <t t-set="done" t-value="company.payment_acquirer_onboarding_done" /> + <t t-set="state" t-value="state.get('payment_acquirer_onboarding_state')" /> </t> </template> <!-- add it in the account invoice onboarding panel --> @@ -25,98 +25,67 @@ <field name="name">payment.acquirer.onboarding.wizard.form</field> <field name="model">payment.acquirer.onboarding.wizard</field> <field name="arch" type="xml"> - <form string="Choose a payment method"> - <div class="mb16"> - Configure our recommended payment methods or - <a type="action" role="button" name="payment.action_payment_acquirer"> - see all other available methods. - </a> - </div> - <div class="row"> - <div class="col-1 mb16"> - <field name="paypal_checked" nolabel="1" /> - </div> - <div> - <label for="paypal_checked" /> - </div> - </div> - <div class="row"> - <div attrs="{'invisible': [('paypal_checked', '=', False)]}" - class="offset-1"> - <a href="https://www.paypal.com" target="_blank">PayPal</a> - is the easiest way to accept payments. It's easy to configure and it offers - <a href="https://www.paypal.com/us/webapps/mpp/merchant-fees" - target="_blank"> - a transparent fee structure. - </a> - <group> - <field name="paypal_email_account" - attrs="{'required': [('paypal_checked', '=', True)]}" /> - <field name="paypal_seller_account" /> - <field name="paypal_pdt_token" - attrs="{'required': [('paypal_checked', '=', True)]}" /> - </group> - <separator /> - </div> - </div> - <div class="row"> - <div class="col-1 mb16"> - <field name="stripe_checked" nolabel="1" /> - </div> - <div> - <label for="stripe_checked" /> - </div> - </div> - <div class="row"> - <div attrs="{'invisible': [('stripe_checked', '=', False)]}" - class="offset-1"> - <a href="https://stripe.com" target="_blank">Stripe</a> - is a payment provider with - <a href="https://stripe.com/pricing" target="_blank"> - a simple fee structure and no hidden costs. - </a> - No other gateway or merchant account is required. We recommend this solution - to manage credit card payments. - <group> - <field name="stripe_secret_key" - attrs="{'required': [('stripe_checked', '=', True)]}" /> - <field name="stripe_publishable_key" - attrs="{'required': [('stripe_checked', '=', True)]}" /> - <a href="https://dashboard.stripe.com/account/apikeys" target="_blank"> - <span class="fa fa-arrow-right">Get my Stripe keys</span> - </a> - </group> - <separator /> - </div> - </div> - <div class="row"> - <div class="col-1"> - <field name="manual_checked" nolabel="1" /> - </div> - <div> - <label for="manual_checked" /> - </div> - </div> - <div class="row"> - <div attrs="{'invisible': [('manual_checked', '=', False)]}" - class="offset-1"> - Provide customers with instructions to pay outside of your platform (wire - transfer, cash on delivery, etc.). - <group> - <field name="manual_name" - attrs="{'required': [('manual_checked', '=', True)]}" /> - <field name="manual_post_msg" - attrs="{'required': [('manual_checked', '=', True)]}" /> - </group> + <form string="Choose a payment method" class="o_onboarding_payment_acquirer_wizard"> + <div class="container"> + <div class="row align-items-start"> + <div class="col col-2" name="left-column"> + <field name="payment_method" widget="radio"/> + </div> + <div class="col offset-md-1" name="right-column"> + <div attrs="{'invisible': [('payment_method', '!=', 'paypal')]}"> + <p> + <a href="https://www.paypal.com" target="_blank">PayPal</a> + is the easiest way to accept payments. It's easy to configure + and it offers + <a href="https://www.paypal.com/us/webapps/mpp/merchant-fees" + target="_blank"> + a transparent fee structure. + </a> + </p> + <group> + <field name="paypal_email_account" attrs="{'required': [('payment_method', '=', 'paypal')]}" /> + <field name="paypal_seller_account" attrs="{'required': [('payment_method', '=', 'paypal')]}" /> + <field name="paypal_pdt_token" attrs="{'required': [('payment_method', '=', 'paypal')]}" /> + </group> + <p> + <a href="https://www.paypal.com/ca/webapps/mpp/product-selection" target="_blank"> + <span class="fa fa-arrow-right"> Create a PayPal Account</span> + </a> + </p> + </div> + <div attrs="{'invisible': [('payment_method', '!=', 'stripe')]}"> + <p> + <a href="https://stripe.com" target="_blank">Stripe</a> + is a payment provider with--> + <a href="https://stripe.com/pricing" target="_blank"> + a simple fee structure and no hidden costs. + </a> + We recommend it to manage credit card payments. + </p> + <group> + <field name="stripe_secret_key" + attrs="{'required': [('payment_method', '=', 'stripe')]}" /> + <field name="stripe_publishable_key" + attrs="{'required': [('payment_method', '=', 'stripe')]}" /> + </group> + <p> + <a href="https://dashboard.stripe.com/account/apikeys" target="_blank"> + <span class="fa fa-arrow-right"> Get my Stripe keys</span> + </a> + </p> + </div> + <div attrs="{'invisible': [('payment_method', '!=', 'manual')]}"> + <group> + <field name="journal_name" attrs="{'required': [('payment_method', '=', 'manual')]}"/> + <field name="acc_number" attrs="{'required': [('payment_method', '=', 'manual')]}"/> + <field name="manual_post_msg" attrs="{'required': [('payment_method', '=', 'manual')]}"/> + </group> + </div> + </div> </div> </div> <footer> - <div class="mb16" name="skip_step_text"> - You can skip this step if invoices are pre-paid. - </div> - <button name="add_payment_methods" string="OK" class="oe_highlight" - type="object" /> - <button name="action_skip_payment_onboarding_payment_acquirer_step" string="Skip this step" + <button name="add_payment_methods" string="Apply" class="oe_highlight" type="object" /> <button special="cancel" string="Cancel" /> </footer> diff --git a/addons/payment/wizards/payment_acquirer_onboarding_wizard.py b/addons/payment/wizards/payment_acquirer_onboarding_wizard.py index 679facc45a9..7523ec5c8d6 100644 --- a/addons/payment/wizards/payment_acquirer_onboarding_wizard.py +++ b/addons/payment/wizards/payment_acquirer_onboarding_wizard.py @@ -8,63 +8,70 @@ from odoo.exceptions import UserError class PaymentWizard(models.TransientModel): _name = 'payment.acquirer.onboarding.wizard' - paypal_checked = fields.Boolean('Paypal', default=lambda self: self._get_default_payment_acquirer_onboarding_value('paypal_checked')) - paypal_email_account = fields.Char('Paypal Email ID', default=lambda self: self._get_default_payment_acquirer_onboarding_value('paypal_email_account')) - paypal_seller_account = fields.Char('Paypal Merchant ID', default=lambda self: self._get_default_payment_acquirer_onboarding_value('paypal_seller_account')) - paypal_pdt_token = fields.Char('Paypal PDT Token', default=lambda self: self._get_default_payment_acquirer_onboarding_value('paypal_pdt_token')) + payment_method = fields.Selection([ + ('paypal', "PayPal"), + ('stripe', "Credit Card (via Stripe)"), + ('manual', "Wire Transfer"), + ], string="Payment Method", default=lambda self: self._get_default_payment_acquirer_onboarding_value('payment_method')) + + paypal_email_account = fields.Char("PayPal Email ID", default=lambda self: self._get_default_payment_acquirer_onboarding_value('paypal_email_account')) + paypal_seller_account = fields.Char("Paypal Merchant ID", default=lambda self: self._get_default_payment_acquirer_onboarding_value('paypal_seller_account')) + paypal_pdt_token = fields.Char("Paypal PDT Token", default=lambda self: self._get_default_payment_acquirer_onboarding_value('paypal_pdt_token')) - stripe_checked = fields.Boolean('Credit Card (via Stripe)', default=lambda self: self._get_default_payment_acquirer_onboarding_value('stripe_checked')) stripe_secret_key = fields.Char(default=lambda self: self._get_default_payment_acquirer_onboarding_value('stripe_secret_key')) stripe_publishable_key = fields.Char(default=lambda self: self._get_default_payment_acquirer_onboarding_value('stripe_publishable_key')) - manual_checked = fields.Boolean('Manual Payment', default=lambda self: self._get_default_payment_acquirer_onboarding_value('manual_checked')) - manual_name = fields.Char('Method name', default=lambda self: self._get_default_payment_acquirer_onboarding_value('manual_name')) - manual_post_msg = fields.Html('Payment instructions', default=lambda self: self._get_default_payment_acquirer_onboarding_value('manual_post_msg')) + journal_name = fields.Char("Journal Name", default=lambda self: self._get_default_payment_acquirer_onboarding_value('journal_name')) + acc_number = fields.Char("Account Number", default=lambda self: self._get_default_payment_acquirer_onboarding_value('acc_number')) + manual_post_msg = fields.Html("Payment instructions") + + @api.onchange('journal_name', 'acc_number') + def _set_manual_post_msg_value(self): + self.manual_post_msg = _('Please make a payment to %s %s') % (self.journal_name or _("Bank") , self.acc_number or _("Account")) _payment_acquirer_onboarding_cache = {} _data_fetched = False - def _get_manual_payment(self, env=None): + def _get_manual_payment_acquirer(self, env=None): if env is None: env = self.env - return env['payment.acquirer'].search( - [('provider', '=', 'transfer'), - ('sequence', '=', 2), - ('company_id', '=', self.env.user.company_id.id)], limit=1) + module_id = env.ref('base.module_payment_transfer').id + return env['payment.acquirer'].search([('module_id', '=', module_id), + ('company_id', '=', env.user.company_id.id)], limit=1) def _get_default_payment_acquirer_onboarding_value(self, key): if not self.env.user._is_admin(): - raise UserError(_('Only administators can access this data.')) + raise UserError(_("Only administrators can access this data.")) if self._data_fetched: return self._payment_acquirer_onboarding_cache.get(key, '') self._data_fetched = True + self._payment_acquirer_onboarding_cache['payment_method'] = self.env.user.company_id.payment_onboarding_payment_method + installed_modules = self.env['ir.module.module'].sudo().search([ ('name', 'in', ('payment_paypal', 'payment_stripe')), ('state', '=', 'installed'), ]).mapped('name') if 'payment_paypal' in installed_modules: - self._payment_acquirer_onboarding_cache['paypal_checked'] = True acquirer = self.env.ref('payment.payment_acquirer_paypal') self._payment_acquirer_onboarding_cache['paypal_email_account'] = acquirer['paypal_email_account'] self._payment_acquirer_onboarding_cache['paypal_seller_account'] = acquirer['paypal_seller_account'] self._payment_acquirer_onboarding_cache['paypal_pdt_token'] = acquirer['paypal_pdt_token'] if 'payment_stripe' in installed_modules: - self._payment_acquirer_onboarding_cache['stripe_checked'] = True acquirer = self.env.ref('payment.payment_acquirer_stripe') self._payment_acquirer_onboarding_cache['stripe_secret_key'] = acquirer['stripe_secret_key'] self._payment_acquirer_onboarding_cache['stripe_publishable_key'] = acquirer['stripe_publishable_key'] - manual_payment = self._get_manual_payment() - # manual payment is checked if the user has renamed it - self._payment_acquirer_onboarding_cache['manual_checked'] = \ - (manual_payment['name'] != 'Wire Transfer') - self._payment_acquirer_onboarding_cache['manual_name'] = manual_payment['name'] + manual_payment = self._get_manual_payment_acquirer() + journal = manual_payment.journal_id + self._payment_acquirer_onboarding_cache['manual_post_msg'] = manual_payment['post_msg'] + self._payment_acquirer_onboarding_cache['journal_name'] = journal.name if journal.name != "Bank" else "" + self._payment_acquirer_onboarding_cache['acc_number'] = journal.bank_acc_number return self._payment_acquirer_onboarding_cache.get(key, '') @@ -73,55 +80,51 @@ class PaymentWizard(models.TransientModel): if module.state not in ('installed', 'to install', 'to upgrade'): module.button_immediate_install() - def _hook_on_save_onboarding_payment_acquirer(self): - # if any payment method is selected activate "Online Payment" in invoicing settings - self._install_module('account_payment') - @api.multi def add_payment_methods(self): - if self.paypal_checked is True: + """ Install required payment acquiers, configure them and mark the + onboarding step as done.""" + + if self.payment_method == 'paypal': self._install_module('payment_paypal') - if self.stripe_checked is True: + if self.payment_method == 'stripe': self._install_module('payment_stripe') - if self.paypal_checked is True or self.stripe_checked is True or self.manual_checked is True: + if self.payment_method in ('paypal', 'stripe', 'manual'): - # the wizard is inherited in `sale` - self._hook_on_save_onboarding_payment_acquirer() + self.env.user.company_id.payment_onboarding_payment_method = self.payment_method # create a new env including the freshly installed module(s) new_env = api.Environment(self.env.cr, self.env.uid, self.env.context) - if self.paypal_checked is True: + if self.payment_method == 'paypal': new_env.ref('payment.payment_acquirer_paypal').write({ 'paypal_email_account': self.paypal_email_account, 'paypal_seller_account': self.paypal_seller_account, 'paypal_pdt_token': self.paypal_pdt_token, 'website_published': True, }) - if self.stripe_checked is True: + if self.payment_method == 'stripe': new_env.ref('payment.payment_acquirer_stripe').write({ 'stripe_secret_key': self.stripe_secret_key, 'stripe_publishable_key': self.stripe_publishable_key, 'website_published': True, }) - if self.manual_checked is True: - self._get_manual_payment(new_env).write({ - 'name': self.manual_name, - 'post_msg': self.manual_post_msg, - 'website_published': True, - }) - self._set_payment_acquirer_onboarding_step_done() - # delete wizard data immediately to get rid of residual credentials - self.unlink() + if self.payment_method == 'manual': + manual_acquirer = self._get_manual_payment_acquirer(new_env) + manual_acquirer.post_msg = self.manual_post_msg + manual_acquirer.website_published = True - return {'type': 'ir.actions.act_window_close'} + journal = manual_acquirer.journal_id + journal.name = self.journal_name + journal.bank_acc_number = self.acc_number - @api.multi - def action_skip_payment_onboarding_payment_acquirer_step(self): + # delete wizard data immediately to get rid of residual credentials + self.unlink() + # the user clicked `apply` and not cancel so we can assume this step is done. self._set_payment_acquirer_onboarding_step_done() return {'type': 'ir.actions.act_window_close'} def _set_payment_acquirer_onboarding_step_done(self): - self.env.user.company_id.write({'payment_acquirer_onboarding_done': True}) + self.env.user.company_id.set_onboarding_step_done('payment_acquirer_onboarding_state') diff --git a/addons/sale/controllers/onboarding.py b/addons/sale/controllers/onboarding.py index 6fa8950ae66..0ab5067cdb3 100644 --- a/addons/sale/controllers/onboarding.py +++ b/addons/sale/controllers/onboarding.py @@ -7,18 +7,20 @@ from odoo.http import request class OnboardingController(http.Controller): - @http.route('/sales/onboarding_quotation_panel', auth='user', type='json') + @http.route('/sales/sale_quotation_onboarding_panel', auth='user', type='json') def sale_quotation_onboarding(self): """ Returns the `banner` for the sale onboarding panel. It can be empty if the user has closed it or if he doesn't have the permission to see it. """ + company = request.env.user.company_id if not request.env.user._is_admin() or \ - request.env.user.company_id.sale_quotation_onboarding_closed: + company.sale_quotation_onboarding_state == 'closed': return {} return { - 'html': request.env.ref('sale.onboarding_quotation_panel').render({ - 'company': request.env.user.company_id, + 'html': request.env.ref('sale.sale_quotation_onboarding_panel').render({ + 'company': company, + 'state': company.get_and_update_sale_quotation_onboarding_state() }) } diff --git a/addons/sale/models/res_company.py b/addons/sale/models/res_company.py index 01b2e7c1fdd..17a5bf2e39e 100644 --- a/addons/sale/models/res_company.py +++ b/addons/sale/models/res_company.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo import api, fields, models +from odoo import api, fields, models, _ class ResCompany(models.Model): @@ -11,60 +11,99 @@ class ResCompany(models.Model): portal_confirmation_sign = fields.Boolean(string='Digital Signature') portal_confirmation_pay = fields.Boolean(string='Electronic Payment') - @api.depends('portal_confirmation_sign') - def _get_sale_onboarding_signature_radio(self): - """ Convert portal_confirmation_sign from boolean to radio. """ - for record in self: - record.portal_confirmation_sign_radio = 'yes' if record.portal_confirmation_sign else 'no' - - @api.depends('portal_confirmation_sign') - def _set_sale_onboarding_signature_radio(self): - """ Convert portal_confirmation_sign from radio to boolean. """ - for record in self: - record.portal_confirmation_sign = record.portal_confirmation_sign_radio == 'yes' - - # the target field, portal_confirmation_sign, is a boolean but we would like to render it as a radio button - portal_confirmation_sign_radio = fields.Selection([('yes', "Yes"), ('no', "No")], "Digital Signature (radio)", - compute=_get_sale_onboarding_signature_radio, inverse=_set_sale_onboarding_signature_radio) - # sale quotation onboarding - sale_quotation_onboarding_closed = fields.Boolean( - string="Onboarding sale quotation closed", - help="Refers to the sale quotation onboarding panel closed state.") - sale_quotation_onboarding_folded = fields.Boolean( - string="Onboarding sale quotation folded", - help="Refers to the sale quotation onboarding panel folded state.") - sale_onboarding_quotation_layout_done = fields.Boolean("Onboarding quotation layout step done", - compute="_compute_sale_onboarding_quotation_layout_done") - sale_onboarding_payment_acquirer_done = fields.Boolean("Sale onboarding payment acquirer step done", - default=False) - sale_onboarding_signature_done = fields.Boolean("Sale onboarding signature step done", - default=False) + sale_quotation_onboarding_state = fields.Selection([('not_done', "Not done"), ('just_done', "Just done"), ('done', "Done"), ('closed', "Closed")], string="State of the sale onboarding panel", default='not_done') + sale_onboarding_order_confirmation_state = fields.Selection([('not_done', "Not done"), ('just_done', "Just done"), ('done', "Done")], string="State of the onboarding confirmation order step", default='not_done') + sale_onboarding_sample_quotation_state = fields.Selection([('not_done', "Not done"), ('just_done', "Just done"), ('done', "Done")], string="State of the onboarding sample quotation step", default='not_done') - @api.model - def action_toggle_fold_sale_quotation_onboarding(self): - """ Toggle the onboarding panel `folded` state. """ - self.env.user.company_id.sale_quotation_onboarding_folded =\ - not self.env.user.company_id.sale_quotation_onboarding_folded + sale_onboarding_payment_method = fields.Selection([ + ('digital_signature', 'Digital signature, without payment'), + ('paypal', 'PayPal'), + ('stripe', 'Stripe'), + ('manual', 'Wire Transfer'), + ('none', 'No digital signature or payment'), + ], string="Sale onboarding selected payment method") @api.model def action_close_sale_quotation_onboarding(self): """ Mark the onboarding panel as closed. """ - self.env.user.company_id.sale_quotation_onboarding_closed = True + self.env.user.company_id.sale_quotation_onboarding_state = 'closed' @api.model - def action_open_sale_onboarding_signature(self): + def action_open_sale_onboarding_payment_acquirer(self): """ Called by onboarding panel above the quotation list.""" - action = self.env.ref('sale.action_open_sale_onboarding_signature').read()[0] - action['res_id'] = self.env.user.company_id.id + self.env.user.company_id.get_chart_of_accounts_or_fail() + action = self.env.ref('sale.action_open_sale_onboarding_payment_acquirer_wizard').read()[0] return action + def _get_sample_sales_order(self): + """ Get a sample quotation or create one if it does not exist. """ + # use current user as partner + partner = self.env.user.partner_id + company_id = self.env.user.company_id.id + # is there already one? + sample_sales_order = self.env['sale.order'].search( + [('company_id', '=', company_id), ('partner_id', '=', partner.id), + ('state', '=', 'draft')], limit=1) + if len(sample_sales_order) == 0: + sample_sales_order = self.env['sale.order'].create({ + 'partner_id': partner.id + }) + # take any existing product or create one + product = self.env['product.product'].search([], limit=1) + if len(product) == 0: + product = self.env['product.product'].create({ + 'name': _('Sample Product') + }) + self.env['sale.order.line'].create({ + 'name': _('Sample Order Line'), + 'product_id': product.id, + 'product_uom_qty': 10, + 'price_unit': 123, + 'order_id': sample_sales_order.id, + }) + return sample_sales_order + @api.model - def action_open_sale_onboarding_payment_acquirer(self): - """ Called by onboarding panel above the quotation list.""" - action = self.env.ref('sale.action_open_sale_onboarding_payment_acquirer_wizard').read()[0] + def action_open_sale_onboarding_sample_quotation(self): + """ Onboarding step for sending a sample quotation. Open a window to compose an email, + with the edi_invoice_template message loaded by default. """ + sample_sales_order = self._get_sample_sales_order() + template = self.env.ref('sale.email_template_edi_sale', False) + action = self.env.ref('sale.action_open_sale_onboarding_sample_quotation').read()[0] + action['context'] = { + 'default_res_id': sample_sales_order.id, + 'default_use_template': bool(template), + 'default_template_id': template and template.id or False, + 'default_model': 'sale.order', + 'default_composition_mode': 'comment', + 'mark_so_as_sent': True, + 'custom_layout': 'mail.mail_notification_paynow', + 'proforma': self.env.context.get('proforma', False), + 'force_email': True, + } return action @api.multi - def action_save_onboarding_signature(self): - self.sale_onboarding_signature_done = True + def action_save_onboarding_quotation_layout(self): + """ Set the onboarding step as done """ + if bool(self.logo) and self.logo != self._get_logo(): + self.set_onboarding_step_done('account_onboarding_invoice_layout_state') + + def get_and_update_sale_quotation_onboarding_state(self): + """ This method is called on the controller rendering method and ensures that the animations + are displayed only one time. """ + steps = [ + 'base_onboarding_company_state', + 'account_onboarding_invoice_layout_state', + 'sale_onboarding_order_confirmation_state', + 'sale_onboarding_sample_quotation_state', + ] + return self.get_and_update_onbarding_state('sale_quotation_onboarding_state', steps) + + @api.model + def action_open_sale_onboarding_quotation_layout(self): + """ Onboarding step for the quotation layout. """ + action = self.env.ref('sale.action_open_sale_onboarding_quotation_layout').read()[0] + action['res_id'] = self.env.user.company_id.id + return action diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index 6b27fd427a9..0f8f69ff898 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -519,6 +519,7 @@ class SaleOrder(models.Model): def message_post(self, **kwargs): if self.env.context.get('mark_so_as_sent'): self.filtered(lambda o: o.state == 'draft').with_context(tracking_disable=True).write({'state': 'sent'}) + self.env.user.company_id.set_onboarding_step_done('sale_onboarding_sample_quotation_state') return super(SaleOrder, self.with_context(mail_post_autofollow=True)).message_post(**kwargs) @api.multi diff --git a/addons/sale/static/src/img/onboarding_quotation_order_tooltip.jpg b/addons/sale/static/src/img/onboarding_quotation_order_tooltip.jpg Binary files differnew file mode 100644 index 00000000000..548a1d35ef2 --- /dev/null +++ b/addons/sale/static/src/img/onboarding_quotation_order_tooltip.jpg diff --git a/addons/sale/static/src/scss/sale_onboarding.scss b/addons/sale/static/src/scss/sale_onboarding.scss new file mode 100644 index 00000000000..6f00b2a60d0 --- /dev/null +++ b/addons/sale/static/src/scss/sale_onboarding.scss @@ -0,0 +1,11 @@ +.o_onboarding_order_confirmation { + & span.o_onboarding_order_confirmation_help img { + display: none; + position: absolute; + bottom:0; + } + & span.o_onboarding_order_confirmation_help:hover img { + display: block + } + +}
\ No newline at end of file diff --git a/addons/sale/views/assets.xml b/addons/sale/views/assets.xml index a736833aabc..519f86babf1 100644 --- a/addons/sale/views/assets.xml +++ b/addons/sale/views/assets.xml @@ -5,7 +5,11 @@ <script type="text/javascript" src="/sale/static/src/js/sale.js"></script> </xpath> </template> - + <template id="assets_backend_onboarding" name="onboarding_assets" inherit_id="web.assets_backend"> + <xpath expr="." position="inside"> + <link rel="stylesheet" type="text/scss" href="/sale/static/src/scss/sale_onboarding.scss"/> + </xpath> + </template> <template id="assets_frontend" inherit_id="web.assets_frontend" name="Sale frontend assets"> <xpath expr="." position="inside"> <link rel="stylesheet" type="text/scss" href="/sale/static/src/scss/sale_portal.scss"/> diff --git a/addons/sale/views/sale_onboarding_views.xml b/addons/sale/views/sale_onboarding_views.xml index 0f4ae9ede2e..3575729652a 100644 --- a/addons/sale/views/sale_onboarding_views.xml +++ b/addons/sale/views/sale_onboarding_views.xml @@ -1,107 +1,129 @@ <?xml version="1.0" encoding="utf-8"?> <odoo> <!-- ONBOARDING STEPS --> - <template id="onboarding_signature_step"> + <template id="onboarding_quotation_layout_step"> <t t-call="base.onboarding_step"> - <t t-set="title">Signature</t> - <t t-set="description">Request a digital signature to confirm orders.</t> + <t t-set="title">Quotation Layout</t> + <t t-set="description">Customize the look of your quotations.</t> <t t-set="done_icon" t-value="'fa-star'" /> <t t-set="done_text">Looks great!</t> - <t t-set="method" t-value="'action_open_sale_onboarding_signature'" /> + <t t-set="method" t-value="'action_open_sale_onboarding_quotation_layout'" /> <t t-set="model" t-value="'res.company'" /> - <t t-set="done" t-value="company.sale_onboarding_signature_done" /> + <t t-set="state" t-value="state.get('account_onboarding_invoice_layout_state')" /> </t> </template> - <template id="sale_onboarding_payment_acquirer_step" primary="True" - inherit_id="payment.onboarding_payment_acquirer_step"> - <xpath expr="//t[@t-set='method']" position="replace"> + <template id="sale_onboarding_order_confirmation_step"> + <t t-call="base.onboarding_step"> + <t t-set="title">Confirmation & payment</t> + <t t-set="description">Choose how to confirm quotations and get paid.</t> <t t-set="method" t-value="'action_open_sale_onboarding_payment_acquirer'" /> - </xpath> - <xpath expr="//t[@t-set='done']" position="replace"> - <t t-set="done" t-value="company.sale_onboarding_payment_acquirer_done" /> - </xpath> + <t t-set="model" t-value="'res.company'" /> + <t t-set="state" t-value="state.get('sale_onboarding_order_confirmation_state')" /> + </t> </template> - <template id="onboarding_quotation_layout_step" primary="True" inherit_id="account.onboarding_invoice_layout_step"> - <xpath expr="//t[@t-set='title']" position="replace"> - <t t-set="title">Quotation Layout</t> - </xpath> - <xpath expr="//t[@t-set='description']" position="replace"> - <t t-set="description">Create and send professional looking quotations.</t> - </xpath> + <template id="sale_onboarding_sample_quotation_step"> + <t t-call="base.onboarding_step"> + <t t-set="title">Sample Quotation</t> + <t t-set="description">Send a quotation to test the customer portal.</t> + <t t-set="method" t-value="'action_open_sale_onboarding_sample_quotation'" /> + <t t-set="model" t-value="'res.company'" /> + <t t-set="state" t-value="state.get('sale_onboarding_sample_quotation_state')" /> + </t> </template> + <!-- ONBOARDING PANEL--> - <template id="onboarding_quotation_panel" name="sale.quotation.onboarding.panel"> + <template id="sale_quotation_onboarding_panel" name="sale.quotation.onboarding.panel"> <t t-call="base.onboarding_container"> <t t-set="classes">o_onboarding_violet</t> <t t-set="bg_image">/sale/static/src/img/sale_quotation_onboarding_bg.jpg</t> <t t-set="close_method">action_close_sale_quotation_onboarding</t> <t t-set="close_model">res.company</t> - <t t-set="fold_method">action_toggle_fold_sale_quotation_onboarding</t> - <t t-set="fold_model">res.company</t> - <t t-set="folded_by_default" t-value="company.sale_quotation_onboarding_folded" /> - <t t-call="base.onboarding_company_step" name="company_step" /> <t t-call="sale.onboarding_quotation_layout_step" name="quotation_layout_step" /> - <t t-call="sale.onboarding_signature_step" name="signature_step" /> - <t t-call="sale.sale_onboarding_payment_acquirer_step" name="payment_acquirer_step" /> + <t t-call="sale.sale_onboarding_order_confirmation_step" name="payment_acquirer_step" /> + <t t-call="sale.sale_onboarding_sample_quotation_step" name="sample_quotation_step" /> </t> </template> - <!-- SIGNATURE FORM --> - <record model="ir.ui.view" id="sale_onboarding_signature_form"> - <field name="name">sale.onboarding.signature.form</field> - <field name="model">res.company</field> + <!-- ORDER CONFIRMATION --> + <record id="sale_onboarding_order_confirmation_form" model="ir.ui.view"> + <field name="name">sale.order.confirmation.onboarding.form</field> + <field name="model">sale.payment.acquirer.onboarding.wizard</field> + <field name="inherit_id" ref="payment.payment_acquirer_onboarding_wizard_form" /> + <field name="mode">primary</field> + <field name="priority">1000</field> <field name="arch" type="xml"> - <form string="Request an electronic signature"> - <div class="panel-body row"><!-- FIXME --> - Quotations can be confirmed automatically based on a customer signature, in - order to automate the delivery process. This functionality is convenient for all - the parties and offers as much legal guarantee as a paper signature. - <br /> - <br /> - Do you want to activate the digital signature? + <xpath expr="//div[@name='left-column']" position="attributes"> + <attribute name="class">col col-4</attribute> + </xpath> + <xpath expr="//div[@name='right-column']" position="inside"> + <div attrs="{'invisible': [('payment_method', '!=', 'digital_signature')]}"> + Digital signature offers as much legal guarantee as a paper signature, with way + more convenience for all parties. </div> - <div class="panel-body row"><!-- FIXME --> - <field name="portal_confirmation_sign_radio" widget="radio" /> + <div attrs="{'invisible': [('payment_method', '!=', 'none')]}"> + The salesperson confirms the quotation, after receiving an approval by email, a call, etc. </div> - <footer> - <button name="action_save_onboarding_signature" string="OK" class="oe_highlight" - type="object" /> - <button special="cancel" string="Cancel" /> - </footer> - </form> + </xpath> </field> </record> - <record id="action_open_sale_onboarding_signature" model="ir.actions.act_window"> - <field name="name">Request a digital signature</field> + <record id="action_open_sale_onboarding_payment_acquirer_wizard" model="ir.actions.act_window"> + <field name="name">Choose a payment method</field> <field name="type">ir.actions.act_window</field> - <field name="res_model">res.company</field> + <field name="res_model">sale.payment.acquirer.onboarding.wizard</field> <field name="view_type">form</field> <field name="view_mode">form</field> - <field name="view_id" ref="sale.sale_onboarding_signature_form" /> + <field name="view_id" ref="sale_onboarding_order_confirmation_form" /> <field name="target">new</field> </record> - <!-- PAYMENT ACQUIRER --> - <record id="sale_onboarding_payment_acquirer_form" model="ir.ui.view"> - <field name="name">sale.onboarding.payment.acquirer.form</field> - <field name="model">sale.payment.acquirer.onboarding.wizard</field> - <field name="inherit_id" ref="payment.payment_acquirer_onboarding_wizard_form"/> + <!-- QUOTATION LAYOUT --> + <record id="sale_onboarding_quotation_layout_form" model="ir.ui.view"> + <field name="name">sale.onboarding.quotation.layout.form</field> + <field name="model">res.company</field> + <field name="inherit_id" ref="base.view_company_document_template_form" /> <field name="mode">primary</field> <field name="priority">1000</field> <field name="arch" type="xml"> - <xpath expr="//div[@name='skip_step_text']" position="replace"> - <div class="mb16"> - You can skip this step if you don't manage payments on quotations. - </div> + <xpath expr="//button[@special='save']" position="replace"> + <button string="Apply" class="btn btn-primary" type="object" name="action_save_onboarding_quotation_layout" /> </xpath> - </field> + </field> </record> - <record id="action_open_sale_onboarding_payment_acquirer_wizard" model="ir.actions.act_window"> - <field name="name">Choose a payment method</field> + <record id="action_open_sale_onboarding_quotation_layout" model="ir.actions.act_window"> + <field name="name">Configure your document layout</field> <field name="type">ir.actions.act_window</field> - <field name="res_model">sale.payment.acquirer.onboarding.wizard</field> + <field name="res_model">res.company</field> + <field name="view_mode">form</field> <field name="view_type">form</field> + <field name="view_id" ref="sale_onboarding_quotation_layout_form" /> + <field name="target">new</field> + </record> + <!-- SAMPLE QUOTATION --> + <record model="ir.ui.view" id="sale_onboarding_sample_quotation_form"> + <field name="name">mail.compose.message.sale.onboarding.sample.quotation.form</field> + <field name="model">mail.compose.message</field> + <field name="inherit_id" ref="mail.email_compose_message_wizard_form" /> + <field name="priority">1000</field> + <field name="mode">primary</field> + <field name="arch" type="xml"> + <xpath expr="//span[@name='document_followers_text']" position="attributes"> + <attribute name="invisible">1</attribute> + </xpath> + <xpath expr="//button[@name='save_as_template']" position="attributes"> + <attribute name="invisible">1</attribute> + </xpath> + <xpath expr="//field[@name='template_id']" position="attributes"> + <attribute name="invisible">1</attribute> + </xpath> + </field> + </record> + + <record id="action_open_sale_onboarding_sample_quotation" model="ir.actions.act_window"> + <field name="name">Send a sample quotation.</field> + <field name="type">ir.actions.act_window</field> + <field name="res_model">mail.compose.message</field> <field name="view_mode">form</field> - <field name="view_id" ref="sale.sale_onboarding_payment_acquirer_form" /> + <field name="view_type">form</field> + <field name="view_id" ref="sale_onboarding_sample_quotation_form" /> <field name="target">new</field> </record> </odoo> diff --git a/addons/sale/views/sale_views.xml b/addons/sale/views/sale_views.xml index 69f9eabf864..6d63dbebf95 100644 --- a/addons/sale/views/sale_views.xml +++ b/addons/sale/views/sale_views.xml @@ -196,7 +196,7 @@ <field name="mode">primary</field> <field name="arch" type="xml"> <xpath expr="//tree" position="attributes"> - <attribute name="banner_route">/sales/onboarding_quotation_panel</attribute> + <attribute name="banner_route">/sales/sale_quotation_onboarding_panel</attribute> </xpath> </field> </record> diff --git a/addons/sale/wizard/payment_acquirer_onboarding_wizard.py b/addons/sale/wizard/payment_acquirer_onboarding_wizard.py index 6bc92ec825e..d81da663c13 100644 --- a/addons/sale/wizard/payment_acquirer_onboarding_wizard.py +++ b/addons/sale/wizard/payment_acquirer_onboarding_wizard.py @@ -1,18 +1,36 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. - -from odoo import models +from odoo import api, fields, models class PaymentWizard(models.TransientModel): + """ Override for the sale quotation onboarding panel. """ + _inherit = 'payment.acquirer.onboarding.wizard' _name = 'sale.payment.acquirer.onboarding.wizard' - def _hook_on_save_onboarding_payment_acquirer(self): - """ Override. Activate the option in sales settings. """ - self.env.user.company_id.portal_confirmation_pay = True + def _get_default_payment_method(self): + return self.env.user.company_id.sale_onboarding_payment_method + + payment_method = fields.Selection([ + ('digital_signature', 'Digital signature, without payment'), + ('paypal', 'PayPal'), + ('stripe', 'Stripe'), + ('manual', 'Wire Transfer'), + ('none', 'No digital signature or payment'), + ], default=_get_default_payment_method) + # def _set_payment_acquirer_onboarding_step_done(self): """ Override. """ - self.env.user.company_id.sale_onboarding_payment_acquirer_done = True + self.env.user.company_id.set_onboarding_step_done('sale_onboarding_order_confirmation_state') + + @api.multi + def add_payment_methods(self, *args, **kwargs): + self.env.user.company_id.sale_onboarding_payment_method = self.payment_method + if self.payment_method == 'digital_signature': + self.env.user.company_id.portal_confirmation_sign = True + if self.payment_method in ('paypal', 'stripe'): + self.env.user.company_id.portal_confirmation_pay = True # TODO self.digital_signature + return super(PaymentWizard, self).add_payment_methods(*args, **kwargs) diff --git a/addons/web/static/src/js/views/abstract_controller.js b/addons/web/static/src/js/views/abstract_controller.js index ef95ea72696..aed09c363cc 100644 --- a/addons/web/static/src/js/views/abstract_controller.js +++ b/addons/web/static/src/js/views/abstract_controller.js @@ -283,22 +283,22 @@ var AbstractController = AbstractAction.extend(ControlPanelMixin, { }); }, /** - * Renders the html provided by the route specified by the - * bannerRoute attribute on the controller (banner_route in the template). - * Renders it before the view output and add a css class 'o_has_banner' to it. - * There can be only one banner displayed at a time. - * - * If the banner contains stylesheet links, they are moved to <head> - * (and will only be fetched once). - * - * Route example: - * @http.route('/module/hello', auth='user', type='json') - * def hello(self): - * return {'html': '<h1>hello, world</h1>'} - * - * @private - * @returns {Deferred} - */ + * Renders the html provided by the route specified by the + * bannerRoute attribute on the controller (banner_route in the template). + * Renders it before the view output and add a css class 'o_has_banner' to it. + * There can be only one banner displayed at a time. + * + * If the banner contains stylesheet links or js files, they are moved to <head> + * (and will only be fetched once). + * + * Route example: + * @http.route('/module/hello', auth='user', type='json') + * def hello(self): + * return {'html': '<h1>hello, world</h1>'} + * + * @private + * @returns {Deferred} + */ _renderBanner: function () { if (this.bannerRoute !== undefined) { var self = this; @@ -315,13 +315,16 @@ var AbstractController = AbstractAction.extend(ControlPanelMixin, { if (self._$banner && self._$banner.remove) { self._$banner.remove(); } - // Stylesheets are moved to <head> and we wait for them to be loaded - // to prevent displaying unstyled content. + // Css and js are moved to <head> var defs = []; $('link[rel="stylesheet"]', $banner).each(function (i, link) { defs.push(ajax.loadCSS(link.href)); link.remove(); }); + $('script[type="text/javascript"]', $banner).each(function (i, js) { + defs.push(ajax.loadJS(js.src)); + js.remove(); + }); return $.when.apply($, defs).then(function () { $banner.prependTo(self.$el); self._$banner = $banner; diff --git a/addons/web/static/src/scss/banner.scss b/addons/web/static/src/scss/banner.scss index b119d9ab273..d7237203897 100644 --- a/addons/web/static/src/scss/banner.scss +++ b/addons/web/static/src/scss/banner.scss @@ -1,5 +1,8 @@ .o_has_banner { .o_view_nocontent { top: 30%; + @media screen and (max-height: map-get($grid-breakpoints, 'md')) { + position: relative; + } } } diff --git a/addons/web/static/src/scss/form_view.scss b/addons/web/static/src/scss/form_view.scss index 65c75b4d406..755572ceb25 100644 --- a/addons/web/static/src/scss/form_view.scss +++ b/addons/web/static/src/scss/form_view.scss @@ -607,17 +607,15 @@ &.o_company_document_layout { .image_selection_container { div { - width: 50%; + display: inline-block; img { margin-left: 0 !important; } } } - td { - padding: 0 45px 0 0; - } img[name="logo"] { - max-height: 300px; + max-height: 100px; + max-width: 300px; } } } @@ -648,6 +646,12 @@ width: 100%; } } + .o_onboarding_payment_acquirer_wizard { + a[type="action"] { + color: $link-color; + cursor: pointer; + } + } } @media print { diff --git a/addons/web/views/webclient_templates.xml b/addons/web/views/webclient_templates.xml index 5d1ff38695f..924c5353b9d 100644 --- a/addons/web/views/webclient_templates.xml +++ b/addons/web/views/webclient_templates.xml @@ -174,6 +174,7 @@ <link rel="stylesheet" type="text/scss" href="/web/static/src/scss/dropdown_menu.scss"/> <link rel="stylesheet" type="text/scss" href="/web/static/src/scss/search_view_extra.scss"/> <link rel="stylesheet" type="text/scss" href="/web/static/src/scss/data_export.scss"/> + <link rel="stylesheet" type="text/scss" href="/base/static/src/scss/onboarding.scss"/> <link rel="stylesheet" type="text/scss" href="/web/static/src/scss/base_settings.scss"/> <script type="text/javascript" src="/base/static/src/js/res_config_settings.js"></script> diff --git a/addons/website_sale/models/res_company.py b/addons/website_sale/models/res_company.py index c82e478ab26..aeaa4ffcbcf 100644 --- a/addons/website_sale/models/res_company.py +++ b/addons/website_sale/models/res_company.py @@ -7,11 +7,11 @@ from odoo import api, fields, models class ResCompany(models.Model): _inherit = 'res.company' - website_sale_onboarding_payment_acquirer_done =\ - fields.Boolean("Website sale onboarding payment acquirer step done", default=False) + website_sale_onboarding_payment_acquirer_state = fields.Selection([('not_done', "Not done"), ('just_done', "Just done"), ('done', "Done")], string="State of the website sale onboarding payment acquirer step", default='not_done') @api.model def action_open_website_sale_onboarding_payment_acquirer(self): """ Called by onboarding panel above the quotation list.""" + self.env.user.company_id.get_chart_of_accounts_or_fail() action = self.env.ref('website_sale.action_open_website_sale_onboarding_payment_acquirer_wizard').read()[0] return action diff --git a/addons/website_sale/views/onboarding_views.xml b/addons/website_sale/views/onboarding_views.xml index 6004dfab302..8efd0a582f1 100644 --- a/addons/website_sale/views/onboarding_views.xml +++ b/addons/website_sale/views/onboarding_views.xml @@ -5,27 +5,18 @@ <xpath expr="//t[@t-set='method']" position="replace"> <t t-set="method" t-value="'action_open_website_sale_onboarding_payment_acquirer'" /> </xpath> - <xpath expr="//t[@t-set='done']" position="replace"> - <t t-set="done" t-value="company.website_sale_onboarding_payment_acquirer_done" /> + <xpath expr="//t[@t-set='state']" position="replace"> + <t t-set="state" t-value="state.get('website_sale_onboarding_payment_acquirer_state')" /> </xpath> </template> - <record id="website_sale_onboarding_payment_acquirer_form" model="ir.ui.view"> - <field name="name">website.sale.onboarding.payment.acquirer.form</field> - <field name="model">website.sale.payment.acquirer.onboarding.wizard</field> - <field name="inherit_id" ref="payment.payment_acquirer_onboarding_wizard_form"/> - <field name="mode">primary</field> - <field name="priority">1000</field> - <field name="arch" type="xml"> - <xpath expr="//div[@name='skip_step_text']" position="replace"/> - </field> - </record> + <record id="action_open_website_sale_onboarding_payment_acquirer_wizard" model="ir.actions.act_window"> <field name="name">Choose a payment method</field> <field name="type">ir.actions.act_window</field> <field name="res_model">website.sale.payment.acquirer.onboarding.wizard</field> <field name="view_type">form</field> <field name="view_mode">form</field> - <field name="view_id" ref="website_sale_onboarding_payment_acquirer_form" /> + <field name="view_id" ref="payment.payment_acquirer_onboarding_wizard_form" /> <field name="target">new</field> </record> </odoo> diff --git a/addons/website_sale/wizard/payment_acquirer_onboarding_wizard.py b/addons/website_sale/wizard/payment_acquirer_onboarding_wizard.py index 688b740d72e..18531d1c337 100644 --- a/addons/website_sale/wizard/payment_acquirer_onboarding_wizard.py +++ b/addons/website_sale/wizard/payment_acquirer_onboarding_wizard.py @@ -10,4 +10,4 @@ class PaymentWizard(models.TransientModel): def _set_payment_acquirer_onboarding_step_done(self): """ Override. """ - self.env.user.company_id.website_sale_onboarding_payment_acquirer_done = True + self.env.user.company_id.set_onboarding_step_done('website_sale_onboarding_payment_acquirer_state') diff --git a/addons/website_theme_install/models/__init__.py b/addons/website_theme_install/models/__init__.py index 480a1b847c7..de58fda6e31 100644 --- a/addons/website_theme_install/models/__init__.py +++ b/addons/website_theme_install/models/__init__.py @@ -2,4 +2,3 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from . import ir_module_module -from . import res_company diff --git a/addons/website_theme_install/models/res_company.py b/addons/website_theme_install/models/res_company.py deleted file mode 100644 index ce1e3e719a7..00000000000 --- a/addons/website_theme_install/models/res_company.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from odoo import api, fields, models -from ast import literal_eval - - -class ResCompany(models.Model): - _inherit = 'res.company' - - def _compute_website_theme_onboarding_done(self): - """ The step is marked as done if one theme is installed. """ - # we need the same domain as the existing action - action = self.env.ref('website_theme_install.theme_install_kanban_action').read()[0] - domain = literal_eval(action['domain']) - domain.append(('state', '=', 'installed')) - installed_themes_count = self.env['ir.module.module'].sudo().search_count(domain) - for record in self: - record.website_theme_onboarding_done = (installed_themes_count > 0) - - website_theme_onboarding_done = fields.Boolean("Onboarding website theme step done", - compute='_compute_website_theme_onboarding_done') - - @api.model - def action_open_website_theme_selector(self): - action = self.env.ref('website_theme_install.theme_install_kanban_action').read()[0] - action['target'] = 'new' - return action diff --git a/addons/website_theme_install/views/views.xml b/addons/website_theme_install/views/views.xml index 4ccb733a4e0..3b6cd89db61 100644 --- a/addons/website_theme_install/views/views.xml +++ b/addons/website_theme_install/views/views.xml @@ -95,15 +95,4 @@ action = { <a href="/web#action=website_theme_install.theme_install_kanban_action">See themes here</a> </xpath> </template> - <!-- ONBOARDING --> - <template id="onboarding_website_theme_step"> - <t t-call="base.onboarding_step"> - <t t-set="title">Website Theme</t> - <t t-set="description">Select a theme for your wonderful website.</t> - <t t-set="btn_text">Let's start!</t> - <t t-set="method" t-value="'action_open_website_theme_selector'" /> - <t t-set="model" t-value="'res.company'" /> - <t t-set="done" t-value="company.website_theme_onboarding_done" /> - </t> - </template> </odoo> diff --git a/odoo/addons/base/data/report_paperformat_data.xml b/odoo/addons/base/data/report_paperformat_data.xml index 28d69f53863..3e03da5339f 100644 --- a/odoo/addons/base/data/report_paperformat_data.xml +++ b/odoo/addons/base/data/report_paperformat_data.xml @@ -2,7 +2,7 @@ <odoo> <data noupdate="1"> <record id="paperformat_euro" model="report.paperformat"> - <field name="name">European A4</field> + <field name="name">A4</field> <field name="default" eval="True" /> <field name="format">A4</field> <field name="page_height">0</field> diff --git a/odoo/addons/base/models/ir_actions_report.py b/odoo/addons/base/models/ir_actions_report.py index ca75aa35141..a1761a6759b 100644 --- a/odoo/addons/base/models/ir_actions_report.py +++ b/odoo/addons/base/models/ir_actions_report.py @@ -98,7 +98,7 @@ class IrActionsReport(models.Model): groups_id = fields.Many2many('res.groups', 'res_groups_report_rel', 'uid', 'gid', string='Groups') multi = fields.Boolean(string='On Multiple Doc.', help="If set to true, the action will not be displayed on the right toolbar of a form view.") - paperformat_id = fields.Many2one('report.paperformat', 'Paper format') + paperformat_id = fields.Many2one('report.paperformat', 'Paper Format') print_report_name = fields.Char('Printed Report Name', help="This is the filename of the report going to download. Keep empty to not change the report filename. You can use a python expression with the 'object' and 'time' variables.") attachment_use = fields.Boolean(string='Reload from Attachment', diff --git a/odoo/addons/base/models/res_company.py b/odoo/addons/base/models/res_company.py index c7f390600e0..d06ceb55170 100644 --- a/odoo/addons/base/models/res_company.py +++ b/odoo/addons/base/models/res_company.py @@ -55,7 +55,7 @@ class Company(models.Model): website = fields.Char(related='partner_id.website') vat = fields.Char(related='partner_id.vat', string="Tax ID") company_registry = fields.Char() - paperformat_id = fields.Many2one('report.paperformat', 'Paper format', default=lambda self: self.env.ref('base.paperformat_euro', raise_if_not_found=False)) + paperformat_id = fields.Many2one('report.paperformat', 'Paper Format', default=lambda self: self.env.ref('base.paperformat_euro', raise_if_not_found=False)) external_report_layout = fields.Selection([ ('background', 'Background'), ('boxed', 'Boxed'), @@ -66,8 +66,9 @@ class Company(models.Model): _sql_constraints = [ ('name_uniq', 'unique (name)', 'The company name must be unique !') ] - base_onboarding_company_done = fields.Boolean("Onboarding company step done", - compute="_compute_base_onboarding_company_done") + + base_onboarding_company_state = fields.Selection([ + ('not_done', "Not done"), ('just_done', "Just done"), ('done', "Done")], string="State of the onboarding company step", default='not_done') @api.model_cr def init(self): @@ -239,6 +240,7 @@ class Company(models.Model): currency = self.env['res.currency'].browse(values['currency_id']) if not currency.active: currency.write({'active': True}) + return super(Company, self).write(values) @api.constrains('parent_id') @@ -266,15 +268,37 @@ class Company(models.Model): else: return res - @api.depends('street') - def _compute_base_onboarding_company_done(self): - """ The company onboarding step is marked as done if street1 is filled. """ - for record in self: - record.base_onboarding_company_done = bool(record.street) - @api.model def action_open_base_onboarding_company(self): """ Onboarding step for company basic information. """ action = self.env.ref('base.action_open_base_onboarding_company').read()[0] action['res_id'] = self.env.user.company_id.id return action + + def set_onboarding_step_done(self, step_name): + if self[step_name] == 'not_done': + self[step_name] = 'just_done' + + def get_and_update_onbarding_state(self, onboarding_state, steps_states): + """ Needed to display onboarding animations only one time. """ + old_values = {} + all_done = True + for step_state in steps_states: + old_values[step_state] = self[step_state] + if self[step_state] == 'just_done': + self[step_state] = 'done' + all_done = all_done and self[step_state] == 'done' + + if all_done: + if self[onboarding_state] == 'not_done': + # string `onboarding_state` instead of variable name is not an error + old_values['onboarding_state'] = 'just_done' + else: + old_values['onboarding_state'] = 'done' + self[onboarding_state] = 'done' + return old_values + + @api.multi + def action_save_onboarding_company_step(self): + if bool(self.street): + self.set_onboarding_step_done('base_onboarding_company_state') diff --git a/odoo/addons/base/static/src/scss/onboarding.scss b/odoo/addons/base/static/src/scss/onboarding.scss index 3140e0280f2..283d3dc3f6b 100644 --- a/odoo/addons/base/static/src/scss/onboarding.scss +++ b/odoo/addons/base/static/src/scss/onboarding.scss @@ -16,23 +16,31 @@ $o-onboarding-violet-bg-2: #475DA4; $o-onboarding-violet-ui-1: #F39D9B; $o-onboarding-violet-ui-2: #FBEEC8; +@mixin o-onboarding-vertical-gradient($startColor: #555, $endColor: #333) { + background-color: mix($startColor, $endColor); + background-image: -webkit-gradient(linear, left top, left bottom, from($startColor), to($endColor)); + background-image: -webkit-linear-gradient(top, $startColor, $endColor); + background-image: -moz-linear-gradient(top, $startColor, $endColor); + background-image: -ms-linear-gradient(top, $startColor, $endColor); + background-image: -o-linear-gradient(top, $startColor, $endColor); + background-image: linear-gradient(to bottom, $startColor, $endColor); +} + .o_onboarding { position: relative; overflow: hidden; - background: white center; + background: white center top; background-size: cover; - background-position: center top; border-bottom: 1px solid gray('300'); .o_onboarding_wrap { padding: $o-horizontal-padding*2 $o-horizontal-padding; } - - .o_onboarding_folded_title { - display: none; + .o_onboarding_step_title { + cursor: pointer; } - .o_onboarding_btn_fold, .o_onboarding_btn_close { + .o_onboarding_btn_close { @include o-position-absolute(auto, 0, 0); padding: 6px $o-horizontal-padding + 6px; cursor: pointer; @@ -40,10 +48,25 @@ $o-onboarding-violet-ui-2: #FBEEC8; .o_onboarding_btn_close { top: 0; + bottom: auto; } - - .modal-body, .modal-title { - color: black; + .o_onboarding_completed_message { + @include o-position-absolute(auto, 0,0,0); + //transform: translateY(115%); + //transition: transform $o-onboarding-dot-timing; + box-shadow: 0 2px 10px gray(600); + padding: $o-horizontal-padding; + background-color: white; + color: $o-main-text-color; + opacity: 0; + z-index: -1; + &.o_onboarding_steps_just_done { + animation: o_onb_fadeIn ($o-onboarding-dot-timing*3); + } + &.o_onboarding_steps_done { + opacity: 1; + z-index: auto; + } } .o_onboarding_steps, .o_onboarding_step { @@ -56,10 +79,6 @@ $o-onboarding-violet-ui-2: #FBEEC8; @include media-breakpoint-down(sm) { flex-flow: column nowrap; - - .o_onboarding_folded_title { - border-right: none; - } } } @@ -83,7 +102,7 @@ $o-onboarding-violet-ui-2: #FBEEC8; &:first-of-type { .o_onboarding_progress { - display: none; + display: none !important; } .o_onboarding_dot { @@ -92,6 +111,9 @@ $o-onboarding-violet-ui-2: #FBEEC8; } .o_onboarding_step_content { + @include media-breakpoint-up(lg) { + min-width: 230px; + } max-width: 230px; padding: 0 10px; @@ -166,14 +188,9 @@ $o-onboarding-violet-ui-2: #FBEEC8; } .o_onboarding_all_done { - animation: bounceIn ($o-onboarding-dot-timing*2); display: block; } - .o_onboarding_step_content_info { - animation: o_onb_fadeIn ($o-onboarding-dot-timing*3); - } - + .o_onboarding_done { .o_onboarding_dot { transition-delay: 0s; @@ -188,100 +205,24 @@ $o-onboarding-violet-ui-2: #FBEEC8; } } } - } - - @keyframes o_onb_fadeIn { - 0% {opacity: 0} - 33% {opacity: 0} - 100% {opacity: 1} - } - - // ========= Transition ========= - &.collapsing { - transition: all .25s; - min-height: 30px; - } - - // ========= Folded Design ========= - // ====================================== - - &.collapse:not(.in) { - min-height: 30px; - background-image: none!important; - display: block; - - .o_onboarding_hide_folded { - display: none!important; - } - .o_onboarding_btn_fold { - top: 0; - left: 0; - transform: scaleY(-1); - } + &.o_onboarding_just_done { - .o_onboarding_wrap { - padding: 0 $o-horizontal-padding; - - .o_onboarding_steps { - padding: 0; - justify-content: baseline; - } - - .o_onboarding_folded_title { - padding: 6px 10px 6px 0; - - @include media-breakpoint-up(md) { - border-right: 1px solid gray('300'); - } + .o_onboarding_all_done { + animation: bounceIn ($o-onboarding-dot-timing*2); + display: block; } - - .o_onboarding_folded_title, .o_onboarding_step { - display: inline-flex; + .o_onboarding_step_content_info { + animation: o_onb_fadeIn ($o-onboarding-dot-timing*3); } + } - .o_onboarding_step { - align-items: flex-start; - flex: 0 1 auto; - padding-right: 12px; - - &:not(:last-of-type) { - &:before, &:after { - content: " "; - @include o-position-absolute(0, 0); - - border-top: floor($o-statusbar-height/2) solid transparent; - border-bottom: ceil($o-statusbar-height/2) solid transparent; - border-right: none; - border-left: $o-statusbar-arrow-width solid $o-view-background-color; - -moz-transform: scale(0.9999); // Smooth the triangle on firefox - } - - &:before { - right: -1px; - border-left-color: gray('300'); - } - } - - .o_onboarding_step_content { - padding: 0; - display: flex; - } - - .o_onboarding_step_content_info .o_onboarding_step_title { - margin: 7px!important; - font-size: $font-size-base; - } - - .o_onboarding_all_done { - margin: 6px 10px 0 -4px!important; + } - > i:before { - content: "\f00c"; - } - } - } - } + @keyframes o_onb_fadeIn { + 0% {opacity: 0} + 33% {opacity: 0} + 100% {opacity: 1} } // ========= Color Variations ========= @@ -289,12 +230,12 @@ $o-onboarding-violet-ui-2: #FBEEC8; // ========= Variation Mixin ========== @mixin o-onboarding-variation($bg1, $bg2, $ui1, $ui2) { - &, .o_onboarding_btn_fold, .o_onboarding_step_title, .o_onboarding_step_action, a.o_onboarding_all_done, .o_onboarding_all_done > .fa, .o_onboarding_btn_close { - color: white; + &, .o_onboarding_step_title, .o_onboarding_step_action, a.o_onboarding_all_done, .o_onboarding_all_done > .fa, .o_onboarding_btn_close { + color: white !important; } .o_onboarding_wrap { - background-image: linear-gradient(to bottom, fade-out($bg1, 0.5), $bg2); + @include o-onboarding-vertical-gradient(fade-out($bg1, 0.5), $bg2); box-shadow: inset 0 -7px 20px -5px fade-out(black, 0.7); } @@ -324,15 +265,15 @@ $o-onboarding-violet-ui-2: #FBEEC8; } // ========= Variation Templates ========== - &.o_onboarding_blue.collapse.in, &.o_onboarding_blue.collapsing { + &.o_onboarding_blue { @include o-onboarding-variation($o-onboarding-blue-bg-1, $o-onboarding-blue-bg-2, $o-onboarding-blue-ui-1, $o-onboarding-blue-ui-2); } - &.o_onboarding_orange.collapse.in, &.o_onboarding_orange.collapsing { + &.o_onboarding_orange { @include o-onboarding-variation($o-onboarding-orange-bg-1, $o-onboarding-orange-bg-2, $o-onboarding-orange-ui-1, $o-onboarding-orange-ui-2); } - &.o_onboarding_violet.collapse.in, &.o_onboarding_violet.collapsing { + &.o_onboarding_violet { @include o-onboarding-variation($o-onboarding-violet-bg-1, $o-onboarding-violet-bg-2, $o-onboarding-violet-ui-1, $o-onboarding-violet-ui-2); } @@ -451,10 +392,10 @@ $o-onboarding-violet-ui-2: #FBEEC8; } } - .o_onboarding_done { + .o_onboarding_just_done { // Activate confetti cannon when step is done .o_onboarding_confetti { display: block; } } -}
\ No newline at end of file +} diff --git a/odoo/addons/base/views/onboarding_views.xml b/odoo/addons/base/views/onboarding_views.xml index dde94009462..3f7adffe9bc 100644 --- a/odoo/addons/base/views/onboarding_views.xml +++ b/odoo/addons/base/views/onboarding_views.xml @@ -1,85 +1,89 @@ <?xml version="1.0" encoding="utf-8"?> <odoo> <template id="onboarding_container"> - <div class="o_onboarding_container collapse in show"> - <t t-call-assets="base.assets_onboarding" /> - <div t-att-class="'o_onboarding ' + classes + (' collapse' if folded_by_default else ' collapse in show')" + <div class="modal o_onboarding_modal" tabindex="-1" role="dialog"> + <div class="modal-dialog" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <h5 class="modal-title">Closing the onboarding panel</h5> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"> + <span aria-label="Close">&times;</span> + </button> + </div> + <div class="modal-body"> + <p>Are you sure you want to close this onboarding panel? + This action is definitive.</p> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-secondary" data-dismiss="modal">No + </button> + <a type="action" class="btn btn-primary" data-dismiss="modal" + data-toggle="collapse" href=".o_onboarding_container" + t-att-data-model="close_model" t-att-data-method="close_method"> + Yes + </a> + </div> + </div> + </div> + </div> + <div class="o_onboarding_container collapse show"> + <div t-att-class="'o_onboarding ' + ('o_onboarding_completed ' if steps_completed else ' ') + classes" t-att-style="'background-image:url(' + bg_image + ')'"> <div class="o_onboarding_wrap"> - <a t-if="not no_close_btn" href="#" data-toggle="modal" data-target=".o_onboarding_modal" - class="float-right o_onboarding_btn_close o_onboarding_hide_folded"> + <a href="#" data-toggle="modal" data-target=".o_onboarding_modal" + class="float-right o_onboarding_btn_close"> <i class="fa fa-times" title="Close the onboarding panel" /> </a> - <!-- Modal --> - <div class="o_onboarding_modal modal fade" tabindex="-1" role="dialog" aria-labelledby="onboardingCloseLabel"> - <div class="modal-dialog" role="document"> - <div class="modal-content"> - <div class="modal-header"> - <h4 class="modal-title" id="onboardingCloseLabel">Closing the onboarding panel</h4> - <button type="button" class="close" data-dismiss="modal" aria-label="Close"> - <span aria-label="close the modal">&times;</span> - </button> - </div> - <div class="modal-body"> - Are you sure you want to close this onboarding panel? - This action is definitive. - </div> - <div class="modal-footer"> - <button type="button" class="btn btn-secondary" data-dismiss="modal"> - No - </button> - <a type="action" class="btn btn-primary" data-dismiss="modal" - data-toggle="collapse" href=".o_onboarding_container" - t-att-data-model="close_model" - t-att-data-method="close_method"> - Yes - </a> - </div> - </div> - </div> - </div> <div class="o_onboarding_steps"> - <span t-if="not no_fold_btn" class="o_onboarding_folded_title"> - Configuration steps: - </span> <t t-raw="0" /> </div> - <a t-if="not no_fold_btn or folded_by_default" data-toggle="collapse" - href=".o_onboarding" type="action" t-att-data-model="fold_model" - t-att-data-method="fold_method" class="o_onboarding_btn_fold text-right"> - <i class="fa fa-chevron-up" title="Fold the onboarding panel" /> - </a> + <div t-att-class="'o_onboarding_completed_message text-center' + (' o_onboarding_steps_done' if state.get('onboarding_state') in ('done', 'just_done') else '') + (' o_onboarding_steps_just_done' if state.get('onboarding_state') == 'just_done' else '')"> + <i class="fa fa-check text-success mr8" /> + <span class="h3 mr16"> + <t t-if="text_completed" t-esc="text_completed" /> + <t t-else="">Nice work!</t> + </span> + <div class="btn btn-primary btn-sm"> + <a type="action" class="btn btn-primary" data-toggle="collapse" href=".o_onboarding_container" + t-att-data-model="close_model" t-att-data-method="close_method"> + Close + </a> + </div> + </div> </div> </div> </div> </template> <template id="onboarding_step"> - <div t-att-class="'o_onboarding_step ' + ('o_onboarding_done' if done else 'o_onboarding_current')"> + <div t-att-class="'o_onboarding_step ' + ('o_onboarding_done' if state in ('done', 'just_done') else 'o_onboarding_current') + (' o_onboarding_just_done' if state == 'just_done' else '')"> <t t-call="base.onboarding_confetti" /> - <div class="o_onboarding_progress o_onboarding_hide_folded d-none d-md-inline-block progress"> + <div class="o_onboarding_progress d-none d-md-inline-block progress"> <div class="progress-bar"></div> </div> - <span class="o_onboarding_dot o_onboarding_hide_folded d-none d-md-inline-block fa fa-check" /> + <span class="o_onboarding_dot d-none d-md-inline-block fa fa-check" /> <div class="o_onboarding_step_content"> <div class="o_onboarding_step_content_info"> <h4 class="o_onboarding_step_title mt16"> - <t t-esc="title" /> + <a type="action" data-reload-on-close="true" role="button" + t-att-data-method="method" t-att-data-model="model"> + <t t-esc="title" /> + </a> </h4> - <p class="o_onboarding_hide_folded mb16"> + <p class="mb16"> <t t-esc="description" /> </p> </div> - <a class="o_onboarding_step_action o_onboarding_hide_folded btn" type="action" + <a class="o_onboarding_step_action btn" type="action" data-reload-on-close="true" role="button" t-att-data-method="method" t-att-data-model="model"> <t t-if="btn_text" t-esc="btn_text" /> <t t-else="">Let's do it</t> </a> - <a class="o_onboarding_all_done mt16" t-att-name="form_ref" type="action" + <a class="o_onboarding_all_done mt16" type="action" data-reload-on-close="true" role="button" t-att-data-method="method" t-att-data-model="model"> <i t-attf-class="text-success mr6 fa #{done_icon if done_icon else 'fa-check'}" /> - <span class="o_onboarding_hide_folded"> + <span> <t t-if="done_text" t-esc="done_text" /> <t t-else="">All done!</t> </span> @@ -132,22 +136,15 @@ <template id="onboarding_company_step"> <t t-call="base.onboarding_step"> <t t-set="title">Company Data</t> - <t t-set="description">Your information is displayed on the invoices you send to - clients. + <t t-set="description"> + Set your company's data for documents header/footer. </t> <t t-set="btn_text">Let's start!</t> <t t-set="method" t-value="'action_open_base_onboarding_company'" /> <t t-set="model" t-value="'res.company'" /> - <t t-set="done" t-value="company.base_onboarding_company_done" /> + <t t-set="state" t-value="state.get('base_onboarding_company_state')" /> </t> </template> - <template id="assets_onboarding"> - <!-- Include backend mixins and variables, completed with bootstrap ones --> - <t t-call="web._assets_backend_helpers"/> - <link rel="stylesheet" type="text/scss" href="/web/static/lib/bootstrap/scss/_variables.scss"/> - - <link rel="stylesheet" type="text/scss" href="/base/static/src/scss/onboarding.scss"/> - </template> <!-- COMPANY FORM --> <record id="base_onboarding_company_form" model="ir.ui.view"> <field name="name">base.company.onboarding.form</field> @@ -156,13 +153,10 @@ <field name="mode">primary</field> <field name="priority">1000</field> <field name="arch" type="xml"> - <field name="logo" position="attributes"> - <attribute name="attrs">{'invisible': 1}</attribute> - </field> <xpath expr="//group[@name='social_media']" position="replace" /> <form position="inside"> <footer position="replace"> - <button special="save" string="Save" class="btn btn-primary" type="object" /> + <button name="action_save_onboarding_company_step" class="btn btn-primary" type="object" string="Apply" /> <button special="cancel" string="Cancel" /> </footer> </form> diff --git a/odoo/addons/base/views/res_company_views.xml b/odoo/addons/base/views/res_company_views.xml index e5940c39fbf..12ba0f6514d 100644 --- a/odoo/addons/base/views/res_company_views.xml +++ b/odoo/addons/base/views/res_company_views.xml @@ -187,44 +187,37 @@ <field name="priority">2048</field> <field name="arch" type="xml"> <form class="o_company_document_layout"> - <group col="2"> - <group> - <label for="external_report_layout" string="Template" colspan="2" /> - <field name="external_report_layout" colspan="2" nolabel="1" - class="image_selection_container" - widget="image_selection" options="{ - 'background': { - 'image_link': '/base/static/img/preview_background.png', - 'preview_link': '/base/static/pdf/preview_background.pdf' - }, - 'boxed': { - 'image_link': '/base/static/img/preview_boxed.png', - 'preview_link': '/base/static/pdf/preview_boxed.pdf' - }, - 'clean': { - 'image_link': '/base/static/img/preview_clean.png', - 'preview_link': '/base/static/pdf/preview_clean.pdf' - }, - 'standard': { - 'image_link': '/base/static/img/preview_standard.png', - 'preview_link': '/base/static/pdf/preview_standard.pdf' - } - }" /> - </group> - <group> - <label for="logo" colspan="2" string="Company logo" /> - <field name="logo" widget="image" nolabel="1" colspan="2" /> - <label for="report_header" colspan="2" string="Company Tagline" /> - <field name="report_header" nolabel="1" colspan="2" - placeholder="e.g. Global Business Solutions" /> - <label for="report_footer" colspan="2" - string="Extra information in the footer" /> - <field name="report_footer" nolabel="1" colspan="2" - placeholder="e.g. Opening hours, bank accounts (one per line)" /> - </group> + <group> + <field name="logo" widget="image" /> + <field name="external_report_layout" string="Layout" + class="image_selection_container" + widget="image_selection" options="{ + 'background': { + 'image_link': '/base/static/img/preview_background.png', + 'preview_link': '/base/static/pdf/preview_background.pdf' + }, + 'boxed': { + 'image_link': '/base/static/img/preview_boxed.png', + 'preview_link': '/base/static/pdf/preview_boxed.pdf' + }, + 'clean': { + 'image_link': '/base/static/img/preview_clean.png', + 'preview_link': '/base/static/pdf/preview_clean.pdf' + }, + 'standard': { + 'image_link': '/base/static/img/preview_standard.png', + 'preview_link': '/base/static/pdf/preview_standard.pdf' + } + }" /> + <field name="report_header" widget="char" + placeholder="e.g. Global Business Solutions" /> + <field name="report_footer" string="Footer" + placeholder="e.g. Opening hours, bank accounts (one per line)" /> + <field name="paperformat_id" widget="selection" /> </group> <footer> - <button special="save" string="Save" class="btn btn-primary" type="object" /> + <button special="save" string="Save" + class="btn btn-primary" type="object" /> <button special="cancel" string="Cancel" /> </footer> </form> |
