Apply promotion per total amount of sale
issue9776 review314721002
This commit is contained in:
1 parent
d91f33af48
commit
1befa3c939
6 files changed
+178
-16
No files matched your search
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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')
|
||||
@@ -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
@@ -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>
|
||||
Reference in new issue
Block a user