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