diff --git a/modules/product_measurements/CHANGELOG b/modules/product_measurements/CHANGELOG
index ad367aa4ea..24b55f5f69 100644
--- a/modules/product_measurements/CHANGELOG
+++ b/modules/product_measurements/CHANGELOG
@@ -1,3 +1,4 @@
+* Add volume and weight to price list formula
Version 7.6.0 - 2025-04-28
--------------------------
diff --git a/modules/product_measurements/product.py b/modules/product_measurements/product.py
index 656985b3e6..25ee33302e 100644
--- a/modules/product_measurements/product.py
+++ b/modules/product_measurements/product.py
@@ -1,5 +1,8 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
+
+from decimal import Decimal
+
from trytond.model import fields
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Bool, Eval, Id
@@ -120,3 +123,70 @@ class Template(metaclass=PoolMeta):
class Product(metaclass=PoolMeta):
__name__ = 'product.product'
+
+
+class PriceList(metaclass=PoolMeta):
+ __name__ = 'product.price_list'
+
+ def get_context_formula(self, product, quantity, uom, pattern=None):
+ pool = Pool()
+ UoM = pool.get('product.uom')
+ ModelData = pool.get('ir.model.data')
+
+ liter = UoM(ModelData.get_id('product', 'uom_liter'))
+ kilogram = UoM(ModelData.get_id('product', 'uom_kilogram'))
+
+ context = super().get_context_formula(
+ product, quantity, uom, pattern=pattern)
+ volume = weight = 0
+ if product:
+ if product.volume is not None:
+ volume = UoM.compute_qty(
+ product.volume_uom, product.volume, liter, round=False)
+ if product.weight is not None:
+ weight = UoM.compute_qty(
+ product.weight_uom, product.weight, kilogram, round=False)
+ context['names']['volume'] = Decimal(str(volume))
+ context['names']['weight'] = Decimal(str(weight))
+ return context
+
+
+class PriceListLine(metaclass=PoolMeta):
+ __name__ = 'product.price_list.line'
+
+ volume_uom = fields.Many2One(
+ 'product.uom', "Volume UoM",
+ domain=[('category', '=', Id('product', 'uom_cat_volume'))],
+ help="Leave empty for liter.")
+ weight_uom = fields.Many2One(
+ 'product.uom', "Weight UoM",
+ domain=[('category', '=', Id('product', 'uom_cat_weight'))],
+ help="Leave empty for kilogram.")
+
+ @classmethod
+ def __setup__(cls):
+ super().__setup__()
+ cls.formula.help += ("\n"
+ "-volume: the volume of 1 unit of product\n"
+ "-weight: the weight of 1 unit of product")
+
+ def get_unit_price(self, **context):
+ pool = Pool()
+ UoM = pool.get('product.uom')
+ ModelData = pool.get('ir.model.data')
+
+ if self.volume_uom:
+ context['names'] = context['names'].copy()
+ liter = UoM(ModelData.get_id('product', 'uom_liter'))
+ volume = UoM.compute_qty(
+ liter, float(context['names']['volume']), self.volume_uom,
+ round=False)
+ context['names']['volume'] = Decimal(str(volume))
+ if self.weight_uom:
+ context['names'] = context['names'].copy()
+ kilogram = UoM(ModelData.get_id('product', 'uom_kilogram'))
+ weight = UoM.compute_qty(
+ kilogram, float(context['names']['weight']), self.weight_uom,
+ round=False)
+ context['names']['weight'] = Decimal(str(weight))
+ return super().get_unit_price(**context)
diff --git a/modules/product_measurements/product.xml b/modules/product_measurements/product.xml
index c1be0f025d..fc0bebe3b4 100644
--- a/modules/product_measurements/product.xml
+++ b/modules/product_measurements/product.xml
@@ -11,4 +11,11 @@ this repository contains the full copyright notices and license terms. -->
+
+
+ product.price_list.line
+
+ product_price_list_line_form
+
+
diff --git a/modules/product_measurements/setup.py b/modules/product_measurements/setup.py
index bd07f9f7ab..0f7c47cc9f 100755
--- a/modules/product_measurements/setup.py
+++ b/modules/product_measurements/setup.py
@@ -47,6 +47,10 @@ for dep in info.get('depends', []):
requires.append(get_require_version('trytond_%s' % dep))
requires.append(get_require_version('trytond'))
+tests_require = [
+ get_require_version('trytond_product_price_list'),
+ ]
+
setup(name=name,
version=version,
description='Tryton module to add measurements to product',
@@ -116,6 +120,9 @@ setup(name=name,
license='GPL-3',
python_requires='>=3.9',
install_requires=requires,
+ extras_require={
+ 'test': tests_require,
+ },
zip_safe=False,
entry_points="""
[trytond.modules]
diff --git a/modules/product_measurements/tests/test_module.py b/modules/product_measurements/tests/test_module.py
index c0493c8bff..f39aecd96f 100644
--- a/modules/product_measurements/tests/test_module.py
+++ b/modules/product_measurements/tests/test_module.py
@@ -1,12 +1,85 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
-from trytond.tests.test_tryton import ModuleTestCase
+from decimal import Decimal
+
+from trytond.modules.company.tests import create_company, set_company
+from trytond.pool import Pool
+from trytond.tests.test_tryton import ModuleTestCase, with_transaction
class ProductMeasurementsTestCase(ModuleTestCase):
- 'Test ProductMeasurements module'
+ "Test Product Measurements module"
module = 'product_measurements'
+ extras = ['product_price_list']
+
+ @with_transaction()
+ def test_price_list_context_formula_volume(self):
+ "Test price list context formula with volume"
+ pool = Pool()
+ Template = pool.get('product.template')
+ Product = pool.get('product.product')
+ UoM = pool.get('product.uom')
+ PriceList = pool.get('product.price_list')
+
+ unit, = UoM.search([('name', '=', "Unit")])
+ cm3, = UoM.search([('name', '=', "Cubic centimeter")])
+ inch3, = UoM.search([('name', '=', "Cubic inch")])
+
+ company = create_company()
+ with set_company(company):
+ template = Template(
+ name="Product", default_uom=unit, products=None,
+ list_price=Decimal('100'),
+ volume=2, volume_uom=cm3)
+ template.save()
+ product = Product(template=template)
+ product.save()
+
+ price_list = PriceList(
+ name="List", price='list_price',
+ lines=[{
+ 'formula': 'unit_price + 5 * volume',
+ 'volume_uom': inch3}])
+ price_list.save()
+
+ self.assertEqual(
+ price_list.compute(product, 5, unit),
+ Decimal('100.6102'))
+
+ @with_transaction()
+ def test_price_list_context_formula_weight(self):
+ "Test price list context formula with weight"
+ pool = Pool()
+ Template = pool.get('product.template')
+ Product = pool.get('product.product')
+ UoM = pool.get('product.uom')
+ PriceList = pool.get('product.price_list')
+
+ unit, = UoM.search([('name', '=', "Unit")])
+ gram, = UoM.search([('name', '=', "Gram")])
+ pound, = UoM.search([('name', '=', "Pound")])
+
+ company = create_company()
+ with set_company(company):
+ template = Template(
+ name="Product", default_uom=unit, products=None,
+ list_price=Decimal('100'),
+ weight=5, weight_uom=gram)
+ template.save()
+ product = Product(template=template)
+ product.save()
+
+ price_list = PriceList(
+ name="List", price='list_price',
+ lines=[{
+ 'formula': 'unit_price + 5 * weight',
+ 'weight_uom': pound}])
+ price_list.save()
+
+ self.assertEqual(
+ price_list.compute(product, 5, unit),
+ Decimal('100.0551'))
del ModuleTestCase
diff --git a/modules/product_measurements/tryton.cfg b/modules/product_measurements/tryton.cfg
index 1e0687bba9..3bbd4a5dd1 100644
--- a/modules/product_measurements/tryton.cfg
+++ b/modules/product_measurements/tryton.cfg
@@ -4,6 +4,8 @@ depends:
ir
product
res
+extras_depend:
+ product_price_list
xml:
product.xml
@@ -11,3 +13,8 @@ xml:
model:
product.Template
product.Product
+
+[register product_price_list]
+model:
+ product.PriceList
+ product.PriceListLine
diff --git a/modules/product_measurements/view/product_price_list_line_form.xml b/modules/product_measurements/view/product_price_list_line_form.xml
new file mode 100644
index 0000000000..83e08e3763
--- /dev/null
+++ b/modules/product_measurements/view/product_price_list_line_form.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+