details: https://code.tryton.org/tryton/commit/2d2af73e62f2
branch: default
user: Cédric Krier <[email protected]>
date: Fri Oct 17 17:59:53 2025 +0200
description:
Allow a different gross price to be set for each variant
Closes #14299
diffstat:
modules/sale_point/CHANGELOG | 1 +
modules/sale_point/product.py | 95 +++++++++++++++++++----
modules/sale_point/sale.py | 4 +-
modules/sale_point/tests/scenario_sale_point.rst | 3 +
4 files changed, 85 insertions(+), 18 deletions(-)
diffs (153 lines):
diff -r ddc9995716f4 -r 2d2af73e62f2 modules/sale_point/CHANGELOG
--- a/modules/sale_point/CHANGELOG Sat Nov 22 10:45:44 2025 +0100
+++ b/modules/sale_point/CHANGELOG Fri Oct 17 17:59:53 2025 +0200
@@ -1,3 +1,4 @@
+* Allow a different gross price to be set for each variant
Version 7.6.0 - 2025-04-28
--------------------------
diff -r ddc9995716f4 -r 2d2af73e62f2 modules/sale_point/product.py
--- a/modules/sale_point/product.py Sat Nov 22 10:45:44 2025 +0100
+++ b/modules/sale_point/product.py Fri Oct 17 17:59:53 2025 +0200
@@ -7,7 +7,25 @@
from trytond.pyson import Eval
-class Template(metaclass=PoolMeta):
+class _GrossPriceMixin:
+ __slots__ = ()
+
+ @fields.depends(
+ 'gross_price', 'account_category', methods=['customer_taxes_used'])
+ def on_change_gross_price(self):
+ pool = Pool()
+ Date = pool.get('ir.date')
+ Tax = pool.get('account.tax')
+ if self.gross_price is None or not self.account_category:
+ return
+ today = Date.today()
+ self.list_price = round_price(Tax.reverse_compute(
+ self.gross_price,
+ self.customer_taxes_used,
+ today))
+
+
+class Template(_GrossPriceMixin, metaclass=PoolMeta):
__name__ = 'product.template'
gross_price = fields.MultiValue(fields.Numeric(
@@ -26,27 +44,72 @@
return pool.get('product.gross_price')
return super().multivalue_model(field)
- @fields.depends(
- 'gross_price', 'account_category', methods=['customer_taxes_used'])
- def on_change_gross_price(self):
+
+class Product(_GrossPriceMixin, metaclass=PoolMeta):
+ __name__ = 'product.product'
+
+ gross_price = fields.MultiValue(fields.Numeric(
+ "Gross Price", digits=price_digits,
+ states={
+ 'invisible': ~Eval('salable', False),
+ },
+ help="The price with default tax included.\n"
+ "Leave empty to use the gross price of the product."))
+ gross_prices = fields.One2Many(
+ 'product.gross_price', 'product', "Gross Prices")
+ gross_price_used = fields.Function(fields.Numeric(
+ "Gross Price", digits=price_digits,
+ help="The price with default tax included."),
+ 'get_gross_price_used')
+
+ @classmethod
+ def multivalue_model(cls, field):
pool = Pool()
- Date = pool.get('ir.date')
- Tax = pool.get('account.tax')
- if self.gross_price is None or not self.account_category:
- return
- today = Date.today()
- self.list_price = round_price(Tax.reverse_compute(
- self.gross_price,
- self.customer_taxes_used,
- today))
+ if field == 'gross_price':
+ return pool.get('product.gross_price')
+ return super().multivalue_model(field)
+
+ def set_multivalue(self, name, value, save=True, **pattern):
+ if name == 'gross_price':
+ pattern.setdefault('template', self.template.id)
+ return super().set_multivalue(name, value, save=save, **pattern)
+ def get_multivalue(self, name, **pattern):
+ if name == 'gross_price':
+ pattern.setdefault('template', self.template.id)
+ return super().get_multivalue(name, **pattern)
-class Product(metaclass=PoolMeta):
- __name__ = 'product.product'
+ @fields.depends('_parent_template.id')
+ def on_change_gross_price(self):
+ return super().on_change_gross_price()
+
+ def get_gross_price_used(self, name):
+ gross_price = self.get_multivalue('gross_price')
+ if gross_price is None:
+ gross_price = self.template.get_multivalue('gross_price')
+ return gross_price
class GrossPrice(ModelSQL, CompanyValueMixin):
__name__ = 'product.gross_price'
template = fields.Many2One(
- 'product.template', "Template", ondelete='CASCADE')
+ 'product.template', "Template", ondelete='CASCADE', required=True,
+ context={
+ 'company': Eval('company', -1),
+ },
+ depends=['company'])
+ product = fields.Many2One(
+ 'product.product', "Product", ondelete='CASCADE',
+ domain=[
+ ('template', '=', Eval('template', -1)),
+ ],
+ context={
+ 'company': Eval('company', -1),
+ },
+ depends=['company'])
gross_price = fields.Numeric("Gross Price", digits=price_digits)
+
+ @classmethod
+ def __setup__(cls):
+ super().__setup__()
+ cls.company.required = True
diff -r ddc9995716f4 -r 2d2af73e62f2 modules/sale_point/sale.py
--- a/modules/sale_point/sale.py Sat Nov 22 10:45:44 2025 +0100
+++ b/modules/sale_point/sale.py Fri Oct 17 17:59:53 2025 +0200
@@ -541,8 +541,8 @@
self.unit_list_price = None
if self.product and self.sale and self.sale.point:
if (self.sale.point.tax_included
- and self.product.gross_price is not None):
- self.unit_gross_price = self.product.gross_price
+ and self.product.gross_price_used is not None):
+ self.unit_gross_price = self.product.gross_price_used
self.unit_list_price = round_price(Tax.reverse_compute(
self.unit_gross_price, self.taxes,
date=self.sale.date))
diff -r ddc9995716f4 -r 2d2af73e62f2
modules/sale_point/tests/scenario_sale_point.rst
--- a/modules/sale_point/tests/scenario_sale_point.rst Sat Nov 22 10:45:44
2025 +0100
+++ b/modules/sale_point/tests/scenario_sale_point.rst Fri Oct 17 17:59:53
2025 +0200
@@ -67,6 +67,9 @@
Decimal('9.9174')
>>> template.save()
>>> goods, = template.products
+ >>> goods.gross_price = Decimal('14.0000')
+ >>> goods.list_price
+ Decimal('11.5702')
>>> template = ProductTemplate()
>>> template.name = 'service'