diff --git a/addons/account/__manifest__.py b/addons/account/__manifest__.py
index fdb296eb7b1..fd56de27e03 100644
--- a/addons/account/__manifest__.py
+++ b/addons/account/__manifest__.py
@@ -6,11 +6,7 @@
'summary': 'Send Invoices and Track Payments',
'sequence': 30,
'description': """
-Invoicing & Payments
-====================
-The specific and easy-to-use Invoicing system in Odoo allows you to keep track of your accounting, even when you are not an accountant. It provides an easy way to follow up on your vendors and customers.
-
-You could use this simplified accounting in case you work with an (external) account to keep your books, and you still want to keep track of payments. This module also offers you an easy method of registering payments, without having to encode complete abstracts of account.
+Core mechanisms for the accounting modules. To display the menuitems, install the module account_invoicing.
""",
'category': 'Accounting',
'website': 'https://www.odoo.com/page/billing',
@@ -75,7 +71,7 @@ You could use this simplified accounting in case you work with an (external) acc
"static/src/xml/account_report_backend.xml",
],
'installable': True,
- 'application': True,
+ 'application': False,
'auto_install': False,
'post_init_hook': '_auto_install_l10n',
}
diff --git a/addons/account/views/account_menuitem.xml b/addons/account/views/account_menuitem.xml
index af0457d9b08..92e59ad21e1 100644
--- a/addons/account/views/account_menuitem.xml
+++ b/addons/account/views/account_menuitem.xml
@@ -6,9 +6,13 @@
+
+
+
+
diff --git a/addons/account/views/product_view.xml b/addons/account/views/product_view.xml
index 2cc9b5a67b6..f8979bd45da 100644
--- a/addons/account/views/product_view.xml
+++ b/addons/account/views/product_view.xml
@@ -24,7 +24,7 @@
-
+
diff --git a/addons/account_accountant/__manifest__.py b/addons/account_accountant/__manifest__.py
index f8af161bd47..d9f442828b4 100644
--- a/addons/account_accountant/__manifest__.py
+++ b/addons/account_accountant/__manifest__.py
@@ -14,7 +14,7 @@ It gives the Administrator user access to all accounting features such as journa
It assigns manager and user access rights to the Administrator for the accounting application and only user rights to the Demo user.
""",
'website': 'https://www.odoo.com/page/accounting',
- 'depends': ['account', 'web_tour'],
+ 'depends': ['account_invoicing', 'web_tour'],
'data': [
'data/account_accountant_data.xml',
'security/account_accountant_security.xml',
diff --git a/addons/account_accountant/data/account_accountant_data.xml b/addons/account_accountant/data/account_accountant_data.xml
index 11970da9d7d..8bc3efb8d40 100644
--- a/addons/account_accountant/data/account_accountant_data.xml
+++ b/addons/account_accountant/data/account_accountant_data.xml
@@ -3,7 +3,7 @@
diff --git a/addons/account_invoicing/__init__.py b/addons/account_invoicing/__init__.py
new file mode 100644
index 00000000000..67dee8c60db
--- /dev/null
+++ b/addons/account_invoicing/__init__.py
@@ -0,0 +1,2 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
diff --git a/addons/account_invoicing/__manifest__.py b/addons/account_invoicing/__manifest__.py
new file mode 100644
index 00000000000..34df30dd845
--- /dev/null
+++ b/addons/account_invoicing/__manifest__.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+{
+ 'name': 'Invoicing Management',
+ 'version': '1.0',
+ 'summary': 'Send Invoices and Track Payments',
+ 'sequence': 30,
+ 'description': """
+Invoicing & Payments
+====================
+The specific and easy-to-use Invoicing system in Odoo allows you to keep track of your accounting, even when you are not an accountant. It provides an easy way to follow up on your vendors and customers.
+
+You could use this simplified accounting in case you work with an (external) account to keep your books, and you still want to keep track of payments. This module also offers you an easy method of registering payments, without having to encode complete abstracts of account.
+ """,
+ 'category': 'Invoicing Management',
+ 'website': 'https://www.odoo.com/page/billing',
+ 'depends': ['account'],
+ 'data': [
+ 'views/account_menuitem_views.xml',
+ 'views/product_template_views.xml',
+ ],
+ 'demo': [
+ ],
+ 'qweb': [
+ ],
+ 'application': True,
+}
diff --git a/addons/account/static/description/account_sc_00.png b/addons/account_invoicing/static/description/account_sc_00.png
similarity index 100%
rename from addons/account/static/description/account_sc_00.png
rename to addons/account_invoicing/static/description/account_sc_00.png
diff --git a/addons/account/static/description/account_sc_01.png b/addons/account_invoicing/static/description/account_sc_01.png
similarity index 100%
rename from addons/account/static/description/account_sc_01.png
rename to addons/account_invoicing/static/description/account_sc_01.png
diff --git a/addons/account/static/description/account_sc_03.png b/addons/account_invoicing/static/description/account_sc_03.png
similarity index 100%
rename from addons/account/static/description/account_sc_03.png
rename to addons/account_invoicing/static/description/account_sc_03.png
diff --git a/addons/account/static/description/account_voucher.png b/addons/account_invoicing/static/description/account_voucher.png
similarity index 100%
rename from addons/account/static/description/account_voucher.png
rename to addons/account_invoicing/static/description/account_voucher.png
diff --git a/addons/account/static/description/analysis.png b/addons/account_invoicing/static/description/analysis.png
similarity index 100%
rename from addons/account/static/description/analysis.png
rename to addons/account_invoicing/static/description/analysis.png
diff --git a/addons/account/static/description/icon.png b/addons/account_invoicing/static/description/icon.png
similarity index 100%
rename from addons/account/static/description/icon.png
rename to addons/account_invoicing/static/description/icon.png
diff --git a/addons/account/static/description/index.html b/addons/account_invoicing/static/description/index.html
similarity index 100%
rename from addons/account/static/description/index.html
rename to addons/account_invoicing/static/description/index.html
diff --git a/addons/account_invoicing/views/account_menuitem_views.xml b/addons/account_invoicing/views/account_menuitem_views.xml
new file mode 100644
index 00000000000..947b92fe033
--- /dev/null
+++ b/addons/account_invoicing/views/account_menuitem_views.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/addons/account_invoicing/views/product_template_views.xml b/addons/account_invoicing/views/product_template_views.xml
new file mode 100644
index 00000000000..b9ce26f3f36
--- /dev/null
+++ b/addons/account_invoicing/views/product_template_views.xml
@@ -0,0 +1,13 @@
+
+
+
+ product.template.invoice.policy
+ product.template
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/addons/payment/data/payment_acquirer_data.xml b/addons/payment/data/payment_acquirer_data.xml
index 0271aebdd52..5908c6e5366 100644
--- a/addons/payment/data/payment_acquirer_data.xml
+++ b/addons/payment/data/payment_acquirer_data.xml
@@ -155,13 +155,12 @@
Custom
- none
- A generic payment method to send payment instructions to any payment acquirer.
+ A generic payment acquirer to send payment instructions to any payment acquirer.
Online Payment
diff --git a/addons/payment/models/account_payment.py b/addons/payment/models/account_payment.py
index 60ddcc60b4d..5a15e71e6e5 100644
--- a/addons/payment/models/account_payment.py
+++ b/addons/payment/models/account_payment.py
@@ -10,7 +10,7 @@ class AccountPayment(models.Model):
_inherit = 'account.payment'
payment_transaction_id = fields.Many2one('payment.transaction', string="Payment Transaction")
- payment_token_id = fields.Many2one('payment.token', string="Saved payment token", domain=[('acquirer_id.auto_confirm', '!=', 'authorize')],
+ payment_token_id = fields.Many2one('payment.token', string="Saved payment token", domain=[('acquirer_id.capture_manually', '=', False)],
help="Note that tokens from acquirers set to only authorize transactions (instead of capturing the amount) are not available.")
@api.onchange('partner_id')
@@ -18,14 +18,14 @@ class AccountPayment(models.Model):
res = {}
if self.partner_id:
partners = self.partner_id | self.partner_id.commercial_partner_id | self.partner_id.commercial_partner_id.child_ids
- res['domain'] = {'payment_token_id': [('partner_id', 'in', partners.ids), ('acquirer_id.auto_confirm', '!=', 'authorize')]}
+ res['domain'] = {'payment_token_id': [('partner_id', 'in', partners.ids), ('acquirer_id.capture_manually', '=', False)]}
return res
@api.onchange('payment_method_id', 'journal_id')
def _onchange_payment_method(self):
if self.payment_method_code == 'electronic':
- self.payment_token_id = self.env['payment.token'].search([('partner_id', '=', self.partner_id.id), ('acquirer_id.auto_confirm', '!=', 'authorize')], limit=1)
+ self.payment_token_id = self.env['payment.token'].search([('partner_id', '=', self.partner_id.id), ('acquirer_id.capture_manually', '=', False)], limit=1)
else:
self.payment_token_id = False
@@ -38,7 +38,7 @@ class AccountPayment(models.Model):
return account_payment
def _do_payment(self):
- if self.payment_token_id.acquirer_id.auto_confirm == 'authorize':
+ if self.payment_token_id.acquirer_id.capture_manually:
raise ValidationError(_('This feature is not available for payment acquirers set to the "Authorize" mode.\n'
'Please use a token from another provider than %s.') % self.payment_token_id.acquirer_id.name)
reference = "P-%s-%s" % (self.id, datetime.datetime.now().strftime('%y%m%d_%H%M%S'))
diff --git a/addons/payment/models/payment_acquirer.py b/addons/payment/models/payment_acquirer.py
index 92f0da0bc74..bbb054a7a0b 100644
--- a/addons/payment/models/payment_acquirer.py
+++ b/addons/payment/models/payment_acquirer.py
@@ -71,15 +71,22 @@ class PaymentAcquirer(models.Model):
website_published = fields.Boolean(
'Visible in Portal / Website', copy=False,
help="Make this payment acquirer available (Customer invoices, etc.)")
- auto_confirm = fields.Selection([
- ('none', 'No automatic confirmation'),
- ('authorize', 'Authorize the amount and confirm the order on acquirer confirmation (capture manually)'),
- ('confirm_so', 'Authorize & capture the amount and confirm the order on acquirer confirmation'),
- ('generate_and_pay_invoice', 'Authorize & capture the amount, confirm the order and auto-validate the invoice on acquirer confirmation')],
- string='Order Confirmation', default='confirm_so', required=True)
+ # Formerly associated to `authorize` option from auto_confirm
+ capture_manually = fields.Boolean(string="Capture Amount Manually",
+ help="Capture the amount from Odoo, when the delivery is completed.")
+ # Formerly associated to `generate_and_pay_invoice` option from auto_confirm
journal_id = fields.Many2one(
- 'account.journal', 'Payment Journal',
- help="Account journal used for automatic payment reconciliation.")
+ 'account.journal', 'Payment Journal', domain=[('type', '=', 'bank')],
+ default=lambda self: self.env['account.journal'].search([('type', '=', 'bank')], limit=1),
+ help="""Payments will be registered into this journal. If you get paid straight on your bank account,
+ select your bank account. If you get paid in batch for several transactions, create a specific
+ payment journal for this payment acquirer to easily manage the bank reconciliation. You hold
+ the amount in a temporary transfer account of your books (created automatically when you create
+ the payment journal). Then when you get paid on your bank account by the payment acquirer, you
+ reconcile the bank statement line with this temporary transfer account. Use reconciliation
+ templates to do it in one-click.""")
+ specific_countries = fields.Boolean(string="Specific Countries",
+ help="If you leave it empty, the payment acquirer will be available for all the countries.")
country_ids = fields.Many2many(
'res.country', 'payment_country_rel',
'payment_id', 'country_id', 'Countries',
@@ -116,7 +123,7 @@ class PaymentAcquirer(models.Model):
"If you manage subscriptions (recurring invoicing), you need it to automatically charge the customer when you "
"issue an invoice.")
token_implemented = fields.Boolean('Saving Card Data supported', compute='_compute_feature_support')
-
+ authorize_implemented = fields.Boolean('Authorize Mechanism Supported', compute='_compute_feature_support')
fees_implemented = fields.Boolean('Fees Computation Supported', compute='_compute_feature_support')
fees_active = fields.Boolean('Add Extra Fees')
fees_dom_fixed = fields.Float('Fixed domestic fees')
@@ -142,11 +149,11 @@ class PaymentAcquirer(models.Model):
"resized as a 64x64px image, with aspect ratio preserved. "
"Use this field anywhere a small image is required.")
- @api.multi
def _compute_feature_support(self):
feature_support = self._get_feature_support()
for acquirer in self:
acquirer.fees_implemented = acquirer.provider in feature_support['fees']
+ acquirer.authorize_implemented = acquirer.provider in feature_support['authorize']
acquirer.token_implemented = acquirer.provider in feature_support['tokenize']
@api.multi
@@ -158,13 +165,6 @@ class PaymentAcquirer(models.Model):
return False
return True
- @api.constrains('auto_confirm')
- def _check_authorization_support(self):
- for acquirer in self:
- if acquirer.auto_confirm == 'authorize' and acquirer.provider not in self._get_feature_support()['authorize']:
- raise ValidationError(_('You cannot capture payments manually with this payment method. Please choose another Order Confirmation mode (in Configuration tab).'))
- return True
-
_constraints = [
(_check_required_if_provider, 'Required fields not filled', []),
]
@@ -185,13 +185,22 @@ class PaymentAcquirer(models.Model):
@api.model
def create(self, vals):
image_resize_images(vals)
+ vals = self._check_journal_id(vals)
return super(PaymentAcquirer, self).create(vals)
@api.multi
def write(self, vals):
image_resize_images(vals)
+ vals = self._check_journal_id(vals)
return super(PaymentAcquirer, self).write(vals)
+ def _check_journal_id(self, vals):
+ if not vals.get('journal_id', False):
+ default_journal = self.env['account.journal'].search([('type', '=', 'bank')], limit=1)
+ if default_journal:
+ vals.update({'journal_id': default_journal.id})
+ return vals
+
@api.multi
def toggle_website_published(self):
self.write({'website_published': not self.website_published})
diff --git a/addons/payment/views/payment_views.xml b/addons/payment/views/payment_views.xml
index 1a3f60e47cb..deaf1dee0bd 100644
--- a/addons/payment/views/payment_views.xml
+++ b/addons/payment/views/payment_views.xml
@@ -8,16 +8,6 @@
id='root_payment_menu'
parent='account.menu_finance_configuration'/>
-
-
- Payment Transactions
- payment.transaction
- form
- tree,form
- [('acquirer_id','=',active_id)]
- {'default_acquirer_id': active_id}
-
-
payment.acquirer.formpayment.acquirer
@@ -26,16 +16,11 @@
+
diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py
index 8af8e148a9e..ae104629215 100644
--- a/addons/website_sale/controllers/main.py
+++ b/addons/website_sale/controllers/main.py
@@ -768,7 +768,7 @@ class WebsiteSale(http.Controller):
tx.partner_id == tx.sale_order_id.partner_id):
try:
s2s_result = tx.s2s_do_transaction()
- valid_state = 'authorized' if tx.acquirer_id.auto_confirm == 'authorize' else 'done'
+ valid_state = 'authorized' if tx.acquirer_id.capture_manually else 'done'
if not s2s_result or tx.state != valid_state:
return dict(success=False, error=_("Payment transaction failed (%s)") % tx.state_message)
else:
diff --git a/addons/website_sale/data/web_planner_data.xml b/addons/website_sale/data/web_planner_data.xml
index 43ad4c7d846..29bdcc5638c 100644
--- a/addons/website_sale/data/web_planner_data.xml
+++ b/addons/website_sale/data/web_planner_data.xml
@@ -115,14 +115,14 @@
Reassurance arguments
- Anticipate your customers questions & worries on practical details like shipping rates & policies, return & replacement policies, payment methods & security and your product's availability (In Stock, Not Available, etc.).
+ Anticipate your customers questions & worries on practical details like shipping rates & policies, return & replacement policies, payment acquirers & security and your product's availability (In Stock, Not Available, etc.).
-
Put the practical details (shipping, payment methods, etc.) as links in the footer; That way, they will be accessible
+
Put the practical details (shipping, payment acquirers, etc.) as links in the footer; That way, they will be accessible
all your product pages.
To collect payments, you can either request your customers to process it manually (wire transfer, etc.) or redirect them to payment acquirers.
Using payment acquirers costs you money, so take some time to compare them.
-
You can setup 3 types of payment methods in Odoo:
+
You can setup 3 types of payment acquirers in Odoo:
Some acquirers like Authorize.net and Ingenico can be used as payment gateways with your own payment processor.
-
Tip for B2B users: you can create new manually-processed payment methods (check, purchase order, etc.) by renaming 'Wire Transfer' or duplicating it.
+
Tip for B2B users: you can create new manually-processed payment acquirers (check, purchase order, etc.) by renaming 'Wire Transfer' or duplicating it.
diff --git a/addons/website_sale/models/res_config.py b/addons/website_sale/models/res_config.py
index b30f2751320..c6eeb9d328c 100644
--- a/addons/website_sale/models/res_config.py
+++ b/addons/website_sale/models/res_config.py
@@ -39,6 +39,7 @@ class WebsiteConfigSettings(models.TransientModel):
module_website_sale_wishlist = fields.Boolean("Wishlists ", help='Installs *e-Commerce Wishlist*')
module_website_sale_comparison = fields.Boolean("Product Comparator", help='Installs *e-Commerce Comparator*')
+ module_account_invoicing = fields.Boolean("Invoicing")
module_sale_stock = fields.Boolean("Delivery Orders")
# the next 2 fields represent sale_pricelist_setting from sale.config.settings, they are split here for the form view, to improve usability
@@ -69,9 +70,10 @@ class WebsiteConfigSettings(models.TransientModel):
group='base.group_portal,base.group_user,base.group_public')
default_invoice_policy = fields.Selection([
- ('order', 'Ordered quantities'),
- ('delivery', 'Delivered quantities or service hours')
+ ('order', 'Invoice what is ordered'),
+ ('delivery', 'Invoice what is delivered')
], 'Invoicing Policy', default='order')
+ automatic_invoice = fields.Boolean("Automatic Invoice")
group_multi_currency = fields.Boolean(string='Multi-Currencies', implied_group='base.group_multi_currency')
@@ -80,6 +82,16 @@ class WebsiteConfigSettings(models.TransientModel):
('subtotal', 'Tax-Excluded Prices')],
"Product Prices", default='total')
+ @api.multi
+ def set_automatic_invoice(self):
+ value = self.module_account_invoicing and self.default_invoice_policy == 'order' and self.automatic_invoice
+ self.env['ir.config_parameter'].sudo().set_param('website_sale.automatic_invoice', value)
+
+ @api.model
+ def get_default_automatic_invoice(self, fields):
+ value = self.env['ir.config_parameter'].sudo().get_param('website_sale.automatic_invoice', default=False)
+ return {'automatic_invoice': value}
+
@api.model
def get_default_sale_delivery_settings(self, fields):
sale_delivery_settings = 'none'
diff --git a/addons/website_sale/models/sale_order.py b/addons/website_sale/models/sale_order.py
index a933dd3574d..1319fbc2e84 100644
--- a/addons/website_sale/models/sale_order.py
+++ b/addons/website_sale/models/sale_order.py
@@ -3,7 +3,7 @@
import logging
import random
-from odoo import api, models, fields, tools, _
+from odoo import api, models, fields, _
from odoo.http import request
from odoo.exceptions import UserError, ValidationError
from odoo.tools import pycompat
@@ -23,6 +23,16 @@ class SaleOrder(models.Model):
payment_acquirer_id = fields.Many2one('payment.acquirer', string='Payment Acquirer', copy=False)
payment_tx_id = fields.Many2one('payment.transaction', string='Last Transaction', copy=False)
only_services = fields.Boolean(compute='_compute_cart_info', string='Only Services')
+ can_directly_mark_as_paid = fields.Boolean(compute='_compute_can_directly_mark_as_paid',
+ string="Can be directly marked as paid", store=True,
+ help="""Checked if the sales order can directly be marked as paid, i.e. if the quotation
+ is sent or confirmed and if the payment acquire is of the type transfer or manual""")
+
+ @api.depends('state', 'payment_tx_id', 'payment_tx_id.state',
+ 'payment_acquirer_id', 'payment_acquirer_id.provider')
+ def _compute_can_directly_mark_as_paid(self):
+ for order in self:
+ order.can_directly_mark_as_paid = order.state in ['sent', 'sale'] and order.payment_tx_id and order.payment_acquirer_id.provider in ['transfer', 'manual']
@api.multi
@api.depends('website_order_line.product_uom_qty', 'website_order_line.product_id')
@@ -166,316 +176,21 @@ class SaleOrder(models.Model):
accessory_products -= order.website_order_line.mapped('product_id')
return random.sample(accessory_products, len(accessory_products))
-
-class Website(models.Model):
- _inherit = 'website'
-
- pricelist_id = fields.Many2one('product.pricelist', compute='_compute_pricelist_id', string='Default Pricelist')
- currency_id = fields.Many2one('res.currency', related='pricelist_id.currency_id', string='Default Currency')
- salesperson_id = fields.Many2one('res.users', string='Salesperson')
- salesteam_id = fields.Many2one('crm.team', string='Sales Channel')
- pricelist_ids = fields.One2many('product.pricelist', compute="_compute_pricelist_ids",
- string='Price list available for this Ecommerce/Website')
-
- @api.one
- def _compute_pricelist_ids(self):
- self.pricelist_ids = self.env["product.pricelist"].search([("website_id", "=", self.id)])
-
- @api.multi
- def _compute_pricelist_id(self):
- for website in self:
- if website._context.get('website_id') != website.id:
- website = website.with_context(website_id=website.id)
- website.pricelist_id = website.get_current_pricelist()
-
- # This method is cached, must not return records! See also #8795
- @tools.ormcache('self.env.uid', 'country_code', 'show_visible', 'website_pl', 'current_pl', 'all_pl', 'partner_pl', 'order_pl')
- def _get_pl_partner_order(self, country_code, show_visible, website_pl, current_pl, all_pl, partner_pl=False, order_pl=False):
- """ Return the list of pricelists that can be used on website for the current user.
- :param str country_code: code iso or False, If set, we search only price list available for this country
- :param bool show_visible: if True, we don't display pricelist where selectable is False (Eg: Code promo)
- :param int website_pl: The default pricelist used on this website
- :param int current_pl: The current pricelist used on the website
- (If not selectable but the current pricelist we had this pricelist anyway)
- :param list all_pl: List of all pricelist available for this website
- :param int partner_pl: the partner pricelist
- :param int order_pl: the current cart pricelist
- :returns: list of pricelist ids
- """
- pricelists = self.env['product.pricelist']
- if country_code:
- for cgroup in self.env['res.country.group'].search([('country_ids.code', '=', country_code)]):
- for group_pricelists in cgroup.pricelist_ids:
- if not show_visible or group_pricelists.selectable or group_pricelists.id in (current_pl, order_pl):
- pricelists |= group_pricelists
-
- partner = self.env.user.partner_id
- is_public = self.user_id.id == self.env.user.id
- if not is_public and (not pricelists or (partner_pl or partner.property_product_pricelist.id) != website_pl):
- if partner.property_product_pricelist.website_id:
- pricelists |= partner.property_product_pricelist
-
- if not pricelists: # no pricelist for this country, or no GeoIP
- pricelists |= all_pl.filtered(lambda pl: not show_visible or pl.selectable or pl.id in (current_pl, order_pl))
- else:
- pricelists |= all_pl.filtered(lambda pl: not show_visible and pl.sudo().code)
-
- # This method is cached, must not return records! See also #8795
- return pricelists.ids
-
- def _get_pl(self, country_code, show_visible, website_pl, current_pl, all_pl):
- pl_ids = self._get_pl_partner_order(country_code, show_visible, website_pl, current_pl, all_pl)
- return self.env['product.pricelist'].browse(pl_ids)
-
- def get_pricelist_available(self, show_visible=False):
-
- """ Return the list of pricelists that can be used on website for the current user.
- Country restrictions will be detected with GeoIP (if installed).
- :param bool show_visible: if True, we don't display pricelist where selectable is False (Eg: Code promo)
- :returns: pricelist recordset
- """
- website = request.website
- if not request.website:
- if self.env.context.get('website_id'):
- website = self.browse(self.env.context['website_id'])
- else:
- website = self.search([], limit=1)
- isocountry = request.session.geoip and request.session.geoip.get('country_code') or False
- partner = self.env.user.partner_id
- order_pl = partner.last_website_so_id and partner.last_website_so_id.state == 'draft' and partner.last_website_so_id.pricelist_id
- partner_pl = partner.property_product_pricelist
- pricelists = website._get_pl_partner_order(isocountry, show_visible,
- website.user_id.sudo().partner_id.property_product_pricelist.id,
- request.session.get('website_sale_current_pl'),
- website.pricelist_ids,
- partner_pl=partner_pl and partner_pl.id or None,
- order_pl=order_pl and order_pl.id or None)
- return self.env['product.pricelist'].browse(pricelists)
-
- def is_pricelist_available(self, pl_id):
- """ Return a boolean to specify if a specific pricelist can be manually set on the website.
- Warning: It check only if pricelist is in the 'selectable' pricelists or the current pricelist.
- :param int pl_id: The pricelist id to check
- :returns: Boolean, True if valid / available
- """
- return pl_id in self.get_pricelist_available(show_visible=False).ids
-
- def get_current_pricelist(self):
- """
- :returns: The current pricelist record
- """
- # The list of available pricelists for this user.
- # If the user is signed in, and has a pricelist set different than the public user pricelist
- # then this pricelist will always be considered as available
- available_pricelists = self.get_pricelist_available()
- pl = None
- partner = self.env.user.partner_id
- if request.session.get('website_sale_current_pl'):
- # `website_sale_current_pl` is set only if the user specifically chose it:
- # - Either, he chose it from the pricelist selection
- # - Either, he entered a coupon code
- pl = self.env['product.pricelist'].browse(request.session['website_sale_current_pl'])
- if pl not in available_pricelists:
- pl = None
- request.session.pop('website_sale_current_pl')
- if not pl:
- # If the user has a saved cart, it take the pricelist of this cart, except if
- # the order is no longer draft (It has already been confirmed, or cancelled, ...)
- pl = partner.last_website_so_id.state == 'draft' and partner.last_website_so_id.pricelist_id
- if not pl:
- # The pricelist of the user set on its partner form.
- # If the user is not signed in, it's the public user pricelist
- pl = partner.property_product_pricelist
- if available_pricelists and pl not in available_pricelists:
- # If there is at least one pricelist in the available pricelists
- # and the chosen pricelist is not within them
- # it then choose the first available pricelist.
- # This can only happen when the pricelist is the public user pricelist and this pricelist is not in the available pricelist for this localization
- # If the user is signed in, and has a special pricelist (different than the public user pricelist),
- # then this special pricelist is amongs these available pricelists, and therefore it won't fall in this case.
- pl = available_pricelists[0]
-
- if not pl:
- _logger.error('Fail to find pricelist for partner "%s" (id %s)', partner.name, partner.id)
- return pl
-
- @api.multi
- def sale_product_domain(self):
- return [("sale_ok", "=", True)]
-
- @api.model
- def sale_get_payment_term(self, partner):
- DEFAULT_PAYMENT_TERM = 'account.account_payment_term_immediate'
- return self.env.ref(DEFAULT_PAYMENT_TERM, False).id or partner.property_payment_term_id.id
-
- @api.multi
- def _prepare_sale_order_values(self, partner, pricelist):
+ def action_mark_as_paid(self):
+ """ Mark directly a sales order as paid if:
+ - State: Quotation Sent, or sales order
+ - Provider: wire transfer or manual config
+ The transaction is marked as done
+ The invoice may be generated and marked as paid if configured in the website settings
+ """
self.ensure_one()
- affiliate_id = request.session.get('affiliate_id')
- salesperson_id = affiliate_id if self.env['res.users'].sudo().browse(affiliate_id).exists() else request.website.salesperson_id.id
- addr = partner.address_get(['delivery', 'invoice'])
- values = {
- 'partner_id': partner.id,
- 'pricelist_id': pricelist.id,
- 'payment_term_id': self.sale_get_payment_term(partner),
- 'team_id': self.salesteam_id.id,
- 'partner_invoice_id': addr['invoice'],
- 'partner_shipping_id': addr['delivery'],
- 'user_id': salesperson_id or self.salesperson_id.id,
- }
- company = self.company_id or pricelist.company_id
- if company:
- values['company_id'] = company.id
-
- return values
-
- @api.multi
- def sale_get_order(self, force_create=False, code=None, update_pricelist=False, force_pricelist=False):
- """ Return the current sales order after mofications specified by params.
- :param bool force_create: Create sales order if not already existing
- :param str code: Code to force a pricelist (promo code)
- If empty, it's a special case to reset the pricelist with the first available else the default.
- :param bool update_pricelist: Force to recompute all the lines from sales order to adapt the price with the current pricelist.
- :param int force_pricelist: pricelist_id - if set, we change the pricelist with this one
- :returns: browse record for the current sales order
- """
- self.ensure_one()
- partner = self.env.user.partner_id
- sale_order_id = request.session.get('sale_order_id')
- if not sale_order_id:
- last_order = partner.last_website_so_id
- available_pricelists = self.get_pricelist_available()
- # Do not reload the cart of this user last visit if the cart is no longer draft or uses a pricelist no longer available.
- sale_order_id = last_order.state == 'draft' and last_order.pricelist_id in available_pricelists and last_order.id
-
- pricelist_id = request.session.get('website_sale_current_pl') or self.get_current_pricelist().id
-
- if self.env['product.pricelist'].browse(force_pricelist).exists():
- pricelist_id = force_pricelist
- request.session['website_sale_current_pl'] = pricelist_id
- update_pricelist = True
-
- if not self._context.get('pricelist'):
- self = self.with_context(pricelist=pricelist_id)
-
- # Test validity of the sale_order_id
- sale_order = self.env['sale.order'].sudo().browse(sale_order_id).exists() if sale_order_id else None
-
- # create so if needed
- if not sale_order and (force_create or code):
- # TODO cache partner_id session
- pricelist = self.env['product.pricelist'].browse(pricelist_id).sudo()
- so_data = self._prepare_sale_order_values(partner, pricelist)
- sale_order = self.env['sale.order'].sudo().create(so_data)
-
- # set fiscal position
- if request.website.partner_id.id != partner.id:
- sale_order.onchange_partner_shipping_id()
- else: # For public user, fiscal position based on geolocation
- country_code = request.session['geoip'].get('country_code')
- if country_code:
- country_id = request.env['res.country'].search([('code', '=', country_code)], limit=1).id
- fp_id = request.env['account.fiscal.position'].sudo()._get_fpos_by_region(country_id)
- sale_order.fiscal_position_id = fp_id
- else:
- # if no geolocation, use the public user fp
- sale_order.onchange_partner_shipping_id()
-
- request.session['sale_order_id'] = sale_order.id
-
- if request.website.partner_id.id != partner.id:
- partner.write({'last_website_so_id': sale_order.id})
-
- if sale_order:
- # case when user emptied the cart
- if not request.session.get('sale_order_id'):
- request.session['sale_order_id'] = sale_order.id
-
- # check for change of pricelist with a coupon
- pricelist_id = pricelist_id or partner.property_product_pricelist.id
-
- # check for change of partner_id ie after signup
- if sale_order.partner_id.id != partner.id and request.website.partner_id.id != partner.id:
- flag_pricelist = False
- if pricelist_id != sale_order.pricelist_id.id:
- flag_pricelist = True
- fiscal_position = sale_order.fiscal_position_id.id
-
- # change the partner, and trigger the onchange
- sale_order.write({'partner_id': partner.id})
- sale_order.onchange_partner_id()
- sale_order.onchange_partner_shipping_id() # fiscal position
- sale_order['payment_term_id'] = self.sale_get_payment_term(partner)
-
- # check the pricelist : update it if the pricelist is not the 'forced' one
- values = {}
- if sale_order.pricelist_id:
- if sale_order.pricelist_id.id != pricelist_id:
- values['pricelist_id'] = pricelist_id
- update_pricelist = True
-
- # if fiscal position, update the order lines taxes
- if sale_order.fiscal_position_id:
- sale_order._compute_tax_id()
-
- # if values, then make the SO update
- if values:
- sale_order.write(values)
-
- # check if the fiscal position has changed with the partner_id update
- recent_fiscal_position = sale_order.fiscal_position_id.id
- if flag_pricelist or recent_fiscal_position != fiscal_position:
- update_pricelist = True
-
- if code and code != sale_order.pricelist_id.code:
- code_pricelist = self.env['product.pricelist'].sudo().search([('code', '=', code)], limit=1)
- if code_pricelist:
- pricelist_id = code_pricelist.id
- update_pricelist = True
- elif code is not None and sale_order.pricelist_id.code:
- # code is not None when user removes code and click on "Apply"
- pricelist_id = partner.property_product_pricelist.id
- update_pricelist = True
-
- # update the pricelist
- if update_pricelist:
- request.session['website_sale_current_pl'] = pricelist_id
- values = {'pricelist_id': pricelist_id}
- sale_order.write(values)
- for line in sale_order.order_line:
- if line.exists():
- sale_order._cart_update(product_id=line.product_id.id, line_id=line.id, add_qty=0)
-
+ if self.can_directly_mark_as_paid:
+ self.action_confirm()
+ if self.env['ir.config_parameter'].sudo().get_param('website_sale.automatic_invoice', default=False):
+ self.payment_tx_id._generate_and_pay_invoice(self.payment_tx_id, self.payment_acquirer_id.provider)
+ self.payment_tx_id.state = 'done'
else:
- request.session['sale_order_id'] = None
- return None
-
- return sale_order
-
- def sale_get_transaction(self):
- tx_id = request.session.get('sale_transaction_id')
- if tx_id:
- transaction = self.env['payment.transaction'].sudo().browse(tx_id)
- # Ugly hack for SIPS: SIPS does not allow to reuse a payment reference, even if the
- # payment was not not proceeded. For example:
- # - Select SIPS for payment
- # - Be redirected to SIPS website
- # - Go back to eCommerce without paying
- # - Be redirected to SIPS website again => error
- # Since there is no link module between 'website_sale' and 'payment_sips', we prevent
- # here to reuse any previous transaction for SIPS.
- if transaction.state != 'cancel' and transaction.acquirer_id.provider != 'sips':
- return transaction
- else:
- request.session['sale_transaction_id'] = False
- return False
-
- def sale_reset(self):
- request.session.update({
- 'sale_order_id': False,
- 'sale_transaction_id': False,
- 'website_sale_current_pl': False,
- })
+ raise ValidationError(_("The quote should be sent and the payment acquirer type should be manual or wire transfer"))
class ResCountry(models.Model):
diff --git a/addons/website_sale/models/website.py b/addons/website_sale/models/website.py
index 8cf1e6b78ae..2878c0bfc20 100644
--- a/addons/website_sale/models/website.py
+++ b/addons/website_sale/models/website.py
@@ -1,12 +1,325 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from odoo import api, models
+import logging
+
+from odoo import api, fields, models, tools
+
+from odoo.http import request
+
+_logger = logging.getLogger(__name__)
class Website(models.Model):
_inherit = 'website'
+ pricelist_id = fields.Many2one('product.pricelist', compute='_compute_pricelist_id', string='Default Pricelist')
+ currency_id = fields.Many2one('res.currency', related='pricelist_id.currency_id', string='Default Currency')
+ salesperson_id = fields.Many2one('res.users', string='Salesperson')
+ salesteam_id = fields.Many2one('crm.team', string='Sales Channel')
+ pricelist_ids = fields.One2many('product.pricelist', compute="_compute_pricelist_ids",
+ string='Price list available for this Ecommerce/Website')
+
+ @api.one
+ def _compute_pricelist_ids(self):
+ self.pricelist_ids = self.env["product.pricelist"].search([("website_id", "=", self.id)])
+
+ @api.multi
+ def _compute_pricelist_id(self):
+ for website in self:
+ if website._context.get('website_id') != website.id:
+ website = website.with_context(website_id=website.id)
+ website.pricelist_id = website.get_current_pricelist()
+
+ # This method is cached, must not return records! See also #8795
+ @tools.ormcache('self.env.uid', 'country_code', 'show_visible', 'website_pl', 'current_pl', 'all_pl', 'partner_pl', 'order_pl')
+ def _get_pl_partner_order(self, country_code, show_visible, website_pl, current_pl, all_pl, partner_pl=False, order_pl=False):
+ """ Return the list of pricelists that can be used on website for the current user.
+ :param str country_code: code iso or False, If set, we search only price list available for this country
+ :param bool show_visible: if True, we don't display pricelist where selectable is False (Eg: Code promo)
+ :param int website_pl: The default pricelist used on this website
+ :param int current_pl: The current pricelist used on the website
+ (If not selectable but the current pricelist we had this pricelist anyway)
+ :param list all_pl: List of all pricelist available for this website
+ :param int partner_pl: the partner pricelist
+ :param int order_pl: the current cart pricelist
+ :returns: list of pricelist ids
+ """
+ pricelists = self.env['product.pricelist']
+ if country_code:
+ for cgroup in self.env['res.country.group'].search([('country_ids.code', '=', country_code)]):
+ for group_pricelists in cgroup.pricelist_ids:
+ if not show_visible or group_pricelists.selectable or group_pricelists.id in (current_pl, order_pl):
+ pricelists |= group_pricelists
+
+ partner = self.env.user.partner_id
+ is_public = self.user_id.id == self.env.user.id
+ if not is_public and (not pricelists or (partner_pl or partner.property_product_pricelist.id) != website_pl):
+ if partner.property_product_pricelist.website_id:
+ pricelists |= partner.property_product_pricelist
+
+ if not pricelists: # no pricelist for this country, or no GeoIP
+ pricelists |= all_pl.filtered(lambda pl: not show_visible or pl.selectable or pl.id in (current_pl, order_pl))
+ else:
+ pricelists |= all_pl.filtered(lambda pl: not show_visible and pl.sudo().code)
+
+ # This method is cached, must not return records! See also #8795
+ return pricelists.ids
+
+ def _get_pl(self, country_code, show_visible, website_pl, current_pl, all_pl):
+ pl_ids = self._get_pl_partner_order(country_code, show_visible, website_pl, current_pl, all_pl)
+ return self.env['product.pricelist'].browse(pl_ids)
+
+ def get_pricelist_available(self, show_visible=False):
+
+ """ Return the list of pricelists that can be used on website for the current user.
+ Country restrictions will be detected with GeoIP (if installed).
+ :param bool show_visible: if True, we don't display pricelist where selectable is False (Eg: Code promo)
+ :returns: pricelist recordset
+ """
+ website = request.website
+ if not request.website:
+ if self.env.context.get('website_id'):
+ website = self.browse(self.env.context['website_id'])
+ else:
+ website = self.search([], limit=1)
+ isocountry = request.session.geoip and request.session.geoip.get('country_code') or False
+ partner = self.env.user.partner_id
+ order_pl = partner.last_website_so_id and partner.last_website_so_id.state == 'draft' and partner.last_website_so_id.pricelist_id
+ partner_pl = partner.property_product_pricelist
+ pricelists = website._get_pl_partner_order(isocountry, show_visible,
+ website.user_id.sudo().partner_id.property_product_pricelist.id,
+ request.session.get('website_sale_current_pl'),
+ website.pricelist_ids,
+ partner_pl=partner_pl and partner_pl.id or None,
+ order_pl=order_pl and order_pl.id or None)
+ return self.env['product.pricelist'].browse(pricelists)
+
+ def is_pricelist_available(self, pl_id):
+ """ Return a boolean to specify if a specific pricelist can be manually set on the website.
+ Warning: It check only if pricelist is in the 'selectable' pricelists or the current pricelist.
+ :param int pl_id: The pricelist id to check
+ :returns: Boolean, True if valid / available
+ """
+ return pl_id in self.get_pricelist_available(show_visible=False).ids
+
+ def get_current_pricelist(self):
+ """
+ :returns: The current pricelist record
+ """
+ # The list of available pricelists for this user.
+ # If the user is signed in, and has a pricelist set different than the public user pricelist
+ # then this pricelist will always be considered as available
+ available_pricelists = self.get_pricelist_available()
+ pl = None
+ partner = self.env.user.partner_id
+ if request.session.get('website_sale_current_pl'):
+ # `website_sale_current_pl` is set only if the user specifically chose it:
+ # - Either, he chose it from the pricelist selection
+ # - Either, he entered a coupon code
+ pl = self.env['product.pricelist'].browse(request.session['website_sale_current_pl'])
+ if pl not in available_pricelists:
+ pl = None
+ request.session.pop('website_sale_current_pl')
+ if not pl:
+ # If the user has a saved cart, it take the pricelist of this cart, except if
+ # the order is no longer draft (It has already been confirmed, or cancelled, ...)
+ pl = partner.last_website_so_id.state == 'draft' and partner.last_website_so_id.pricelist_id
+ if not pl:
+ # The pricelist of the user set on its partner form.
+ # If the user is not signed in, it's the public user pricelist
+ pl = partner.property_product_pricelist
+ if available_pricelists and pl not in available_pricelists:
+ # If there is at least one pricelist in the available pricelists
+ # and the chosen pricelist is not within them
+ # it then choose the first available pricelist.
+ # This can only happen when the pricelist is the public user pricelist and this pricelist is not in the available pricelist for this localization
+ # If the user is signed in, and has a special pricelist (different than the public user pricelist),
+ # then this special pricelist is amongs these available pricelists, and therefore it won't fall in this case.
+ pl = available_pricelists[0]
+
+ if not pl:
+ _logger.error('Fail to find pricelist for partner "%s" (id %s)', partner.name, partner.id)
+ return pl
+
+ @api.multi
+ def sale_product_domain(self):
+ return [("sale_ok", "=", True)]
+
+ @api.model
+ def sale_get_payment_term(self, partner):
+ DEFAULT_PAYMENT_TERM = 'account.account_payment_term_immediate'
+ return self.env.ref(DEFAULT_PAYMENT_TERM, False).id or partner.property_payment_term_id.id
+
+ @api.multi
+ def _prepare_sale_order_values(self, partner, pricelist):
+ self.ensure_one()
+ affiliate_id = request.session.get('affiliate_id')
+ salesperson_id = affiliate_id if self.env['res.users'].sudo().browse(affiliate_id).exists() else request.website.salesperson_id.id
+ addr = partner.address_get(['delivery', 'invoice'])
+ values = {
+ 'partner_id': partner.id,
+ 'pricelist_id': pricelist.id,
+ 'payment_term_id': self.sale_get_payment_term(partner),
+ 'team_id': self.salesteam_id.id,
+ 'partner_invoice_id': addr['invoice'],
+ 'partner_shipping_id': addr['delivery'],
+ 'user_id': salesperson_id or self.salesperson_id.id,
+ }
+ company = self.company_id or pricelist.company_id
+ if company:
+ values['company_id'] = company.id
+
+ return values
+
+ @api.multi
+ def sale_get_order(self, force_create=False, code=None, update_pricelist=False, force_pricelist=False):
+ """ Return the current sales order after mofications specified by params.
+ :param bool force_create: Create sales order if not already existing
+ :param str code: Code to force a pricelist (promo code)
+ If empty, it's a special case to reset the pricelist with the first available else the default.
+ :param bool update_pricelist: Force to recompute all the lines from sales order to adapt the price with the current pricelist.
+ :param int force_pricelist: pricelist_id - if set, we change the pricelist with this one
+ :returns: browse record for the current sales order
+ """
+ self.ensure_one()
+ partner = self.env.user.partner_id
+ sale_order_id = request.session.get('sale_order_id')
+ if not sale_order_id:
+ last_order = partner.last_website_so_id
+ available_pricelists = self.get_pricelist_available()
+ # Do not reload the cart of this user last visit if the cart is no longer draft or uses a pricelist no longer available.
+ sale_order_id = last_order.state == 'draft' and last_order.pricelist_id in available_pricelists and last_order.id
+
+ pricelist_id = request.session.get('website_sale_current_pl') or self.get_current_pricelist().id
+
+ if self.env['product.pricelist'].browse(force_pricelist).exists():
+ pricelist_id = force_pricelist
+ request.session['website_sale_current_pl'] = pricelist_id
+ update_pricelist = True
+
+ if not self._context.get('pricelist'):
+ self = self.with_context(pricelist=pricelist_id)
+
+ # Test validity of the sale_order_id
+ sale_order = self.env['sale.order'].sudo().browse(sale_order_id).exists() if sale_order_id else None
+
+ # create so if needed
+ if not sale_order and (force_create or code):
+ # TODO cache partner_id session
+ pricelist = self.env['product.pricelist'].browse(pricelist_id).sudo()
+ so_data = self._prepare_sale_order_values(partner, pricelist)
+ sale_order = self.env['sale.order'].sudo().create(so_data)
+
+ # set fiscal position
+ if request.website.partner_id.id != partner.id:
+ sale_order.onchange_partner_shipping_id()
+ else: # For public user, fiscal position based on geolocation
+ country_code = request.session['geoip'].get('country_code')
+ if country_code:
+ country_id = request.env['res.country'].search([('code', '=', country_code)], limit=1).id
+ fp_id = request.env['account.fiscal.position'].sudo()._get_fpos_by_region(country_id)
+ sale_order.fiscal_position_id = fp_id
+ else:
+ # if no geolocation, use the public user fp
+ sale_order.onchange_partner_shipping_id()
+
+ request.session['sale_order_id'] = sale_order.id
+
+ if request.website.partner_id.id != partner.id:
+ partner.write({'last_website_so_id': sale_order.id})
+
+ if sale_order:
+ # case when user emptied the cart
+ if not request.session.get('sale_order_id'):
+ request.session['sale_order_id'] = sale_order.id
+
+ # check for change of pricelist with a coupon
+ pricelist_id = pricelist_id or partner.property_product_pricelist.id
+
+ # check for change of partner_id ie after signup
+ if sale_order.partner_id.id != partner.id and request.website.partner_id.id != partner.id:
+ flag_pricelist = False
+ if pricelist_id != sale_order.pricelist_id.id:
+ flag_pricelist = True
+ fiscal_position = sale_order.fiscal_position_id.id
+
+ # change the partner, and trigger the onchange
+ sale_order.write({'partner_id': partner.id})
+ sale_order.onchange_partner_id()
+ sale_order.onchange_partner_shipping_id() # fiscal position
+ sale_order['payment_term_id'] = self.sale_get_payment_term(partner)
+
+ # check the pricelist : update it if the pricelist is not the 'forced' one
+ values = {}
+ if sale_order.pricelist_id:
+ if sale_order.pricelist_id.id != pricelist_id:
+ values['pricelist_id'] = pricelist_id
+ update_pricelist = True
+
+ # if fiscal position, update the order lines taxes
+ if sale_order.fiscal_position_id:
+ sale_order._compute_tax_id()
+
+ # if values, then make the SO update
+ if values:
+ sale_order.write(values)
+
+ # check if the fiscal position has changed with the partner_id update
+ recent_fiscal_position = sale_order.fiscal_position_id.id
+ if flag_pricelist or recent_fiscal_position != fiscal_position:
+ update_pricelist = True
+
+ if code and code != sale_order.pricelist_id.code:
+ code_pricelist = self.env['product.pricelist'].sudo().search([('code', '=', code)], limit=1)
+ if code_pricelist:
+ pricelist_id = code_pricelist.id
+ update_pricelist = True
+ elif code is not None and sale_order.pricelist_id.code:
+ # code is not None when user removes code and click on "Apply"
+ pricelist_id = partner.property_product_pricelist.id
+ update_pricelist = True
+
+ # update the pricelist
+ if update_pricelist:
+ request.session['website_sale_current_pl'] = pricelist_id
+ values = {'pricelist_id': pricelist_id}
+ sale_order.write(values)
+ for line in sale_order.order_line:
+ if line.exists():
+ sale_order._cart_update(product_id=line.product_id.id, line_id=line.id, add_qty=0)
+
+ else:
+ request.session['sale_order_id'] = None
+ return None
+
+ return sale_order
+
+ def sale_get_transaction(self):
+ tx_id = request.session.get('sale_transaction_id')
+ if tx_id:
+ transaction = self.env['payment.transaction'].sudo().browse(tx_id)
+ # Ugly hack for SIPS: SIPS does not allow to reuse a payment reference, even if the
+ # payment was not not proceeded. For example:
+ # - Select SIPS for payment
+ # - Be redirected to SIPS website
+ # - Go back to eCommerce without paying
+ # - Be redirected to SIPS website again => error
+ # Since there is no link module between 'website_sale' and 'payment_sips', we prevent
+ # here to reuse any previous transaction for SIPS.
+ if transaction.state != 'cancel' and transaction.acquirer_id.provider != 'sips':
+ return transaction
+ else:
+ request.session['sale_transaction_id'] = False
+ return False
+
+ def sale_reset(self):
+ request.session.update({
+ 'sale_order_id': False,
+ 'sale_transaction_id': False,
+ 'website_sale_current_pl': False,
+ })
+
@api.model
def action_dashboard_redirect(self):
if self.env.user.has_group('sales_team.group_sale_salesman'):
diff --git a/addons/website_sale/static/src/xml/website_sale_dashboard.xml b/addons/website_sale/static/src/xml/website_sale_dashboard.xml
index 9bf95eeb50f..09d080d6b38 100644
--- a/addons/website_sale/static/src/xml/website_sale_dashboard.xml
+++ b/addons/website_sale/static/src/xml/website_sale_dashboard.xml
@@ -2,20 +2,13 @@
-