diff --git a/addons/stock/models/stock_picking.py b/addons/stock/models/stock_picking.py index 783ce46899c..ba636a93e06 100644 --- a/addons/stock/models/stock_picking.py +++ b/addons/stock/models/stock_picking.py @@ -523,13 +523,15 @@ class Picking(models.Model): ''' picking_moves_state_map = defaultdict(dict) picking_move_lines = defaultdict(set) - for move in self.env['stock.move'].search([('picking_id', 'in', self.ids), ('scrapped', '=', False)]): + for move in self.env['stock.move'].search([('picking_id', 'in', self.ids)]): picking_id = move.picking_id move_state = move.state picking_moves_state_map[picking_id.id].update({ 'any_draft': picking_moves_state_map[picking_id.id].get('any_draft', False) or move_state == 'draft', 'all_cancel': picking_moves_state_map[picking_id.id].get('all_cancel', True) and move_state == 'cancel', 'all_cancel_done': picking_moves_state_map[picking_id.id].get('all_cancel_done', True) and move_state in ('cancel', 'done'), + 'all_done_are_scrapped': picking_moves_state_map[picking_id.id].get('all_done_are_scrapped', True) and (move.scrapped if move_state == 'done' else True), + 'any_cancel_and_not_scrapped': picking_moves_state_map[picking_id.id].get('any_cancel_and_not_scrapped', False) or (move_state == 'cancel' and not move.scrapped), }) picking_move_lines[picking_id.id].add(move.id) for picking in self: @@ -541,7 +543,10 @@ class Picking(models.Model): elif picking_moves_state_map[picking_id]['all_cancel']: picking.state = 'cancel' elif picking_moves_state_map[picking_id]['all_cancel_done']: - picking.state = 'done' + if picking_moves_state_map[picking_id]['all_done_are_scrapped'] and picking_moves_state_map[picking_id]['any_cancel_and_not_scrapped']: + picking.state = 'cancel' + else: + picking.state = 'done' else: relevant_move_state = self.env['stock.move'].browse(picking_move_lines[picking_id])._get_relevant_state_among_moves() if picking.immediate_transfer and relevant_move_state not in ('draft', 'cancel', 'done'): diff --git a/addons/stock/tests/test_move.py b/addons/stock/tests/test_move.py index c4b4ccd004b..6d344044aa4 100644 --- a/addons/stock/tests/test_move.py +++ b/addons/stock/tests/test_move.py @@ -4655,6 +4655,68 @@ class StockMove(TransactionCase): self.assertEqual(list(warning.keys())[0], 'warning', 'Warning message was not returned') self.assertEqual(scrap.location_id, child_loc1, 'Location was not auto-corrected') + def test_scrap_8(self): + """ + Suppose a user wants to scrap some products thanks to internal moves. + This test checks the state of the picking based on few cases + """ + scrap_location = self.env['stock.location'].search([('company_id', '=', self.env.company.id), ('scrap_location', '=', True)], limit=1) + internal_operation = self.env['stock.picking.type'].with_context(active_test=False).search([('code', '=', 'internal'), ('company_id', '=', self.env.company.id)], limit=1) + internal_operation.active = True + + product01 = self.product + product02 = self.env['product.product'].create({ + 'name': 'SuperProduct', + 'type': 'product', + }) + + self.env['stock.quant']._update_available_quantity(product01, self.stock_location, 3) + self.env['stock.quant']._update_available_quantity(product02, self.stock_location, 1) + + scrap_picking01, scrap_picking02, scrap_picking03 = self.env['stock.picking'].create([{ + 'location_id': self.stock_location.id, + 'location_dest_id': scrap_location.id, + 'picking_type_id': internal_operation.id, + 'move_ids': [(0, 0, { + 'name': 'Scrap %s' % product.display_name, + 'location_id': self.stock_location.id, + 'location_dest_id': scrap_location.id, + 'product_id': product.id, + 'product_uom': product.uom_id.id, + 'product_uom_qty': 1.0, + 'picking_type_id': internal_operation.id, + }) for product in products], + } for products in [(product01,), (product01,), (product01, product02)]]) + + (scrap_picking01 + scrap_picking02 + scrap_picking03).action_confirm() + + # All SM are processed + scrap_picking01.move_ids.quantity_done = 1 + scrap_picking01.button_validate() + + # All SM are cancelled + scrap_picking02.action_cancel() + + # Process one SM and cancel the other one + pick03_prod01_move = scrap_picking03.move_ids.filtered(lambda sm: sm.product_id == product01) + pick03_prod02_move = scrap_picking03.move_ids - pick03_prod01_move + pick03_prod01_move.quantity_done = 1 + pick03_prod02_move._action_cancel() + scrap_picking03.button_validate() + + self.assertEqual(scrap_picking01.move_ids.state, 'done') + self.assertEqual(scrap_picking01.state, 'done') + + self.assertEqual(scrap_picking02.move_ids.state, 'cancel') + self.assertEqual(scrap_picking02.state, 'cancel') + + self.assertEqual(pick03_prod01_move.state, 'done') + self.assertEqual(pick03_prod02_move.state, 'cancel') + self.assertEqual(scrap_picking03.state, 'done') + + self.assertEqual(self.env['stock.quant']._get_available_quantity(product01, self.stock_location), 1) + self.assertEqual(self.env['stock.quant']._get_available_quantity(product02, self.stock_location), 1) + def test_in_date_1(self): """ Check that moving a tracked quant keeps the incoming date. """