Allow a different list price to be set for each variant

Closes #14154
This commit is contained in:
Cédric Krier
2025-10-07 16:23:45 +02:00
parent 965aa923c7
commit 6ba56ce0c8
7 changed files with 112 additions and 22 deletions
+1
View File
@@ -1,3 +1,4 @@
* Allow a different list price to be set for each variant
Version 7.6.0 - 2025-04-28
--------------------------
+3
View File
@@ -60,6 +60,9 @@ It does, however, have some properties that are specific to each variant
such as the description, cost price, and suffix code which is important to
distinguish between different variants.
Instead of using the template's list price, a list price can be set on the
variant.
The identifiers that are used to refer to a product are also specific to a
product variant.
A product identifier is made up from a type and a code.
+2
View File
@@ -20,6 +20,8 @@ properties of the shirt, such as the name, unit of measurement, categories,
and so on, would be the same.
So in this case you would create a single *Product* to represent the shirt,
and a *Variant* for each of the different sizes.
Then, you can either define a different list price for each variant or use the
list price from the product only.
Structuring your products like this can help you manage and update them.
.. tip::
+52 -8
View File
@@ -149,6 +149,23 @@ class Template(
return pool.get('product.cost_price_method')
return super().multivalue_model(field)
def multivalue_records(self, field):
records = super().multivalue_records(field)
if field == 'list_price':
# Sort to get record with empty product first
records = sorted(records, key=lambda r: r.product is not None)
return records
def get_multivalue(self, name, **pattern):
if name == 'list_price':
pattern.setdefault('product', None)
return super().get_multivalue(name, **pattern)
def set_multivalue(self, name, value, save=True, **pattern):
if name == 'list_price':
pattern.setdefault('product', None)
return super().set_multivalue(name, value, save=save, **pattern)
@classmethod
def order_code(cls, tables):
table, _ = tables[None]
@@ -401,6 +418,19 @@ class Product(
identifiers = fields.One2Many(
'product.identifier', 'product', "Identifiers",
help="Other identifiers associated with the variant.")
list_price = fields.MultiValue(fields.Numeric(
"List Price", digits=price_digits,
states={
'readonly': ~Eval('context', {}).get('company'),
},
help="The standard price the variant is sold at.\n"
"Leave empty to use the list price of the product."))
list_prices = fields.One2Many(
'product.list_price', 'product', "List Prices")
list_price_used = fields.Function(fields.Numeric(
"List Price", digits=price_digits,
help="The standard price the variant is sold at."),
'get_list_price_used')
cost_price = fields.MultiValue(fields.Numeric(
"Cost Price", digits=price_digits,
states={
@@ -553,7 +583,9 @@ class Product(
@classmethod
def multivalue_model(cls, field):
pool = Pool()
if field == 'cost_price':
if field == 'list_price':
return pool.get('product.list_price')
elif field == 'cost_price':
return pool.get('product.cost_price')
return super().multivalue_model(field)
@@ -562,12 +594,16 @@ class Product(
if name in {'cost_price', 'list_price'} and not value:
if not pattern.get('company', context.get('company')):
return []
if name == 'list_price':
pattern.setdefault('template', self.template.id)
return super().set_multivalue(name, value, save=save, **pattern)
def get_multivalue(self, name, **pattern):
if isinstance(self._fields[name], TemplateFunction):
return self.template.get_multivalue(name, **pattern)
else:
if name == 'list_price':
pattern.setdefault('template', self.template.id)
return super().get_multivalue(name, **pattern)
@classmethod
@@ -693,12 +729,11 @@ class Product(
default.setdefault('replacing')
return super().copy(products, default=default)
@property
def list_price_used(self):
transaction = Transaction()
with transaction.reset_context(), \
transaction.set_context(self._context):
return self.template.get_multivalue('list_price')
def get_list_price_used(self, name):
list_price = self.get_multivalue('list_price')
if list_price is None:
list_price = self.template.get_multivalue('list_price')
return list_price
@classmethod
def sync_code(cls, products):
@@ -747,7 +782,16 @@ class Product(
class ProductListPrice(ModelSQL, CompanyValueMixin):
__name__ = 'product.list_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),
},
@@ -27,30 +27,62 @@ Create a template::
>>> template.save()
>>> len(template.products)
1
>>> product, = template.products
>>> product.code
>>> product1, = template.products
>>> product1.code
'PROD'
>>> product.suffix_code = "001"
>>> product.save()
>>> product.code
>>> product1.suffix_code = "001"
>>> product1.save()
>>> product1.code
'PROD001'
Create a variant::
>>> Product = Model.get('product.product')
>>> product = Product()
>>> product.template = template
>>> product.name
>>> product2 = Product()
>>> product2.template = template
>>> product2.name
'Product'
>>> product.list_price
>>> product2.suffix_code = "002"
>>> product2.save()
>>> product2.list_price_used
Decimal('42.0000')
>>> product.suffix_code = "002"
>>> product.save()
>>> product.list_price
Decimal('42.0000')
>>> product.code
>>> product2.code
'PROD002'
Change variant list price::
>>> product2.list_price = Decimal('50.0000')
>>> product2.save()
>>> product2.list_price
Decimal('50.0000')
>>> product2.list_price_used
Decimal('50.0000')
>>> product1.reload()
>>> product1.list_price
>>> product1.list_price_used
Decimal('42.0000')
>>> template.reload()
>>> template.list_price
Decimal('42.0000')
Change product list price::
>>> template.list_price = Decimal('40.0000')
>>> template.save()
>>> product2.reload()
>>> product2.list_price
Decimal('50.0000')
>>> product2.list_price_used
Decimal('50.0000')
>>> product1.reload()
>>> product1.list_price
>>> product1.list_price_used
Decimal('40.0000')
Change template code::
>>> template.code = "PRD"
@@ -12,6 +12,11 @@ this repository contains the full copyright notices and license terms. -->
</group>
<label name="active"/>
<field name="active"/>
<label name="list_price"/>
<field name="list_price"/>
<newline/>
<label name="cost_price"/>
<field name="cost_price"/>
<field name="identifiers" colspan="4" pre_validate="1" view_ids="product.identifier_view_list_sequence"/>
+3
View File
@@ -4,4 +4,7 @@ this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="//field[@name='products']" position="replace">
</xpath>
<xpath expr="//field[@name='list_price']" position="replace">
<field name="list_price_used"/>
</xpath>
</data>