Remove unit price to compute price list
Instead the value of unit price comes from the product and is defined on the price list.
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
* Define the value of 'unit_price' on price list
|
||||
* Remove unit price to compute price list
|
||||
* Remove party to compute price list
|
||||
|
||||
Version 6.8.0 - 2023-05-01
|
||||
|
||||
@@ -17,7 +17,7 @@ from .exceptions import FormulaError
|
||||
|
||||
|
||||
@total_ordering
|
||||
class Null:
|
||||
class Null(Decimal):
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, Null) or other is None:
|
||||
return True
|
||||
@@ -55,6 +55,12 @@ class PriceList(DeactivableMixin, ModelSQL, ModelView):
|
||||
('product_default', "Product Default"),
|
||||
], "Unit", required=True,
|
||||
help="The unit in which the quantity is expressed.")
|
||||
price = fields.Selection([
|
||||
(None, ""),
|
||||
('list_price', "List price"),
|
||||
('cost_price', "Cost Price"),
|
||||
], "Price",
|
||||
help="The value used for 'unit_price'.")
|
||||
lines = fields.One2Many(
|
||||
'product.price_list.line', 'price_list', "Lines",
|
||||
help="Add price formulas for different criteria.\n"
|
||||
@@ -72,28 +78,31 @@ class PriceList(DeactivableMixin, ModelSQL, ModelView):
|
||||
def default_unit(cls):
|
||||
return 'product_default'
|
||||
|
||||
def get_context_formula(
|
||||
self, product, unit_price, quantity, uom, pattern=None):
|
||||
def get_context_formula(self, product, quantity, uom, pattern=None):
|
||||
if product:
|
||||
cost_price = product.get_multivalue('cost_price') or Decimal('0')
|
||||
list_price = product.list_price_used
|
||||
if list_price is None:
|
||||
list_price = unit_price
|
||||
else:
|
||||
cost_price = Decimal('0')
|
||||
list_price = unit_price
|
||||
list_price = Null()
|
||||
if self.price == 'list_price':
|
||||
unit_price = list_price
|
||||
elif self.price == 'cost_price':
|
||||
unit_price = cost_price
|
||||
else:
|
||||
unit_price = Null()
|
||||
return {
|
||||
'names': {
|
||||
'unit_price': unit_price if unit_price is not None else Null(),
|
||||
'cost_price': cost_price if unit_price is not None else Null(),
|
||||
'list_price': list_price if unit_price is not None else Null(),
|
||||
'cost_price': cost_price if cost_price is not None else Null(),
|
||||
'list_price': list_price if list_price is not None else Null(),
|
||||
},
|
||||
}
|
||||
|
||||
def get_uom(self, product):
|
||||
return product.default_uom
|
||||
|
||||
def compute(self, product, unit_price, quantity, uom, pattern=None):
|
||||
def compute(self, product, quantity, uom, pattern=None):
|
||||
Uom = Pool().get('product.uom')
|
||||
|
||||
def parents(categories):
|
||||
@@ -114,14 +123,13 @@ class PriceList(DeactivableMixin, ModelSQL, ModelView):
|
||||
self.get_uom(product), round=False) if product else quantity
|
||||
|
||||
context = self.get_context_formula(
|
||||
product, unit_price, quantity, uom, pattern=pattern)
|
||||
product, quantity, uom, pattern=pattern)
|
||||
for line in self.lines:
|
||||
if line.match(pattern):
|
||||
unit_price = line.get_unit_price(**context)
|
||||
if isinstance(unit_price, Null):
|
||||
unit_price = None
|
||||
break
|
||||
return unit_price
|
||||
return unit_price
|
||||
|
||||
|
||||
class PriceListLine(sequence_ordered(), ModelSQL, ModelView, MatchMixin):
|
||||
@@ -169,7 +177,7 @@ class PriceListLine(sequence_ordered(), ModelSQL, ModelView, MatchMixin):
|
||||
return
|
||||
for line in lines:
|
||||
context = line.price_list.get_context_formula(
|
||||
product=None, unit_price=Decimal('0'), quantity=0, uom=None)
|
||||
product=None, quantity=0, uom=None)
|
||||
try:
|
||||
if not isinstance(line.get_unit_price(**context), Decimal):
|
||||
raise ValueError
|
||||
|
||||
@@ -5,6 +5,7 @@ from decimal import Decimal
|
||||
|
||||
from trytond.modules.company.tests import (
|
||||
CompanyTestMixin, create_company, set_company)
|
||||
from trytond.modules.product_price_list.price_list import Null
|
||||
from trytond.pool import Pool
|
||||
from trytond.tests.test_tryton import ModuleTestCase, with_transaction
|
||||
|
||||
@@ -13,6 +14,17 @@ class ProductPriceListTestCase(CompanyTestMixin, ModuleTestCase):
|
||||
'Test ProductPriceList module'
|
||||
module = 'product_price_list'
|
||||
|
||||
def test_null(self):
|
||||
"Test Null object"
|
||||
none = None
|
||||
self.assertEqual(Null() == Null(), True)
|
||||
self.assertEqual(Null() == none, True)
|
||||
self.assertEqual(Null() == 0, False)
|
||||
self.assertEqual(Null() < 1, True)
|
||||
self.assertEqual(Null() < 0, False)
|
||||
self.assertEqual(Null() + 1, Null())
|
||||
self.assertEqual(1 + Null(), Null())
|
||||
|
||||
@with_transaction()
|
||||
def test_price_list(self):
|
||||
'Test price_list'
|
||||
@@ -44,6 +56,7 @@ class ProductPriceListTestCase(CompanyTestMixin, ModuleTestCase):
|
||||
|
||||
price_list, = PriceList.create([{
|
||||
'name': 'Default Price List',
|
||||
'price': 'list_price',
|
||||
'lines': [('create', [{
|
||||
'quantity': 10.0,
|
||||
'product': variant.id,
|
||||
@@ -70,9 +83,7 @@ class ProductPriceListTestCase(CompanyTestMixin, ModuleTestCase):
|
||||
]
|
||||
for product, quantity, unit, result in tests:
|
||||
self.assertEqual(
|
||||
price_list.compute(
|
||||
product, product.list_price, quantity, unit),
|
||||
result)
|
||||
price_list.compute(product, quantity, unit), result)
|
||||
|
||||
@with_transaction()
|
||||
def test_price_list_category(self):
|
||||
@@ -106,6 +117,7 @@ class ProductPriceListTestCase(CompanyTestMixin, ModuleTestCase):
|
||||
|
||||
price_list, = PriceList.create([{
|
||||
'name': "Price List",
|
||||
'price': 'list_price',
|
||||
'lines': [('create', [{
|
||||
'category': category.id,
|
||||
'formula': 'unit_price * 0.8',
|
||||
@@ -114,23 +126,18 @@ class ProductPriceListTestCase(CompanyTestMixin, ModuleTestCase):
|
||||
}])],
|
||||
}])
|
||||
|
||||
self.assertEqual(
|
||||
price_list.compute(product, product.list_price, 1, unit),
|
||||
Decimal(8))
|
||||
self.assertEqual(price_list.compute(product, 1, unit), Decimal(8))
|
||||
|
||||
template.categories = []
|
||||
template.save()
|
||||
|
||||
self.assertEqual(
|
||||
price_list.compute(product, product.list_price, 1, unit),
|
||||
Decimal(10))
|
||||
price_list.compute(product, 1, unit), Decimal(10))
|
||||
|
||||
template.categories = [child_category]
|
||||
template.save()
|
||||
|
||||
self.assertEqual(
|
||||
price_list.compute(product, product.list_price, 1, unit),
|
||||
Decimal(8))
|
||||
self.assertEqual(price_list.compute(product, 1, unit), Decimal(8))
|
||||
|
||||
@with_transaction()
|
||||
def test_price_list_cost_price(self):
|
||||
@@ -162,16 +169,12 @@ class ProductPriceListTestCase(CompanyTestMixin, ModuleTestCase):
|
||||
}])],
|
||||
}])
|
||||
|
||||
self.assertEqual(
|
||||
price_list.compute(product, product.list_price, 1, unit),
|
||||
Decimal(0))
|
||||
self.assertEqual(price_list.compute(product, 1, unit), Decimal(0))
|
||||
|
||||
product.cost_price = Decimal(5)
|
||||
product.save()
|
||||
|
||||
self.assertEqual(
|
||||
price_list.compute(product, product.list_price, 1, unit),
|
||||
Decimal(6))
|
||||
self.assertEqual(price_list.compute(product, 1, unit), Decimal(6))
|
||||
|
||||
@with_transaction()
|
||||
def test_price_list_list_price(self):
|
||||
@@ -203,9 +206,7 @@ class ProductPriceListTestCase(CompanyTestMixin, ModuleTestCase):
|
||||
}])],
|
||||
}])
|
||||
|
||||
self.assertEqual(
|
||||
price_list.compute(product, Decimal(0), 1, unit),
|
||||
Decimal(8))
|
||||
self.assertEqual(price_list.compute(product, 1, unit), Decimal(8))
|
||||
|
||||
@with_transaction()
|
||||
def test_price_list_with_none(self):
|
||||
@@ -224,9 +225,7 @@ class ProductPriceListTestCase(CompanyTestMixin, ModuleTestCase):
|
||||
'formula': 'list_price * 0.8',
|
||||
}])],
|
||||
}])
|
||||
self.assertEqual(
|
||||
price_list.compute(None, None, 1, unit),
|
||||
None)
|
||||
self.assertEqual(price_list.compute(None, 1, unit), None)
|
||||
|
||||
|
||||
del ModuleTestCase
|
||||
|
||||
@@ -19,6 +19,8 @@ this repository contains the full copyright notices and license terms. -->
|
||||
|
||||
<label name="unit"/>
|
||||
<field name="unit"/>
|
||||
<label name="price"/>
|
||||
<field name="price"/>
|
||||
|
||||
<notebook colspan="4">
|
||||
<page name="lines">
|
||||
|
||||
@@ -22,12 +22,11 @@ class PriceList(metaclass=PoolMeta):
|
||||
def open_lines(cls, price_lists):
|
||||
pass
|
||||
|
||||
def compute(self, product, unit_price, quantity, uom, pattern=None):
|
||||
def compute(self, product, quantity, uom, pattern=None):
|
||||
context = Transaction().context
|
||||
pattern = pattern.copy() if pattern is not None else {}
|
||||
pattern.setdefault('date', context.get('date'))
|
||||
return super().compute(
|
||||
product, unit_price, quantity, uom, pattern=pattern)
|
||||
return super().compute(product, quantity, uom, pattern=pattern)
|
||||
|
||||
|
||||
class PriceListLine(metaclass=PoolMeta):
|
||||
|
||||
@@ -20,19 +20,32 @@ class ProductPriceListDatesTestCase(CompanyTestMixin, ModuleTestCase):
|
||||
pool = Pool()
|
||||
PriceList = pool.get('product.price_list')
|
||||
|
||||
company = create_company()
|
||||
with set_company(company):
|
||||
price_list, = PriceList.create([{
|
||||
'name': "Price List",
|
||||
'lines': [('create', [{
|
||||
field: date,
|
||||
'formula': 'unit_price * 0.9',
|
||||
}, {
|
||||
'formula': 'unit_price',
|
||||
}])],
|
||||
}])
|
||||
price_list, = PriceList.create([{
|
||||
'name': "Price List",
|
||||
'price': 'list_price',
|
||||
'lines': [('create', [{
|
||||
field: date,
|
||||
'formula': 'unit_price * 0.9',
|
||||
}, {
|
||||
'formula': 'unit_price',
|
||||
}])],
|
||||
}])
|
||||
return price_list
|
||||
|
||||
def create_product(self, list_price=Decimal(10)):
|
||||
pool = Pool()
|
||||
Product = pool.get('product.product')
|
||||
Template = pool.get('product.template')
|
||||
Uom = pool.get('product.uom')
|
||||
|
||||
unit, = Uom.search([('name', '=', "Unit")])
|
||||
template = Template(
|
||||
name="Template", list_price=list_price, default_uom=unit)
|
||||
template.save()
|
||||
product = Product(template=template)
|
||||
product.save()
|
||||
return product
|
||||
|
||||
@with_transaction()
|
||||
def test_price_list_start_date(self):
|
||||
"Test price list with start date"
|
||||
@@ -42,18 +55,20 @@ class ProductPriceListDatesTestCase(CompanyTestMixin, ModuleTestCase):
|
||||
today = Date.today()
|
||||
tomorrow = today + datetime.timedelta(days=1)
|
||||
|
||||
price_list = self.create_price_list('start_date', tomorrow)
|
||||
company = create_company()
|
||||
with set_company(company):
|
||||
product = self.create_product()
|
||||
uom = product.default_uom
|
||||
price_list = self.create_price_list('start_date', tomorrow)
|
||||
|
||||
self.assertEqual(
|
||||
price_list.compute(
|
||||
product=None, unit_price=Decimal(10), quantity=1, uom=None,
|
||||
pattern={'date': today}),
|
||||
Decimal(10))
|
||||
self.assertEqual(
|
||||
price_list.compute(
|
||||
product=None, unit_price=Decimal(10), quantity=1, uom=None,
|
||||
pattern={'date': tomorrow}),
|
||||
Decimal(9))
|
||||
self.assertEqual(
|
||||
price_list.compute(
|
||||
product, 1, uom, pattern={'date': today}),
|
||||
Decimal(10))
|
||||
self.assertEqual(
|
||||
price_list.compute(
|
||||
product, 1, uom, pattern={'date': tomorrow}),
|
||||
Decimal(9))
|
||||
|
||||
@with_transaction()
|
||||
def test_price_list_end_date(self):
|
||||
@@ -64,18 +79,20 @@ class ProductPriceListDatesTestCase(CompanyTestMixin, ModuleTestCase):
|
||||
today = Date.today()
|
||||
yesterday = today - datetime.timedelta(days=1)
|
||||
|
||||
price_list = self.create_price_list('end_date', yesterday)
|
||||
company = create_company()
|
||||
with set_company(company):
|
||||
product = self.create_product()
|
||||
uom = product.default_uom
|
||||
price_list = self.create_price_list('end_date', yesterday)
|
||||
|
||||
self.assertEqual(
|
||||
price_list.compute(
|
||||
product=None, unit_price=Decimal(10), quantity=1, uom=None,
|
||||
pattern={'date': today}),
|
||||
Decimal(10))
|
||||
self.assertEqual(
|
||||
price_list.compute(
|
||||
product=None, unit_price=Decimal(10), quantity=1, uom=None,
|
||||
pattern={'date': yesterday}),
|
||||
Decimal(9))
|
||||
self.assertEqual(
|
||||
price_list.compute(
|
||||
product, 1, uom, pattern={'date': today}),
|
||||
Decimal(10))
|
||||
self.assertEqual(
|
||||
price_list.compute(
|
||||
product, 1, uom, pattern={'date': yesterday}),
|
||||
Decimal(9))
|
||||
|
||||
@with_transaction()
|
||||
def test_price_list_with_context_date(self):
|
||||
@@ -86,20 +103,20 @@ class ProductPriceListDatesTestCase(CompanyTestMixin, ModuleTestCase):
|
||||
today = Date.today()
|
||||
tomorrow = today + datetime.timedelta(days=1)
|
||||
|
||||
price_list = self.create_price_list('start_date', tomorrow)
|
||||
company = create_company()
|
||||
with set_company(company):
|
||||
product = self.create_product()
|
||||
uom = product.default_uom
|
||||
price_list = self.create_price_list('start_date', tomorrow)
|
||||
|
||||
with Transaction().set_context(date=today):
|
||||
self.assertEqual(
|
||||
price_list.compute(
|
||||
product=None, unit_price=Decimal(10),
|
||||
quantity=1, uom=None),
|
||||
Decimal(10))
|
||||
with Transaction().set_context(date=tomorrow):
|
||||
self.assertEqual(
|
||||
price_list.compute(
|
||||
product=None, unit_price=Decimal(10),
|
||||
quantity=1, uom=None),
|
||||
Decimal(9))
|
||||
with Transaction().set_context(date=today):
|
||||
self.assertEqual(
|
||||
price_list.compute(product, 1, uom),
|
||||
Decimal(10))
|
||||
with Transaction().set_context(date=tomorrow):
|
||||
self.assertEqual(
|
||||
price_list.compute(product, 1, uom),
|
||||
Decimal(9))
|
||||
|
||||
|
||||
del ModuleTestCase
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
from trytond.model import fields, tree
|
||||
from trytond.modules.product_price_list.price_list import Null
|
||||
from trytond.pool import PoolMeta
|
||||
|
||||
|
||||
@@ -10,13 +11,14 @@ class PriceList(tree(), metaclass=PoolMeta):
|
||||
|
||||
parent = fields.Many2One('product.price_list', "Parent")
|
||||
|
||||
def get_context_formula(
|
||||
self, product, unit_price, quantity, uom, pattern=None):
|
||||
def get_context_formula(self, product, quantity, uom, pattern=None):
|
||||
context = super().get_context_formula(
|
||||
product, unit_price, quantity, uom, pattern=pattern)
|
||||
product, quantity, uom, pattern=pattern)
|
||||
if self.parent:
|
||||
parent_unit_price = self.parent.compute(
|
||||
product, unit_price, quantity, uom, pattern=pattern)
|
||||
product, quantity, uom, pattern=pattern)
|
||||
if parent_unit_price is None:
|
||||
parent_unit_price = Null()
|
||||
context['names']['parent_unit_price'] = parent_unit_price
|
||||
return context
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ class ProductPriceListParentTestCase(CompanyTestMixin, ModuleTestCase):
|
||||
|
||||
price_list_parent, = PriceList.create([{
|
||||
'name': "Parent",
|
||||
'price': 'list_price',
|
||||
'lines': [('create', [{
|
||||
'formula': 'unit_price * 2',
|
||||
}])],
|
||||
@@ -47,9 +48,7 @@ class ProductPriceListParentTestCase(CompanyTestMixin, ModuleTestCase):
|
||||
}])
|
||||
|
||||
self.assertEqual(
|
||||
price_list.compute(
|
||||
product, product.list_price, 1, unit),
|
||||
Decimal('40'))
|
||||
price_list.compute(product, 1, unit), Decimal('40'))
|
||||
|
||||
@with_transaction()
|
||||
def test_line_formula_help(self):
|
||||
|
||||
@@ -30,8 +30,7 @@ class Product(metaclass=PoolMeta):
|
||||
uom = context_uom or self.purchase_uom
|
||||
if uom.category != self.purchase_uom.category:
|
||||
uom = self.purchase_uom
|
||||
unit_price = price_list.compute(
|
||||
self, unit_price, quantity, uom)
|
||||
unit_price = price_list.compute(self, quantity, uom)
|
||||
if price_list.tax_included and taxes:
|
||||
unit_price = Tax.reverse_compute(unit_price, taxes, today)
|
||||
return unit_price
|
||||
|
||||
@@ -32,7 +32,7 @@ class Product(metaclass=PoolMeta):
|
||||
uom = context_uom or self.sale_uom
|
||||
if uom.category != self.sale_uom.category:
|
||||
uom = self.sale_uom
|
||||
unit_price = price_list.compute(self, unit_price, quantity, uom)
|
||||
unit_price = price_list.compute(self, quantity, uom)
|
||||
if price_list.tax_included and taxes and unit_price is not None:
|
||||
unit_price = Tax.reverse_compute(
|
||||
unit_price, taxes, today)
|
||||
|
||||
@@ -109,7 +109,7 @@ Create payment term::
|
||||
Create a price List and assign it to customer::
|
||||
|
||||
>>> PriceList = Model.get('product.price_list')
|
||||
>>> price_list = PriceList(name='Retail')
|
||||
>>> price_list = PriceList(name='Retail', price='list_price')
|
||||
>>> price_list_line = price_list.lines.new()
|
||||
>>> price_list_line.quantity = 10.0
|
||||
>>> price_list_line.product = product
|
||||
|
||||
Reference in New Issue
Block a user