[FIX] website_sale: limit pricelist to it's website if set

Before this commit:
1. Non Public User would get the main company pricelist returned when calling
   `get_current_pricelist`. As this is saved in an `ir.property`
   (`property_product_pricelist`) that is not website dependant, that pricelist
   would always be returned on every website.
   This is a problem if that pricelist is website limited, you should not be
   able to use it on other websites.
2. Public User would not have a valid pricelist on other website than the
   default one. Indeed, every pricelists are restricted that default website.
   It would result in prices shown as "0" (without currency symbol) on the shop
3. Pricelists set as selectable without a website_id set would not be
   selectable
4. Public User would not get a fallback pricelist when reading its
   `property_product_pricelist`. Indeed, the public user's partner is not
   active and since the refactoring of this field compute method to be a multi,
   it would end up doing a search() instead of a browse(). Thus, it would find
   a pricelist for the public user but it would not set it.

This commit:
1. We override and check that pricelists returnes by the `ir.property` are
   indeed available on the current website.
2. Remove the website restriction on `Public Pricelist` so other websites have
   at least one pricelist available.
3. `pricelist_ids` on `website` now return all available pricelists including
   the ones that are generic (no website set)
4. We search for inactive partners too.

Part of this commit fixes part of 27861
Github-25109
This commit is contained in:
Romain Derie
2019-03-14 10:28:02 +00:00
parent 08a17b6883
commit 2d970942ba
6 changed files with 81 additions and 15 deletions
+25 -6
View File
@@ -314,34 +314,53 @@ class Pricelist(models.Model):
res = self._get_partner_pricelist_multi([partner_id], company_id)
return res[partner_id].id
def _get_partner_pricelist_multi_search_domain_hook(self):
return []
def _get_partner_pricelist_multi_filter_hook(self):
return self
def _get_partner_pricelist_multi(self, partner_ids, company_id=None):
""" Retrieve the applicable pricelist for given partners in a given company.
It will return the first found pricelist in this order:
First, the pricelist of the specific property (res_id set), this one
is created when saving a pricelist on the partner form view.
Else, it will return the pricelist of the partner country group
Else, it will return the generic property (res_id not set), this one
is created on the company creation.
Else, it will return the first available pricelist
:param company_id: if passed, used for looking up properties,
instead of current user's company
:return: a dict {partner_id: pricelist}
"""
Partner = self.env['res.partner']
# `partner_ids` might be ID from inactive uers. We should use active_test
# as we will do a search() later (real case for website public user).
Partner = self.env['res.partner'].with_context(active_test=False)
Property = self.env['ir.property'].with_context(force_company=company_id or self.env.user.company_id.id)
Pricelist = self.env['product.pricelist']
pl_domain = self._get_partner_pricelist_multi_search_domain_hook()
# retrieve values of property
# if no specific property, try to find a fitting pricelist
result = Property.get_multi('property_product_pricelist', Partner._name, partner_ids)
remaining_partner_ids = [pid for pid, val in result.items() if not val]
remaining_partner_ids = [pid for pid, val in result.items() if not val or
not val._get_partner_pricelist_multi_filter_hook()]
if remaining_partner_ids:
# get fallback pricelist when no pricelist for a given country
pl_fallback = (
Pricelist.search([('country_group_ids', '=', False)], limit=1) or
Pricelist.search(pl_domain + [('country_group_ids', '=', False)], limit=1) or
Property.get('property_product_pricelist', 'res.partner') or
Pricelist.search([], limit=1)
Pricelist.search(pl_domain, limit=1)
)
# group partners by country, and find a pricelist for each country
domain = [('id', 'in', remaining_partner_ids)]
groups = Partner.read_group(domain, ['country_id'], ['country_id'])
for group in groups:
country_id = group['country_id'] and group['country_id'][0]
pl = Pricelist.search([('country_group_ids.country_ids', '=', country_id)], limit=1)
pl = Pricelist.search(pl_domain + [('country_group_ids.country_ids', '=', country_id)], limit=1)
pl = pl or pl_fallback
for pid in Partner.search(group['__domain']).ids:
result[pid] = pl
+1 -1
View File
@@ -40,7 +40,7 @@
<record model="product.pricelist" id="product.list0">
<field name="selectable" eval="False" />
<field name="website_id" ref="website.default_website" />
<field name="website_id" eval="False"/>
</record>
</data>
<data>
-1
View File
@@ -224,7 +224,6 @@
<record model="product.pricelist" id="product.list0">
<field name="selectable" eval="True" />
<field name="website_id" ref="website.default_website" />
<field name="sequence">3</field>
<field name="currency_id" ref="base.USD"/>
</record>
+45 -2
View File
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, tools, _
from odoo.addons import decimal_precision as dp
from odoo.addons.website.models import ir_http
from odoo.tools.translate import html_translate
@@ -17,9 +18,12 @@ class ProductPricelist(models.Model):
_inherit = "product.pricelist"
def _default_website(self):
return self.env['website'].search([], limit=1)
""" Find the first company's website, if there is one. """
company_id = self.env.user.company_id.id
domain = [('company_id', '=', company_id)]
return self.env['website'].search(domain, limit=1)
website_id = fields.Many2one('website', string="website", default=_default_website)
website_id = fields.Many2one('website', string="Website", default=_default_website)
code = fields.Char(string='E-commerce Promotional Code', groups="base.group_user")
selectable = fields.Boolean(help="Allow the end user to choose this price list")
@@ -48,6 +52,45 @@ class ProductPricelist(models.Model):
self.clear_cache()
return res
def _get_partner_pricelist_multi_search_domain_hook(self):
domain = super(ProductPricelist, self)._get_partner_pricelist_multi_search_domain_hook()
website = ir_http.get_request_website()
if website:
domain += self._get_website_pricelists_domain(website.id)
return domain
def _get_partner_pricelist_multi_filter_hook(self):
res = super(ProductPricelist, self)._get_partner_pricelist_multi_filter_hook()
website = ir_http.get_request_website()
if website:
res = res.filtered(lambda pl: pl._is_available_on_website(website.id))
return res
@api.multi
def _is_available_on_website(self, website_id):
""" To be able to be used on a website, a pricelist should either:
- Have its `website_id` set to current website (specific pricelist).
- Have no `website_id` set and should be `selectable` (generic pricelist)
or should have a `code` (generic promotion).
Note: A pricelist without a website_id, not selectable and without a
code is a backend pricelist.
Change in this method should be reflected in `_get_website_pricelists_domain`.
"""
self.ensure_one()
return self.website_id.id == website_id or (not self.website_id and (self.selectable or self.code))
def _get_website_pricelists_domain(self, website_id):
''' Check above `_is_available_on_website` for explanation.
Change in this method should be reflected in `_is_available_on_website`.
'''
return [
'|', ('website_id', '=', website_id),
'&', ('website_id', '=', False),
'|', ('selectable', '=', True), ('code', '!=', False),
]
class ProductPublicCategory(models.Model):
_name = "product.public.category"
+9 -4
View File
@@ -32,7 +32,12 @@ class Website(models.Model):
@api.one
def _compute_pricelist_ids(self):
self.pricelist_ids = self.env["product.pricelist"].search([("website_id", "=", self.id)])
""" Return the pricelists that can be used directly or indirectly on
the website.
"""
Pricelist = self.env["product.pricelist"]
domain = Pricelist._get_website_pricelists_domain(self.id)
self.pricelist_ids = Pricelist.search(domain)
@api.multi
def _compute_pricelist_id(self):
@@ -66,8 +71,8 @@ class Website(models.Model):
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
# `property_product_pricelist` is already multi-website compliant
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))
@@ -75,7 +80,7 @@ class Website(models.Model):
pricelists |= all_pl.filtered(lambda pl: pl.sudo().code)
# This method is cached, must not return records! See also #8795
return pricelists.ids
return pricelists.filtered(lambda pl: pl._is_available_on_website(self.id)).ids
# DEPRECATED (Not used anymore) -> Remove me in master (saas12.3)
def _get_pl(self, country_code, show_visible, website_pl, current_pl, all_pl):
+1 -1
View File
@@ -200,7 +200,7 @@
<field name="arch" type="xml">
<field name='currency_id' position='after'>
<field name="selectable"/>
<field name="website_id" options="{'no_create': True}" string="Allow to use on " placeholder="None website"/>
<field name="website_id" options="{'no_create': True}" />
</field>
</field>
</record>