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:
Cédric Krier
2023-03-30 19:11:27 +02:00
parent 024a6bf0a3
commit 5a23012694
11 changed files with 123 additions and 96 deletions
+2
View File
@@ -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
+21 -13
View File
@@ -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
+22 -23
View File
@@ -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">
+2 -3
View File
@@ -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
+6 -4
View File
@@ -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):
+1 -2
View File
@@ -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
+1 -1
View File
@@ -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