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