[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:
committed by
Victor Feyens
parent
14308462e4
commit
9e99a9df46
@@ -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):
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
|
||||
@@ -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'))
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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', '<', 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"/>
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 < 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 < 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,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')
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user