@@ -1,3 +1,4 @@
|
||||
* Allow a different list price to be set for each variant
|
||||
|
||||
Version 7.6.0 - 2025-04-28
|
||||
--------------------------
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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::
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user