From 2d970942bae15a6e5378a05deec18295435c235f Mon Sep 17 00:00:00 2001 From: Romain Derie Date: Tue, 12 Mar 2019 10:06:55 +0000 Subject: [PATCH] [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 --- addons/product/models/product_pricelist.py | 31 +++++++++++--- addons/website_sale/data/data.xml | 2 +- addons/website_sale/data/demo.xml | 1 - addons/website_sale/models/product.py | 47 ++++++++++++++++++++- addons/website_sale/models/website.py | 13 ++++-- addons/website_sale/views/product_views.xml | 2 +- 6 files changed, 81 insertions(+), 15 deletions(-) diff --git a/addons/product/models/product_pricelist.py b/addons/product/models/product_pricelist.py index 6ca1a827927..61d3feff07a 100644 --- a/addons/product/models/product_pricelist.py +++ b/addons/product/models/product_pricelist.py @@ -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 diff --git a/addons/website_sale/data/data.xml b/addons/website_sale/data/data.xml index 81639c17cd0..ce716bebba9 100644 --- a/addons/website_sale/data/data.xml +++ b/addons/website_sale/data/data.xml @@ -40,7 +40,7 @@ - + diff --git a/addons/website_sale/data/demo.xml b/addons/website_sale/data/demo.xml index 97217d3d11a..25f03e0f915 100644 --- a/addons/website_sale/data/demo.xml +++ b/addons/website_sale/data/demo.xml @@ -224,7 +224,6 @@ - 3 diff --git a/addons/website_sale/models/product.py b/addons/website_sale/models/product.py index 5bc579637e6..d9c421e76b9 100644 --- a/addons/website_sale/models/product.py +++ b/addons/website_sale/models/product.py @@ -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" diff --git a/addons/website_sale/models/website.py b/addons/website_sale/models/website.py index 0f57030c95a..c781f36d977 100644 --- a/addons/website_sale/models/website.py +++ b/addons/website_sale/models/website.py @@ -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): diff --git a/addons/website_sale/views/product_views.xml b/addons/website_sale/views/product_views.xml index 52b57be8deb..b1503c4d8ff 100644 --- a/addons/website_sale/views/product_views.xml +++ b/addons/website_sale/views/product_views.xml @@ -200,7 +200,7 @@ - +