[REF] *: use onboarding module

* = account{_payment}, base, onboarding, payment{_stripe},
sale{_management}, web, website_sale

Use the dedicated onboarding module introduced in 16.0 instead of
the res.company model to store onboarding progress.

It allows
 * onboarding steps to be reused across panels
 * to support steps that should be completed per-database or per-company
 * to clean the res.company model from many fields and methods,
 * to remove many views, controllers, actions

Module-specific notes:
* account: We also clean the remaining two steps that are not
part of an accounting panel but make the most sense to be kept here.
* account_payment: Following 8e4e8eb8, the payment provider step is
added to the invoicing onboarding panel. We apply this change here too.
Also impacts the website_sale_dashboard panel (see related ENT PR).
(The "sale tax" one is currently used for to the website sale dashboard).
* payment: Note that the step was already not part of an onboarding
panel within this module.
* website_sale: We clean
  * a field not used (The website_sale dashboard onboarding panel used
  the payment_provider_onboarding_state field).
  * a method that was only called from website_sale_dashboard, so it is
  moved there. See related ENT PR.

Includes a few tests.

Moving views/templates/styling, as well as cleaning residual onboarding-related fields and methods in base, including populate.

This also includes restoring the "onboarding_complete" overlay panel
animating it to disappear after a few seconds so that it doesn't hide
text and block buttons to re-open steps.

Task-3025136

Part-of: odoo/odoo#104223
This commit is contained in:
Florian Charlier
2023-06-30 23:37:50 +02:00
parent 518a4e4c43
commit 77f9ff50db
65 changed files with 754 additions and 882 deletions
+3 -2
View File
@@ -14,7 +14,7 @@ You could use this simplified accounting in case you work with an (external) acc
""",
'category': 'Accounting/Accounting',
'website': 'https://www.odoo.com/app/invoicing',
'depends' : ['base_setup', 'product', 'analytic', 'portal', 'digest'],
'depends': ['base_setup', 'onboarding', 'product', 'analytic', 'portal', 'digest'],
'data': [
'security/account_security.xml',
'security/ir.model.access.csv',
@@ -22,6 +22,7 @@ You could use this simplified accounting in case you work with an (external) acc
'data/digest_data.xml',
'views/account_report.xml',
'data/mail_template_data.xml',
'data/onboarding_data.xml',
'views/account_payment_view.xml',
'wizard/account_automatic_entry_wizard_views.xml',
'wizard/account_unreconcile_view.xml',
@@ -59,7 +60,6 @@ You could use this simplified accounting in case you work with an (external) acc
'views/account_journal_dashboard_view.xml',
'views/account_portal_templates.xml',
'views/report_payment_receipt_templates.xml',
'views/account_onboarding_templates.xml',
'data/service_cron.xml',
'views/account_incoterms_view.xml',
'data/account_incoterms_data.xml',
@@ -94,6 +94,7 @@ You could use this simplified accounting in case you work with an (external) acc
'account/static/src/components/**/*',
'account/static/src/js/tours/account.js',
'account/static/src/xml/**/*',
'account/static/src/views/**/*.js',
],
'web.assets_frontend': [
'account/static/src/js/account_portal_sidebar.js',
-1
View File
@@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import onboarding
from . import portal
from . import terms
-49
View File
@@ -1,49 +0,0 @@
from odoo import http
from odoo.http import request
class OnboardingController(http.Controller):
@http.route('/account/account_invoice_onboarding', auth='user', type='json')
def account_invoice_onboarding(self, context=None):
""" Returns the `banner` for the account invoice onboarding panel.
It can be empty if the user has closed it or if he doesn't have
the permission to see it. """
# Forward `allowed_company_ids` in the context
# so `request.env.company` returns the company selected in the dropdown
if context:
request.update_context(**context)
company = request.env.company
if not request.env.is_admin() or \
company.account_invoice_onboarding_state == 'closed':
return {}
return {
'html': request.env['ir.qweb']._render('account.account_invoice_onboarding_panel', {
'company': company,
'state': company.get_and_update_account_invoice_onboarding_state()
})
}
@http.route('/account/account_dashboard_onboarding', auth='user', type='json')
def account_dashboard_onboarding(self, context=None):
""" 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. """
# Forward `allowed_company_ids` in the context
# so `request.env.company` returns the company selected in the dropdown
if context:
request.update_context(**context)
company = request.env.company
if not request.env.is_admin() or \
company.account_dashboard_onboarding_state == 'closed':
return {}
return {
'html': request.env['ir.qweb']._render('account.account_dashboard_onboarding_panel', {
'company': company,
'state': company.get_and_update_account_dashboard_onboarding_state()
})
}
+134
View File
@@ -0,0 +1,134 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- ONBOARDING STEPS -->
<!-- INVOICING -->
<record id="onboarding_onboarding_step_company_data" model="onboarding.onboarding.step">
<field name="title">Company Data</field>
<field name="description">Set your company's data for documents header/footer.</field>
<field name="button_text">Let's start!</field>
<field name="done_text">Looks great!</field>
<field name="panel_step_open_action_name">action_open_step_company_data</field>
<field name="step_image" type="base64" file="base/static/img/onboarding_company-data.png"></field>
<field name="step_image_filename">onboarding_company-data.png</field>
<field name="step_image_alt">Onboarding Company Data</field>
<field name="sequence">1</field>
</record>
<record id="onboarding_onboarding_step_base_document_layout" model="onboarding.onboarding.step">
<field name="title">Documents Layout</field>
<field name="description">Customize the look of your documents.</field>
<field name="button_text">Customize</field>
<field name="done_text">Looks great!</field>
<field name="panel_step_open_action_name">action_open_step_base_document_layout</field>
<field name="step_image" type="base64" file="base/static/img/onboarding_quotation-layout.png"></field>
<field name="step_image_filename">onboarding_quotation-layout.png</field>
<field name="step_image_alt">Onboarding Documents Layout</field>
<field name="sequence">3</field>
</record>
<record id="onboarding_onboarding_step_create_invoice" model="onboarding.onboarding.step">
<field name="title">Create Invoice</field>
<field name="description">Create your first invoice.</field>
<field name="button_text">Create</field>
<field name="done_text">First invoice sent!</field>
<field name="panel_step_open_action_name">action_open_step_create_invoice</field>
<field name="step_image" type="base64" file="base/static/img/onboarding_default.png"></field>
<field name="step_image_filename">onboarding_default.png</field>
<field name="step_image_alt">Onboarding Create Invoice</field>
<field name="sequence">4</field>
</record>
<!-- DASHBOARD -->
<record id="onboarding_onboarding_step_fiscal_year" model="onboarding.onboarding.step">
<field name="title">Accounting Periods</field>
<field name="description">Define your fiscal years &amp; tax returns periodicity.</field>
<field name="button_text">Configure</field>
<field name="done_text">Step completed!</field>
<field name="panel_step_open_action_name">action_open_step_fiscal_year</field>
<field name="step_image" type="base64" file="base/static/img/onboarding_accounting-periods.png"></field>
<field name="step_image_filename">onboarding_accounting-periods.png</field>
<field name="step_image_alt">Onboarding Accounting Periods</field>
<field name="sequence">1</field>
</record>
<record id="onboarding_onboarding_step_bank_account" model="onboarding.onboarding.step">
<field name="title">Bank Account</field>
<field name="description">Connect your financial accounts in seconds.</field>
<field name="button_text">Add a bank account</field>
<field name="done_text">Step Completed!</field>
<field name="panel_step_open_action_name">action_open_step_bank_account</field>
<field name="step_image" type="base64" file="base/static/img/onboarding_puzzle.png"></field>
<field name="step_image_filename">onboarding_puzzle.png</field>
<field name="step_image_alt">Onboarding Bank Account</field>
<field name="sequence">2</field>
</record>
<record id="onboarding_onboarding_step_default_taxes" model="onboarding.onboarding.step">
<field name="title">Taxes</field>
<field name="description">Set default Taxes for sales and purchase transactions.</field>
<field name="button_text">Review</field>
<field name="done_text">Taxes set!</field>
<field name="panel_step_open_action_name">action_open_step_default_taxes</field>
<field name="step_image" type="base64" file="base/static/img/onboarding_taxes.png"></field>
<field name="step_image_filename">onboarding_taxes.png</field>
<field name="step_image_alt">Onboarding Taxes</field>
<field name="sequence">3</field>
</record>
<record id="onboarding_onboarding_step_chart_of_accounts" model="onboarding.onboarding.step">
<field name="title">Chart of Accounts</field>
<field name="description">Set up your chart of accounts and record initial balances.</field>
<field name="button_text">Review</field>
<field name="done_text">Chart of accounts set!</field>
<field name="panel_step_open_action_name">action_open_step_chart_of_accounts</field>
<field name="step_image" type="base64" file="base/static/img/onboarding_chart-of-accounts.png"></field>
<field name="step_image_filename">onboarding_chart-of-accounts.png</field>
<field name="step_image_alt">Onboarding Bank Account</field>
<field name="sequence">4</field>
</record>
<!-- WITHOUT PANEL -->
<record id="onboarding_onboarding_step_setup_bill" model="onboarding.onboarding.step">
<field name="title">Import Vendor Bills</field>
<field name="sequence">1000</field>
</record>
<record id="onboarding_onboarding_step_sales_tax" model="onboarding.onboarding.step">
<field name="title">Taxes</field>
<!-- Fields values used if/when added in a panel within other modules -->
<field name="description">Choose a default sales tax for your products.</field>
<field name="button_text">Set taxes</field>
<field name="done_text">Step Completed!</field>
<field name="panel_step_open_action_name">action_open_step_sales_tax</field>
<field name="step_image" type="base64" file="base/static/img/onboarding_puzzle.png"></field>
<field name="step_image_filename">onboarding_puzzle.png</field>
<field name="step_image_alt">Onboarding Bank Account</field>
<field name="sequence">100</field> <!-- after "Online Payment" -->
</record>
<!-- ONBOARDING PANELS -->
<record id="onboarding_onboarding_account_invoice" model="onboarding.onboarding">
<field name="name">Invoicing Onboarding</field>
<field name="step_ids" eval="[
Command.link(ref('account.onboarding_onboarding_step_company_data')),
Command.link(ref('account.onboarding_onboarding_step_base_document_layout')),
Command.link(ref('account.onboarding_onboarding_step_create_invoice'))
]"/>
<field name="route_name">account_invoice</field>
<field name="panel_close_action_name">action_close_panel_account_invoice</field>
</record>
<record id="onboarding_onboarding_account_dashboard" model="onboarding.onboarding">
<field name="name">Account Dashboard Onboarding</field>
<field name="step_ids" eval="[
Command.link(ref('account.onboarding_onboarding_step_fiscal_year')),
Command.link(ref('account.onboarding_onboarding_step_bank_account')),
Command.link(ref('account.onboarding_onboarding_step_default_taxes')),
Command.link(ref('account.onboarding_onboarding_step_chart_of_accounts'))
]"/>
<field name="route_name">account_dashboard</field>
<field name="panel_close_action_name">action_close_panel_account_dashboard</field>
</record>
</data>
</odoo>
+2
View File
@@ -37,5 +37,7 @@ from . import ir_actions_report
from . import ir_module
from . import res_currency
from . import account_report
from . import onboarding_onboarding
from . import onboarding_onboarding_step
from . import template_generic_coa
from . import uom_uom
@@ -620,7 +620,7 @@ class account_journal(models.Model):
""" This function is called by the "Import" button of Vendor Bills,
visible on dashboard if no bill has been created yet.
"""
self.env.company.sudo().set_onboarding_step_done('account_setup_bill_state')
self.env['onboarding.onboarding.step'].action_validate_step('account.onboarding_onboarding_step_setup_bill')
new_wizard = self.env['account.tour.upload.bill'].create({})
view_id = self.env.ref('account.account_tour_upload_bill').id
@@ -777,14 +777,3 @@ class account_journal(models.Model):
def create_internal_transfer(self):
"""return action to create a internal transfer"""
return self.open_payments_action('transfer', mode='form')
#####################
# Setup Steps Stuff #
#####################
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.sudo().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_state = 'not_done'
+5 -159
View File
@@ -26,14 +26,6 @@ MONTH_SELECTION = [
('12', 'December'),
]
ONBOARDING_STEP_STATES = [
('not_done', "Not done"),
('just_done', "Just done"),
('done', "Done"),
]
DASHBOARD_ONBOARDING_STATES = ONBOARDING_STEP_STATES + [('closed', 'Closed')]
class ResCompany(models.Model):
_name = "res.company"
_inherit = ["res.company", "mail.thread"]
@@ -104,20 +96,6 @@ class ResCompany(models.Model):
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.", readonly=False)
account_opening_date = fields.Date(string='Opening Entry', default=lambda self: fields.Date.context_today(self).replace(month=1, day=1), required=True, help="That is the date of the opening entry.")
# Fields marking the completion of a setup step
account_setup_bank_data_state = fields.Selection(ONBOARDING_STEP_STATES, string="State of the onboarding bank data step", default='not_done')
account_setup_fy_data_state = fields.Selection(ONBOARDING_STEP_STATES, string="State of the onboarding fiscal year step", default='not_done')
account_setup_coa_state = fields.Selection(ONBOARDING_STEP_STATES, string="State of the onboarding charts of account step", default='not_done')
account_setup_taxes_state = fields.Selection(ONBOARDING_STEP_STATES, string="State of the onboarding Taxes step", default='not_done')
account_onboarding_invoice_layout_state = fields.Selection(ONBOARDING_STEP_STATES, string="State of the onboarding invoice layout step", default='not_done')
account_onboarding_create_invoice_state = fields.Selection(ONBOARDING_STEP_STATES, string="State of the onboarding create invoice step", compute='_compute_account_onboarding_create_invoice_state')
#this field must be there to ensure that the create_invoice_state stay complete and because we can't use a dependencies on account move
account_onboarding_create_invoice_state_flag = fields.Boolean(default=False, store=True)
account_onboarding_sale_tax_state = fields.Selection(ONBOARDING_STEP_STATES, string="State of the onboarding sale tax step", default='not_done')
# account dashboard onboarding
account_invoice_onboarding_state = fields.Selection(DASHBOARD_ONBOARDING_STATES, string="State of the account invoice onboarding panel", default='not_done')
account_dashboard_onboarding_state = fields.Selection(DASHBOARD_ONBOARDING_STATES, string="State of the account dashboard onboarding panel", default='not_done')
invoice_terms = fields.Html(string='Default Terms and Conditions', translate=True)
terms_type = fields.Selection([('plain', 'Add a Note'), ('html', 'Add a link to a Web Page')],
string='Terms & Conditions format', default='plain')
@@ -125,8 +103,6 @@ class ResCompany(models.Model):
sanitize_attributes=False,
compute='_compute_invoice_terms_html', store=True, readonly=False)
account_setup_bill_state = fields.Selection(ONBOARDING_STEP_STATES, string="State of the onboarding bill step", default='not_done')
# Needed in the Point of Sale
account_default_pos_receivable_account_id = fields.Many2one('account.account', string="Default PoS Receivable Account")
@@ -232,17 +208,6 @@ class ResCompany(models.Model):
foreign_vat_fpos = self.env['account.fiscal.position'].search([('company_id', '=', record.id), ('foreign_vat', '!=', False)])
record.account_enabled_tax_country_ids = foreign_vat_fpos.country_id + record.account_fiscal_country_id
@api.depends('account_onboarding_create_invoice_state_flag')
def _compute_account_onboarding_create_invoice_state(self):
for record in self:
if record.account_onboarding_create_invoice_state_flag:
record.account_onboarding_create_invoice_state = 'done'
elif self.env['account.move'].search([('company_id', '=', record.id), ('move_type', '=', 'out_invoice')], limit=1):
record.account_onboarding_create_invoice_state = 'just_done'
record.account_onboarding_create_invoice_state_flag = True
else:
record.account_onboarding_create_invoice_state = 'not_done'
@api.depends('terms_type')
def _compute_invoice_terms_html(self):
for company in self.filtered(lambda company: is_html_empty(company.invoice_terms_html) and company.terms_type == 'html'):
@@ -252,40 +217,6 @@ class ResCompany(models.Model):
if html:
company.invoice_terms_html = html
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_onboarding_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 provider in this case). """
return [
'base_onboarding_company_state',
'account_onboarding_invoice_layout_state',
'account_onboarding_create_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_onboarding_state(
'account_dashboard_onboarding_state',
self.get_account_dashboard_onboarding_steps_states_names()
)
def get_account_dashboard_onboarding_steps_states_names(self):
""" Necessary to add/edit steps from other modules (account_winbooks_import in this case). """
return [
'account_setup_bank_data_state',
'account_setup_fy_data_state',
'account_setup_coa_state',
'account_setup_taxes_state',
]
def get_new_account_code(self, current_code, old_prefix, new_prefix):
digits = len(current_code)
return new_prefix + current_code.replace(old_prefix, '', 1).lstrip('0').rjust(digits-len(new_prefix), '0')
@@ -409,58 +340,17 @@ class ResCompany(models.Model):
@api.model
def setting_init_bank_account_action(self):
""" Called by the 'Bank Accounts' button of the setup bar."""
""" Called by the 'Bank Accounts' button of the setup bar or from the Financial configuration menu."""
view_id = self.env.ref('account.setup_bank_account_wizard').id
return {'type': 'ir.actions.act_window',
'name': _('Create a Bank Account'),
'res_model': 'account.setup.bank.manual.config',
'target': 'new',
'view_mode': 'form',
'views': [[view_id, 'form']],
}
@api.model
def setting_init_fiscal_year_action(self):
""" Called by the 'Fiscal Year Opening' button of the setup bar."""
company = self.env.company
new_wizard = self.env['account.financial.year.op'].create({'company_id': company.id})
view_id = self.env.ref('account.setup_financial_year_opening_form').id
return {
'type': 'ir.actions.act_window',
'name': _('Accounting Periods'),
'view_mode': 'form',
'res_model': 'account.financial.year.op',
'name': _('Create a Bank Account'),
'res_model': 'account.setup.bank.manual.config',
'target': 'new',
'res_id': new_wizard.id,
'view_mode': 'form',
'views': [[view_id, 'form']],
}
@api.model
def setting_chart_of_accounts_action(self):
""" Called by the 'Chart of Accounts' button of the setup bar."""
company = self.env.company
company.sudo().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():
return 'account.action_account_form'
# Then, we open will open a custom tree view allowing to edit opening balances of the account
view_id = self.env.ref('account.init_accounts_tree').id
# Hide the current year earnings account as it is automatically computed
domain = [('account_type', '!=', 'equity_unaffected'), ('company_id', '=', company.id)]
return {
'type': 'ir.actions.act_window',
'name': _('Chart of Accounts'),
'res_model': 'account.account',
'view_mode': 'tree',
'limit': 99999999,
'search_view_id': [self.env.ref('account.view_account_search').id],
'views': [[view_id, 'list']],
'domain': domain,
}
@api.model
def create_op_move_if_non_existant(self):
""" Creates an empty opening move in 'draft' state for the current company
@@ -559,53 +449,9 @@ class ResCompany(models.Model):
'credit': debit_diff,
})
@api.model
def action_close_account_invoice_onboarding(self):
""" Mark the invoice onboarding panel as closed. """
self.env.company.account_invoice_onboarding_state = 'closed'
@api.model
def action_close_account_dashboard_onboarding(self):
""" Mark the dashboard onboarding panel as closed. """
self.env.company.account_dashboard_onboarding_state = 'closed'
@api.model
def action_open_account_onboarding_sale_tax(self):
""" Onboarding step for the invoice layout. """
action = self.env["ir.actions.actions"]._for_xml_id("account.action_open_account_onboarding_sale_tax")
action['res_id'] = self.env.company.id
return action
@api.model
def action_open_account_onboarding_create_invoice(self):
return self.env["ir.actions.actions"]._for_xml_id("account.action_open_account_onboarding_create_invoice")
@api.model
def action_open_taxes_onboarding(self):
""" Called by the 'Taxes' button of the setup bar."""
company = self.env.company
company.sudo().set_onboarding_step_done('account_setup_taxes_state')
view_id_list = self.env.ref('account.view_onboarding_tax_tree').id
view_id_form = self.env.ref('account.view_tax_form').id
return {
'type': 'ir.actions.act_window',
'name': _('Taxes'),
'res_model': 'account.tax',
'target': 'current',
'views': [[view_id_list, 'list'], [view_id_form, 'form']],
'context': {'search_default_sale': True, 'search_default_purchase': True, 'active_test': False},
}
def action_save_onboarding_invoice_layout(self):
""" Set the onboarding step as done """
if bool(self.external_report_layout_id):
self.set_onboarding_step_done('account_onboarding_invoice_layout_state')
def action_save_onboarding_sale_tax(self):
""" Set the onboarding step as done """
self.set_onboarding_step_done('account_onboarding_sale_tax_state')
self.env['onboarding.onboarding.step'].action_validate_step('account.onboarding_onboarding_step_sales_tax')
def get_chart_of_accounts_or_fail(self):
account = self.env['account.account'].search([('company_id', '=', self.id)], limit=1)
@@ -0,0 +1,29 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, models
class Onboarding(models.Model):
_inherit = 'onboarding.onboarding'
# Invoice Onboarding
@api.model
def action_close_panel_account_invoice(self):
self.action_close_panel('account.onboarding_onboarding_account_invoice')
def _prepare_rendering_values(self):
"""Compute existence of invoices for company."""
self.ensure_one()
if self == self.env.ref('account.onboarding_onboarding_account_invoice', raise_if_not_found=False):
step = self.env.ref('account.onboarding_onboarding_step_create_invoice', raise_if_not_found=False)
if step and step.current_step_state == 'not_done':
if self.env['account.move'].search(
[('company_id', '=', self.env.company.id), ('move_type', '=', 'out_invoice')], limit=1
):
step.action_set_just_done()
return super()._prepare_rendering_values()
# Dashboard Onboarding
@api.model
def action_close_panel_account_dashboard(self):
self.action_close_panel('account.onboarding_onboarding_account_dashboard')
@@ -0,0 +1,133 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import _, api, models
class OnboardingStep(models.Model):
_inherit = 'onboarding.onboarding.step'
# COMMON STEPS
@api.model
def action_open_step_company_data(self):
"""Set company's basic information."""
action = {
'type': 'ir.actions.act_window',
'name': _('Set your company data'),
'res_model': 'res.company',
'res_id': self.env.company.id,
'views': [(self.env.ref('account.res_company_form_view_onboarding').id, "form")],
'target': 'new',
}
return action
@api.model
def action_open_step_base_document_layout(self):
view_id = self.env.ref('web.view_base_document_layout').id
return {
'name': _('Configure your document layout'),
'type': 'ir.actions.act_window',
'res_model': 'base.document.layout',
'target': 'new',
'views': [(view_id, 'form')],
}
@api.model
def action_validate_step_base_document_layout(self):
"""Set the onboarding(s) step as done only if layout is set."""
step = self.env.ref('account.onboarding_onboarding_step_base_document_layout', raise_if_not_found=False)
if not step or not self.env.company.external_report_layout_id:
return False
return self.action_validate_step('account.onboarding_onboarding_step_base_document_layout')
# INVOICE ONBOARDING
@api.model
def action_open_step_bank_account(self):
return self.env.company.setting_init_bank_account_action()
@api.model
def action_open_step_create_invoice(self):
return {
'type': 'ir.actions.act_window',
'name': _('Create first invoice'),
'views': [(self.env.ref("account.view_move_form").id, 'form')],
'res_model': 'account.move',
'context': {'default_move_type': 'out_invoice'},
}
# DASHBOARD ONBOARDING
@api.model
def action_open_step_fiscal_year(self):
company = self.env.company
company.create_op_move_if_non_existant()
new_wizard = self.env['account.financial.year.op'].create({'company_id': company.id})
view_id = self.env.ref('account.setup_financial_year_opening_form').id
return {
'type': 'ir.actions.act_window',
'name': _('Accounting Periods'),
'view_mode': 'form',
'res_model': 'account.financial.year.op',
'target': 'new',
'res_id': new_wizard.id,
'views': [[view_id, 'form']],
}
@api.model
def action_open_step_default_taxes(self):
""" Called by the 'Taxes' button of the setup bar."""
self.action_validate_step('account.onboarding_onboarding_step_default_taxes')
view_id_list = self.env.ref('account.view_onboarding_tax_tree').id
view_id_form = self.env.ref('account.view_tax_form').id
return {
'type': 'ir.actions.act_window',
'name': _('Taxes'),
'res_model': 'account.tax',
'target': 'current',
'views': [[view_id_list, 'list'], [view_id_form, 'form']],
'context': {'search_default_sale': True, 'search_default_purchase': True, 'active_test': False},
}
@api.model
def action_open_step_chart_of_accounts(self):
""" Called by the 'Chart of Accounts' button of the dashboard onboarding panel."""
company = self.env.company
self.sudo().action_validate_step('account.onboarding_onboarding_step_chart_of_accounts')
# If an opening move has already been posted, we open the tree view showing all the accounts
if company.opening_move_posted():
return 'account.action_account_form'
# Otherwise, we create the opening move
company.create_op_move_if_non_existant()
# Then, we open will open a custom tree view allowing to edit opening balances of the account
view_id = self.env.ref('account.init_accounts_tree').id
# Hide the current year earnings account as it is automatically computed
domain = [('account_type', '!=', 'equity_unaffected'), ('company_id', '=', company.id)]
return {
'type': 'ir.actions.act_window',
'name': _('Chart of Accounts'),
'res_model': 'account.account',
'view_mode': 'tree',
'limit': 99999999,
'search_view_id': [self.env.ref('account.view_account_search').id],
'views': [[view_id, 'list']],
'domain': domain,
}
# STEPS WITHOUT PANEL
@api.model
def action_open_step_sales_tax(self):
view_id = self.env.ref('account.res_company_form_view_onboarding_sale_tax').id
return {
'type': 'ir.actions.act_window',
'name': _('Sales tax'),
'res_id': self.env.company.id,
'res_model': 'res.company',
'target': 'new',
'view_mode': 'form',
'views': [[view_id, 'form']],
}
+10 -10
View File
@@ -14,29 +14,29 @@ registry.category("web_tour.tours").add('account_tour', {
steps: [
...stepUtils.goToAppSteps('account.menu_finance', _t('Send invoices to your customers in no time with the <b>Invoicing app</b>.')),
{
trigger: "a.o_onboarding_step_action[data-method=action_open_base_onboarding_company]",
trigger: "a.o_onboarding_step_action[data-method=action_open_step_company_data]",
content: _t("Start by checking your company's data."),
position: "bottom",
skip_trigger: 'a[data-method=action_open_base_onboarding_company].o_onboarding_step_action__done',
skip_trigger: 'a[data-method=action_open_step_company_data].o_onboarding_step_action__done',
}, {
trigger: "button[name=action_save_onboarding_company_step]",
extra_trigger: "a.o_onboarding_step_action[data-method=action_open_base_onboarding_company]",
trigger: "button.o_form_button_save",
extra_trigger: "a.o_onboarding_step_action[data-method=action_open_step_company_data]",
content: _t("Looks good. Let's continue."),
position: "bottom",
skip_trigger: 'a[data-method=action_open_base_onboarding_company].o_onboarding_step_action__done',
skip_trigger: 'a[data-method=action_open_step_company_data].o_onboarding_step_action__done',
}, {
trigger: "a.o_onboarding_step_action[data-method=action_open_base_document_layout]",
trigger: "a.o_onboarding_step_action[data-method=action_open_step_base_document_layout]",
content: _t("Customize your layout."),
position: "bottom",
skip_trigger: 'a[data-method=action_open_base_document_layout].o_onboarding_step_action__done',
skip_trigger: 'a[data-method=action_open_step_base_document_layout].o_onboarding_step_action__done',
}, {
trigger: "button[name=document_layout_save]",
extra_trigger: "a.o_onboarding_step_action[data-method=action_open_base_document_layout]",
extra_trigger: "a.o_onboarding_step_action[data-method=action_open_step_base_document_layout]",
content: _t("Once everything is as you want it, validate."),
position: "top",
skip_trigger: 'a[data-method=action_open_base_document_layout].o_onboarding_step_action__done',
skip_trigger: 'a[data-method=action_open_step_base_document_layout].o_onboarding_step_action__done',
}, {
trigger: "a.o_onboarding_step_action[data-method=action_open_account_onboarding_create_invoice]",
trigger: "a[data-method=action_open_step_create_invoice]",
content: _t("Now, we'll create your first invoice."),
position: "bottom",
}, {
@@ -0,0 +1,12 @@
/** @odoo-module **/
import OnboardingStepFormController from "@onboarding/views/form/onboarding_step_form_controller";
/**
* Validate the onboarding step on saving a record of another model (here `res.company`).
**/
export default class CompanyOnboardingFormController extends OnboardingStepFormController {
get stepName() {
return "account.onboarding_onboarding_step_company_data";
}
}
@@ -0,0 +1,15 @@
/** @odoo-module **/
import { formView } from "@web/views/form/form_view";
import { registry } from "@web/core/registry";
import CompanyOnboardingFormController from "./res_company_onboarding_form_controller.js";
const CompanyOnboardingFormView = {
...formView,
Controller: CompanyOnboardingFormController,
};
registry.category("views").add("company_onboarding_form", CompanyOnboardingFormView);
@@ -3,7 +3,7 @@
<field name="name">account.journal.dashboard.kanban</field>
<field name="model">account.journal</field>
<field name="arch" type="xml">
<kanban create="false" class="o_kanban_dashboard o_account_kanban" banner_route="/account/account_dashboard_onboarding" js_class="account_dashboard_kanban">
<kanban create="false" class="o_kanban_dashboard o_account_kanban" banner_route="/onboarding/account_dashboard" js_class="account_dashboard_kanban">
<field name="id"/>
<field name="name"/>
<field name="type"/>
+1 -1
View File
@@ -447,7 +447,7 @@
<field name="mode">primary</field>
<field name="arch" type="xml">
<xpath expr="//tree" position="attributes">
<attribute name="banner_route">/account/account_invoice_onboarding</attribute>
<attribute name="banner_route">/onboarding/account_invoice</attribute>
</xpath>
<field name="currency_id" position="attributes">
<attribute name="string">Invoice Currency</attribute>
@@ -1,156 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- ONBOARDING STEPS -->
<template id="onboarding_invoice_layout_step">
<t t-call="base.onboarding_step">
<t t-set="title">Invoice Layout</t>
<t t-set="description">Customize the look of your invoices.</t>
<t t-set="image" t-value="'/base/static/img/onboarding_quotation-layout.png'"/>
<t t-set="alt" t-value="'Invoice Layout'"/>
<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_base_document_layout'" />
<t t-set="model" t-value="'base.document.layout'" />
<t t-set="state" t-value="state.get('account_onboarding_invoice_layout_state')" />
</t>
</template>
<template id="onboarding_create_invoice_step">
<t t-call="base.onboarding_step">
<t t-set="title">Create Invoice</t>
<t t-set="description">
Create your first invoice.
</t>
<t t-set="image" t-value="'/base/static/img/onboarding_default.png'"/>
<t t-set="alt" t-value="'Onboarding Create Invoice'"/>
<t t-set="btn_text">Create</t>
<t t-set="done_text">First invoice sent!</t>
<t t-set="method" t-value="'action_open_account_onboarding_create_invoice'" />
<t t-set="model" t-value="'res.company'" />
<t t-set="state" t-value="state.get('account_onboarding_create_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">
Connect your financial accounts in seconds.
</t>
<t t-set="image" t-value="'/base/static/img/onboarding_puzzle.png'"/>
<t t-set="alt" t-value="'Onboarding Bank Account'"/>
<t t-set="btn_text">Add a bank account</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">Accounting Periods</t>
<t t-set="description">Define your fiscal years &amp; tax returns periodicity.</t>
<t t-set="image" t-value="'/base/static/img/onboarding_accounting-periods.png'"/>
<t t-set="alt" t-value="'Onboarding Accounting Periods'"/>
<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="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 record initial balances.</t>
<t t-set="image" t-value="'/base/static/img/onboarding_chart-of-accounts.png'"/>
<t t-set="alt" t-value="'Onboarding Chart of Accounts'"/>
<t t-set="btn_text">Review</t>
<t t-set="done_text">Chart of accounts set.</t>
<t t-set="method" t-value="'setting_chart_of_accounts_action'" />
<t t-set="model" t-value="'res.company'" />
<t t-set="state" t-value="state.get('account_setup_coa_state')" />
</t>
</template>
<template id="onboarding_taxes_step">
<t t-call="base.onboarding_step">
<t t-set="title">Taxes</t>
<t t-set="description">Set default Taxes for sales and purchase transactions.</t>
<t t-set="image" t-value="'/base/static/img/onboarding_taxes.png'"/>
<t t-set="alt" t-value="'Onboarding Taxes'"/>
<t t-set="btn_text">Review</t>
<t t-set="done_text">Taxes set.</t>
<t t-set="method" t-value="'action_open_taxes_onboarding'" />
<t t-set="model" t-value="'res.company'" />
<t t-set="state" t-value="state.get('account_setup_taxes_state')" />
</t>
</template>
<!-- ONBOARDING PANELS -->
<template id="account_invoice_onboarding_panel" name="account.invoice.onboarding.panel">
<t t-call="base.onboarding_container">
<t t-set="close_method" t-value="'action_close_account_invoice_onboarding'"/>
<t t-set="close_model" t-value="'res.company'"/>
<t t-set="text_completed">Congratulations! You are all set.</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_create_invoice_step" name="create_invoice_step" />
</t>
</template>
<template id="account_dashboard_onboarding_panel" name="account.dashboard.onboarding.panel">
<t t-call="base.onboarding_container">
<t t-set="close_method" t-value="'action_close_account_dashboard_onboarding'"/>
<t t-set="close_model" t-value="'res.company'"/>
<t t-call="account.onboarding_fiscal_year_step" name="fiscal_year_step" />
<t t-call="account.onboarding_bank_account_step" name="bank_account_step" />
<t t-call="account.onboarding_taxes_step" name="taxes_step" />
<t t-call="account.onboarding_chart_of_account_step" name="chart_of_account_step" />
</t>
</template>
<!-- SAMPLE INVOICE EMAIL -->
<record id="action_open_account_onboarding_create_invoice" model="ir.actions.act_window">
<field name="name">Create first invoice</field>
<field name="res_model">account.move</field>
<field name="view_mode">form</field>
<field name="view_id" ref="account.view_move_form" />
<field name="context">{'default_move_type': 'out_invoice'}</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 sales tax for your products.</t>
<t t-set="btn_text">Set taxes</t>
<t t-set="image" t-value="'/base/static/img/onboarding_taxes.png'"/>
<t t-set="alt" t-value="'Onboarding Taxes'"/>
<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 class="mb16">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" data-hotkey="q" />
<button string="Cancel" class="btn-secondary" special="cancel" data-hotkey="z" />
</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="res_model">res.company</field>
<field name="view_mode">form</field>
<field name="view_id" ref="account_invoice_onboarding_sale_tax_form" />
<field name="target">new</field>
</record>
</odoo>
@@ -33,4 +33,35 @@
</field>
</record>
<!-- Onboarding -->
<record id="res_company_form_view_onboarding" model="ir.ui.view">
<field name="name">res.company.form.view.onboarding</field>
<field name="model">res.company</field>
<field name="inherit_id" ref="base.view_company_form"/>
<field name="mode">primary</field>
<field name="priority">1000</field>
<field name="arch" type="xml">
<xpath expr="//group[@name='social_media']" position="replace"/>
<form position="attributes">
<attribute name="js_class">company_onboarding_form</attribute>
</form>
</field>
</record>
<record id="res_company_form_view_onboarding_sale_tax" model="ir.ui.view">
<field name="name">res.company.form.view.onboarding.sale.tax</field>
<field name="model">res.company</field>
<field name="priority">1000</field>
<field name="arch" type="xml">
<form>
<div class="mb16">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" data-hotkey="q" />
<button string="Cancel" class="btn-secondary" special="cancel" data-hotkey="z" />
</footer>
</form>
</field>
</record>
</odoo>
@@ -5,7 +5,9 @@ class BaseDocumentLayout(models.TransientModel):
_inherit = 'base.document.layout'
def document_layout_save(self):
"""Save layout and onboarding step progress, return super() result"""
res = super(BaseDocumentLayout, self).document_layout_save()
for wizard in self:
wizard.company_id.action_save_onboarding_invoice_layout()
if step := self.env.ref('account.onboarding_onboarding_step_base_document_layout', raise_if_not_found=False):
for company_id in self.company_id:
step.with_company(company_id).action_set_just_done()
return res
+3 -3
View File
@@ -60,7 +60,7 @@ class FinancialYearOpeningWizard(models.TransientModel):
return super().write(vals)
def action_save_onboarding_fiscal_year(self):
self.env.company.sudo().set_onboarding_step_done('account_setup_fy_data_state')
return self.env['onboarding.onboarding.step'].action_validate_step('account.onboarding_onboarding_step_fiscal_year')
class SetupBarBankConfigWizard(models.TransientModel):
@@ -146,7 +146,7 @@ class SetupBarBankConfigWizard(models.TransientModel):
selected_journal.name = record.new_journal_name
def validate(self):
""" Called by the validation button of this wizard. Serves as an
"""Called by the validation button of this wizard. Serves as an
extension hook in account_bank_statement_import.
"""
self.linked_journal_id.mark_bank_setup_as_done_action()
return self.env["onboarding.onboarding.step"].action_validate_step("account.onboarding_onboarding_step_bank_account")
+1 -1
View File
@@ -9,6 +9,7 @@
'auto_install': ['account'],
'data': [
'data/ir_config_parameter.xml',
'data/onboarding_data.xml',
'security/ir.model.access.csv',
'security/ir_rules.xml',
@@ -19,7 +20,6 @@
'views/account_move_views.xml',
'views/account_journal_views.xml',
'views/account_payment_views.xml',
'views/onboarding_templates.xml',
'views/payment_provider_views.xml',
'views/payment_transaction_views.xml',
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="onboarding_onboarding_step_payment_provider" model="onboarding.onboarding.step">
<field name="title">Online Payments</field>
<field name="description">Enable credit &amp; debit card payments supported by Stripe.</field>
<field name="button_text">Activate Stripe</field>
<field name="panel_step_open_action_name">action_open_step_payment_provider</field>
<field name="step_image" type="base64" file="base/static/img/onboarding_bank-account.png"></field>
<field name="step_image_filename">onboarding_bank-account.png</field>
<field name="step_image_alt">Onboarding Online Payments</field>
<field name="onboarding_ids" eval="[
Command.link(ref('account.onboarding_onboarding_account_invoice'))
]"></field>
</record>
</data>
</odoo>
+1 -1
View File
@@ -5,6 +5,6 @@ from . import account_move
from . import account_payment
from . import account_payment_method
from . import account_payment_method_line
from . import onboarding_onboarding_step
from . import payment_provider
from . import payment_transaction
from . import res_company
@@ -0,0 +1,20 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, models
class OnboardingStep(models.Model):
_inherit = 'onboarding.onboarding.step'
@api.model
def action_open_step_payment_provider(self):
self.env.company.payment_onboarding_payment_method = 'stripe'
menu = self.env.ref('account_payment.payment_provider_menu', raise_if_not_found=False)
menu_id = menu.id if menu else None
return self.env.company._run_payment_onboarding_step(menu_id)
@api.model
def action_validate_step_payment_provider(self):
validation_response = super().action_validate_step_payment_provider()
self.action_validate_step("account_payment.onboarding_onboarding_step_payment_provider")
return validation_response
@@ -1,19 +0,0 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, models
class ResCompany(models.Model):
_inherit = 'res.company'
@api.model
def action_open_payment_onboarding(self):
self.env.company.payment_onboarding_payment_method = 'stripe'
menu = self.env.ref('account_payment.payment_provider_menu', raise_if_not_found=False)
menu_id = menu and menu.id
return self._run_payment_onboarding_step(menu_id)
def get_account_invoice_onboarding_steps_states_names(self):
""" Override of `account` to add the state of the payment onboarding step. """
steps = super().get_account_invoice_onboarding_steps_states_names()
return steps + ['payment_provider_onboarding_state']
@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="account_payment_onboarding_panel" inherit_id="account.account_invoice_onboarding_panel" priority="30">
<xpath expr="//t[@name='create_invoice_step']" position="after">
<t t-call="account_payment.payment_onboarding_step" name="payment_step"/>
</xpath>
</template>
<template id="payment_onboarding_step" name="Payment Onboarding Step">
<t t-call="base.onboarding_step">
<t t-set="title">Online Payments</t>
<t t-set="description">Enable credit &amp; debit card payments supported by Stripe</t>
<t t-set="image" t-value="'/base/static/img/onboarding_bank-account.png'"/>
<t t-set="alt" t-value="'Online Payments'"/>
<t t-set="btn_text">Activate Stripe</t>
<t t-set="done_text">Online payments enabled</t>
<t t-set="model" t-value="'res.company'"/>
<t t-set="method" t-value="'action_open_payment_onboarding'"/>
<t t-set="state" t-value="state.get('payment_provider_onboarding_state')"/>
</t>
</template>
</odoo>
+7 -2
View File
@@ -13,9 +13,8 @@ This module allows to manage onboardings and their progress
'depends': ['base'],
'installable': True,
'data': [
'data/onboarding_data.xml',
'views/onboarding_views.xml',
'views/onboarding_templates.xml',
'views/onboarding_views.xml',
'views/onboarding_menus.xml',
'security/ir.model.access.csv',
],
@@ -23,6 +22,12 @@ This module allows to manage onboardings and their progress
'web.assets_backend': [
'onboarding/static/src/**/*',
],
"web.dark_mode_variables": [
('before', 'onboarding/static/src/scss/onboarding.variables.scss', 'onboarding/static/src/scss/onboarding.variables.dark.scss'),
],
'web._assets_primary_variables': [
'onboarding/static/src/scss/onboarding.variables.scss',
]
},
'license': 'LGPL-3',
}
@@ -1,28 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="onboarding_panel">
<t t-call="onboarding.onboarding_container">
<t t-foreach="steps" t-as="step">
<t t-call="base.onboarding_step">
<t t-set="title" t-value="step.title"/>
<t t-set="description" t-value="step.description"/>
<t t-set="done_icon" t-value="step.done_icon"/>
<t t-set="btn_text" t-value="step.button_text"/>
<t t-set="done_text" t-value="step.done_text"/>
<t t-set="image" t-value="'/web/image/onboarding.onboarding.step/'+str(step.id)+'/step_image'"/>
<t t-set="alt" t-value="step.step_image_alt"/>
<!-- Model/method used by JS implementation of banners for each step-->
<t t-set="method" t-value="step.panel_step_open_action_name"/>
<t t-set="model">onboarding.onboarding.step</t>
<!-- to mimic first implementation of onboarding, the 'state' queried holds
a rendering state for all steps. See _get_and_update_onboarding_state -->
<t t-set="state" t-value="state.get(step.id)"/>
</t>
</t>
</t>
</template>
<template id="onboarding_container" inherit_id="base.onboarding_container">
<xpath expr="//div[hasclass('o_onboarding_completed_message')]" position="replace"/>
</template>
</odoo>
@@ -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
from odoo.addons.onboarding.models.onboarding_progress import ONBOARDING_PROGRESS_STATES
@@ -15,6 +15,10 @@ class Onboarding(models.Model):
route_name = fields.Char('One word name', required=True)
step_ids = fields.Many2many('onboarding.onboarding.step', string='Onboarding steps')
text_completed = fields.Char(
'Message at completion', default=_('Nice work! Your configuration is done.'),
help='Text shown on onboarding when completed')
is_per_company = fields.Boolean(
'Should be done per company?', compute='_compute_is_per_company', readonly=True, store=False,
)
@@ -124,6 +128,7 @@ class Onboarding(models.Model):
'close_model': 'onboarding.onboarding',
'steps': self.step_ids,
'state': self.current_progress_id._get_and_update_onboarding_state(),
'text_completed': self.text_completed,
}
return values
@@ -11,8 +11,10 @@ $o-onboarding-step-width: map-get($container-max-widths, 'lg') / 4 !default;
}
.o_onboarding_completed_message {
opacity: 0;
box-shadow: 0 -7px 20px -5px rgba(#000, 0.3);
animation: o_onboarding_slideInUp $o-onboarding-base-time ease-out;
animation: o_onboarding_slideInUpDownOut;
animation-duration: 6s;
}
// = Step Design
@@ -114,6 +116,11 @@ $o-onboarding-step-width: map-get($container-max-widths, 'lg') / 4 !default;
}
}
@keyframes o_onboarding_slideInUpDownOut {
25%, 85% { transform: translate3d(0, -80%, 0); opacity: 1; }
0%, 100% { transform: translate3d(0, 0, 0); }
}
@keyframes o_onboarding_zoomIn {
from {
transform: scale3d(0.8, 0.8, 0.8);
+1
View File
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import case
from . import test_onboarding
from . import test_onboarding_concurrency
+40
View File
@@ -0,0 +1,40 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests.common import TransactionCase
class TransactionCaseOnboarding(TransactionCase):
def assert_step_is_done(self, step, also_with_company=None):
self.assertIn(
step.current_progress_step_id.step_state, {'done', 'just_done'},
f'Expected done-like current state for step {step.id} for {step.env.company.name}')
self.assertEqual(step.current_progress_step_id.step_state, step.current_step_state)
if also_with_company:
self.assert_step_is_done(step.with_company(also_with_company))
def assert_step_is_not_done(self, step, also_with_company=None):
self.assertIn(
step.current_progress_step_id.step_state, {'not_done', False},
f'Expected "not_done" current state for step {step.id} for {step.env.company.name}')
self.assertEqual(step.current_step_state, 'not_done')
if also_with_company:
self.assert_step_is_not_done(step.with_company(also_with_company))
def assert_onboarding_is_done(self, onboarding, also_with_company=None):
self.assertIn(
onboarding.current_progress_id.onboarding_state, {'done', 'just_done'},
f'Expected done-like current state for onboarding "{onboarding.name}" '
f'for "{onboarding.env.company.name}"')
self.assertEqual(onboarding.current_progress_id.onboarding_state,
onboarding.current_onboarding_state)
if also_with_company:
self.assert_onboarding_is_done(onboarding.with_company(also_with_company))
def assert_onboarding_is_not_done(self, onboarding, also_with_company=None):
self.assertIn(
onboarding.current_progress_id.onboarding_state, {'not_done', False},
f'Expected `"not_done"` or `False` current state for onboarding {onboarding.name} '
f'for {onboarding.env.company.name}')
self.assertEqual(onboarding.current_onboarding_state, 'not_done')
if also_with_company:
self.assert_onboarding_is_not_done(onboarding.with_company(also_with_company))
+2 -37
View File
@@ -1,10 +1,10 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests.common import TransactionCase
from odoo.addons.onboarding.tests.case import TransactionCaseOnboarding
class TestOnboardingCommon(TransactionCase):
class TestOnboardingCommon(TransactionCaseOnboarding):
@classmethod
def setUpClass(cls):
super().setUpClass()
@@ -53,41 +53,6 @@ class TestOnboardingCommon(TransactionCase):
# Create progress records as would happen through the controller
(cls.onboarding_1 + cls.onboarding_2).with_company(cls.company_1)._search_or_create_progress()
def assert_step_is_done(self, step, also_with_company=None):
self.assertIn(
step.current_progress_step_id.step_state, {'done', 'just_done'},
f'Expected done-like current state for step {step.id} for {step.env.company}')
self.assertEqual(step.current_progress_step_id.step_state, step.current_step_state)
if also_with_company:
self.assert_step_is_done(step.with_company(also_with_company))
def assert_step_is_not_done(self, step, also_with_company=None):
self.assertIn(
step.current_progress_step_id.step_state, {'not_done', False},
f'Expected "not_done" current state for step {step.id} for {step.env.company}')
self.assertEqual(step.current_step_state, 'not_done')
if also_with_company:
self.assert_step_is_not_done(step.with_company(also_with_company))
def assert_onboarding_is_done(self, onboarding, also_with_company=None):
self.assertIn(
onboarding.current_progress_id.onboarding_state, {'done', 'just_done'},
f'Expected done-like current state for onboarding {onboarding.name} '
f'for {onboarding.env.company}')
self.assertEqual(onboarding.current_progress_id.onboarding_state,
onboarding.current_onboarding_state)
if also_with_company:
self.assert_onboarding_is_done(onboarding.with_company(also_with_company))
def assert_onboarding_is_not_done(self, onboarding, also_with_company=None):
self.assertIn(
onboarding.current_progress_id.onboarding_state, {'not_done', False},
f'Expected `"not_done"` or `False` current state for onboarding {onboarding.name} '
f'for {onboarding.env.company}')
self.assertEqual(onboarding.current_onboarding_state, 'not_done')
if also_with_company:
self.assert_onboarding_is_not_done(onboarding.with_company(also_with_company))
def activate_company(self, company):
self.onboarding_1_step_1 = self.onboarding_1_step_1.with_company(company)
self.onboarding_1_step_2 = self.onboarding_1_step_2.with_company(company)
@@ -1,5 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<template id="onboarding_panel">
<t t-call="onboarding.onboarding_container">
<t t-foreach="steps" t-as="step">
<t t-call="onboarding.onboarding_step">
<t t-set="title" t-value="step.title"/>
<t t-set="description" t-value="step.description"/>
<t t-set="done_icon" t-value="step.done_icon"/>
<t t-set="btn_text" t-value="step.button_text"/>
<t t-set="done_text" t-value="step.done_text"/>
<t t-set="image" t-value="'/web/image/onboarding.onboarding.step/'+str(step.id)+'/step_image'"/>
<t t-set="alt" t-value="step.step_image_alt"/>
<!-- Model/method used by JS implementation of banners for each step-->
<t t-set="method" t-value="step.panel_step_open_action_name"/>
<t t-set="model">onboarding.onboarding.step</t>
<!-- to mimic first implementation of onboarding, the 'state' queried holds
a rendering state for all steps. See _get_and_update_onboarding_state -->
<t t-set="state" t-value="state.get(step.id)"/>
</t>
</t>
</t>
</template>
<template id="onboarding_container">
<div class="modal o_onboarding_modal o_technical_modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
@@ -13,8 +35,8 @@
</div>
<div class="modal-footer justify-content-start">
<a type="action" class="btn btn-primary" data-bs-dismiss="modal"
data-o-hide-banner="true"
t-att-data-model="close_model" t-att-data-method="close_method">
data-o-hide-banner="true" t-att-data-model="close_model" t-att-data-method="close_method"
>
Remove
</a>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
@@ -29,17 +51,15 @@
<a href="#" data-bs-toggle="modal" data-bs-target=".o_onboarding_modal" class="o_onboarding_btn_close position-absolute top-0 end-0 py-2 px-3 h2 z-index-1" title="Close the onboarding panel"><i class="oi oi-close"/></a>
<div class="o_onboarding_steps d-flex" t-out="0"/>
<div t-if="state.get('onboarding_state') in ('done', 'just_done')"
<div t-if="state.get('onboarding_state') == 'just_done'"
t-att-state="state.get('onboarding_state')"
class="o_onboarding_completed_message position-absolute end-0 bottom-0 start-0 border-bottom py-4 bg-white d-flex align-items-center justify-content-center">
class="o_onboarding_completed_message position-absolute end-0 start-0 border-bottom py-4 bg-white d-flex align-items-center justify-content-center">
<span class="h3 m-0">
<i class="fa fa-check text-success me-3" />
<t t-if="text_completed" t-out="text_completed" />
<t t-else="">Nice work! Your configuration is done.</t>
</span>
<a type="action" class="btn btn-primary ms-4" data-bs-toggle="collapse" href=".o_onboarding_container" t-att-data-model="close_model" t-att-data-method="close_method">
Close
Close Panel
</a>
</div>
</div>
@@ -79,44 +99,4 @@
</div>
</div>
</template>
<!-- ONBOARDING STEPS -->
<template id="onboarding_company_step">
<t t-call="base.onboarding_step">
<t t-set="title">Company Data</t>
<t t-set="description">
Set your company's data for documents header/footer.
</t>
<t t-set="image" t-value="'/base/static/img/onboarding_company-data.png'"/>
<t t-set="alt" t-value="'Onboarding Company Data'"/>
<t t-set="btn_text">Let's start!</t>
<t t-set="method" t-value="'action_open_base_onboarding_company'" />
<t t-set="model" t-value="'res.company'" />
<t t-set="state" t-value="state.get('base_onboarding_company_state')" />
</t>
</template>
<!-- COMPANY FORM -->
<record id="base_onboarding_company_form" model="ir.ui.view">
<field name="name">base.company.onboarding.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">1000</field>
<field name="arch" type="xml">
<xpath expr="//group[@name='social_media']" position="replace" />
<form position="inside">
<footer position="replace">
<button name="action_save_onboarding_company_step" class="btn btn-primary" type="object" string="Apply" data-hotkey="q"/>
<button special="cancel" data-hotkey="z" string="Cancel" />
</footer>
</form>
</field>
</record>
<record id="action_open_base_onboarding_company" model="ir.actions.act_window">
<field name="name">Set your company data</field>
<field name="res_model">res.company</field>
<field name="view_mode">form</field>
<field name="view_id" ref="base_onboarding_company_form" />
<field name="target">new</field>
</record>
</odoo>
+2 -1
View File
@@ -5,8 +5,9 @@
'version': '2.0',
'category': 'Hidden',
'summary': "The payment engine used by payment provider modules.",
'depends': ['portal'],
'depends': ['onboarding', 'portal'],
'data': [
'data/onboarding_data.xml',
'data/payment_method_data.xml',
'data/payment_provider_data.xml',
'data/payment_cron.xml',
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- ONBOARDING STEPS (WITHOUT PANEL)-->
<record id="onboarding_onboarding_step_payment_provider" model="onboarding.onboarding.step">
<field name="title">Online Payments</field>
<field name="sequence">99</field>
</record>
</data>
</odoo>
+1
View File
@@ -5,5 +5,6 @@ from . import payment_method
from . import payment_provider
from . import payment_token
from . import payment_transaction
from . import onboarding_onboarding_step
from . import res_company
from . import res_partner
@@ -0,0 +1,12 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, models
class OnboardingStep(models.Model):
_inherit = 'onboarding.onboarding.step'
@api.model
def action_validate_step_payment_provider(self):
"""Override to validate other steps as well."""
return self.action_validate_step("payment.onboarding_onboarding_step_payment_provider")
-16
View File
@@ -6,10 +6,6 @@ from odoo import api, fields, models
class ResCompany(models.Model):
_inherit = 'res.company'
payment_provider_onboarding_state = fields.Selection(
string="State of the onboarding payment provider step",
selection=[('not_done', "Not done"), ('just_done', "Just done"), ('done', "Done")],
default='not_done')
payment_onboarding_payment_method = fields.Selection(
string="Selected onboarding payment method",
selection=[
@@ -56,15 +52,3 @@ class ResCompany(models.Model):
modules_sudo = self.env['ir.module.module'].sudo().search([('name', 'in', module_names)])
STATES = ['installed', 'to install', 'to upgrade']
modules_sudo.filtered(lambda m: m.state not in STATES).button_immediate_install()
def _mark_payment_onboarding_step_as_done(self):
""" Mark the payment onboarding step as done.
:return: None
"""
self.set_onboarding_step_done('payment_provider_onboarding_state')
def get_account_invoice_onboarding_steps_states_names(self):
""" Override of account. """
steps = super().get_account_invoice_onboarding_steps_states_names()
return steps + ['payment_provider_onboarding_state']
@@ -134,13 +134,10 @@ class PaymentWizard(models.TransientModel):
if payment_method == 'stripe':
return self._start_stripe_onboarding()
# the user clicked `apply` and not cancel so we can assume this step is done.
self._set_payment_provider_onboarding_step_done()
# the user clicked `apply` and not cancel, so we can assume this step is done.
self.env['onboarding.onboarding.step'].sudo().action_validate_step_payment_provider()
return {'type': 'ir.actions.act_window_close'}
def _set_payment_provider_onboarding_step_done(self):
self.env.company.sudo().set_onboarding_step_done('payment_provider_onboarding_state')
def _start_stripe_onboarding(self):
""" Start Stripe Connect onboarding. """
menu = self.env.ref('account_payment.payment_provider_menu', False)
@@ -12,7 +12,7 @@ class OnboardingController(http.Controller):
@http.route(_onboarding_return_url, type='http', methods=['GET'], auth='user')
def stripe_return_from_onboarding(self, provider_id, menu_id):
""" Redirect the user to the provider form of the onboarded Stripe account.
"""Redirect the user to the provider form of the onboarded Stripe account.
The user is redirected to this route by Stripe after or during (if the user clicks on a
dedicated button) the onboarding.
@@ -23,7 +23,9 @@ class OnboardingController(http.Controller):
`ir.ui.menu` id
"""
stripe_provider = request.env['payment.provider'].browse(int(provider_id))
stripe_provider.company_id._mark_payment_onboarding_step_as_done()
request.env['onboarding.onboarding.step'].with_company(
stripe_provider.company_id
).action_validate_step_payment_provider()
action = request.env.ref('payment_stripe.action_payment_provider_onboarding')
get_params_string = url_encode({'action': action.id, 'id': provider_id, 'menu_id': menu_id})
return request.redirect(f'/web?#{get_params_string}')
@@ -134,7 +134,7 @@ class PaymentProvider(models.Model):
self.ensure_one()
if self.state == 'enabled':
self.company_id._mark_payment_onboarding_step_as_done()
self.env["onboarding.onboarding.step"].action_validate_step_payment_provider()
action = {'type': 'ir.actions.act_window_close'}
else:
# Account creation
+1 -1
View File
@@ -31,6 +31,7 @@ This module contains all the common features of Sales Management and eCommerce.
'data/mail_message_subtype_data.xml',
'data/mail_template_data.xml',
'data/ir_config_parameter.xml', # Needs mail_template_data
'data/onboarding_data.xml',
'wizard/account_accrued_orders_wizard_views.xml',
'wizard/mass_cancel_orders_views.xml',
@@ -51,7 +52,6 @@ This module contains all the common features of Sales Management and eCommerce.
'views/product_product_views.xml',
'views/product_views.xml',
'views/res_partner_views.xml',
'views/sale_onboarding_views.xml',
'views/sale_order_line_views.xml',
'views/sale_portal_templates.xml',
'views/utm_campaign_views.xml',
-1
View File
@@ -2,5 +2,4 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import catalog
from . import onboarding
from . import portal
-31
View File
@@ -1,31 +0,0 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.http import request, route, Controller
class OnboardingController(Controller):
@route('/sales/sale_quotation_onboarding_panel', auth='user', type='json')
def sale_quotation_onboarding(self, context=None):
""" 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. """
# Forward `allowed_company_ids` in the context
# so `request.env.company` returns the company selected in the dropdown
if context:
request.update_context(**context)
company = request.env.company
if not request.env.is_admin() or \
company.sale_quotation_onboarding_state == 'closed':
return {}
return {
'html': request.env['ir.qweb']._render(
'sale.sale_quotation_onboarding_panel',
{
'company': company,
'state': company._get_and_update_sale_quotation_onboarding_state()
}
)
}
+41
View File
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- Sale Quotation - ONBOARDING STEPS -->
<record id="onboarding_onboarding_step_sale_order_confirmation" model="onboarding.onboarding.step">
<field name="title">Order Confirmation</field>
<field name="description">Choose between electronic signatures or online payments.</field>
<field name="button_text">Set payments</field>
<field name="panel_step_open_action_name">action_open_step_sale_order_confirmation</field>
<field name="step_image" type="base64" file="base/static/img/onboarding_default.png"></field>
<field name="step_image_filename">onboarding_default.png</field>
<field name="step_image_alt">Onboarding Order Confirmation</field>
<field name="sequence">6</field>
</record>
<record id="onboarding_onboarding_step_sample_quotation" model="onboarding.onboarding.step">
<field name="title">Sample Quotation</field>
<field name="description">Send a quotation to test the customer portal.</field>
<field name="button_text">Send sample</field>
<field name="panel_step_open_action_name">action_open_step_sample_quotation</field>
<field name="step_image" type="base64" file="base/static/img/onboarding_sample-quotation.png"></field>
<field name="step_image_filename">onboarding_sample-quotation.png</field>
<field name="step_image_alt">Onboarding Sample Quotation</field>
<field name="sequence">7</field>
</record>
<!-- Sale Quotation - ONBOARDING PANEL -->
<record id="onboarding_onboarding_sale_quotation" model="onboarding.onboarding">
<field name="name">Sale Quotation Onboarding</field>
<field name="step_ids" eval="[
Command.link(ref('sale.onboarding_onboarding_step_sale_order_confirmation')),
Command.link(ref('sale.onboarding_onboarding_step_sample_quotation')),
Command.link(ref('account.onboarding_onboarding_step_company_data')),
Command.link(ref('account.onboarding_onboarding_step_base_document_layout'))
]"/>
<field name="route_name">sale_quotation</field>
<field name="panel_close_action_name">action_close_panel_sale_quotation</field>
</record>
</data>
</odoo>
+2
View File
@@ -5,6 +5,8 @@ from . import analytic
from . import account_move
from . import account_move_line
from . import crm_team
from . import onboarding_onboarding
from . import onboarding_onboarding_step
from . import payment_provider
from . import payment_transaction
from . import product_product
@@ -0,0 +1,12 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, models
class Onboarding(models.Model):
_inherit = 'onboarding.onboarding'
# Sale Quotation Onboarding
@api.model
def action_close_panel_sale_quotation(self):
self.action_close_panel('sale.onboarding_onboarding_sale_quotation')
@@ -0,0 +1,100 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
from odoo import _, api, Command, models
from odoo.modules.module import get_resource_path
from odoo.tools import file_open
class OnboardingStep(models.Model):
_inherit = 'onboarding.onboarding.step'
@api.model
def action_open_step_sale_order_confirmation(self):
self.env.company.get_chart_of_accounts_or_fail()
action = {
'type': 'ir.actions.act_window',
'name': _('Choose how to confirm quotations'),
'res_model': 'sale.payment.provider.onboarding.wizard',
'view_mode': 'form',
'views': [(self.env.ref('payment.payment_provider_onboarding_wizard_form').id, 'form')],
'target': 'new',
}
return action
@api.model
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.company.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 not sample_sales_order:
# take any existing product or create one
product = self.env['product.product'].search([], limit=1)
if not product:
default_image_path = get_resource_path('product', 'static/img', 'product_product_13-image.jpg')
with file_open(default_image_path, 'rb') as default_image_stream:
product = self.env['product.product'].create({
'name': _('Sample Product'),
'active': False,
'image_1920': base64.b64encode(default_image_stream.read()),
})
product.product_tmpl_id.active = False
sample_sales_order = self.env['sale.order'].create({
'partner_id': partner.id,
'order_line': [
Command.create({
'name': _('Sample Order Line'),
'product_id': product.id,
'product_uom_qty': 10,
'price_unit': 123,
})
]
})
return sample_sales_order
@api.model
def action_open_step_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)
self.env['mail.compose.message'].with_context(
mark_so_as_sent=True,
default_email_layout_xmlid='mail.mail_notification_layout_with_responsible_signature',
proforma=self.env.context.get('proforma', False),
).create({
'res_ids': sample_sales_order.ids,
'template_id': template.id if template else False,
'model': sample_sales_order._name,
'composition_mode': 'comment',
})._action_send_mail()
self.action_validate_step('sale.onboarding_onboarding_step_sample_quotation')
sale_quotation_onboarding = self.env.ref('sale.onboarding_onboarding_sale_quotation', raise_if_not_found=False)
if sale_quotation_onboarding:
sale_quotation_onboarding.action_close()
view_id = self.env.ref('sale.view_order_form').id
action = self.env['ir.actions.actions']._for_xml_id('sale.action_orders')
action.update({
'view_mode': 'form',
'views': [[view_id, 'form']],
'target': 'main',
})
return action
@api.model
def action_validate_step_payment_provider(self):
validation_response = super().action_validate_step_payment_provider()
if self.env.company.sale_onboarding_payment_method: # Set if the flow is/was done from the Sales panel
return self.action_validate_step('sale.onboarding_onboarding_step_sale_order_confirmation')
return validation_response
+1 -119
View File
@@ -1,11 +1,6 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
from odoo import api, fields, models, _
from odoo.fields import Command
from odoo.modules.module import get_module_resource
from odoo.addons.account.models.company import DASHBOARD_ONBOARDING_STATES, ONBOARDING_STEP_STATES
from odoo import fields, models
class ResCompany(models.Model):
@@ -39,18 +34,6 @@ class ResCompany(models.Model):
)
# sale onboarding
sale_quotation_onboarding_state = fields.Selection(
selection=DASHBOARD_ONBOARDING_STATES,
string="State of the sale onboarding panel",
default='not_done')
sale_onboarding_order_confirmation_state = fields.Selection(
selection=ONBOARDING_STEP_STATES,
string="State of the onboarding confirmation order step",
default='not_done')
sale_onboarding_sample_quotation_state = fields.Selection(
selection=ONBOARDING_STEP_STATES,
string="State of the onboarding sample quotation step",
default='not_done')
sale_onboarding_payment_method = fields.Selection(
selection=[
('digital_signature', "Sign online"),
@@ -60,104 +43,3 @@ class ResCompany(models.Model):
('manual', "Manual Payment"),
],
string="Sale onboarding selected payment method")
@api.model
def action_close_sale_quotation_onboarding(self):
""" Mark the onboarding panel as closed. """
self.env.company.sale_quotation_onboarding_state = 'closed'
@api.model
def action_open_sale_onboarding_payment_provider(self):
""" Called by onboarding panel above the quotation list."""
self.env.company.get_chart_of_accounts_or_fail()
return self.env['ir.actions.actions']._for_xml_id('sale.action_open_sale_payment_provider_onboarding_wizard')
def _mark_payment_onboarding_step_as_done(self):
""" Override of payment to mark the sale onboarding step as done.
The payment onboarding step of Sales is only marked as done if it was started from Sales.
This prevents incorrectly marking the step as done if another module's payment onboarding
step was marked as done.
:return: None
"""
super()._mark_payment_onboarding_step_as_done()
if self.sale_onboarding_payment_method: # The onboarding step was started from Sales
self.set_onboarding_step_done('sale_onboarding_order_confirmation_state')
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.company.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 not sample_sales_order:
# take any existing product or create one
product = self.env['product.product'].search([], limit=1)
if not product:
default_image_path = get_module_resource('product', 'static/img', 'product_product_13-image.jpg')
product = self.env['product.product'].create({
'name': _("Sample Product"),
'active': False,
'image_1920': base64.b64encode(open(default_image_path, 'rb').read())
})
product.product_tmpl_id.write({'active': False})
sample_sales_order = self.env['sale.order'].create({
'partner_id': partner.id,
'order_line': [
Command.create({
'name': _("Sample Order Line"),
'product_id': product.id,
'product_uom_qty': 10,
'price_unit': 123,
})
]
})
return sample_sales_order
@api.model
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)
self.env['mail.compose.message'].with_context(
mark_so_as_sent=True,
default_email_layout_xmlid='mail.mail_notification_layout_with_responsible_signature',
proforma=self.env.context.get('proforma', False),
force_email=True,
).create({
'res_ids': sample_sales_order.ids,
'template_id': template.id if template else False,
'model': sample_sales_order._name,
'composition_mode': 'comment',
})._action_send_mail()
self.set_onboarding_step_done('sale_onboarding_sample_quotation_state')
self.action_close_sale_quotation_onboarding()
action = self.env['ir.actions.actions']._for_xml_id('sale.action_orders')
action.update({
'views': [[self.env.ref('sale.view_order_form').id, 'form']],
'view_mode': 'form',
'target': 'main',
})
return action
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_onboarding_state('sale_quotation_onboarding_state', steps)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

+14 -14
View File
@@ -23,58 +23,58 @@ registry.category("web_tour.tours").add("sale_tour", {
position: "bottom",
edition: "enterprise"
}, {
trigger: 'a.o_onboarding_step_action.btn[data-method=action_open_base_onboarding_company]',
trigger: 'a.o_onboarding_step_action.btn[data-method=action_open_step_company_data]',
extra_trigger: ".o_sale_order",
content: _t("Start by checking your company's data."),
position: "bottom",
skip_trigger: 'a[data-method=action_open_base_onboarding_company].o_onboarding_step_action__done',
skip_trigger: 'a[data-method=action_open_step_company_data].o_onboarding_step_action__done',
}, {
trigger: 'input[id=street_0]',
content: _t("Complete your company's data"),
position: "bottom",
skip_trigger: 'a[data-method=action_open_base_onboarding_company].o_onboarding_step_action__done',
skip_trigger: 'a[data-method=action_open_step_company_data].o_onboarding_step_action__done',
}, {
trigger: ".modal-content button[name='action_save_onboarding_company_step']",
trigger: ".modal-content button.o_form_button_save",
content: _t("Looks good. Let's continue."),
position: "left",
skip_trigger: 'a[data-method=action_open_base_onboarding_company].o_onboarding_step_action__done',
skip_trigger: 'a[data-method=action_open_step_company_data].o_onboarding_step_action__done',
}, stepUtils.showAppsMenuItem(),
{
trigger: ".o_app[data-menu-xmlid='sale.sale_menu_root']",
skip_trigger: 'a[data-method=action_open_base_onboarding_company].o_onboarding_step_action__done',
skip_trigger: 'a[data-method=action_open_step_company_data].o_onboarding_step_action__done',
edition: "community",
auto: true,
}, {
trigger: ".o_app[data-menu-xmlid='sale.sale_menu_root']",
skip_trigger: 'a[data-method=action_open_base_onboarding_company].o_onboarding_step_action__done',
skip_trigger: 'a[data-method=action_open_step_company_data].o_onboarding_step_action__done',
edition: "enterprise",
auto: true,
}, {
trigger: 'a.o_onboarding_step_action.btn[data-method=action_open_base_document_layout]',
trigger: 'a.o_onboarding_step_action.btn[data-method=action_open_step_base_document_layout]',
extra_trigger: ".o_sale_order",
content: _t("Customize your quotes and orders."),
position: "bottom",
skip_trigger: 'a[data-method=action_open_base_document_layout].o_onboarding_step_action__done',
skip_trigger: 'a[data-method=action_open_step_base_document_layout].o_onboarding_step_action__done',
}, {
trigger: "button[name='document_layout_save']",
extra_trigger: ".o_sale_order",
content: _t("Good job, let's continue."),
position: "top", // dot NOT move to bottom, it would cause a resize flicker
skip_trigger: 'a[data-method=action_open_base_document_layout].o_onboarding_step_action__done',
skip_trigger: 'a[data-method=action_open_step_base_document_layout].o_onboarding_step_action__done',
}, {
trigger: 'a.o_onboarding_step_action.btn[data-method=action_open_sale_onboarding_payment_provider]',
trigger: 'a.o_onboarding_step_action.btn[data-method=action_open_step_sale_order_confirmation]',
extra_trigger: ".o_sale_order",
content: _t("To speed up order confirmation, we can activate electronic signatures or payments."),
position: "bottom",
skip_trigger: 'a[data-method=action_open_sale_onboarding_payment_provider].o_onboarding_step_action__done',
skip_trigger: 'a[data-method=action_open_step_sale_order_confirmation].o_onboarding_step_action__done',
}, {
trigger: "button[name='add_payment_methods']",
extra_trigger: ".o_sale_order",
content: _t("Lets keep electronic signature for now."),
position: "bottom",
skip_trigger: 'a[data-method=action_open_sale_onboarding_payment_provider].o_onboarding_step_action__done',
skip_trigger: 'a[data-method=action_open_step_sale_order_confirmation].o_onboarding_step_action__done',
}, {
trigger: 'a.o_onboarding_step_action.btn[data-method=action_open_sale_onboarding_sample_quotation]',
trigger: 'a.o_onboarding_step_action.btn[data-method=action_open_step_sample_quotation]',
extra_trigger: ".o_sale_order",
content: _t("Now, we'll create a sample quote."),
position: "bottom",
+1
View File
@@ -5,6 +5,7 @@ from . import test_access_rights
from . import test_accrued_sale_orders
from . import test_common
from . import test_controllers
from . import test_onboarding
from . import test_onchange
from . import test_payment_flow
from . import test_reinvoice
+30
View File
@@ -0,0 +1,30 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.addons.onboarding.tests.case import TransactionCaseOnboarding
class TestOnboarding(TransactionCaseOnboarding):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.account_payment_provider_step = cls.env.ref(
"account_payment.onboarding_onboarding_step_payment_provider"
)
cls.sale_quotation_order_confirmation_step = cls.env.ref(
"sale.onboarding_onboarding_step_sale_order_confirmation"
)
def test_payment_provider_account_doesnt_validate_sales(self):
self.assert_step_is_not_done(self.sale_quotation_order_confirmation_step)
self.env["onboarding.onboarding.step"].action_validate_step_payment_provider()
self.assert_step_is_not_done(self.sale_quotation_order_confirmation_step)
# Set field as in payment_provider_onboarding_wizard's add_payment_method override
self.env.company.sale_onboarding_payment_method = "stripe"
self.env["onboarding.onboarding.step"].action_validate_step_payment_provider()
self.assert_step_is_done(self.sale_quotation_order_confirmation_step)
def test_payment_provider_sales_validates_account(self):
self.assert_step_is_not_done(self.account_payment_provider_step)
self.env["onboarding.onboarding.step"].action_validate_step_payment_provider()
self.assert_step_is_done(self.account_payment_provider_step)
+1 -1
View File
@@ -15,4 +15,4 @@ class TestOnboarding(HttpCase):
('state', '=', 'draft')
]).state = 'cancel'
self.env['product.product'].search([]).active = False
self.env.company._get_sample_sales_order()
self.env['onboarding.onboarding.step']._get_sample_sales_order()
@@ -1,62 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- ONBOARDING STEPS -->
<template id="onboarding_quotation_layout_step">
<t t-call="base.onboarding_step">
<t t-set="title">Quotation Layout</t>
<t t-set="description">Customize the look of your quotations.</t>
<t t-set="image" t-value="'/base/static/img/onboarding_quotation-layout.png'"/>
<t t-set="alt" t-value="'Onboarding Quotation Layout'"/>
<t t-set="done_icon" t-value="'fa-star'" />
<t t-set="done_text">Looks great!</t>
<t t-set="btn_text">Customize</t>
<t t-set="model" t-value="'base.document.layout'" />
<t t-set="method" t-value="'action_open_base_document_layout'" />
<t t-set="state" t-value="state.get('account_onboarding_invoice_layout_state')" />
</t>
</template>
<template id="sale_onboarding_order_confirmation_step">
<t t-call="base.onboarding_step">
<t t-set="title">Order Confirmation</t>
<t t-set="description">Choose between electronic signatures or online payments.</t>
<t t-set="image" t-value="'/base/static/img/onboarding_default.png'"/>
<t t-set="alt" t-value="'Onboarding Quotation Layout'"/>
<t t-set="btn_text">Set payments</t>
<t t-set="method" t-value="'action_open_sale_onboarding_payment_provider'" />
<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="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="image" t-value="'/base/static/img/onboarding_sample-quotation.png'"/>
<t t-set="alt" t-value="'Onboarding Quotation Layout'"/>
<t t-set="btn_text">Send sample</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="sale_quotation_onboarding_panel" name="sale.quotation.onboarding.panel">
<t t-call="base.onboarding_container">
<t t-set="close_method" t-value="'action_close_sale_quotation_onboarding'" />
<t t-set="close_model" t-value="'res.company'" />
<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.sale_onboarding_order_confirmation_step" name="payment_provider_step" />
<t t-call="sale.sale_onboarding_sample_quotation_step" name="sample_quotation_step" />
</t>
</template>
<!-- ORDER CONFIRMATION -->
<record id="action_open_sale_payment_provider_onboarding_wizard" model="ir.actions.act_window">
<field name="name">Choose how to confirm quotations</field>
<field name="res_model">sale.payment.provider.onboarding.wizard</field>
<field name="view_mode">form</field>
<field name="view_id" ref="payment.payment_provider_onboarding_wizard_form" />
<field name="target">new</field>
</record>
</odoo>
+1 -1
View File
@@ -215,7 +215,7 @@
<field name="mode">primary</field>
<field name="arch" type="xml">
<xpath expr="//tree" position="attributes">
<attribute name="banner_route">/sales/sale_quotation_onboarding_panel</attribute>
<attribute name="banner_route">/onboarding/sale_quotation</attribute>
</xpath>
</field>
</record>
@@ -20,11 +20,6 @@ class PaymentWizard(models.TransientModel):
('paypal', "PayPal"),
('manual', "Custom payment instructions"),
], default=_get_default_payment_method)
#
def _set_payment_provider_onboarding_step_done(self):
""" Override. """
self.env.company.sudo().set_onboarding_step_done('sale_onboarding_order_confirmation_state')
def add_payment_methods(self):
self.env.company.sale_onboarding_payment_method = self.payment_method
+2 -2
View File
@@ -11,8 +11,8 @@ class TestUi(HttpCase):
self.start_tour("/web", 'sale_tour', login="admin", step_delay=100)
def test_02_sale_tour_company_onboarding_done(self):
self.env.company.set_onboarding_step_done('base_onboarding_company_state')
self.start_tour("/web", 'sale_tour', login="admin", step_delay=100)
self.env["onboarding.onboarding.step"].action_validate_step("account.onboarding_onboarding_step_company_data")
self.start_tour("/web", "sale_tour", login="admin", step_delay=100)
def test_03_sale_quote_tour(self):
self.env['res.partner'].create({'name': 'Agrolait', 'email': 'agro@lait.be'})
-5
View File
@@ -205,7 +205,6 @@ This module provides the core of the Odoo Web Client.
'web/static/src/legacy/scss/ace.scss',
'web/static/src/legacy/scss/fields.scss',
'web/static/src/legacy/scss/views.scss',
'base/static/src/scss/onboarding.scss',
'web/static/src/legacy/scss/attachment_preview.scss',
'web/static/src/legacy/scss/base_document_layout.scss',
'web/static/src/legacy/scss/fields_extra.scss',
@@ -505,9 +504,6 @@ This module provides the core of the Odoo Web Client.
('include', 'web.assets_backend'),
'web/static/src/**/*.dark.scss',
],
"web.dark_mode_variables": [
('before', 'base/static/src/scss/onboarding.variables.scss', 'base/static/src/scss/onboarding.variables.dark.scss'),
],
# ---------------------------------------------------------------------
# SUB BUNDLES
@@ -525,7 +521,6 @@ This module provides the core of the Odoo Web Client.
'web._assets_primary_variables': [
'web/static/src/scss/primary_variables.scss',
'web/static/src/**/*.variables.scss',
'base/static/src/scss/onboarding.variables.scss',
],
'web._assets_secondary_variables': [
'web/static/src/scss/secondary_variables.scss',
@@ -246,14 +246,6 @@ class BaseDocumentLayout(models.TransientModel):
return tools.rgb_to_hex(primary), tools.rgb_to_hex(secondary)
@api.model
def action_open_base_document_layout(self, action_ref=None):
if not action_ref:
action_ref = 'web.action_base_document_layout_configurator'
res = self.env["ir.actions.actions"]._for_xml_id(action_ref)
self.env[res["res_model"]].check_access_rights('write')
return res
def document_layout_save(self):
# meant to be overridden
return self.env.context.get('report_action') or {'type': 'ir.actions.act_window_close'}
+1 -10
View File
@@ -1,21 +1,12 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
from odoo import models
class ResCompany(models.Model):
_inherit = 'res.company'
website_sale_onboarding_payment_provider_state = fields.Selection([('not_done', "Not done"), ('just_done', "Just done"), ('done', "Done")], string="State of the website sale onboarding payment provider step", default='not_done')
@api.model
def action_open_website_sale_onboarding_payment_provider(self):
""" Called by onboarding panel above the quotation list."""
self.env.company.payment_onboarding_payment_method = 'stripe'
menu_id = self.env.ref('website.menu_website_dashboard').id
return self._run_payment_onboarding_step(menu_id)
def _get_default_pricelist_vals(self):
""" Override of product. Called at company creation or activation of the pricelist setting.
-1
View File
@@ -72,7 +72,6 @@ The kernel of Odoo, needed for all installation.
'views/ir_property_views.xml',
'views/res_config_settings_views.xml',
'views/report_paperformat_views.xml',
'views/onboarding_views.xml',
'security/ir.model.access.csv',
],
'demo': [
-36
View File
@@ -68,8 +68,6 @@ class Company(models.Model):
company_registry = fields.Char(related='partner_id.company_registry', string="Company ID", readonly=False)
paperformat_id = fields.Many2one('report.paperformat', 'Paper format', default=lambda self: self.env.ref('base.paperformat_euro', raise_if_not_found=False))
external_report_layout_id = fields.Many2one('ir.ui.view', 'Document Template')
base_onboarding_company_state = fields.Selection([
('not_done', "Not done"), ('just_done', "Just done"), ('done', "Done")], string="State of the onboarding company step", default='not_done')
font = fields.Selection([("Lato", "Lato"), ("Roboto", "Roboto"), ("Open_Sans", "Open Sans"), ("Montserrat", "Montserrat"), ("Oswald", "Oswald"), ("Raleway", "Raleway"), ('Tajawal', 'Tajawal')], default="Lato")
primary_color = fields.Char()
secondary_color = fields.Char()
@@ -284,40 +282,6 @@ class Company(models.Model):
return (self.env['ir.actions.report'].search([('report_name', '=', report_name)], limit=1)
.report_action(docids))
@api.model
def action_open_base_onboarding_company(self):
""" Onboarding step for company basic information. """
action = self.env["ir.actions.actions"]._for_xml_id("base.action_open_base_onboarding_company")
action['res_id'] = self.env.company.id
return action
def set_onboarding_step_done(self, step_name):
if self[step_name] == 'not_done':
self[step_name] = 'just_done'
def _get_and_update_onboarding_state(self, onboarding_state, steps_states):
""" Needed to display onboarding animations only one time. """
old_values = {}
all_done = True
for step_state in steps_states:
old_values[step_state] = self[step_state]
if self[step_state] == 'just_done':
self[step_state] = 'done'
all_done = all_done and self[step_state] == 'done'
if all_done:
if self[onboarding_state] == 'not_done':
# string `onboarding_state` instead of variable name is not an error
old_values['onboarding_state'] = 'just_done'
else:
old_values['onboarding_state'] = 'done'
self[onboarding_state] = 'done'
return old_values
def action_save_onboarding_company_step(self):
if bool(self.street):
self.set_onboarding_step_done('base_onboarding_company_state')
@api.model
def _get_main_company(self):
try:
-2
View File
@@ -29,8 +29,6 @@ class Partner(models.Model):
('name', populate.constant('company_{counter}')),
('sequence', populate.randint(0, 100)),
('company_registry', populate.iterate([False, 'company_registry_{counter}'])),
('base_onboarding_company_state', populate.iterate(
[False] + [e[0] for e in type(self).base_onboarding_company_state.selection])),
('primary_color', populate.iterate([False, '', '#ff7755'])),
('secondary_color', populate.iterate([False, '', '#ffff55'], seed='primary_color')),
('currency_id', populate.iterate(active_currencies)),