[FIX] website(_sale): allow to use and access website pricelist

Before this commit, if the website's company was different than the user
company, the ecommerce would use the user's company instead of its own
pricelists.

Step to reproduce:
  - Set company2 to website1
  - Create a pricelist1 for company1 and a pricelist2 for company2
  - With an user from company1, visit the website1.
  - The pricelist used will be the one from the user, pricelist1 and not the
    pricelist2 from company2 which is the website1 company.
This commit is contained in:
Romain Derie
2019-03-14 10:28:02 +00:00
parent 2d970942ba
commit a5a5a57e48
7 changed files with 157 additions and 4 deletions
@@ -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.
+9 -1
View File
@@ -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):
+13 -1
View File
@@ -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})
+1
View File
@@ -30,4 +30,5 @@
'qweb': ['static/src/xml/*.xml'],
'installable': True,
'application': True,
'uninstall_hook': 'uninstall_hook',
}
+11
View File
@@ -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"
+26 -1
View File
@@ -52,6 +52,31 @@
<field name="implied_ids" eval="[(4, ref('sale.group_delivery_invoice_address'))]"/>
</record>
<!--
Multi-company/Multi-website compliant:
We can't add a condition on domain_force without losing `product`
ir.rule domain_force. It is better to disabled them to be able to
reenable them on `website_sale` uninstall.
Don't override domain_force or we will need to hardcode the original
domain in `uninstall_hook` rather than just reenabling records.
-->
<record id="product.product_pricelist_comp_rule" model="ir.rule">
<field name="active" eval="False"/>
</record>
<record id="product.product_pricelist_item_comp_rule" model="ir.rule">
<field name="active" eval="False"/>
</record>
<record id="product_pricelist_comp_rule" model="ir.rule">
<field name="name">product pricelist company rule</field>
<field name="model_id" ref="product.model_product_pricelist"/>
<field name="global" eval="True"/>
<field name="domain_force">[('company_id','in',(False,user.company_id.id,website.company_id.id))]</field>
</record>
<record id="product_pricelist_item_comp_rule" model="ir.rule">
<field name="name">product pricelist item company rule</field>
<field name="model_id" ref="product.model_product_pricelist_item"/>
<field name="global" eval="True"/>
<field name="domain_force">[('company_id','in',(False,user.company_id.id,website.company_id.id))]</field>
</record>
</odoo>
@@ -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