summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoreco-odoo <eco@odoo.com>2018-05-28 12:35:54 +0200
committerYannick Tivisse <yti@odoo.com>2018-07-20 11:59:12 +0200
commitd1ec3306b8a75f8b961192ef5cf39b6f454f3792 (patch)
tree61e675ac67ef45bcce916f95584392d73df953e8
parent9744fecd058c707687a743c40fa4748f182a6275 (diff)
[ADD] account: onboarding panel for invoicing
Add an onboarding panel for invoicing, it appears above the invoice list. After going through the steps the system should be all set to send real invoices. In the payment module, add a payment acquirer step and add it to the invoice onboarding panel. task: 60668
-rw-r--r--addons/account/__manifest__.py1
-rw-r--r--addons/account/controllers/__init__.py1
-rw-r--r--addons/account/controllers/onboarding.py21
-rw-r--r--addons/account/models/account_invoice.py1
-rw-r--r--addons/account/models/company.py112
-rw-r--r--addons/account/static/src/img/account_invoice_onboarding_bg.jpgbin0 -> 23728 bytes
-rw-r--r--addons/account/views/account_invoice_view.xml15
-rw-r--r--addons/account/views/account_onboarding_templates.xml82
-rw-r--r--addons/mail/wizard/mail_compose_message_view.xml2
-rw-r--r--addons/payment/__init__.py3
-rw-r--r--addons/payment/__manifest__.py1
-rw-r--r--addons/payment/models/__init__.py1
-rw-r--r--addons/payment/models/res_company.py15
-rw-r--r--addons/payment/views/payment_acquirer_onboarding_templates.xml135
-rw-r--r--addons/payment/wizards/__init__.py1
-rw-r--r--addons/payment/wizards/payment_acquirer_onboarding_wizard.py127
16 files changed, 513 insertions, 5 deletions
diff --git a/addons/account/__manifest__.py b/addons/account/__manifest__.py
index 9654e47f01e..c65eed84e2f 100644
--- a/addons/account/__manifest__.py
+++ b/addons/account/__manifest__.py
@@ -63,6 +63,7 @@ Core mechanisms for the accounting modules. To display the menuitems, install th
'data/payment_receipt_data.xml',
'wizard/setup_wizards_view.xml',
'views/account_dashboard_setup_bar.xml',
+ 'views/account_onboarding_templates.xml',
'wizard/account_report_tax_view.xml',
'views/report_tax.xml',
'views/res_users_views.xml',
diff --git a/addons/account/controllers/__init__.py b/addons/account/controllers/__init__.py
index e5a6112c48f..6d24304de46 100644
--- a/addons/account/controllers/__init__.py
+++ b/addons/account/controllers/__init__.py
@@ -2,4 +2,5 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import mail
+from . import onboarding
from . import portal
diff --git a/addons/account/controllers/onboarding.py b/addons/account/controllers/onboarding.py
new file mode 100644
index 00000000000..ee51395add8
--- /dev/null
+++ b/addons/account/controllers/onboarding.py
@@ -0,0 +1,21 @@
+from odoo import http
+from odoo.http import request
+
+
+class OnboardingController(http.Controller):
+
+ @http.route('/account/account_invoice_onboarding', auth='user', type='json')
+ def account_invoice_onboarding(self):
+ """ Returns the `banner` for the account invoice onboarding panel.
+ It can be empty if the user has closed it or if he doesn't have
+ the permission to see it. """
+
+ if not request.env.user._is_admin() or \
+ request.env.user.company_id.account_invoice_onboarding_closed:
+ return {}
+
+ return {
+ 'html': request.env.ref('account.account_invoice_onboarding_panel').render({
+ 'company': request.env.user.company_id,
+ })
+ }
diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py
index 190640cb34d..a38e33ebac1 100644
--- a/addons/account/models/account_invoice.py
+++ b/addons/account/models/account_invoice.py
@@ -621,6 +621,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
return super(AccountInvoice, self.with_context(mail_post_autofollow=True)).message_post(**kwargs)
@api.model
diff --git a/addons/account/models/company.py b/addons/account/models/company.py
index 65162b07490..1206cd9ccd9 100644
--- a/addons/account/models/company.py
+++ b/addons/account/models/company.py
@@ -6,7 +6,7 @@ import time
from dateutil.relativedelta import relativedelta
from odoo import fields, models, api, _
-from odoo.exceptions import ValidationError, UserError
+from odoo.exceptions import ValidationError, UserError, RedirectWarning
from odoo.tools.misc import DEFAULT_SERVER_DATE_FORMAT
from odoo.tools.float_utils import float_round, float_is_zero
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT, date_utils
@@ -67,6 +67,19 @@ Best Regards,'''))
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)
+
@api.multi
def _check_lock_dates(self, vals):
'''Check the lock dates for the current companies. This can't be done in a api.constrains because we need
@@ -398,3 +411,100 @@ Best Regards,'''))
'debit': credit_diff,
'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_open_account_onboarding_invoice_layout(self):
+ """ Onboarding step for the invoice layout. """
+ action = self.env.ref('account.action_open_account_onboarding_invoice_layout').read()[0]
+ action['res_id'] = self.env.user.company_id.id
+ return action
+
+ @api.model
+ def _get_sample_invoice(self):
+ """ Get a sample invoice or create one if it does not exist. """
+ # use current user as partner
+ partner = self.env.user.partner_id
+
+ company_id = self.env.user.company_id.id
+ # try to find an existing sample invoice
+ sample_invoice = self.env['account.invoice'].search(
+ [('company_id', '=', company_id),
+ ('partner_id', '=', partner.id)], limit=1)
+
+ 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"))
+
+ journal = self.env['account.journal'].search([('company_id', '=', company_id)], limit=1)
+ if len(journal) == 0:
+ action = self.env.ref('account.action_account_journal_form')
+ msg = _("We cannot find any journal for this company. You should create one."
+ "\nPlease go to Configuration > Journals.")
+ raise RedirectWarning(msg, action.id, _("Go to the journal configuration"))
+
+ sample_invoice = self.env['account.invoice'].create({
+ 'name': _("Sample invoice"),
+ 'journal_id': journal.id,
+ 'partner_id': partner.id,
+ })
+ # sample invoice lines
+ self.env['account.invoice.line'].create({
+ 'name': _("Sample invoice line name"),
+ 'invoice_id': sample_invoice.id,
+ 'account_id': account.id,
+ 'price_unit': 199.99,
+ 'quantity': 2,
+ })
+ self.env['account.invoice.line'].create({
+ 'name': _("Sample invoice line name 2"),
+ 'invoice_id': sample_invoice.id,
+ 'account_id': account.id,
+ 'price_unit': 25,
+ 'quantity': 1,
+ })
+ return sample_invoice
+
+ @api.model
+ def action_open_account_onboarding_sample_invoice(self):
+ """ Onboarding step for sending a sample invoice. Open a window to compose an email,
+ with the edi_invoice_template message loaded by default. """
+ sample_invoice = self._get_sample_invoice()
+ template = self.env.ref('account.email_template_edi_invoice', False)
+ action = self.env.ref('account.action_open_account_onboarding_sample_invoice').read()[0]
+ action['context'] = {
+ 'default_res_id': sample_invoice.id,
+ 'default_use_template': bool(template),
+ 'default_template_id': template and template.id or False,
+ 'default_model': 'account.invoice',
+ 'default_composition_mode': 'comment',
+ 'mark_invoice_as_sent': True,
+ 'custom_layout': 'mail.mail_notification_borders',
+ 'force_email': True,
+ }
+ return action
diff --git a/addons/account/static/src/img/account_invoice_onboarding_bg.jpg b/addons/account/static/src/img/account_invoice_onboarding_bg.jpg
new file mode 100644
index 00000000000..7b7e1a524d5
--- /dev/null
+++ b/addons/account/static/src/img/account_invoice_onboarding_bg.jpg
Binary files differ
diff --git a/addons/account/views/account_invoice_view.xml b/addons/account/views/account_invoice_view.xml
index 147e0d56ac7..e6e57606a70 100644
--- a/addons/account/views/account_invoice_view.xml
+++ b/addons/account/views/account_invoice_view.xml
@@ -167,6 +167,18 @@
</field>
</record>
+ <record id="invoice_tree_with_onboarding" model="ir.ui.view">
+ <field name="name">account.invoice.tree.with.onboarding</field>
+ <field name="model">account.invoice</field>
+ <field name="inherit_id" ref="invoice_tree"/>
+ <field name="mode">primary</field>
+ <field name="arch" type="xml">
+ <xpath expr="//tree" position="attributes">
+ <attribute name="banner_route">/account/account_invoice_onboarding</attribute>
+ </xpath>
+ </field>
+ </record>
+
<record model="ir.ui.view" id="invoice_kanban">
<field name="name">account.invoice.kanban</field>
<field name="model">account.invoice</field>
@@ -727,11 +739,10 @@
</field>
</record>
-
<record id="action_invoice_tree1_view1" model="ir.actions.act_window.view">
<field eval="1" name="sequence"/>
<field name="view_mode">tree</field>
- <field name="view_id" ref="invoice_tree"/>
+ <field name="view_id" ref="invoice_tree_with_onboarding"/>
<field name="act_window_id" ref="action_invoice_tree1"/>
</record>
diff --git a/addons/account/views/account_onboarding_templates.xml b/addons/account/views/account_onboarding_templates.xml
new file mode 100644
index 00000000000..f094a8a5bb9
--- /dev/null
+++ b/addons/account/views/account_onboarding_templates.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <!-- ONBOARDING STEPS -->
+ <template id="onboarding_invoice_layout_step">
+ <t t-call="base.onboarding_step">
+ <t t-set="title">Invoice Layout</t>
+ <t t-set="description">Create and send professional looking invoices.</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_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>
+ </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 t-set="btn_text">I'm ready</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>
+ </template>
+ <!-- ONBOARDING PANEL-->
+ <template id="account_invoice_onboarding_panel" name="account.invoice.onboarding.panel">
+ <t t-call="base.onboarding_container">
+ <t t-set="classes">o_onboarding_blue</t>
+ <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-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" />
+ </t>
+ </template>
+ <!-- INVOICE FORM -->
+ <record id="action_open_account_onboarding_invoice_layout" model="ir.actions.act_window">
+ <field name="name">Configure your document template</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="target">new</field>
+ </record>
+ <!-- SAMPLE INVOICE EMAIL -->
+ <record model="ir.ui.view" id="email_compose_onboarding_sample_invoice">
+ <field name="name">mail.compose.message.form.account_invoice_onboarding_sample_invoice
+ </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_account_onboarding_sample_invoice" model="ir.actions.act_window">
+ <field name="name">Send a sample invoice</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="email_compose_onboarding_sample_invoice" />
+ <field name="target">new</field>
+ </record>
+</odoo>
diff --git a/addons/mail/wizard/mail_compose_message_view.xml b/addons/mail/wizard/mail_compose_message_view.xml
index 5d3a7a910dd..9c349c36bc2 100644
--- a/addons/mail/wizard/mail_compose_message_view.xml
+++ b/addons/mail/wizard/mail_compose_message_view.xml
@@ -52,7 +52,7 @@
<span attrs="{'invisible': [('use_active_domain', '=', True)]}">the selected records</span>
<span attrs="{'invisible': [('use_active_domain', '=', False)]}">the current search filter</span>.
</span>
- <span attrs="{'invisible':['|', ('model', '=', False), ('composition_mode', '=', 'mass_mail')]}">Followers of the document and</span>
+ <span name="document_followers_text" attrs="{'invisible':['|', ('model', '=', False), ('composition_mode', '=', 'mass_mail')]}">Followers of the document and</span>
<field name="partner_ids" widget="many2many_tags_email" placeholder="Add contacts to notify..."
context="{'force_email':True, 'show_email':True}"
attrs="{'invisible': [('composition_mode', '!=', 'comment')]}"/>
diff --git a/addons/payment/__init__.py b/addons/payment/__init__.py
index b75bfd8cfe4..4a05b41804c 100644
--- a/addons/payment/__init__.py
+++ b/addons/payment/__init__.py
@@ -2,4 +2,5 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import models
-from . import controllers \ No newline at end of file
+from . import controllers
+from . import wizards
diff --git a/addons/payment/__manifest__.py b/addons/payment/__manifest__.py
index 182e3667fc7..2df0e234ab5 100644
--- a/addons/payment/__manifest__.py
+++ b/addons/payment/__manifest__.py
@@ -14,6 +14,7 @@
'views/payment_views.xml',
'views/account_payment_views.xml',
'views/account_invoice_views.xml',
+ 'views/payment_acquirer_onboarding_templates.xml',
'views/payment_templates.xml',
'views/payment_portal_templates.xml',
'views/assets.xml',
diff --git a/addons/payment/models/__init__.py b/addons/payment/models/__init__.py
index 9758f10d9c9..93f5083aca8 100644
--- a/addons/payment/models/__init__.py
+++ b/addons/payment/models/__init__.py
@@ -5,3 +5,4 @@ from . import account_invoice
from . import res_partner
from . import account_payment
from . import chart_template
+from . import res_company
diff --git a/addons/payment/models/res_company.py b/addons/payment/models/res_company.py
new file mode 100644
index 00000000000..c2b52486563
--- /dev/null
+++ b/addons/payment/models/res_company.py
@@ -0,0 +1,15 @@
+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)
+
+ @api.model
+ def action_open_payment_onboarding_payment_acquirer(self):
+ """ Called by onboarding panel above the customer invoice list."""
+ action = self.env.ref('payment.action_open_payment_onboarding_payment_acquirer_wizard').read()[0]
+ return action
diff --git a/addons/payment/views/payment_acquirer_onboarding_templates.xml b/addons/payment/views/payment_acquirer_onboarding_templates.xml
new file mode 100644
index 00000000000..f2d0274a11c
--- /dev/null
+++ b/addons/payment/views/payment_acquirer_onboarding_templates.xml
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <!-- onboarding step -->
+ <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="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>
+ </template>
+ <!-- add it in the account invoice onboarding panel -->
+ <template id="account_invoice_payment_acquirer_onboarding"
+ name="account invoice payment acquirer onboarding"
+ inherit_id="account.account_invoice_onboarding_panel">
+ <xpath expr="//t[@name='sample_invoice_step']" position="before">
+ <t t-call="payment.onboarding_payment_acquirer_step" name="payment_acquirer_step" />
+ </xpath>
+ </template>
+ <!--PAYMENT ACQUIRER-->
+ <record model="ir.ui.view" id="payment_acquirer_onboarding_wizard_form">
+ <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-xs-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="col-xs-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-xs-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="col-xs-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-xs-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="col-xs-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>
+ </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"
+ type="object" />
+ <button special="cancel" string="Cancel" />
+ </footer>
+ </form>
+ </field>
+ </record>
+ <record id="action_open_payment_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">payment.acquirer.onboarding.wizard</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">form</field>
+ <field name="view_id" ref="payment.payment_acquirer_onboarding_wizard_form" />
+ <field name="target">new</field>
+ </record>
+</odoo>
diff --git a/addons/payment/wizards/__init__.py b/addons/payment/wizards/__init__.py
new file mode 100644
index 00000000000..84b24dc0131
--- /dev/null
+++ b/addons/payment/wizards/__init__.py
@@ -0,0 +1 @@
+from . import payment_acquirer_onboarding_wizard
diff --git a/addons/payment/wizards/payment_acquirer_onboarding_wizard.py b/addons/payment/wizards/payment_acquirer_onboarding_wizard.py
new file mode 100644
index 00000000000..679facc45a9
--- /dev/null
+++ b/addons/payment/wizards/payment_acquirer_onboarding_wizard.py
@@ -0,0 +1,127 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, models, fields, _
+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'))
+
+ 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'))
+
+ _payment_acquirer_onboarding_cache = {}
+ _data_fetched = False
+
+ def _get_manual_payment(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)
+
+ def _get_default_payment_acquirer_onboarding_value(self, key):
+ if not self.env.user._is_admin():
+ raise UserError(_('Only administators can access this data.'))
+
+ if self._data_fetched:
+ return self._payment_acquirer_onboarding_cache.get(key, '')
+
+ self._data_fetched = True
+
+ 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']
+ self._payment_acquirer_onboarding_cache['manual_post_msg'] = manual_payment['post_msg']
+
+ return self._payment_acquirer_onboarding_cache.get(key, '')
+
+ def _install_module(self, module_name):
+ module = self.env['ir.module.module'].sudo().search([('name', '=', module_name)])
+ 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:
+ self._install_module('payment_paypal')
+
+ if self.stripe_checked is True:
+ self._install_module('payment_stripe')
+
+ if self.paypal_checked is True or self.stripe_checked is True or self.manual_checked is True:
+
+ # the wizard is inherited in `sale`
+ self._hook_on_save_onboarding_payment_acquirer()
+
+ # 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:
+ 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:
+ new_env.ref('payment.payment_acquirer_stripe').write({
+ 'stripe_secret_key': self.stripe_secret_key,
+ 'stripe_publishable_key': self.stripe_publishable_key,
+ 'website_published': True,
+ })
+ if self.manual_checked is True:
+ self._get_manual_payment(new_env).write({
+ 'name': self.manual_name,
+ 'post_msg': self.manual_post_msg,
+ 'website_published': True,
+ })
+ self._set_payment_acquirer_onboarding_step_done()
+ # delete wizard data immediately to get rid of residual credentials
+ self.unlink()
+
+ return {'type': 'ir.actions.act_window_close'}
+
+ @api.multi
+ def action_skip_payment_onboarding_payment_acquirer_step(self):
+ 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})