Apply promotion per total amount of sale

issue9776
review314721002
This commit is contained in:
Cédric Krier committed 2020-12-22 00:24:45 +01:00
1 parent d91f33af48
commit 1befa3c939
6 files changed
+178 -16

No files matched your search

+2
View File
@@ -1,3 +1,5 @@
* Apply promotion per amount of sale
Version 5.8.0 - 2020-11-02
* Bug fixes (see mercurial logs for details)
* Remove support for Python 3.5
+1
View File
@@ -17,6 +17,7 @@ The criteria are the fields:
- *Company*: The company should be the same as the sale.
- *Price List*: The sale price list.
- *Start Date*/*End Date*: The period for which the promotion is valid.
- *Amount*: The amount of the sale.
- *Quantity*: The sum quantity of the sale lines which have the same *Unit*.
- *Products*: The list of products to apply the promotion.
- *Categories*: The list of product categories to apply the promotion.
+56 -9
View File
@@ -116,6 +116,25 @@ class Promotion(
('company', '=', Eval('company', -1)),
],
depends=['company'])
amount = fields.Numeric(
"Amount", digits=(16, Eval('currency_digits', 2)),
depends=['currency_digits'])
currency = fields.Many2One(
'currency.currency', "Currency",
states={
'required': Bool(Eval('amount', 0)),
},
depends=['amount'])
currency_digits = fields.Function(
fields.Integer("Currency Digits"), 'on_change_with_currency_digits')
untaxed_amount = fields.Boolean(
"Untaxed Amount",
states={
'invisible': ~Eval('amount'),
},
depends=['amount'])
quantity = fields.Float('Quantity', digits=(16, Eval('unit_digits', 2)),
depends=['unit_digits'])
unit = fields.Many2One('product.uom', 'Unit',
@@ -138,12 +157,21 @@ class Promotion(
def default_company():
return Transaction().context.get('company')
@classmethod
def default_untaxed_amount(cls):
return False
@fields.depends('unit')
def on_change_with_unit_digits(self, name=None):
if self.unit:
return self.unit.digits
return 2
@fields.depends('currency')
def on_change_with_currency_digits(self, name=None):
if self.currency:
return self.currency.digits
@classmethod
def validate(cls, promotions):
super().validate(promotions)
@@ -195,25 +223,44 @@ class Promotion(
def get_pattern(self, sale):
pool = Pool()
Currency = pool.get('currency.currency')
Uom = pool.get('product.uom')
Sale = pool.get('sale.sale')
pattern = {}
if not self.unit:
return pattern
quantity = 0
for line in sale.lines:
if self.is_valid_sale_line(line):
quantity += Uom.compute_qty(line.unit, line.quantity,
self.unit)
pattern['quantity'] = quantity
if self.currency:
amount = self.get_sale_amount(Sale(sale.id))
pattern['amount'] = Currency.compute(
sale.currency, amount, self.currency)
if self.unit:
quantity = 0
for line in sale.lines:
if self.is_valid_sale_line(line):
quantity += Uom.compute_qty(line.unit, line.quantity,
self.unit)
pattern['quantity'] = quantity
return pattern
def match(self, pattern):
def sign(amount):
return Decimal(1).copy_sign(amount)
if 'quantity' in pattern:
pattern = pattern.copy()
if self.quantity > pattern.pop('quantity'):
if (self.quantity or 0) > pattern.pop('quantity'):
return False
if 'amount' in pattern:
pattern = pattern.copy()
amount = pattern.pop('amount')
if (sign(self.amount or 0) * sign(amount) >= 0
and abs(self.amount or 0) > abs(amount)):
return False
return super().match(pattern)
def get_sale_amount(self, sale):
if self.untaxed_amount:
return sale.untaxed_amount
else:
return sale.total_amount
def is_valid_sale_line(self, line):
def parents(categories):
+92
View File
@@ -0,0 +1,92 @@
==============================
Sale Promotion Amount Scenario
==============================
Imports::
>>> from decimal import Decimal
>>> from proteus import Model
>>> from trytond.tests.tools import activate_modules
>>> from trytond.modules.company.tests.tools import (
... create_company, get_company)
Activate modules::
>>> config = activate_modules('sale_promotion')
Create company::
>>> _ = create_company()
>>> company = get_company()
Create customer::
>>> Party = Model.get('party.party')
>>> customer = Party(name='Customer')
>>> customer.save()
Create products::
>>> ProductUom = Model.get('product.uom')
>>> ProductTemplate = Model.get('product.template')
>>> Product = Model.get('product.product')
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
>>> template = ProductTemplate()
>>> template.name = 'Product'
>>> template.default_uom = unit
>>> template.type = 'goods'
>>> template.salable = True
>>> template.list_price = Decimal('20')
>>> template.save()
>>> product1 = Product()
>>> product1.template = template
>>> product1.save()
>>> product2 = Product()
>>> product2.template = template
>>> product2.save()
Create Promotion::
>>> Promotion = Model.get('sale.promotion')
>>> promotion = Promotion(name='product 2 free')
>>> promotion.amount = Decimal('100')
>>> promotion.currency = company.currency
>>> promotion.products.extend([product2])
>>> promotion.formula = '0.0'
>>> promotion.save()
Sale enough for promotion::
>>> Sale = Model.get('sale.sale')
>>> sale = Sale()
>>> sale.party = customer
>>> sale_line = sale.lines.new()
>>> sale_line.product = product1
>>> sale_line.quantity = 4
>>> sale_line = sale.lines.new()
>>> sale_line.product = product2
>>> sale_line.quantity = 2
>>> sale.save()
>>> sale.total_amount
Decimal('120.00')
>>> sale.click('quote')
>>> sale.untaxed_amount
Decimal('80.00')
Sale not enough for promotion::
>>> sale = Sale()
>>> sale.party = customer
>>> sale_line = sale.lines.new()
>>> sale_line.product = product1
>>> sale_line.quantity = 2
>>> sale_line = sale.lines.new()
>>> sale_line.product = product2
>>> sale_line.quantity = 2
>>> sale.save()
>>> sale.total_amount
Decimal('80.00')
>>> sale.click('quote')
>>> sale.untaxed_amount
Decimal('80.00')
+4
View File
@@ -21,4 +21,8 @@ def suite():
tearDown=doctest_teardown, encoding='utf-8',
checker=doctest_checker,
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
suite.addTests(doctest.DocFileSuite('scenario_sale_promotion_amount.rst',
tearDown=doctest_teardown, encoding='utf-8',
checker=doctest_checker,
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
return suite
+23 -7
View File
@@ -1,26 +1,42 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<form>
<form col="6">
<label name="name"/>
<field name="name"/>
<label name="company"/>
<field name="company"/>
<label name="active"/>
<field name="active"/>
<label name="start_date"/>
<field name="start_date"/>
<label name="end_date"/>
<field name="end_date"/>
<label name="company"/>
<field name="company"/>
<label name="price_list"/>
<field name="price_list"/>
<label name="formula"/>
<field name="formula" colspan="3"/>
<separator id="pattern" colspan="4"/>
<field name="formula" colspan="5"/>
<separator id="pattern" colspan="6"/>
<label name="amount"/>
<field name="amount"/>
<label name="currency"/>
<field name="currency"/>
<group id="amount" col="-1" colspan="2">
<label name="untaxed_amount"/>
<field name="untaxed_amount" xexpand="0" width="25"/>
</group>
<label name="quantity"/>
<field name="quantity"/>
<label name="unit"/>
<field name="unit"/>
<field name="categories" colspan="2"/>
<field name="products" colspan="2" view_ids="product.product_view_tree_simple"/>
<newline/>
<group id="categories_products" col="2" colspan="6" yexpand="1">
<field name="categories"/>
<field name="products" view_ids="product.product_view_tree_simple"/>
</group>
</form>