Skip to content
Permalink
Browse files

[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.
  • Loading branch information...
rdeodoo committed Mar 12, 2019
1 parent 2d97094 commit a5a5a57e482ccecb3e18abf7f04778ffe3c63d38
@@ -305,6 +305,7 @@ def _price_get_multi(self, pricelist, products_by_qty_by_partner):
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.
@@ -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):
@@ -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})
@@ -30,4 +30,5 @@
'qweb': ['static/src/xml/*.xml'],
'installable': True,
'application': True,
'uninstall_hook': 'uninstall_hook',
}
@@ -91,6 +91,17 @@ def _get_website_pricelists_domain(self, website_id):
'|', ('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"
@@ -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 @@
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 @@ def test_get_pricelist_available_show_with_auto_property(self):
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

1 comment on commit a5a5a57

@rdeodoo

This comment has been minimized.

Copy link
Contributor Author

commented on a5a5a57 Mar 18, 2019

Coming from #28301

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