[REF] *: use onboarding module
Purpose: Use the onboarding module to manage onboardings, steps and their progress for the different companies This PR (and related ENT one) moves these: * `account`:" invoicing" and "dashboard" + "vendor bill" and "sale_tax" steps * `sale`: "quotation" * `account_consolidation`: "dashboard" * `(account_)payment`: payment provider setup step, used in different modules. * `website_sale_dashboard` The onboarding models themselves changed a little to support sharing steps between onboardings or having steps defined in one module, only to be shown on a panel (`onboarding.onboarding`) in another module, mainly by changing the `onboarding.onboarding` <> `onboarding.onboarding.step` and `onboarding.progress` <> `onboarding.progress.step` relations to m2m. Task-3025136 See https://github.com/odoo/enterprise/pull/33818 See https://github.com/odoo/upgrade/pull/4021 closes odoo/odoo#104223 Related: odoo/enterprise#33818 Related: odoo/upgrade#4021 Signed-off-by: Florian Charlier (flch) <flch@odoo.com>
This commit is contained in:
@@ -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,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
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
}
|
||||
@@ -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 & 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>
|
||||
@@ -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'
|
||||
|
||||
@@ -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']],
|
||||
}
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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 & 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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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 & 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>
|
||||
@@ -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 & 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>
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
{
|
||||
'name': 'Onboarding Toolbox',
|
||||
'version': '1.1',
|
||||
'version': '1.2',
|
||||
'category': 'Hidden',
|
||||
'sequence': 9001,
|
||||
'description': """
|
||||
@@ -13,10 +13,21 @@ This module allows to manage onboardings and their progress
|
||||
'depends': ['base'],
|
||||
'installable': True,
|
||||
'data': [
|
||||
'data/onboarding_data.xml',
|
||||
'views/onboarding_templates.xml',
|
||||
'views/onboarding_views.xml',
|
||||
'views/onboarding_menus.xml',
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
'assets': {
|
||||
'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>
|
||||
@@ -2,6 +2,6 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import onboarding_onboarding
|
||||
from . import onboarding_step
|
||||
from . import onboarding_onboarding_step
|
||||
from . import onboarding_progress
|
||||
from . import onboarding_progress_step
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -13,10 +13,15 @@ class Onboarding(models.Model):
|
||||
name = fields.Char('Name of the onboarding', translate=True)
|
||||
# One word identifier used to define the onboarding panel's route: `/onboarding/{route_name}`.
|
||||
route_name = fields.Char('One word name', required=True)
|
||||
step_ids = fields.One2many('onboarding.onboarding.step', 'onboarding_id', 'Onboarding steps')
|
||||
step_ids = fields.Many2many('onboarding.onboarding.step', string='Onboarding steps')
|
||||
|
||||
is_per_company = fields.Boolean('Should be done per company?', default=True)
|
||||
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,
|
||||
)
|
||||
panel_close_action_name = fields.Char(
|
||||
'Closing action', help='Name of the onboarding model action to execute when closing the panel.')
|
||||
|
||||
@@ -36,15 +41,24 @@ class Onboarding(models.Model):
|
||||
('route_name_uniq', 'UNIQUE (route_name)', 'Onboarding alias must be unique.'),
|
||||
]
|
||||
|
||||
@api.depends('progress_ids', 'progress_ids.company_id', 'step_ids', 'step_ids.is_per_company')
|
||||
def _compute_is_per_company(self):
|
||||
# Once an onboarding is made "per-company", there is no drawback to simply still consider
|
||||
# it per-company even when if its last per-company step is unlinked. This allows to avoid
|
||||
# handling the merging of existing progress (step) records.
|
||||
|
||||
onboardings_with_per_company_steps_or_progress = self.filtered(
|
||||
lambda o: o.progress_ids.company_id or (True in o.step_ids.mapped('is_per_company')))
|
||||
onboardings_with_per_company_steps_or_progress.is_per_company = True
|
||||
(self - onboardings_with_per_company_steps_or_progress).is_per_company = False
|
||||
|
||||
@api.depends_context('company')
|
||||
@api.depends('progress_ids', 'progress_ids.is_onboarding_closed', 'progress_ids.onboarding_state')
|
||||
@api.depends('progress_ids', 'progress_ids.is_onboarding_closed', 'progress_ids.onboarding_state', 'progress_ids.company_id')
|
||||
def _compute_current_progress(self):
|
||||
for onboarding in self:
|
||||
current_progress_id = onboarding.progress_ids.filtered(
|
||||
lambda progress: progress.company_id.id in {False, self.env.company.id})
|
||||
if current_progress_id:
|
||||
if len(current_progress_id) > 1:
|
||||
current_progress_id = current_progress_id.sorted('create_date', reverse=True)[0]
|
||||
onboarding.current_onboarding_state = current_progress_id.onboarding_state
|
||||
onboarding.current_progress_id = current_progress_id
|
||||
onboarding.is_onboarding_closed = current_progress_id.is_onboarding_closed
|
||||
@@ -53,28 +67,44 @@ class Onboarding(models.Model):
|
||||
onboarding.current_progress_id = False
|
||||
onboarding.is_onboarding_closed = False
|
||||
|
||||
def write(self, vals):
|
||||
"""Recompute progress step ids if new steps are added/removed."""
|
||||
already_linked_steps = self.step_ids
|
||||
res = super().write(vals)
|
||||
if self.step_ids != already_linked_steps:
|
||||
self.progress_ids._recompute_progress_step_ids()
|
||||
return res
|
||||
|
||||
def action_close(self):
|
||||
"""Close the onboarding panel."""
|
||||
self.current_progress_id.action_close()
|
||||
|
||||
@api.model
|
||||
def action_close_panel(self, xmlid):
|
||||
"""Close the onboarding panel identified by its `xmlid`.
|
||||
|
||||
If not found, quietly do nothing.
|
||||
"""
|
||||
if onboarding := self.env.ref(xmlid, raise_if_not_found=False):
|
||||
onboarding.action_close()
|
||||
|
||||
def action_refresh_progress_ids(self):
|
||||
"""Re-initialize onboarding progress records (after step is_per_company change).
|
||||
|
||||
Meant to be called when `is_per_company` of linked steps is modified (or per-company
|
||||
steps are added to an onboarding).
|
||||
"""
|
||||
onboardings_to_refresh_progress = self.filtered(
|
||||
lambda o: o.is_per_company and o.progress_ids and not o.progress_ids.company_id
|
||||
)
|
||||
onboardings_to_refresh_progress.progress_ids.unlink()
|
||||
onboardings_to_refresh_progress._create_progress()
|
||||
|
||||
def action_toggle_visibility(self):
|
||||
self.current_progress_id.action_toggle_visibility()
|
||||
|
||||
def write(self, values):
|
||||
if 'is_per_company' in values:
|
||||
onboardings_per_company_update = self.filtered(
|
||||
lambda onboarding: onboarding.is_per_company != values['is_per_company'])
|
||||
|
||||
res = super().write(values)
|
||||
|
||||
if 'is_per_company' in values:
|
||||
# When changing this parameter, all progress (onboarding and steps) is reset.
|
||||
onboardings_per_company_update.progress_ids.unlink()
|
||||
return res
|
||||
|
||||
def _search_or_create_progress(self):
|
||||
"""Create Progress record(s) as necessary for the context.
|
||||
"""
|
||||
"""Create Progress record(s) as necessary for the context."""
|
||||
onboardings_without_progress = self.filtered(lambda onboarding: not onboarding.current_progress_id)
|
||||
onboardings_without_progress._create_progress()
|
||||
return self.current_progress_id
|
||||
@@ -83,8 +113,12 @@ class Onboarding(models.Model):
|
||||
return self.env['onboarding.progress'].create([
|
||||
{
|
||||
'company_id': self.env.company.id if onboarding.is_per_company else False,
|
||||
'onboarding_id': onboarding.id
|
||||
} for onboarding in self
|
||||
'onboarding_id': onboarding.id,
|
||||
'progress_step_ids': onboarding.step_ids.progress_ids.filtered(
|
||||
lambda p: p.company_id.id in [False, self.env.company.id]
|
||||
),
|
||||
}
|
||||
for onboarding in self
|
||||
])
|
||||
|
||||
def _prepare_rendering_values(self):
|
||||
@@ -94,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
|
||||
|
||||
+53
-22
@@ -1,8 +1,9 @@
|
||||
# -*- 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, Command, fields, models
|
||||
from odoo.addons.onboarding.models.onboarding_progress import ONBOARDING_PROGRESS_STATES
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class OnboardingStep(models.Model):
|
||||
@@ -11,8 +12,7 @@ class OnboardingStep(models.Model):
|
||||
_order = 'sequence asc, id asc'
|
||||
_rec_name = 'title'
|
||||
|
||||
onboarding_id = fields.Many2one(
|
||||
'onboarding.onboarding', string='Onboarding', readonly=True, required=True, ondelete='cascade')
|
||||
onboarding_ids = fields.Many2many('onboarding.onboarding', string='Onboardings')
|
||||
|
||||
title = fields.Char('Title', translate=True)
|
||||
description = fields.Char('Description', translate=True)
|
||||
@@ -21,14 +21,14 @@ class OnboardingStep(models.Model):
|
||||
help="Text on the panel's button to start this step")
|
||||
done_icon = fields.Char('Font Awesome Icon when completed', default='fa-star')
|
||||
done_text = fields.Char(
|
||||
'Text to show when step is completed', default=_('Step Completed! - Click to review'), translate=True)
|
||||
'Text to show when step is completed', default=_('Step Completed!'), translate=True)
|
||||
step_image = fields.Binary("Step Image")
|
||||
step_image_filename = fields.Char("Step Image Filename")
|
||||
step_image_alt = fields.Char(
|
||||
'Alt Text for the Step Image', default='Onboarding Step Image', translate=True,
|
||||
help='Show when impossible to load the image')
|
||||
panel_step_open_action_name = fields.Char(
|
||||
string='Opening action', required=True,
|
||||
string='Opening action', required=False,
|
||||
help='Name of the onboarding step model action to execute when opening the step, '
|
||||
'e.g. action_open_onboarding_1_step_1')
|
||||
|
||||
@@ -41,27 +41,56 @@ class OnboardingStep(models.Model):
|
||||
'onboarding.progress.step', 'step_id', string='Onboarding Progress Step Records', readonly=True,
|
||||
help='All related Onboarding Progress Step Records (across companies)')
|
||||
|
||||
is_per_company = fields.Boolean('Is per company', default=True)
|
||||
sequence = fields.Integer(default=10)
|
||||
|
||||
@api.depends_context('company')
|
||||
@api.depends('progress_ids', 'progress_ids.step_state')
|
||||
def _compute_current_progress(self):
|
||||
# When `is_per_company` is changed, `progress_ids` is updated (see `write`) which triggers this `_compute`.
|
||||
existing_progress_steps = self.progress_ids.filtered_domain([
|
||||
('step_id', 'in', self.ids),
|
||||
('progress_id.company_id', 'in', [False, self.env.company.id]),
|
||||
('company_id', 'in', [False, self.env.company.id]),
|
||||
])
|
||||
for step in self:
|
||||
if step in existing_progress_steps.step_id:
|
||||
current_progress_step_id = existing_progress_steps.filtered(
|
||||
lambda progress_step: progress_step.step_id == step)
|
||||
if len(current_progress_step_id) > 1:
|
||||
current_progress_step_id = current_progress_step_id.sorted('create_date', reverse=True)[0]
|
||||
step.current_progress_step_id = current_progress_step_id
|
||||
step.current_step_state = current_progress_step_id.step_state
|
||||
else:
|
||||
step.current_progress_step_id = False
|
||||
step.current_step_state = 'not_done'
|
||||
|
||||
@api.constrains('onboarding_ids')
|
||||
def check_step_on_onboarding_has_action(self):
|
||||
if steps_without_action := self.filtered(lambda step: step.onboarding_ids and not step.panel_step_open_action_name):
|
||||
raise ValidationError(_(
|
||||
'An "Opening Action" is required for the following steps to be '
|
||||
'linked to an onboarding panel: %(step_titles)s',
|
||||
step_titles=steps_without_action.mapped('title'),
|
||||
))
|
||||
|
||||
def write(self, vals):
|
||||
new_is_per_company = vals.get('is_per_company')
|
||||
steps_changing_is_per_company = (
|
||||
self.browse() if new_is_per_company is None
|
||||
else self.filtered(lambda step: step.is_per_company != new_is_per_company)
|
||||
)
|
||||
already_linked_onboardings = self.onboarding_ids
|
||||
|
||||
res = super().write(vals)
|
||||
|
||||
# Progress is reset (to be done per-company or, for steps, to have a single record)
|
||||
if steps_changing_is_per_company:
|
||||
steps_changing_is_per_company.progress_ids.unlink()
|
||||
self.onboarding_ids.action_refresh_progress_ids()
|
||||
|
||||
if self.onboarding_ids - already_linked_onboardings:
|
||||
self.onboarding_ids.progress_ids._recompute_progress_step_ids()
|
||||
|
||||
return res
|
||||
|
||||
def action_set_just_done(self):
|
||||
# Make sure progress records exist for the current context (company)
|
||||
steps_without_progress = self.filtered(lambda step: not step.current_progress_step_id)
|
||||
@@ -82,21 +111,23 @@ class OnboardingStep(models.Model):
|
||||
return super()._get_placeholder_filename(field)
|
||||
|
||||
def _create_progress_steps(self):
|
||||
"""Create progress step records as necessary to validate steps.
|
||||
|
||||
Only considers existing `onboarding.progress` records for the current
|
||||
company or without company (depending on `is_per_company`).
|
||||
"""
|
||||
onboarding_progress_records = self.env['onboarding.progress'].search([
|
||||
('onboarding_id', 'in', self.onboarding_id.ids),
|
||||
('onboarding_id', 'in', self.onboarding_ids.ids),
|
||||
('company_id', 'in', [False, self.env.company.id])
|
||||
])
|
||||
progress_step_values = []
|
||||
for onboarding_progress_record in onboarding_progress_records:
|
||||
progress_step_values += [
|
||||
{
|
||||
'onboarding_id': onboarding_progress_record.onboarding_id.id,
|
||||
'progress_id': onboarding_progress_record.id,
|
||||
'step_id': step_id.id,
|
||||
}
|
||||
for step_id
|
||||
in self.filtered(lambda step: self.onboarding_id == onboarding_progress_record.onboarding_id)
|
||||
if step_id not in onboarding_progress_record.progress_step_ids.step_id
|
||||
]
|
||||
|
||||
progress_step_values = [
|
||||
{
|
||||
'step_id': step_id.id,
|
||||
'progress_ids': [
|
||||
Command.link(onboarding_progress_record.id)
|
||||
for onboarding_progress_record
|
||||
in onboarding_progress_records.filtered(lambda p: step_id in p.onboarding_id.step_ids)],
|
||||
'company_id': self.env.company.id if step_id.is_per_company else False,
|
||||
} for step_id in self
|
||||
]
|
||||
return self.env['onboarding.progress.step'].create(progress_step_values)
|
||||
@@ -22,9 +22,10 @@ class OnboardingProgress(models.Model):
|
||||
company_id = fields.Many2one('res.company')
|
||||
onboarding_id = fields.Many2one(
|
||||
'onboarding.onboarding', 'Related onboarding tracked', required=True, ondelete='cascade')
|
||||
progress_step_ids = fields.One2many('onboarding.progress.step', 'progress_id', 'Progress Steps Trackers')
|
||||
progress_step_ids = fields.Many2many('onboarding.progress.step', string='Progress Steps Trackers')
|
||||
|
||||
def init(self):
|
||||
"""Make sure there aren't multiple records for the same onboarding and company."""
|
||||
# not in _sql_constraint because COALESCE is not supported for PostgreSQL constraint
|
||||
self.env.cr.execute("""
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS onboarding_progress_onboarding_company_uniq
|
||||
@@ -33,15 +34,19 @@ class OnboardingProgress(models.Model):
|
||||
|
||||
@api.depends('onboarding_id.step_ids', 'progress_step_ids', 'progress_step_ids.step_state')
|
||||
def _compute_onboarding_state(self):
|
||||
progress_steps_data = self.env['onboarding.progress.step']._read_group(
|
||||
[('progress_id', 'in', self.ids), ('step_state', 'in', ['just_done', 'done'])],
|
||||
['progress_id'], ['__count']
|
||||
)
|
||||
result = {progress.id: count for progress, count in progress_steps_data}
|
||||
for progress in self:
|
||||
progress.onboarding_state = (
|
||||
'not_done' if result.get(progress.id, 0) != len(progress.onboarding_id.step_ids)
|
||||
else 'done')
|
||||
'not_done' if (
|
||||
len(progress.progress_step_ids.filtered(lambda p: p.step_state in {'just_done', 'done'}))
|
||||
!= len(progress.onboarding_id.step_ids)
|
||||
)
|
||||
else 'done'
|
||||
)
|
||||
|
||||
def _recompute_progress_step_ids(self):
|
||||
"""Update progress steps when a step (with existing progress) is added to an onboarding."""
|
||||
for progress in self:
|
||||
progress.progress_step_ids = progress.onboarding_id.step_ids.current_progress_step_id
|
||||
|
||||
def action_close(self):
|
||||
self.is_onboarding_closed = True
|
||||
@@ -51,9 +56,11 @@ class OnboardingProgress(models.Model):
|
||||
progress.is_onboarding_closed = not progress.is_onboarding_closed
|
||||
|
||||
def _get_and_update_onboarding_state(self):
|
||||
"""Used to fetch the progress of an onboarding for rendering its panel and is expected to
|
||||
be called by the onboarding controller. It also has the responsibility of updating the
|
||||
'just_done' states into 'done' so that the 'just_done' states are only rendered once.
|
||||
"""Fetch the progress of an onboarding for rendering its panel.
|
||||
|
||||
This method is expected to only be called by the onboarding controller.
|
||||
It also has the responsibility of updating the 'just_done' state into
|
||||
'done' so that the 'just_done' states are only rendered once.
|
||||
"""
|
||||
self.ensure_one()
|
||||
onboarding_states_values = {}
|
||||
|
||||
@@ -10,18 +10,21 @@ class OnboardingProgressStep(models.Model):
|
||||
_description = 'Onboarding Progress Step Tracker'
|
||||
_rec_name = 'step_id'
|
||||
|
||||
progress_id = fields.Many2one(
|
||||
'onboarding.progress', 'Related Onboarding Progress Tracker', required=True, ondelete='cascade')
|
||||
progress_ids = fields.Many2many('onboarding.progress', string='Related Onboarding Progress Tracker')
|
||||
step_state = fields.Selection(
|
||||
ONBOARDING_PROGRESS_STATES, string='Onboarding Step Progress', default='not_done')
|
||||
onboarding_id = fields.Many2one(related='progress_id.onboarding_id', string='Onboarding')
|
||||
step_id = fields.Many2one(
|
||||
'onboarding.onboarding.step', string='Onboarding Step', required=True, ondelete='cascade')
|
||||
|
||||
_sql_constraints = [
|
||||
('progress_step_uniq', 'unique (progress_id, step_id)',
|
||||
'There cannot be multiple records of the same onboarding step completion for the same Progress record.'),
|
||||
]
|
||||
company_id = fields.Many2one('res.company', ondelete='cascade')
|
||||
|
||||
def init(self):
|
||||
"""Make sure there aren't multiple records for the same onboarding step and company."""
|
||||
# not in _sql_constraint because COALESCE is not supported for PostgreSQL constraint
|
||||
self.env.cr.execute("""
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS onboarding_progress_step_company_uniq
|
||||
ON onboarding_progress_step (step_id, COALESCE(company_id, 0))
|
||||
""")
|
||||
|
||||
def action_consolidate_just_done(self):
|
||||
was_just_done = self.filtered(lambda progress: progress.step_state == 'just_done')
|
||||
|
||||
+8
-1
@@ -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);
|
||||
@@ -0,0 +1,49 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { FormController } from "@web/views/form/form_controller";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
/**
|
||||
* Controller to use for an onboarding step dialog, not the
|
||||
* onboarding.onboarding.step form view itself.
|
||||
*/
|
||||
export default class OnboardingStepFormController extends FormController {
|
||||
setup() {
|
||||
super.setup();
|
||||
this.action = useService('action');
|
||||
this.orm = useService('orm');
|
||||
}
|
||||
/**
|
||||
* If necessary, mark the step as done and reload the main view.
|
||||
* @override
|
||||
*/
|
||||
async onRecordSaved(record) {
|
||||
const { reloadOnFirstValidation, reloadAlways } = this.stepConfig;
|
||||
const validationResponse = await this.orm.call(
|
||||
'onboarding.onboarding.step',
|
||||
'action_validate_step',
|
||||
[this.stepName],
|
||||
);
|
||||
if (reloadAlways || (reloadOnFirstValidation && validationResponse === "JUST_DONE")) {
|
||||
this.action.restore(this.action.currentController.jsId);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns the name of the onboarding step to validate after the dialog
|
||||
* record is saved
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
get stepName() {
|
||||
return ''
|
||||
}
|
||||
/**
|
||||
* Returns whether to reload the page (useful if the current
|
||||
* view needs to be updated).
|
||||
*
|
||||
* @returns {{reloadAlways: boolean, reloadOnFirstValidation: boolean}}
|
||||
*/
|
||||
get stepConfig() {
|
||||
return { reloadAlways: false, reloadOnFirstValidation: false };
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
@@ -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()
|
||||
@@ -18,12 +18,12 @@ class TestOnboardingCommon(TransactionCase):
|
||||
})
|
||||
cls.user_admin.company_ids |= cls.company_2
|
||||
|
||||
cls.onboarding_1 = cls.env['onboarding.onboarding'].create([
|
||||
cls.onboarding_1, cls.onboarding_2 = cls.env['onboarding.onboarding'].create([
|
||||
{
|
||||
'name': 'Test Onboarding 1',
|
||||
'name': f'Test Onboarding {onboarding_id}',
|
||||
'route_name': f'onboarding{onboarding_id}',
|
||||
'is_per_company': False,
|
||||
'route_name': 'onboarding1'
|
||||
}
|
||||
} for onboarding_id in range(2)
|
||||
])
|
||||
|
||||
# create a fake action for step opening
|
||||
@@ -36,49 +36,22 @@ class TestOnboardingCommon(TransactionCase):
|
||||
cls.onboarding_1_step_1, cls.onboarding_1_step_2 = cls.env['onboarding.onboarding.step'].create([
|
||||
{
|
||||
'title': f'Test Onboarding 1 - Step {step_n}',
|
||||
'onboarding_id': cls.onboarding_1.id,
|
||||
'onboarding_ids': [cls.onboarding_1.id],
|
||||
'is_per_company': False,
|
||||
'panel_step_open_action_name': 'action_fake_open_onboarding_step',
|
||||
}
|
||||
for step_n in range(1, 3)
|
||||
])
|
||||
|
||||
# Add one of these in onboarding_2, and an "original" one
|
||||
cls.onboarding_2.step_ids = [cls.onboarding_1_step_1.id]
|
||||
cls.onboarding_2_step_2 = cls.env['onboarding.onboarding.step'].create([{
|
||||
'title': 'Test Onboarding 2 - Step 2',
|
||||
'onboarding_ids': [cls.onboarding_2.id],
|
||||
'is_per_company': False,
|
||||
'panel_step_open_action_name': 'action_fake_open_onboarding_step',
|
||||
}])
|
||||
# Create progress records as would happen through the controller
|
||||
cls.onboarding_1.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.assertEqual(
|
||||
onboarding.current_progress_id.onboarding_state, 'not_done',
|
||||
f'Expected "not_done" 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))
|
||||
(cls.onboarding_1 + cls.onboarding_2).with_company(cls.company_1)._search_or_create_progress()
|
||||
|
||||
def activate_company(self, company):
|
||||
self.onboarding_1_step_1 = self.onboarding_1_step_1.with_company(company)
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
|
||||
from psycopg2 import IntegrityError
|
||||
|
||||
from odoo import Command
|
||||
from odoo.addons.onboarding.tests.common import TestOnboardingCommon
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
@@ -48,7 +50,8 @@ class TestOnboarding(TestOnboardingCommon):
|
||||
# Adding new step resets onboarding state to 'not_done' even if closed
|
||||
onboarding_1_step_3 = self.env['onboarding.onboarding.step'].create({
|
||||
'title': 'Test Onboarding 1 - Step 3',
|
||||
'onboarding_id': self.onboarding_1.id,
|
||||
'onboarding_ids': [self.onboarding_1.id],
|
||||
'is_per_company': False,
|
||||
'panel_step_open_action_name': 'action_fake_open_onboarding_step',
|
||||
})
|
||||
self.assert_step_is_not_done(onboarding_1_step_3)
|
||||
@@ -68,7 +71,8 @@ class TestOnboarding(TestOnboardingCommon):
|
||||
# Adding new step resets onboarding state to 'not_done'
|
||||
self.env['onboarding.onboarding.step'].create({
|
||||
'title': 'Test Onboarding 1 - Step 4',
|
||||
'onboarding_id': self.onboarding_1.id,
|
||||
'onboarding_ids': [self.onboarding_1.id],
|
||||
'is_per_company': False,
|
||||
'panel_step_open_action_name': 'action_fake_open_onboarding_step',
|
||||
})
|
||||
|
||||
@@ -85,14 +89,16 @@ class TestOnboarding(TestOnboardingCommon):
|
||||
# Completing onboarding as company_1
|
||||
self.assertEqual(self.env.company, self.company_1)
|
||||
|
||||
# Updating onboarding to per-company
|
||||
self.onboarding_1.is_per_company = True
|
||||
# Updating onboarding (and steps) to per-company
|
||||
self.onboarding_1_step_1.is_per_company = True
|
||||
|
||||
# Required after progress reset (simulate role of controller)
|
||||
self.onboarding_1._search_or_create_progress()
|
||||
|
||||
self.onboarding_1_step_1.action_set_just_done()
|
||||
self.assert_step_is_done(self.onboarding_1_step_1)
|
||||
|
||||
self.assertFalse(self.onboarding_1_step_2.is_per_company)
|
||||
self.onboarding_1_step_2.action_set_just_done()
|
||||
self.assert_onboarding_is_done(self.onboarding_1)
|
||||
|
||||
@@ -101,19 +107,14 @@ class TestOnboarding(TestOnboardingCommon):
|
||||
# First access from company_2
|
||||
self.onboarding_1._search_or_create_progress()
|
||||
|
||||
# Blank state for company 2
|
||||
# Blank state for company 2 for step 1
|
||||
self.assert_step_is_not_done(self.onboarding_1_step_1)
|
||||
# But step 2 is done
|
||||
self.assert_step_is_done(self.onboarding_1_step_2)
|
||||
self.assert_onboarding_is_not_done(self.onboarding_1)
|
||||
|
||||
# But no change for company 1
|
||||
self.assert_step_is_done(self.onboarding_1_step_1.with_company(self.company_1))
|
||||
self.assert_onboarding_is_done(self.onboarding_1.with_company(self.company_1))
|
||||
|
||||
self.onboarding_1_step_1.action_set_just_done()
|
||||
self.assert_step_is_done(self.onboarding_1_step_1)
|
||||
self.assert_onboarding_is_not_done(self.onboarding_1)
|
||||
self.onboarding_1_step_2.with_company(self.company_2).action_set_just_done()
|
||||
self.assert_step_is_done(self.onboarding_1_step_2)
|
||||
self.assert_onboarding_is_done(self.onboarding_1)
|
||||
|
||||
# is_onboarding_closed status is also company-independent
|
||||
@@ -122,21 +123,32 @@ class TestOnboarding(TestOnboardingCommon):
|
||||
self.assertFalse(self.onboarding_1.with_company(self.company_1).current_progress_id.is_onboarding_closed)
|
||||
|
||||
def test_onboarding_to_company_change(self):
|
||||
"""Checks that changing onboarding to per-company resets completions states.
|
||||
"""
|
||||
""" Checks that changing an onboarding step to per-company resets
|
||||
completion states."""
|
||||
# Completing onboarding as company_1
|
||||
self.assertEqual(self.env.company, self.company_1)
|
||||
self.onboarding_1_step_1.action_set_just_done()
|
||||
self.onboarding_1_step_2.action_set_just_done()
|
||||
self.assert_onboarding_is_done(self.onboarding_1)
|
||||
|
||||
# Updating onboarding to per-company
|
||||
self.onboarding_1.is_per_company = True
|
||||
# Updating onboarding step 1 to per-company
|
||||
self.onboarding_1_step_1.is_per_company = True
|
||||
self.assertTrue(self.onboarding_1.is_per_company)
|
||||
# Required after progress reset (simulate role of controller)
|
||||
self.onboarding_1._search_or_create_progress()
|
||||
|
||||
self.assert_onboarding_is_not_done(self.onboarding_1)
|
||||
|
||||
def test_onboarding_shared_steps(self):
|
||||
self.onboarding_2_step_2.action_set_just_done()
|
||||
self.assert_step_is_done(self.onboarding_2_step_2)
|
||||
# Completing common step is also required to be "done"
|
||||
self.assert_onboarding_is_not_done(self.onboarding_2)
|
||||
|
||||
self.onboarding_1_step_1.action_set_just_done()
|
||||
self.assert_onboarding_is_not_done(self.onboarding_1)
|
||||
self.assert_onboarding_is_done(self.onboarding_2)
|
||||
|
||||
@mute_logger('odoo.sql_db')
|
||||
def test_progress_no_company_uniqueness(self):
|
||||
"""Check that there cannot be two progress records created for
|
||||
@@ -159,8 +171,8 @@ class TestOnboarding(TestOnboardingCommon):
|
||||
See also ``test_progress_no_company_uniqueness``
|
||||
"""
|
||||
# Updating onboarding to per-company
|
||||
self.onboarding_1.is_per_company = True
|
||||
# Required after progress reset (simulate role of controller)
|
||||
self.onboarding_1_step_1.is_per_company = True
|
||||
# Create an onboarding_progress (simulate role of controller)
|
||||
self.onboarding_1._search_or_create_progress()
|
||||
|
||||
with self.assertRaises(IntegrityError):
|
||||
@@ -168,3 +180,38 @@ class TestOnboarding(TestOnboardingCommon):
|
||||
'onboarding_id': self.onboarding_1.id,
|
||||
'company_id': self.env.company.id
|
||||
})
|
||||
|
||||
def test_onboarding_step_without_onboarding(self):
|
||||
self.step_initially_w_o_onboarding = self.env['onboarding.onboarding.step'].create({
|
||||
'title': 'Step Initially Without Onboarding',
|
||||
})
|
||||
self.assertEqual(self.step_initially_w_o_onboarding.current_step_state, 'not_done')
|
||||
self.step_initially_w_o_onboarding.action_set_just_done()
|
||||
|
||||
self.assert_step_is_done(self.step_initially_w_o_onboarding)
|
||||
|
||||
self.onboarding_3 = self.env['onboarding.onboarding'].create({
|
||||
'name': 'Test Onboarding 3',
|
||||
'route_name': 'onboarding3',
|
||||
})
|
||||
self.onboarding_3._search_or_create_progress()
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
self.step_initially_w_o_onboarding.onboarding_ids = [Command.link(self.onboarding_3.id)]
|
||||
|
||||
self.step_initially_w_o_onboarding.write({
|
||||
'panel_step_open_action_name': 'action_fake_open_onboarding_step'
|
||||
})
|
||||
self.step_initially_w_o_onboarding.onboarding_ids = [Command.link(self.onboarding_3.id)]
|
||||
|
||||
with self.subTest('Progress records are recreated for companies with completed steps'):
|
||||
# Onboarding is done as only step was already done by company 1
|
||||
self.assert_onboarding_is_done(self.onboarding_3)
|
||||
|
||||
# Not by company 2
|
||||
self.onboarding_3.with_company(self.company_2)._search_or_create_progress()
|
||||
self.assert_onboarding_is_not_done(self.onboarding_3.with_company(self.company_2))
|
||||
|
||||
# But it can
|
||||
self.step_initially_w_o_onboarding.with_company(self.company_2).action_set_just_done()
|
||||
self.assert_onboarding_is_done(self.onboarding_3.with_company(self.company_2))
|
||||
|
||||
+28
-48
@@ -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>
|
||||
@@ -36,7 +36,9 @@
|
||||
</group>
|
||||
<notebook>
|
||||
<page name="Onboarding steps">
|
||||
<field name="step_ids"/>
|
||||
<group string="Steps">
|
||||
<field name="step_ids" nolabel="1" colspan="2"/>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
@@ -51,8 +53,9 @@
|
||||
<tree string="Onboarding Steps">
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="title"/>
|
||||
<field name="onboarding_id" optional="hide"/>
|
||||
<field name="onboarding_ids" optional="hide"/>
|
||||
<field name="current_step_state"/>
|
||||
<field name="is_per_company" optional="hide"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
@@ -65,10 +68,16 @@
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="title"/>
|
||||
<field name="onboarding_id"/>
|
||||
<field name="current_step_state"/>
|
||||
<field name="panel_step_open_action_name"/>
|
||||
<field name="is_per_company"/>
|
||||
</group>
|
||||
<notebook>
|
||||
<page name="Onboardings using this step">
|
||||
<group string="Onboardings">
|
||||
<field name="onboarding_ids" nolabel="1" colspan="2"/>
|
||||
</group>
|
||||
</page>
|
||||
<page name="Step rendering">
|
||||
<group>
|
||||
<field name="description"/>
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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>
|
||||
@@ -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")
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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,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 |
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
@@ -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'})
|
||||
|
||||
@@ -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,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.
|
||||
|
||||
|
||||
@@ -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': [
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)),
|
||||
|
||||
Reference in New Issue
Block a user