diff --git a/CHANGELOG b/CHANGELOG
index ea93e33738..22a7e6d119 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,4 @@
+* Add warning to prevent move in the future
* Add relate to open inventory lines
* Use simplified view for moves list on shipments
* Add a relate from product to its moves
diff --git a/exceptions.py b/exceptions.py
index dcbd48466f..80c579c3f2 100644
--- a/exceptions.py
+++ b/exceptions.py
@@ -28,5 +28,9 @@ class MoveOriginWarning(UserWarning):
pass
+class MoveFutureWarning(UserWarning):
+ pass
+
+
class ProductCostPriceError(ValidationError):
pass
diff --git a/message.xml b/message.xml
index ff6d8fa822..31b04ec816 100644
--- a/message.xml
+++ b/message.xml
@@ -86,5 +86,8 @@ because the type does not support moves and location has existing moves.
The source and destination of stock move must be different.
+
+ The moves "%(moves)s" have effective dates in the future.
+
diff --git a/move.py b/move.py
index 6980681d30..27eaabb991 100644
--- a/move.py
+++ b/move.py
@@ -21,7 +21,7 @@ from trytond.pool import Pool
from trytond.modules.product import price_digits, round_price
-from .exceptions import MoveOriginWarning
+from .exceptions import MoveOriginWarning, MoveFutureWarning
STATES = {
'readonly': Eval('state').in_(['cancelled', 'assigned', 'done']),
@@ -616,6 +616,29 @@ class Move(Workflow, ModelSQL, ModelView):
def do(cls, moves):
pool = Pool()
Product = pool.get('product.product')
+ Date = pool.get('ir.date')
+ Warning = pool.get('res.user.warning')
+ today_cache = {}
+
+ def in_future(move):
+ if move.company not in today_cache:
+ with Transaction().set_context(company=move.company.id):
+ today_cache[move.company] = Date.today()
+ today = today_cache[move.company]
+ if move.effective_date and move.effective_date > today:
+ return move
+ future_moves = list(filter(in_future, moves))
+ if future_moves:
+ names = ', '.join(m.rec_name for m in future_moves[:5])
+ if len(future_moves) > 5:
+ names += '...'
+ warning_name = (
+ '%s.effective_date_future' % hashlib.md5(
+ str(future_moves).encode('utf-8')).hexdigest())
+ if Warning.check(warning_name):
+ raise MoveFutureWarning(warning_name,
+ gettext('stock.msg_effective_date_in_the_future',
+ moves=names))
def set_cost_values(cost_values):
Value = Product.multivalue_model('cost_price')
diff --git a/tests/scenario_stock_move_in_future.rst b/tests/scenario_stock_move_in_future.rst
new file mode 100644
index 0000000000..a1ebb70265
--- /dev/null
+++ b/tests/scenario_stock_move_in_future.rst
@@ -0,0 +1,71 @@
+=============================
+Stock Move In Future Scenario
+=============================
+
+Imports::
+
+ >>> import datetime
+ >>> from dateutil.relativedelta import relativedelta
+ >>> 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)
+ >>> today = datetime.date.today()
+ >>> tomorrow = today + relativedelta(days=1)
+
+Activate modules::
+
+ >>> config = activate_modules('stock')
+
+Create company::
+
+ >>> _ = create_company()
+ >>> company = get_company()
+
+Create product::
+
+ >>> 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.list_price = Decimal('20')
+ >>> template.save()
+ >>> product, = template.products
+ >>> product.cost_price = Decimal('1')
+ >>> product.save()
+
+Get stock locations::
+
+ >>> Location = Model.get('stock.location')
+ >>> supplier_loc, = Location.find([('code', '=', 'SUP')])
+ >>> storage_loc, = Location.find([('code', '=', 'STO')])
+
+A warning is raised when doing a move in the future::
+
+ >>> Move = Model.get('stock.move')
+ >>> move = Move()
+ >>> move.product = product
+ >>> move.quantity = 1
+ >>> move.from_location = supplier_loc
+ >>> move.to_location = storage_loc
+ >>> move.currency = company.currency
+ >>> move.effective_date = tomorrow
+ >>> move.quantity = 2
+ >>> move.unit_price = Decimal('1')
+ >>> move.save()
+ >>> move.click('do') # doctest: +IGNORE_EXCEPTION_DETAIL
+ Traceback (most recent call last):
+ ...
+ MoveFutureWarning: ...
+
+But it can be done for today::
+
+ >>> move.effective_date = today
+ >>> move.click('do')
+ >>> move.state
+ 'done'
diff --git a/tests/test_stock.py b/tests/test_stock.py
index 9c02616fe3..74a6a9eb4f 100644
--- a/tests/test_stock.py
+++ b/tests/test_stock.py
@@ -1552,4 +1552,9 @@ def suite():
tearDown=doctest_teardown, encoding='utf-8',
checker=doctest_checker,
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
+ suite.addTests(doctest.DocFileSuite(
+ 'scenario_stock_move_in_future.rst',
+ tearDown=doctest_teardown, encoding='utf-8',
+ checker=doctest_checker,
+ optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
return suite