diff --git a/addons/event_booth_sale/models/event_booth_category.py b/addons/event_booth_sale/models/event_booth_category.py index c128a044c33..7c33c93f6fc 100644 --- a/addons/event_booth_sale/models/event_booth_category.py +++ b/addons/event_booth_sale/models/event_booth_category.py @@ -45,7 +45,7 @@ class EventBoothCategory(models.Model): for category in self: product = category.product_id list_price = product.list_price + product.price_extra - discount = (list_price - product.price) / list_price if list_price else 0.0 + discount = (list_price - product._get_contextual_price()) / list_price if list_price else 0.0 category.price_reduce = (1.0 - discount) * category.price def _init_column(self, column_name): diff --git a/addons/event_sale/models/event_ticket.py b/addons/event_sale/models/event_ticket.py index 99995aab551..cf63a780bef 100644 --- a/addons/event_sale/models/event_ticket.py +++ b/addons/event_sale/models/event_ticket.py @@ -44,7 +44,7 @@ class EventTemplateTicket(models.Model): ticket.description = False # TODO clean this feature in master - # Feature broken by design, depending on the hacky `price` field on products + # Feature broken by design, depending on the hacky `_get_contextual_price` field on products # context_dependent, core part of the pricelist mess # This field usage should be restricted to the UX, and any use in effective # price computation should be replaced by clear calls to the pricelist API @@ -56,7 +56,9 @@ class EventTemplateTicket(models.Model): # TODO drop price field usage # and ideally remove this whole price_reduce logic # seems strange to not apply pricelist logic but still use pricelist discount... - discount = (product.lst_price - product.price) / product.lst_price if product.lst_price else 0.0 + discount = ( + product.lst_price - product._get_contextual_price() + ) / product.lst_price if product.lst_price else 0.0 ticket.price_reduce = (1.0 - discount) * ticket.price def _init_column(self, column_name): diff --git a/addons/point_of_sale/static/tests/tours/point_of_sale.js b/addons/point_of_sale/static/tests/tours/point_of_sale.js index f86d2d41c78..45cdcec67a4 100644 --- a/addons/point_of_sale/static/tests/tours/point_of_sale.js +++ b/addons/point_of_sale/static/tests/tours/point_of_sale.js @@ -13,35 +13,23 @@ odoo.define('point_of_sale.tour.pricelist', function (require) { } } - function _build_pricelist_context (pricelist, quantity, date) { - return { - pricelist: pricelist.id, - quantity: quantity, - }; - } - - function compare_backend_frontend (product, pricelist_name, quantity) { + function assertProductPrice(product, pricelist_name, quantity, expected_price) { return function () { var pricelist = _.findWhere(posmodel.pricelists, {name: pricelist_name}); var frontend_price = product.get_price(pricelist, quantity); - // ORM applies digits= on non-stored computed field when - // reading. It does not however truncate like it does when - // storing the field. frontend_price = round_di(frontend_price, posmodel.dp['Product Price']); - var context = _build_pricelist_context(pricelist, quantity); - return rpc.query({model: 'product.product', method: 'read', args: [[product.id], ['price']], context: context}) - .then(function (backend_result) { - var debug_info = _.extend(context, { - product: product.id, - product_display_name: product.display_name, - pricelist_name: pricelist.name, - }); - var backend_price = backend_result[0].price; - assert(frontend_price === backend_price, - JSON.stringify(debug_info) + ' DOESN\'T MATCH -> ' + backend_price + ' (backend) != ' + frontend_price + ' (frontend)'); - return Promise.resolve(); - }); + var diff = Math.abs( expected_price - frontend_price ); + + assert(diff < 0.001, + JSON.stringify({ + product: product.id, + product_display_name: product.display_name, + pricelist_name: pricelist_name, + quantity: quantity + }) + ' DOESN\'T MATCH -> ' + expected_price + ' != ' + frontend_price); + + return Promise.resolve(); }; } @@ -60,27 +48,27 @@ odoo.define('point_of_sale.tour.pricelist', function (require) { var product_letter_tray = posmodel.db.search_product_in_category(0, 'Letter Tray')[0]; var product_whiteboard = posmodel.db.search_product_in_category(0, 'Whiteboard')[0]; - compare_backend_frontend(product_letter_tray, 'Public Pricelist', 0, undefined)() - .then(compare_backend_frontend(product_letter_tray, 'Public Pricelist', 1, undefined)) - .then(compare_backend_frontend(product_letter_tray, 'Fixed', 1, undefined)) - .then(compare_backend_frontend(product_wall_shelf, 'Fixed', 1, undefined)) - .then(compare_backend_frontend(product_small_shelf, 'Fixed', 1, undefined)) - .then(compare_backend_frontend(product_wall_shelf, 'Percentage', 1, undefined)) - .then(compare_backend_frontend(product_small_shelf, 'Percentage', 1, undefined)) - .then(compare_backend_frontend(product_magnetic_board, 'Percentage', 1, undefined)) - .then(compare_backend_frontend(product_wall_shelf, 'Formula', 1, undefined)) - .then(compare_backend_frontend(product_small_shelf, 'Formula', 1, undefined)) - .then(compare_backend_frontend(product_magnetic_board, 'Formula', 1, undefined)) - .then(compare_backend_frontend(product_monitor_stand, 'Formula', 1, undefined)) - .then(compare_backend_frontend(product_desk_pad, 'Formula', 1, undefined)) - .then(compare_backend_frontend(product_wall_shelf, 'min_quantity ordering', 1, undefined)) - .then(compare_backend_frontend(product_wall_shelf, 'min_quantity ordering', 2, undefined)) - .then(compare_backend_frontend(product_letter_tray, 'Category vs no category', 1, undefined)) - .then(compare_backend_frontend(product_letter_tray, 'Category', 1, undefined)) - .then(compare_backend_frontend(product_wall_shelf, 'Product template', 1, undefined)) - .then(compare_backend_frontend(product_wall_shelf, 'Dates', 1, undefined)) - .then(compare_backend_frontend(product_small_shelf, 'Pricelist base rounding', 1, undefined)) - .then(compare_backend_frontend(product_whiteboard, 'Public Pricelist', 1, undefined)) + assertProductPrice(product_letter_tray, 'Public Pricelist', 0, 4.8)() + .then(assertProductPrice(product_letter_tray, 'Public Pricelist', 1, 4.8)) + .then(assertProductPrice(product_letter_tray, 'Fixed', 1, 1)) + .then(assertProductPrice(product_wall_shelf, 'Fixed', 1, 2)) + .then(assertProductPrice(product_small_shelf, 'Fixed', 1, 13.95)) + .then(assertProductPrice(product_wall_shelf, 'Percentage', 1, 0)) + .then(assertProductPrice(product_small_shelf, 'Percentage', 1, 0.03)) + .then(assertProductPrice(product_magnetic_board, 'Percentage', 1, 1.98)) + .then(assertProductPrice(product_wall_shelf, 'Formula', 1, 6.86)) + .then(assertProductPrice(product_small_shelf, 'Formula', 1, 2.99)) + .then(assertProductPrice(product_magnetic_board, 'Formula', 1, 11.98)) + .then(assertProductPrice(product_monitor_stand, 'Formula', 1, 8.19)) + .then(assertProductPrice(product_desk_pad, 'Formula', 1, 6.98)) + .then(assertProductPrice(product_wall_shelf, 'min_quantity ordering', 1, 2)) + .then(assertProductPrice(product_wall_shelf, 'min_quantity ordering', 2, 1)) + .then(assertProductPrice(product_letter_tray, 'Category vs no category', 1, 2)) + .then(assertProductPrice(product_letter_tray, 'Category', 1, 2)) + .then(assertProductPrice(product_wall_shelf, 'Product template', 1, 1)) + .then(assertProductPrice(product_wall_shelf, 'Dates', 1, 2)) + .then(assertProductPrice(product_small_shelf, 'Pricelist base rounding', 1, 13.95)) + .then(assertProductPrice(product_whiteboard, 'Public Pricelist', 1, 3.2)) .then(function () { $('.pos').addClass('done-testing'); }); diff --git a/addons/point_of_sale/tests/test_js.py b/addons/point_of_sale/tests/test_js.py index ae851a740bc..f41b65c971e 100644 --- a/addons/point_of_sale/tests/test_js.py +++ b/addons/point_of_sale/tests/test_js.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -import odoo.tests +from odoo.tests import tagged, HttpCase -@odoo.tests.tagged("post_install", "-at_install") -class WebSuite(odoo.tests.HttpCase): +@tagged("post_install", "-at_install") +class WebSuite(HttpCase): def setUp(self): super().setUp() env = self.env(user=self.env.ref('base.user_admin')) diff --git a/addons/product/models/product.py b/addons/product/models/product.py index 14206ca31f8..de11cb95312 100644 --- a/addons/product/models/product.py +++ b/addons/product/models/product.py @@ -75,10 +75,6 @@ class ProductProduct(models.Model): _inherit = ['mail.thread', 'mail.activity.mixin'] _order = 'priority desc, default_code, name, id' - # price: total price, context dependent (partner, pricelist, quantity) - price = fields.Float( - 'Price', compute='_compute_product_price', - digits='Product Price', inverse='_set_product_price') # price_extra: catalog extra value only, sum of variant extra attributes price_extra = fields.Float( 'Variant Price Extra', compute='_compute_product_price_extra', @@ -234,41 +230,6 @@ class ProductProduct(models.Model): def _compute_is_product_variant(self): self.is_product_variant = True - @api.depends_context('pricelist', 'quantity', 'uom', 'date', 'no_variant_attributes_price_extra') - def _compute_product_price(self): - prices = {} - pricelist_id_or_name = self._context.get('pricelist') - if pricelist_id_or_name: - pricelist = None - - # Support context pricelists specified as list, display_name or ID for compatibility - if isinstance(pricelist_id_or_name, list): - pricelist_id_or_name = pricelist_id_or_name[0] - if isinstance(pricelist_id_or_name, str): - pricelist_name_search = self.env['product.pricelist'].name_search(pricelist_id_or_name, operator='=', limit=1) - if pricelist_name_search: - pricelist = self.env['product.pricelist'].browse([pricelist_name_search[0][0]]) - elif isinstance(pricelist_id_or_name, int): - pricelist = self.env['product.pricelist'].browse(pricelist_id_or_name) - - if pricelist: - quantity = self.env.context.get('quantity', 1.0) - uom = self.env['uom.uom'].browse(self.env.context.get('uom')) - date = self.env.context.get('date') - prices = pricelist._get_products_price(self, quantity, uom=uom, date=date) - - for product in self: - product.price = prices.get(product.id, 0.0) - - def _set_product_price(self): - for product in self: - if self._context.get('uom'): - value = self.env['uom.uom'].browse(self._context['uom'])._compute_price(product.price, product.uom_id) - else: - value = product.price - value -= product.price_extra - product.write({'list_price': value}) - @api.onchange('lst_price') def _set_product_lst_price(self): for product in self: @@ -750,6 +711,18 @@ class ProductProduct(models.Model): (tmpl_to_deactivate + tmpl_to_activate).toggle_active() return result + def _get_contextual_price(self): + self.ensure_one() + # YTI TODO: During website_sale cleaning, we should get rid of those crappy context thing + if not self._context.get('pricelist'): + return 0.0 + pricelist = self.env['product.pricelist'].browse(self._context.get('pricelist')) + + quantity = self.env.context.get('quantity', 1.0) + uom = self.env['uom.uom'].browse(self.env.context.get('uom')) + date = self.env.context.get('date') + return pricelist._get_product_price(self, quantity, uom=uom, date=date) + class ProductPackaging(models.Model): _name = "product.packaging" diff --git a/addons/product/models/product_template.py b/addons/product/models/product_template.py index 07a05bea113..90b1267f006 100644 --- a/addons/product/models/product_template.py +++ b/addons/product/models/product_template.py @@ -69,11 +69,6 @@ class ProductTemplate(models.Model): cost_currency_id = fields.Many2one( 'res.currency', 'Cost Currency', compute='_compute_cost_currency_id') - # price fields - # price: total template price, context dependent (pricelist, quantity) - price = fields.Float( - 'Price', compute='_compute_template_price', inverse='_set_template_price', - digits='Product Price') # list_price: catalog price, user defined list_price = fields.Float( 'Sales Price', default=1.0, @@ -99,10 +94,6 @@ class ProductTemplate(models.Model): sale_ok = fields.Boolean('Can be Sold', default=True) purchase_ok = fields.Boolean('Can be Purchased', default=True) - # TODO VFE remove this search magic & the price field? - pricelist_id = fields.Many2one( - 'product.pricelist', 'Pricelist', store=False, - help='Technical field. Used for searching on pricelists, not stored in database.') uom_id = fields.Many2one( 'uom.uom', 'Unit of Measure', default=_get_default_uom_id, required=True, @@ -189,47 +180,6 @@ class ProductTemplate(models.Model): def _compute_cost_currency_id(self): self.cost_currency_id = self.env.company.currency_id.id - @api.depends_context('pricelist', 'partner', 'quantity', 'uom', 'date', 'company') - def _compute_template_price(self): - prices = self._compute_template_price_no_inverse() - for template in self: - template.price = prices.get(template.id, 0.0) - - def _compute_template_price_no_inverse(self): - """The _compute_template_price writes the 'list_price' field with an inverse method - This method allows computing the price without writing the 'list_price' - """ - prices = {} - pricelist_id_or_name = self._context.get('pricelist') - if pricelist_id_or_name: - pricelist = None - - # Support context pricelists specified as list, display_name or ID for compatibility - if isinstance(pricelist_id_or_name, list): - pricelist_id_or_name = pricelist_id_or_name[0] - if isinstance(pricelist_id_or_name, str): - pricelist_data = self.env['product.pricelist'].name_search(pricelist_id_or_name, operator='=', limit=1) - if pricelist_data: - pricelist = self.env['product.pricelist'].browse(pricelist_data[0][0]) - elif isinstance(pricelist_id_or_name, int): - pricelist = self.env['product.pricelist'].browse(pricelist_id_or_name) - - if pricelist: - quantity = self.env.context.get('quantity', 1.0) - uom = self.env['uom.uom'].browse(self.env.context.get('uom')) - date = self.env.context.get('date') - prices = pricelist._get_products_price(self, quantity, uom=uom, date=date) - - return prices - - def _set_template_price(self): - if self._context.get('uom'): - for template in self: - value = self.env['uom.uom'].browse(self._context['uom'])._compute_price(template.price, template.uom_id) - template.write({'list_price': value}) - else: - self.write({'list_price': self.price}) - @api.depends_context('company') @api.depends('product_variant_ids', 'product_variant_ids.standard_price') def _compute_standard_price(self): @@ -1332,3 +1282,15 @@ class ProductTemplate(models.Model): 'label': _('Import Template for Products'), 'template': '/product/static/xls/product_template.xls' }] + + def _get_contextual_price(self): + self.ensure_one() + # YTI TODO: During website_sale cleaning, we should get rid of those crappy context thing + if not self._context.get('pricelist'): + return 0.0 + pricelist = self.env['product.pricelist'].browse(self._context.get('pricelist')) + + quantity = self.env.context.get('quantity', 1.0) + uom = self.env['uom.uom'].browse(self.env.context.get('uom')) + date = self.env.context.get('date') + return pricelist._get_product_price(self, quantity, uom=uom, date=date) diff --git a/addons/product/tests/test_pricelist.py b/addons/product/tests/test_pricelist.py index e097532a7e5..ad2fd02e75f 100644 --- a/addons/product/tests/test_pricelist.py +++ b/addons/product/tests/test_pricelist.py @@ -12,8 +12,10 @@ class TestPricelist(TransactionCase): self.datacard = self.env['product.product'].create({'name': 'Office Lamp'}) self.usb_adapter = self.env['product.product'].create({'name': 'Office Chair'}) self.uom_ton = self.env.ref('uom.product_uom_ton') - self.uom_unit_id = self.ref('uom.product_uom_unit') - self.uom_dozen_id = self.ref('uom.product_uom_dozen') + self.uom_unit = self.env.ref('uom.product_uom_unit') + self.uom_unit_id = self.uom_unit.id + self.uom_dozen = self.env.ref('uom.product_uom_dozen') + self.uom_dozen_id = self.uom_dozen.id self.uom_kgm_id = self.ref('uom.product_uom_kgm') self.public_pricelist = self.env.ref('product.list0') @@ -37,31 +39,23 @@ class TestPricelist(TransactionCase): def test_10_discount(self): # Make sure the price using a pricelist is the same than without after # applying the computation manually - context = {} - public_context = dict(context, pricelist=self.public_pricelist.id) - pricelist_context = dict(context, pricelist=self.sale_pricelist_id.id) + self.assertEqual( + self.public_pricelist._get_product_price(self.usb_adapter, 1.0)*0.9, + self.sale_pricelist_id._get_product_price(self.usb_adapter, 1.0)) - usb_adapter_without_pricelist = self.usb_adapter.with_context(public_context) - usb_adapter_with_pricelist = self.usb_adapter.with_context(pricelist_context) - self.assertEqual(usb_adapter_with_pricelist.price, usb_adapter_without_pricelist.price*0.9) + self.assertEqual( + self.public_pricelist._get_product_price(self.datacard, 1.0)-0.5, + self.sale_pricelist_id._get_product_price(self.datacard, 1.0)) - datacard_without_pricelist = self.datacard.with_context(public_context) - datacard_with_pricelist = self.datacard.with_context(pricelist_context) - self.assertEqual(datacard_with_pricelist.price, datacard_without_pricelist.price-0.5) + self.assertAlmostEqual( + self.sale_pricelist_id._get_product_price(self.usb_adapter, 1.0, uom=self.uom_unit)*12, + self.sale_pricelist_id._get_product_price(self.usb_adapter, 1.0, uom=self.uom_dozen)) - # Make sure that changing the unit of measure does not break the unit - # price (after converting) - unit_context = dict(context, pricelist=self.sale_pricelist_id.id, uom=self.uom_unit_id) - dozen_context = dict(context, pricelist=self.sale_pricelist_id.id, uom=self.uom_dozen_id) - - usb_adapter_unit = self.usb_adapter.with_context(unit_context) - usb_adapter_dozen = self.usb_adapter.with_context(dozen_context) - self.assertAlmostEqual(usb_adapter_unit.price*12, usb_adapter_dozen.price) - datacard_unit = self.datacard.with_context(unit_context) - datacard_dozen = self.datacard.with_context(dozen_context) # price_surcharge applies to product default UoM, here "Units", so surcharge will be multiplied - self.assertAlmostEqual(datacard_unit.price*12, datacard_dozen.price) + self.assertAlmostEqual( + self.sale_pricelist_id._get_product_price(self.datacard, 1.0, uom=self.uom_unit)*12, + self.sale_pricelist_id._get_product_price(self.datacard, 1.0, uom=self.uom_dozen)) def test_20_pricelist_uom(self): # Verify that the pricelist rules are correctly using the product's default UoM diff --git a/addons/product/tests/test_product_attribute_value_config.py b/addons/product/tests/test_product_attribute_value_config.py index d582dad7c4f..5dc4678726a 100644 --- a/addons/product/tests/test_product_attribute_value_config.py +++ b/addons/product/tests/test_product_attribute_value_config.py @@ -17,7 +17,7 @@ class TestProductAttributeValueCommon(TransactionCase): cls.computer = cls.env['product.template'].create({ 'name': 'Super Computer', - 'price': 2000, + 'list_price': 2000, }) cls._add_ssd_attribute() diff --git a/addons/product/tests/test_product_pricelist.py b/addons/product/tests/test_product_pricelist.py index 27286be43f8..5f799998798 100644 --- a/addons/product/tests/test_product_pricelist.py +++ b/addons/product/tests/test_product_pricelist.py @@ -160,74 +160,74 @@ class TestProductPricelist(TransactionCase): # I check sale price of Customizable Desk context = {} context.update({'pricelist': self.customer_pricelist.id, 'quantity': 1}) - ipad_retina_display = self.ipad_retina_display.with_context(context) - msg = "Wrong sale price: Customizable Desk. should be %s instead of %s" % (ipad_retina_display.price, (ipad_retina_display.lst_price-ipad_retina_display.lst_price*(0.10))) - self.assertEqual(float_compare(ipad_retina_display.price, (ipad_retina_display.lst_price-ipad_retina_display.lst_price*(0.10)), precision_digits=2), 0, msg) + product = self.ipad_retina_display + price = self.customer_pricelist._get_product_price(product, quantity=1.0) + msg = "Wrong sale price: Customizable Desk. should be %s instead of %s" % (price, (product.lst_price-product.lst_price*(0.10))) + self.assertEqual(float_compare( + price, (product.lst_price-product.lst_price*(0.10)), precision_digits=2), 0, msg) # I check sale price of Laptop. - laptop_E5023 = self.laptop_E5023.with_context(context) - msg = "Wrong sale price: Laptop. should be %s instead of %s" % (laptop_E5023.price, (laptop_E5023.lst_price + 1)) - self.assertEqual(float_compare(laptop_E5023.price, laptop_E5023.lst_price + 1, precision_digits=2), 0, msg) + product = self.laptop_E5023 + price = self.customer_pricelist._get_product_price(product, quantity=1.0) + msg = "Wrong sale price: Laptop. should be %s instead of %s" % (price, (product.lst_price + 1)) + self.assertEqual(float_compare(price, product.lst_price + 1, precision_digits=2), 0, msg) # I check sale price of IT component. - apple_headphones = self.apple_in_ear_headphones.with_context(context) - msg = "Wrong sale price: IT component. should be %s instead of %s" % (apple_headphones.price, apple_headphones.lst_price) - self.assertEqual(float_compare(apple_headphones.price, apple_headphones.lst_price, precision_digits=2), 0, msg) + product = self.apple_in_ear_headphones + price = self.customer_pricelist._get_product_price(product, quantity=1.0) + msg = "Wrong sale price: IT component. should be %s instead of %s" % (price, product.lst_price) + self.assertEqual(float_compare(price, product.lst_price, precision_digits=2), 0, msg) # I check sale price of IT component if more than 3 Unit. context.update({'quantity': 5}) - laptop_S3450 = self.laptop_S3450.with_context(context) - msg = "Wrong sale price: IT component if more than 3 Unit. should be %s instead of %s" % (laptop_S3450.price, (laptop_S3450.lst_price-laptop_S3450.lst_price*(0.05))) - self.assertEqual(float_compare(laptop_S3450.price, laptop_S3450.lst_price-laptop_S3450.lst_price*(0.05), precision_digits=2), 0, msg) + product = self.laptop_S3450 + price = self.customer_pricelist._get_product_price(product, quantity=5.0) + msg = "Wrong sale price: IT component if more than 3 Unit. should be %s instead of %s" % (price, (product.lst_price-product.lst_price*(0.05))) + self.assertEqual(float_compare(price, product.lst_price-product.lst_price*(0.05), precision_digits=2), 0, msg) # I check sale price of LCD Monitor. - context.update({'quantity': 1}) - ipad_mini = self.ipad_mini.with_context(context) - msg = "Wrong sale price: LCD Monitor. should be %s instead of %s" % (ipad_mini.price, ipad_mini.lst_price) - self.assertEqual(float_compare(ipad_mini.price, ipad_mini.lst_price, precision_digits=2), 0, msg) + product = self.ipad_mini + price = self.customer_pricelist._get_product_price(product, quantity=1.0) + msg = "Wrong sale price: LCD Monitor. should be %s instead of %s" % (price, product.lst_price) + self.assertEqual(float_compare(price, product.lst_price, precision_digits=2), 0, msg) # I check sale price of LCD Monitor on end of year. - context.update({'quantity': 1, 'date': '2011-12-31'}) - ipad_mini = self.ipad_mini.with_context(context) - msg = "Wrong sale price: LCD Monitor on end of year. should be %s instead of %s" % (ipad_mini.price, ipad_mini.lst_price-ipad_mini.lst_price*(0.30)) - self.assertEqual(float_compare(ipad_mini.price, ipad_mini.lst_price-ipad_mini.lst_price*(0.30), precision_digits=2), 0, msg) + price = self.customer_pricelist._get_product_price(product, quantity=1.0, date='2011-12-31') + msg = "Wrong sale price: LCD Monitor on end of year. should be %s instead of %s" % (price, product.lst_price-product.lst_price*(0.30)) + self.assertEqual(float_compare(price, product.lst_price-product.lst_price*(0.30), precision_digits=2), 0, msg) + + # Supplierinfo pricing # I check cost price of LCD Monitor. - context.update({'quantity': 1, 'date': False, 'partner_id': self.res_partner_4.id}) - ipad_mini = self.ipad_mini.with_context(context) - partner = self.res_partner_4.with_context(context) - msg = "Wrong cost price: LCD Monitor. should be 790 instead of %s" % ipad_mini._select_seller(partner_id=partner, quantity=1.0).price - self.assertEqual(float_compare(ipad_mini._select_seller(partner_id=partner, quantity=1.0).price, 790, precision_digits=2), 0, msg) + price = product._select_seller(partner_id=self.res_partner_4, quantity=1.0).price + msg = "Wrong cost price: LCD Monitor. should be 790 instead of %s" % price + self.assertEqual(float_compare(price, 790, precision_digits=2), 0, msg) # I check cost price of LCD Monitor if more than 3 Unit. - context.update({'quantity': 3}) - ipad_mini = self.ipad_mini.with_context(context) - partner = self.res_partner_4.with_context(context) - msg = "Wrong cost price: LCD Monitor if more than 3 Unit.should be 785 instead of %s" % ipad_mini._select_seller(partner_id=partner, quantity=3.0).price - self.assertEqual(float_compare(ipad_mini._select_seller(partner_id=partner, quantity=3.0).price, 785, precision_digits=2), 0, msg) + price = product._select_seller(partner_id=self.res_partner_4, quantity=3.0).price + msg = "Wrong cost price: LCD Monitor if more than 3 Unit.should be 785 instead of %s" % price + self.assertEqual(float_compare(price, 785, precision_digits=2), 0, msg) # Check if the pricelist is applied at precise datetime + product = self.monitor + price = self.customer_pricelist._get_product_price(product, quantity=1.0, date='2020-04-05 08:00:00') context.update({'quantity': 1, 'date': datetime.strptime('2020-04-05 08:00:00', '%Y-%m-%d %H:%M:%S')}) - monitor = self.monitor.with_context(context) - partner = self.res_partner_4.with_context(context) - msg = "Wrong cost price: LCD Monitor. should be 1000 instead of %s" % monitor._select_seller( - partner_id=partner, quantity=1.0).price + msg = "Wrong cost price: LCD Monitor. should be 1000 instead of %s" % price self.assertEqual( - float_compare(monitor.price, monitor.lst_price, precision_digits=2), 0, + float_compare(price, product.lst_price, precision_digits=2), 0, msg) - context.update({'quantity': 1, 'date': datetime.strptime('2020-04-06 10:00:00', '%Y-%m-%d %H:%M:%S')}) - monitor = self.monitor.with_context(context) - msg = "Wrong cost price: LCD Monitor. should be 500 instead of %s" % monitor._select_seller( - partner_id=partner, quantity=1.0).price + price = self.customer_pricelist._get_product_price(product, quantity=1.0, date='2020-04-06 10:00:00') + msg = "Wrong cost price: LCD Monitor. should be 500 instead of %s" % price self.assertEqual( - float_compare(monitor.price, monitor.lst_price/2, precision_digits=2), 0, + float_compare(price, product.lst_price/2, precision_digits=2), 0, msg) # Check if the price is different when we change the pricelist - product = self.product_multi_price.product_tmpl_id.with_context(pricelist=self.customer_pricelist.id, quantity=1) - msg = "Wrong price: Multi Product Price. should be 99 instead of %s" % product.price - self.assertEqual(float_compare(product.price, 99, precision_digits=2), 0) + product = self.product_multi_price + price = self.customer_pricelist._get_product_price(product, quantity=1.0) + msg = "Wrong price: Multi Product Price. should be 99 instead of %s" % price + self.assertEqual(float_compare(price, 99, precision_digits=2), 0) - product = self.product_multi_price.product_tmpl_id.with_context(pricelist=self.business_pricelist.id, quantity=1) - msg = "Wrong price: Multi Product Price. should be 50 instead of %s" % product.price - self.assertEqual(float_compare(product.price, 50, precision_digits=2), 0) + price = self.business_pricelist._get_product_price(product, quantity=1.0) + msg = "Wrong price: Multi Product Price. should be 50 instead of %s" % price + self.assertEqual(float_compare(price, 50, precision_digits=2), 0) diff --git a/addons/product/views/product_views.xml b/addons/product/views/product_views.xml index 7129b6a70e2..592b4a45012 100644 --- a/addons/product/views/product_views.xml +++ b/addons/product/views/product_views.xml @@ -161,7 +161,6 @@ - @@ -337,7 +336,6 @@ - diff --git a/addons/sale/models/account_move_line.py b/addons/sale/models/account_move_line.py index 5c983ac1414..f5562f77e19 100644 --- a/addons/sale/models/account_move_line.py +++ b/addons/sale/models/account_move_line.py @@ -182,12 +182,12 @@ class AccountMoveLine(models.Model): amount = (self.credit or 0.0) - (self.debit or 0.0) if self.product_id.expense_policy == 'sales_price': - return self.product_id.with_context( - partner=order.partner_id, - date_order=order.date_order, - pricelist=order.pricelist_id.id, - uom=self.product_uom_id.id - ).price + return order.pricelist_id._get_product_price( + self.product_id, + 1.0, + self.product_uom_id, + date=order.date_order, + ) uom_precision_digits = self.env['decimal.precision'].precision_get('Product Unit of Measure') if float_is_zero(unit_amount, precision_digits=uom_precision_digits): diff --git a/addons/sale/models/product_template.py b/addons/sale/models/product_template.py index 24035f903c3..a4a8992262b 100644 --- a/addons/sale/models/product_template.py +++ b/addons/sale/models/product_template.py @@ -201,8 +201,7 @@ class ProductTemplate(models.Model): display_image = True quantity = self.env.context.get('quantity', add_qty) - context = dict(self.env.context, quantity=quantity, pricelist=pricelist.id if pricelist else False) - product_template = self.with_context(context) + product_template = self combination = combination or product_template.env['product.template.attribute.value'] @@ -233,13 +232,19 @@ class ProductTemplate(models.Model): no_variant_attributes_price_extra=tuple(no_variant_attributes_price_extra) ) list_price = product.price_compute('list_price')[product.id] - price = product.price if pricelist else list_price + if pricelist: + price = pricelist._get_product_price(product, quantity) + else: + price = list_price display_image = bool(product.image_1920) display_name = product.display_name else: product_template = product_template.with_context(current_attributes_price_extra=[v.price_extra or 0.0 for v in combination]) list_price = product_template.price_compute('list_price')[product_template.id] - price = product_template.price if pricelist else list_price + if pricelist: + price = pricelist._get_product_price(product_template, quantity) + else: + price = list_price display_image = bool(product_template.image_1920) combination_name = combination._get_combination_name() diff --git a/addons/sale_product_configurator/views/templates.xml b/addons/sale_product_configurator/views/templates.xml index d0db1958f1a..919cd41e221 100644 --- a/addons/sale_product_configurator/views/templates.xml +++ b/addons/sale_product_configurator/views/templates.xml @@ -135,7 +135,7 @@ "widget": "monetary", "display_currency": (pricelist or product).currency_id }'/> - +

Option not available

diff --git a/addons/stock/views/product_views.xml b/addons/stock/views/product_views.xml index c5d9aa011f0..7a7b2986429 100644 --- a/addons/stock/views/product_views.xml +++ b/addons/stock/views/product_views.xml @@ -41,7 +41,7 @@ product.product - + @@ -71,7 +71,7 @@ primary - + @@ -199,10 +199,10 @@ product.product - + - + diff --git a/addons/website/tests/test_snippets.py b/addons/website/tests/test_snippets.py index 5c0f2dad7f3..b07c54cd684 100644 --- a/addons/website/tests/test_snippets.py +++ b/addons/website/tests/test_snippets.py @@ -3,13 +3,12 @@ from lxml import html -import odoo -import odoo.tests +from odoo.tests import HttpCase, tagged from odoo.addons.website.tools import MockRequest -@odoo.tests.common.tagged('post_install', '-at_install', 'website_snippets') -class TestSnippets(odoo.tests.HttpCase): +@tagged('post_install', '-at_install', 'website_snippets') +class TestSnippets(HttpCase): def test_01_empty_parents_autoremove(self): self.start_tour("/?enable_editor=1", "snippet_empty_parent_autoremove", login='admin') diff --git a/addons/website_sale/data/data.xml b/addons/website_sale/data/data.xml index bd08eccbe05..ccc5ef78c98 100644 --- a/addons/website_sale/data/data.xml +++ b/addons/website_sale/data/data.xml @@ -113,32 +113,32 @@ response = DynamicFilter._get_products('recently_sold_with', model.env.context) - display_name,description_sale,image_512,price:monetary + display_name,description_sale,image_512 Newest Products - display_name,description_sale,image_512,price:monetary + display_name,description_sale,image_512 Recently Sold Products - display_name,description_sale,image_512,price:monetary + display_name,description_sale,image_512 Recently Viewed Products - display_name,description_sale,image_512,price:monetary + display_name,description_sale,image_512 Accessories for Product True - display_name,description_sale,image_512,price:monetary + display_name,description_sale,image_512 Products Recently Sold With Product True diff --git a/addons/website_sale/data/product_snippet_template_data.xml b/addons/website_sale/data/product_snippet_template_data.xml index 5910394de1b..298b8b89f4a 100644 --- a/addons/website_sale/data/product_snippet_template_data.xml +++ b/addons/website_sale/data/product_snippet_template_data.xml @@ -283,7 +283,7 @@