diff --git a/addons/product/models/product_pricelist.py b/addons/product/models/product_pricelist.py
index 61d3feff07a..a8093611c29 100644
--- a/addons/product/models/product_pricelist.py
+++ b/addons/product/models/product_pricelist.py
@@ -305,6 +305,7 @@ class Pricelist(models.Model):
return pricelist.get_products_price(
list(pycompat.izip(**products_by_qty_by_partner)))
+ # DEPRECATED (Not used anymore, see d39d583b2) -> Remove me in master (saas12.3)
def _get_partner_pricelist(self, partner_id, company_id=None):
""" Retrieve the applicable pricelist for a given partner in a given company.
diff --git a/addons/website/models/ir_rule.py b/addons/website/models/ir_rule.py
index ba5e07128ee..a11ddef1e53 100644
--- a/addons/website/models/ir_rule.py
+++ b/addons/website/models/ir_rule.py
@@ -1,5 +1,6 @@
# coding: utf-8
from odoo import api, models
+from odoo.addons.website.models import ir_http
class IrRule(models.Model):
@@ -8,7 +9,14 @@ class IrRule(models.Model):
@api.model
def _eval_context(self):
res = super(IrRule, self)._eval_context()
- res['website_id'] = self.env['website'].get_current_website().id
+
+ # We need is_frontend to avoid showing website's company items in backend
+ # (that could be different than current company). We can't use
+ # `get_current_website(falback=False)` as it could also return a website
+ # in backend (if domain set & match)..
+ is_frontend = ir_http.get_request_website()
+ Website = self.env['website']
+ res['website'] = is_frontend and Website.get_current_website() or Website
return res
def _compute_domain_keys(self):
diff --git a/addons/website_sale/__init__.py b/addons/website_sale/__init__.py
index 5e8764bb37f..03800bab040 100644
--- a/addons/website_sale/__init__.py
+++ b/addons/website_sale/__init__.py
@@ -1,7 +1,19 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
+from odoo import api, SUPERUSER_ID
from . import controllers
from . import models
from . import wizard
from . import report
+
+
+def uninstall_hook(cr, registry):
+ ''' Need to reenable the `product` pricelist multi-company rule that were
+ disabled to be 'overriden' for multi-website purpose
+ '''
+ env = api.Environment(cr, SUPERUSER_ID, {})
+ pl_rule = env.ref('product.product_pricelist_comp_rule', raise_if_not_found=False)
+ pl_item_rule = env.ref('product.product_pricelist_item_comp_rule', raise_if_not_found=False)
+ multi_company_rules = pl_rule or env['ir.rule']
+ multi_company_rules += pl_item_rule or env['ir.rule']
+ multi_company_rules.write({'active': True})
diff --git a/addons/website_sale/__manifest__.py b/addons/website_sale/__manifest__.py
index 91ce59ec5eb..b07dc32747b 100644
--- a/addons/website_sale/__manifest__.py
+++ b/addons/website_sale/__manifest__.py
@@ -30,4 +30,5 @@
'qweb': ['static/src/xml/*.xml'],
'installable': True,
'application': True,
+ 'uninstall_hook': 'uninstall_hook',
}
diff --git a/addons/website_sale/models/product.py b/addons/website_sale/models/product.py
index d9c421e76b9..66f29184670 100644
--- a/addons/website_sale/models/product.py
+++ b/addons/website_sale/models/product.py
@@ -91,6 +91,17 @@ class ProductPricelist(models.Model):
'|', ('selectable', '=', True), ('code', '!=', False),
]
+ def _get_partner_pricelist_multi(self, partner_ids, company_id=None):
+ ''' If `property_product_pricelist` is read from website, we should use
+ the website's company and not the user's one.
+ Passing a `company_id` to super will avoid using the current user's
+ company.
+ '''
+ website = ir_http.get_request_website()
+ if not company_id and website:
+ company_id = website.company_id.id
+ return super(ProductPricelist, self)._get_partner_pricelist_multi(partner_ids, company_id)
+
class ProductPublicCategory(models.Model):
_name = "product.public.category"
diff --git a/addons/website_sale/security/website_sale.xml b/addons/website_sale/security/website_sale.xml
index 5d73c4d38b7..0af26e6d7d7 100644
--- a/addons/website_sale/security/website_sale.xml
+++ b/addons/website_sale/security/website_sale.xml
@@ -52,6 +52,31 @@
-
+
+
+
+
+
+
+
+
+ product pricelist company rule
+
+
+ [('company_id','in',(False,user.company_id.id,website.company_id.id))]
+
+
+ product pricelist item company rule
+
+
+ [('company_id','in',(False,user.company_id.id,website.company_id.id))]
+
diff --git a/addons/website_sale/tests/test_website_sale_pricelist.py b/addons/website_sale/tests/test_website_sale_pricelist.py
index dc04f55301a..46ae0e56a28 100644
--- a/addons/website_sale/tests/test_website_sale_pricelist.py
+++ b/addons/website_sale/tests/test_website_sale_pricelist.py
@@ -4,7 +4,7 @@ try:
from unittest.mock import patch
except ImportError:
from mock import patch
-from odoo.tests.common import TransactionCase
+from odoo.tests.common import HttpCase, TransactionCase
class TestWebsitePriceList(TransactionCase):
@@ -125,3 +125,98 @@ class TestWebsitePriceList(TransactionCase):
pls = self.get_pl(show, current_pl, country)
self.assertEquals(len(set(pls.mapped('name')) & set(result)), len(pls), 'Test failed for %s (%s %s vs %s %s)'
% (country, len(pls), pls.mapped('name'), len(result), result))
+
+
+def simulate_frontend_context(self, website_id=1):
+ # Mock this method will be enough to simulate frontend context in most methods
+ def get_request_website():
+ return self.env['website'].browse(website_id)
+ patcher = patch('odoo.addons.website.models.ir_http.get_request_website', wraps=get_request_website)
+ patcher.start()
+ self.addCleanup(patcher.stop)
+
+
+class TestWebsitePriceListMultiCompany(TransactionCase):
+ def setUp(self):
+ ''' Create a basic multi-company pricelist environment:
+ - Set up 2 companies with their own company-restricted pricelist each.
+ - Add demo user in those 2 companies
+ - For each company, add that company pricelist to the demo user partner.
+ - Set website's company to company 2
+ - Demo user will still be in company 1
+ '''
+ super(TestWebsitePriceListMultiCompany, self).setUp()
+
+ self.demo_user = self.env.ref('base.user_demo')
+
+ # Create and add demo user to 2 companies
+ self.company1 = self.demo_user.company_id
+ self.company2 = self.env['res.company'].create({'name': 'Test Company'})
+ self.demo_user.company_ids += self.company2
+ # Set company2 as current company for demo user
+ self.website = self.env['website'].browse(1)
+ self.website.company_id = self.company2
+
+ # Create a company pricelist for each company and set it to demo user
+ self.c1_pl = self.env['product.pricelist'].create({
+ 'name': 'Company 1 Pricelist',
+ 'company_id': self.company1.id,
+ })
+ self.c2_pl = self.env['product.pricelist'].create({
+ 'name': 'Company 2 Pricelist',
+ 'company_id': self.company2.id,
+ 'website_id': False,
+ })
+ self.demo_user.partner_id.property_product_pricelist = self.c1_pl
+ # Switch env.user company to create ir.property in company2
+ self.env.user.company_id = self.company2
+ self.demo_user.partner_id.property_product_pricelist = self.c2_pl
+
+ # Ensure everything was done correctly
+ self.assertEqual(self.demo_user.partner_id.with_context(force_company=self.company1.id).property_product_pricelist, self.c1_pl)
+ self.assertEqual(self.demo_user.partner_id.with_context(force_company=self.company2.id).property_product_pricelist, self.c2_pl)
+ irp1 = self.env['ir.property'].search([
+ ('name', '=', 'property_product_pricelist'),
+ ('company_id', '=', self.company1.id),
+ ('res_id', '=', 'res.partner,%s' % self.demo_user.partner_id.id),
+ ('value_reference', '=', 'product.pricelist,%s' % self.c1_pl.id),
+ ])
+ irp2 = self.env['ir.property'].search([
+ ('name', '=', 'property_product_pricelist'),
+ ('company_id', '=', self.company2.id),
+ ('res_id', '=', 'res.partner,%s' % self.demo_user.partner_id.id),
+ ('value_reference', '=', 'product.pricelist,%s' % self.c2_pl.id),
+ ])
+ self.assertEqual(len(irp1 + irp2), 2, "Ensure there is an `ir.property` for demo partner for every company, and that the pricelist is the company specific one.")
+ simulate_frontend_context(self)
+ # ---------------------------------- IR.PROPERTY -------------------------------------
+ # id | name | res_id | company_id | value_reference
+ # ------------------------------------------------------------------------------------
+ # 1 | 'property_product_pricelist' | | 1 | product.pricelist,1
+ # 2 | 'property_product_pricelist' | | 2 | product.pricelist,2
+ # 3 | 'property_product_pricelist' | res.partner,8 | 1 | product.pricelist,10
+ # 4 | 'property_product_pricelist' | res.partner,8 | 2 | product.pricelist,11
+
+ def test_property_product_pricelist_multi_company(self):
+ ''' Test that the `property_product_pricelist` of `res.partner` is read
+ for the company of the website and not the current user company.
+ This is the case if the user visit a website for which the company
+ is not the same as its user's company.
+
+ Here, as demo user (company1), we will visit website1 (company2).
+ It should return the ir.property for demo user for company2 and not
+ for the company1 as we should get the website's company pricelist
+ and not the demo user's current company pricelist.
+ '''
+ # First check: It should return ir.property,4 as company_id is
+ # website.company_id and not env.user.company_id
+ company_id = self.website.company_id.id
+ partner = self.demo_user.partner_id.with_context(force_company=company_id)
+ demo_pl = partner.property_product_pricelist
+ self.assertEqual(demo_pl, self.c2_pl)
+
+ # Second thing to check: It should not error in read right access error
+ # Indeed, the ir.rule for pricelists rights about company should allow to
+ # also read a pricelist from another company if that company is the one
+ # from the currently visited website.
+ self.env(user=self.env.ref('base.user_demo'))['product.pricelist'].browse(demo_pl.id).name