[IMP] product: Remove price/pricelist_id field from template/variant

Purpose
=======

The fields are not used, don't work correctly and there is a specific
report to generate the product prices according to the pricelist and
the ordered quantities
This commit is contained in:
Yannick Tivisse
2021-12-23 15:30:23 +01:00
committed by Victor Feyens
parent 14308462e4
commit 9e99a9df46
22 changed files with 168 additions and 245 deletions
@@ -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):
+4 -2
View File
@@ -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):
@@ -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');
});
+3 -3
View File
@@ -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'))
+12 -39
View File
@@ -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"
+12 -50
View File
@@ -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)
+16 -22
View File
@@ -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
@@ -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()
+46 -46
View File
@@ -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)
-2
View File
@@ -161,7 +161,6 @@
<filter string="Can be Purchased" name="filter_to_purchase" domain="[('purchase_ok', '=', True)]"/>
<separator/>
<field string="Attributes" name="attribute_line_ids" groups="product.group_product_variant"/>
<field name="pricelist_id" context="{'pricelist': self}" filter_domain="[]" groups="product.group_product_pricelist"/>
<filter invisible="1" string="Late Activities" name="activities_overdue"
domain="[('my_activity_date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
help="Show all records which has next action date is before today"/>
@@ -337,7 +336,6 @@
<field name="standard_price" optional="show"/>
<field name="categ_id" optional="hide"/>
<field name="type" optional="hide" readonly="1"/>
<field name="price" invisible="not context.get('pricelist',False)"/>
<field name="uom_id" options="{'no_open': True, 'no_create': True}" groups="uom.group_uom" optional="show" readonly="1"/>
<field name="product_tmpl_id" invisible="1" readonly="1"/>
<field name="active" invisible="1"/>
+6 -6
View File
@@ -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):
+9 -4
View File
@@ -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()
@@ -135,7 +135,7 @@
"widget": "monetary",
"display_currency": (pricelist or product).currency_id
}'/>
<span class="js_raw_price d-none" t-esc="product.price"/>
<span class="js_raw_price d-none" t-esc="product._get_contextual_price()"/>
<p class="css_not_available_msg alert alert-warning">Option not available</p>
</td>
</tr>
+4 -4
View File
@@ -41,7 +41,7 @@
<field name="model">product.product</field>
<field name="inherit_id" ref="product.product_product_tree_view"/>
<field name="arch" type="xml">
<field name="price" position="after">
<field name="type" position="after">
<field name="qty_available" attrs="{'invisible':[('type', '!=', 'product')]}" optional="show" decoration-danger="virtual_available &lt; 0" decoration-warning="virtual_available == 0" decoration-bf="1"/>
<field name="virtual_available" attrs="{'invisible':[('type', '!=', 'product')]}" string="Forecasted Quantity" optional="show" decoration-danger="virtual_available &lt; 0" decoration-warning="virtual_available == 0"/>
</field>
@@ -71,7 +71,7 @@
<field name="mode">primary</field>
<field name="inherit_id" ref="product.product_template_search_view"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='pricelist_id']" position="after">
<xpath expr="//field[@name='attribute_line_ids']" position="after">
<separator/>
<field name="location_id" context="{'location': self}" filter_domain="[]"/>
<field name="warehouse_id" context="{'warehouse': self}" filter_domain="[]"/>
@@ -199,10 +199,10 @@
<field name="model">product.product</field>
<field name="inherit_id" ref="product.product_search_form_view"/>
<field name="arch" type="xml">
<field name="pricelist_id" position="before">
<filter name="activities_overdue" position="after">
<field name="location_id" options="{'no_create': True}" context="{'location': self}"/>
<field name="warehouse_id" context="{'warehouse': self}"/>
</field>
</filter>
</field>
</record>
+3 -4
View File
@@ -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')
+5 -5
View File
@@ -113,32 +113,32 @@ response = DynamicFilter._get_products('recently_sold_with', model.env.context)
<!-- Dynamic Filter -->
<record id="dynamic_filter_newest_products" model="website.snippet.filter">
<field name="filter_id" ref="website_sale.dynamic_snippet_newest_products_filter"/>
<field name="field_names">display_name,description_sale,image_512,price:monetary</field>
<field name="field_names">display_name,description_sale,image_512</field>
<field name="limit" eval="16"/>
<field name="name">Newest Products</field>
</record>
<record id="dynamic_filter_latest_sold_products" model="website.snippet.filter">
<field name="action_server_id" ref="website_sale.dynamic_snippet_latest_sold_products_action"/>
<field name="field_names">display_name,description_sale,image_512,price:monetary</field>
<field name="field_names">display_name,description_sale,image_512</field>
<field name="limit" eval="16"/>
<field name="name">Recently Sold Products</field>
</record>
<record id="dynamic_filter_latest_viewed_products" model="website.snippet.filter">
<field name="action_server_id" ref="website_sale.dynamic_snippet_latest_viewed_products_action"/>
<field name="field_names">display_name,description_sale,image_512,price:monetary</field>
<field name="field_names">display_name,description_sale,image_512</field>
<field name="limit" eval="16"/>
<field name="name">Recently Viewed Products</field>
</record>
<record id="dynamic_filter_cross_selling_accessories" model="website.snippet.filter">
<field name="action_server_id" ref="website_sale.dynamic_snippet_accessories_action"/>
<field name="field_names">display_name,description_sale,image_512,price:monetary</field>
<field name="field_names">display_name,description_sale,image_512</field>
<field name="limit" eval="16"/>
<field name="name">Accessories for Product</field>
<field name="product_cross_selling">True</field>
</record>
<record id="dynamic_filter_cross_selling_recently_sold_with" model="website.snippet.filter">
<field name="action_server_id" ref="website_sale.dynamic_snippet_recently_sold_with_action"/>
<field name="field_names">display_name,description_sale,image_512,price:monetary</field>
<field name="field_names">display_name,description_sale,image_512</field>
<field name="limit" eval="16"/>
<field name="name">Products Recently Sold With Product</field>
<field name="product_cross_selling">True</field>
@@ -283,7 +283,7 @@
</template>
<template id="price_dynamic_filter_template_product_product" name="Dynamic Product Filter Price">
<span t-esc="data['price']" class="font-weight-bold"
<span t-esc="record._get_contextual_price()" class="font-weight-bold"
t-options="{'widget': 'monetary', 'display_currency': website.currency_id}"/>
<del t-if="data.get('has_discounted_price')" class="text-danger ml-1 h6" style="white-space: nowrap;"
t-esc="data['list_price']" t-options="{'widget': 'monetary', 'display_currency': website.currency_id}"/>
@@ -19,13 +19,13 @@ class Product(models.Model):
base_unit_price = fields.Monetary("Price Per Unit", currency_field="currency_id", compute="_compute_base_unit_price")
base_unit_name = fields.Char(compute='_compute_base_unit_name', help='Displays the custom unit for the products if defined or the selected unit of measure otherwise.')
@api.depends('price', 'lst_price', 'base_unit_count')
@api.depends('lst_price', 'base_unit_count')
def _compute_base_unit_price(self):
for product in self:
if not product.id:
product.base_unit_price = 0
else:
product.base_unit_price = product.base_unit_count and (product.price or product.lst_price) / product.base_unit_count
product.base_unit_price = product.base_unit_count and product.lst_price / product.base_unit_count
@api.depends('uom_name', 'base_unit_id')
def _compute_base_unit_name(self):
@@ -71,10 +71,10 @@ class ProductTemplate(models.Model):
if len(template.product_variant_ids) == 1:
template.product_variant_ids.base_unit_id = template.base_unit_id
@api.depends('price', 'list_price', 'base_unit_count')
@api.depends('list_price', 'base_unit_count')
def _compute_base_unit_price(self):
for template in self:
template.base_unit_price = template.base_unit_count and (template.price or template.list_price) / template.base_unit_count
template.base_unit_price = template.base_unit_count and template.list_price / template.base_unit_count
@api.depends('uom_name', 'base_unit_id.name')
def _compute_base_unit_name(self):
@@ -27,7 +27,7 @@
<br/>
</t>
<b class="text-nowrap">
<t t-out="product['price']"/>
<t t-out="product._get_contextual_price()"/>
</b>
</div>
</div>
@@ -73,7 +73,7 @@ class TestWebsiteSaleProductAttributeValueConfig(TestSaleProductAttributeValueCo
test_product = self.env['product.template'].create({
'name': 'Test Product',
'price': 2000,
'list_price': 2000,
}).with_context(website_id=current_website.id)
# Add fixed price for pricelist
@@ -146,7 +146,7 @@ class TestWebsiteSaleProductPricelist(TestSaleProductAttributeValueCommon):
test_product = self.env['product.template'].create({
'name': 'Test Product',
'price': 110,
'list_price': 110,
'taxes_id': [(6, 0, [tax10.id])],
}).with_context(website_id=current_website.id)
@@ -3,6 +3,8 @@
from odoo.addons.website_sale_stock.tests.test_website_sale_stock_product_warehouse import TestWebsiteSaleStockProductWarehouse
from odoo.tests import tagged
class TestWishlistEmail(TestWebsiteSaleStockProductWarehouse):
@classmethod
@@ -62,9 +64,9 @@ class TestWishlistEmail(TestWebsiteSaleStockProductWarehouse):
# email notifications
Wishlist = self.env['product.wishlist']
wish_1 = Wishlist._add_to_wishlist(product_id=self.product_1.id, partner_id=self.partner.id, website_id=self.current_website.id, currency_id=self.currency.id, pricelist_id=self.pricelist.id, price=self.product_1.price)
wish_1 = Wishlist._add_to_wishlist(product_id=self.product_1.id, partner_id=self.partner.id, website_id=self.current_website.id, currency_id=self.currency.id, pricelist_id=self.pricelist.id, price=self.product_1.list_price)
self.assertEqual(wish_1.stock_notification, False)
wish_2 = Wishlist._add_to_wishlist(product_id=self.product_2.id, partner_id=self.partner.id, website_id=self.current_website.id, currency_id=self.currency.id, pricelist_id=self.pricelist.id, price=self.product_2.price)
wish_2 = Wishlist._add_to_wishlist(product_id=self.product_2.id, partner_id=self.partner.id, website_id=self.current_website.id, currency_id=self.currency.id, pricelist_id=self.pricelist.id, price=self.product_2.list_price)
self.assertEqual(wish_2.stock_notification, True)
# No email should be sent