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 /addons | |
| 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
Diffstat (limited to 'addons')
38 files changed, 559 insertions, 854 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> |
