diff --git a/modules/purchase_trade/stock.py b/modules/purchase_trade/stock.py index 287b3e4..bead915 100755 --- a/modules/purchase_trade/stock.py +++ b/modules/purchase_trade/stock.py @@ -1,6 +1,6 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. -from trytond.model import fields +from trytond.model import fields, Workflow from trytond.pool import Pool, PoolMeta from trytond.pyson import Bool, Eval, Id from trytond.model import (ModelSQL, ModelView) @@ -470,13 +470,65 @@ class ShipmentIn(metaclass=PoolMeta): agent = fields.Many2One('party.party',"Booking Agent") service_order_key = fields.Integer("Service Order Key") - @classmethod - def __setup__(cls): - super().__setup__() - cls._buttons.update({ - 'compute': {}, - 'send': {}, - }) + @classmethod + def __setup__(cls): + super().__setup__() + cls._transitions |= set(( + ('started', 'cancelled'), + )) + cls._buttons.update({ + 'compute': {}, + 'send': {}, + }) + + @classmethod + def _stock_move_origin(cls, move): + return 'stock.move,%s' % move.id + + @classmethod + def _delete_draft_account_moves_for_stock_moves(cls, moves): + moves = [move for move in moves if getattr(move, 'id', None)] + if not moves: + return + + AccountMove = Pool().get('account.move') + account_moves = AccountMove.search([ + ('origin', 'in', [cls._stock_move_origin(move) for move in moves]), + ]) + posted = [ + move for move in account_moves + if getattr(move, 'state', None) == 'posted'] + if posted: + names = ', '.join(move.rec_name for move in posted[:5]) + if len(posted) > 5: + names += '...' + raise UserError( + "Cannot cancel started shipment because stock accounting " + "move(s) are already posted: %s" % names) + + draft_moves = [ + move for move in account_moves + if getattr(move, 'state', None) != 'posted'] + if draft_moves: + AccountMove.delete(draft_moves) + + @classmethod + @ModelView.button + @Workflow.transition('cancelled') + def cancel(cls, shipments): + moves = [ + move for shipment in shipments + for move in shipment.incoming_moves + shipment.inventory_moves] + cls._delete_draft_account_moves_for_stock_moves(moves) + super().cancel(shipments) + started_shipments = [ + shipment for shipment in shipments + if shipment.state == 'started'] + if started_shipments: + cls.write(started_shipments, { + 'start_date': None, + 'started_by': None, + }) def get_vessel_type(self,name=None): if self.vessel: diff --git a/modules/purchase_trade/tests/test_module.py b/modules/purchase_trade/tests/test_module.py index 2e57ab9..ebbc9f5 100644 --- a/modules/purchase_trade/tests/test_module.py +++ b/modules/purchase_trade/tests/test_module.py @@ -969,6 +969,41 @@ class PurchaseTradeTestCase(ModuleTestCase): fee_model.save.assert_not_called() product_model.get_by_name.assert_not_called() + def test_shipment_started_cancel_deletes_draft_stock_account_moves(self): + 'started shipment cancel removes draft account moves linked to stock moves' + ShipmentIn = Pool().get('stock.shipment.in') + draft_move = Mock(state='draft') + account_move = Mock() + account_move.search.return_value = [draft_move] + stock_moves = [Mock(id=10), Mock(id=20)] + + with patch('trytond.modules.purchase_trade.stock.Pool') as PoolMock: + PoolMock.return_value.get.return_value = account_move + + ShipmentIn._delete_draft_account_moves_for_stock_moves(stock_moves) + + account_move.search.assert_called_once_with([ + ('origin', 'in', ['stock.move,10', 'stock.move,20']), + ]) + account_move.delete.assert_called_once_with([draft_move]) + + def test_shipment_started_cancel_refuses_posted_stock_account_moves(self): + 'started shipment cancel is blocked by posted stock account moves' + ShipmentIn = Pool().get('stock.shipment.in') + posted_move = Mock(state='posted', rec_name='STJ/1') + account_move = Mock() + account_move.search.return_value = [posted_move] + + with patch('trytond.modules.purchase_trade.stock.Pool') as PoolMock: + PoolMock.return_value.get.return_value = account_move + + with self.assertRaises(UserError): + ShipmentIn._delete_draft_account_moves_for_stock_moves([ + Mock(id=10), + ]) + + account_move.delete.assert_not_called() + @with_transaction() def test_shipment_in_type_detects_dropship(self): 'shipment type is dropship from supplier directly to customer'