diff --git a/CHANGELOG b/CHANGELOG
index 693b3cda70..64c869d6cf 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,6 @@
+* Add list of categories on promotion
+* Remove unit constraint on list of products from promotion
+
Version 4.6.0 - 2017-10-30
* Bug fixes (see mercurial logs for details)
* Support price list when computing unit price
diff --git a/__init__.py b/__init__.py
index 77239252e6..1b6c17a14f 100644
--- a/__init__.py
+++ b/__init__.py
@@ -9,6 +9,7 @@ def register():
Pool.register(
SalePromotion,
SalePromotion_Product,
+ SalePromotion_ProductCategory,
Sale,
SaleLine,
module='sale_promotion', type_='model')
diff --git a/doc/index.rst b/doc/index.rst
index ccdf19e6b4..665078629e 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -18,6 +18,7 @@ The criteria are the fields:
- *Price List*: The sale price list.
- *Start Date*/*End Date*: The period for which the promotion is valid.
- *Quantity*: The sum quantity of the sale lines which have the same *Unit*.
-- *Products*: The list of products to use to compute quantity.
+- *Products*: The list of products to apply the promotion.
+- *Categories*: The list of product categories to apply the promotion.
The new unit price is computed by the field *Formula*.
diff --git a/sale.py b/sale.py
index f075eda0fa..a9a95d3f60 100644
--- a/sale.py
+++ b/sale.py
@@ -14,7 +14,7 @@ from trytond.tools import decistmt
from trytond.modules.product import price_digits
__all__ = ['Sale', 'SaleLine',
- 'SalePromotion', 'SalePromotion_Product']
+ 'SalePromotion', 'SalePromotion_Product', 'SalePromotion_ProductCategory']
class Sale:
@@ -124,14 +124,11 @@ class SalePromotion(ModelSQL, ModelView, MatchMixin):
depends=['quantity'])
unit_digits = fields.Function(fields.Integer('Unit Digits'),
'on_change_with_unit_digits')
- unit_category = fields.Function(fields.Many2One('product.uom.category',
- 'Unit Category'), 'on_change_with_unit_category')
products = fields.Many2Many('sale.promotion-product.product',
- 'promotion', 'product', 'Products',
- domain=[
- ('default_uom_category', '=', Eval('unit_category')),
- ],
- depends=['unit_category'])
+ 'promotion', 'product', 'Products')
+ categories = fields.Many2Many(
+ 'sale.promotion-product.category', 'promotion', 'category',
+ "Categories")
formula = fields.Char('Formula', required=True,
help=('Python expression that will be evaluated with:\n'
'- unit_price: the original unit_price'))
@@ -159,11 +156,6 @@ class SalePromotion(ModelSQL, ModelView, MatchMixin):
return self.unit.digits
return 2
- @fields.depends('unit')
- def on_change_with_unit_category(self, name=None):
- if self.unit:
- return self.unit.category.id
-
@classmethod
def validate(cls, promotions):
super(SalePromotion, cls).validate(promotions)
@@ -235,10 +227,21 @@ class SalePromotion(ModelSQL, ModelView, MatchMixin):
return super(SalePromotion, self).match(pattern)
def is_valid_sale_line(self, line):
- if self.products:
- return line.product in self.products
- elif self.unit:
- return line.unit.category == self.unit.category
+
+ def parents(categories):
+ for category in categories:
+ while category:
+ yield category
+ category = category.parent
+
+ if self.unit and line.unit.category != self.unit.category:
+ return False
+ elif self.products and line.product not in self.products:
+ return False
+ elif (self.categories
+ and not set(parents(line.product.categories_all)).intersection(
+ self.categories)):
+ return False
else:
return True
@@ -290,3 +293,15 @@ class SalePromotion_Product(ModelSQL):
required=True, ondelete='CASCADE', select=True)
product = fields.Many2One('product.product', 'Product',
required=True, ondelete='CASCADE')
+
+
+class SalePromotion_ProductCategory(ModelSQL):
+ "Sale Promotion - Product Category"
+ __name__ = 'sale.promotion-product.category'
+
+ promotion = fields.Many2One(
+ 'sale.promotion', "Promotion",
+ required=True, ondelete='CASCADE', select=True)
+ category = fields.Many2One(
+ 'product.category', "Category",
+ required=True, ondelete='CASCADE')
diff --git a/tests/scenario_sale_promotion.rst b/tests/scenario_sale_promotion.rst
index 65b71b3d5c..3fb3d16e1b 100644
--- a/tests/scenario_sale_promotion.rst
+++ b/tests/scenario_sale_promotion.rst
@@ -41,6 +41,16 @@ Create products::
>>> ProductUom = Model.get('product.uom')
>>> ProductTemplate = Model.get('product.template')
>>> Product = Model.get('product.product')
+ >>> Category = Model.get('product.category')
+
+ >>> category = Category(name="Root")
+ >>> category1 = category.childs.new(name="Child 1")
+ >>> category1b = category1.childs.new(name="Child 1b")
+ >>> category2 = category.childs.new(name="Child 2")
+ >>> category.save()
+ >>> category1, category2 = category.childs
+ >>> category1b, = category1.childs
+
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
>>> template = ProductTemplate()
>>> template.name = 'Product'
@@ -50,6 +60,7 @@ Create products::
>>> template.lead_time = datetime.timedelta(0)
>>> template.list_price = Decimal('20')
>>> template.account_revenue = revenue
+ >>> template.categories.append(Category(category1b.id))
>>> template.save()
>>> product1 = Product()
>>> product1.template = template
@@ -61,6 +72,9 @@ Create products::
>>> product3.template = template
>>> product3.save()
+ >>> template4, = template.duplicate(default={'categories': [category2.id]})
+ >>> product4 = Product(template4.products[0].id)
+
Create payment term::
>>> payment_term = create_payment_term()
@@ -72,7 +86,8 @@ Create Promotion::
>>> promotion = Promotion(name='10% on 10 products 1 or 2')
>>> promotion.quantity = 10
>>> promotion.unit = unit
- >>> promotion.products.extend([product1, product2])
+ >>> promotion.products.extend([product1, product2, product4])
+ >>> promotion.categories.append(Category(category1.id))
>>> promotion.formula = '0.9 * unit_price'
>>> promotion.save()
@@ -90,7 +105,10 @@ Sale enough products for promotion::
>>> sale_line.quantity = 10
>>> sale_line = sale.lines.new()
>>> sale_line.product = product3
- >>> sale_line.quantity = 10
+ >>> sale_line.quantity = 5
+ >>> sale_line = sale.lines.new()
+ >>> sale_line.product = product4
+ >>> sale_line.quantity = 5
>>> sale.save()
>>> sale.untaxed_amount
Decimal('500.00')
diff --git a/view/promotion_form.xml b/view/promotion_form.xml
index 9168d5181b..976a2d7691 100644
--- a/view/promotion_form.xml
+++ b/view/promotion_form.xml
@@ -21,5 +21,6 @@ this repository contains the full copyright notices and license terms. -->
-
+
+