From 0a980363879d59db0f0cfaae5db2980a2ad0821d Mon Sep 17 00:00:00 2001 From: Adrien Widart Date: Tue, 26 Apr 2022 06:58:45 +0000 Subject: [PATCH] [FIX] stock: cancel picking with scrapped SM Cancelling a picking with scrapped and done SM will cancel this SM too. To reproduce the issue: 1. In Settings, enable "Storage Locations" 2. Create a storable product P 3. Update the quantity of P: 10 in WH/Stock 4. Create a planned delivery order DO with 2 x P 5. Mark DO as todo, Check availability 6. Scrap 2 x P 7. Cancel DO 8. Open the SM of the scrapping Error: The SM is cancelled. This is incorrect: the move was done, so there is a quant with 2 x P in scrap location. There is now an incoherence between quants and SMs report. OPW-2805604 closes odoo/odoo#89987 X-original-commit: bd3324acc3ec227438d2cb25a1df1e0fca2a2789 Signed-off-by: Arnold Moyaux Signed-off-by: Adrien Widart --- addons/stock/models/stock_move.py | 4 +-- addons/stock/models/stock_picking.py | 2 +- addons/stock/tests/test_stock_flow.py | 40 +++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py index e57fe7c1bcd..44691b67733 100644 --- a/addons/stock/models/stock_move.py +++ b/addons/stock/models/stock_move.py @@ -1565,7 +1565,7 @@ class StockMove(models.Model): def _action_cancel(self): if any(move.state == 'done' and not move.scrapped for move in self): raise UserError(_('You cannot cancel a stock move that has been set to \'Done\'. Create a return in order to reverse the moves which took place.')) - moves_to_cancel = self.filtered(lambda m: m.state != 'cancel') + moves_to_cancel = self.filtered(lambda m: m.state != 'cancel' and not (m.state == 'done' and m.scrapped)) # self cannot contain moves that are either cancelled or done, therefore we can safely # unlink all associated move_line_ids moves_to_cancel._do_unreserve() @@ -1580,7 +1580,7 @@ class StockMove(models.Model): if all(state in ('done', 'cancel') for state in siblings_states): move.move_dest_ids.write({'procure_method': 'make_to_stock'}) move.move_dest_ids.write({'move_orig_ids': [(3, move.id, 0)]}) - self.write({ + moves_to_cancel.write({ 'state': 'cancel', 'move_orig_ids': [(5, 0, 0)], 'procure_method': 'make_to_stock', diff --git a/addons/stock/models/stock_picking.py b/addons/stock/models/stock_picking.py index 399c79e589b..783ce46899c 100644 --- a/addons/stock/models/stock_picking.py +++ b/addons/stock/models/stock_picking.py @@ -523,7 +523,7 @@ 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)]): + for move in self.env['stock.move'].search([('picking_id', 'in', self.ids), ('scrapped', '=', False)]): picking_id = move.picking_id move_state = move.state picking_moves_state_map[picking_id.id].update({ diff --git a/addons/stock/tests/test_stock_flow.py b/addons/stock/tests/test_stock_flow.py index bdfdbd15b32..c9d98ee0d92 100644 --- a/addons/stock/tests/test_stock_flow.py +++ b/addons/stock/tests/test_stock_flow.py @@ -2180,3 +2180,43 @@ class TestStockFlow(TestStockCommon): picking.write({'partner_id': partner_2.id}) self.assertEqual(picking.move_ids.partner_id, partner_2) + + def test_cancel_picking_with_scrapped_products(self): + """ + The user scraps some products of a picking, then cancel this picking + The test ensures that the scrapped SM is not cancelled + """ + stock_location = self.env['stock.location'].browse(self.stock_location) + self.env['stock.quant']._update_available_quantity(self.productA, stock_location, 10) + + picking = self.env['stock.picking'].create({ + 'picking_type_id': self.picking_type_out, + 'location_id': self.supplier_location, + 'location_dest_id': self.stock_location, + }) + move = self.env['stock.move'].create({ + 'name': self.productA.name, + 'product_id': self.productA.id, + 'product_uom_qty': 1, + 'product_uom': self.productA.uom_id.id, + 'picking_id': picking.id, + 'location_id': self.supplier_location, + 'location_dest_id': self.stock_location, + }) + + picking.action_confirm() + picking.action_assign() + + scrap = self.env['stock.scrap'].create({ + 'picking_id': picking.id, + 'product_id': self.productA.id, + 'product_uom_id': self.productA.uom_id.id, + 'scrap_qty': 1.0, + }) + scrap.do_scrap() + + picking.action_cancel() + + self.assertEqual(picking.state, 'cancel') + self.assertEqual(move.state, 'cancel') + self.assertEqual(scrap.move_id.state, 'done')