Skip to content
Permalink
Browse files

[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
  • Loading branch information...
rdeodoo committed Mar 12, 2019
1 parent 08a17b6 commit 2d970942bae15a6e5378a05deec18295435c235f
@@ -314,34 +314,53 @@ def _get_partner_pricelist(self, partner_id, company_id=None):
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
@@ -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>
@@ -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>
@@ -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 @@ def unlink(self):
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"
@@ -32,7 +32,12 @@ def _default_recovery_mail_template(self):

@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,16 +71,16 @@ def _get_pl_partner_order(self, country_code, show_visible, website_pl, current_
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))
if not show_visible and not country_code:
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):
@@ -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>

1 comment on commit 2d97094

@rdeodoo

This comment has been minimized.

Copy link
Contributor Author

commented on 2d97094 Mar 18, 2019

Coming from #28301

Please sign in to comment.
You can’t perform that action at this time.