From 043cf8e4181a5fc647a88d94956866e8d95d3ca7 Mon Sep 17 00:00:00 2001 From: clesgow Date: Wed, 7 Dec 2022 12:45:38 +0000 Subject: [PATCH] [FIX] stock: Fix package_level unlink Steps to reproduce : - Create two products - Create a planned internal transfer with one of each product from Stock -> Location A - Hit "Mark as todo" - Hit "Put in pack" - Validate the picking - Create an new planned internal transfer, with the same products and quantities, but from Location A -> Location B - Remove the destination package for one product. - This will remove the destination package for all products in the same package. When unlinking a package level, it will unlink all moves and move_lines at the same time. Problem is that if a single move_line is updated in a picking where multiple move_lines are in the same package, it will unlink it for all the move_lines instead. closes odoo/odoo#107448 X-original-commit: 9dcbc420f50ece3356dff57cbdc467c7893ff2c2 Signed-off-by: Tiffany Chang --- addons/stock/models/stock_move_line.py | 4 +- addons/stock/tests/test_packing.py | 66 ++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/addons/stock/models/stock_move_line.py b/addons/stock/models/stock_move_line.py index 57fb796dced..9a8e7d80ae4 100644 --- a/addons/stock/models/stock_move_line.py +++ b/addons/stock/models/stock_move_line.py @@ -368,7 +368,9 @@ class StockMoveLine(models.Model): # TODO: make package levels less of a pain and fix this package_level = ml.package_level_id ml.package_level_id = False - package_level.unlink() + # Only need to unlink the package level if it's empty. Otherwise will unlink it to still valid move lines. + if not package_level.move_line_ids: + package_level.unlink() # When we try to write on a reserved move line any fields from `triggers` or directly # `reserved_uom_qty` (the actual reserved quantity), we need to make sure the associated diff --git a/addons/stock/tests/test_packing.py b/addons/stock/tests/test_packing.py index f033fc82d0e..d67f23ad9d7 100644 --- a/addons/stock/tests/test_packing.py +++ b/addons/stock/tests/test_packing.py @@ -1263,3 +1263,69 @@ class TestPacking(TestPackingCommon): self.assertEqual(picking.state, 'done') self.assertEqual(picking.move_ids.quantity_done, 0.4) self.assertEqual(len(picking.move_line_ids.result_package_id), 2) + + def test_put_out_of_pack_transfer(self): + """ When a transfer has multiple products all in the same package, removing a product from the destination package + (i.e. removing it from the package but still putting it in the same location) shouldn't remove it for other products. """ + loc_1 = self.env['stock.location'].create({ + 'name': 'Location A', + 'location_id': self.stock_location.id, + }) + loc_2 = self.env['stock.location'].create({ + 'name': 'Location B', + 'location_id': self.stock_location.id, + }) + pack = self.env['stock.quant.package'].create({'name': 'New Package'}) + self.env['stock.quant']._update_available_quantity(self.productA, loc_1, 5, package_id=pack) + self.env['stock.quant']._update_available_quantity(self.productB, loc_1, 4, package_id=pack) + + picking = self.env['stock.picking'].create({ + 'location_id': loc_1.id, + 'location_dest_id': loc_2.id, + 'picking_type_id': self.warehouse.int_type_id.id, + }) + moveA = self.env['stock.move'].create({ + 'name': self.productA.name, + 'product_id': self.productA.id, + 'product_uom_qty': 5, + 'product_uom': self.productA.uom_id.id, + 'picking_id': picking.id, + 'location_id': loc_1.id, + 'location_dest_id': loc_2.id, + }) + moveB = self.env['stock.move'].create({ + 'name': self.productB.name, + 'product_id': self.productB.id, + 'product_uom_qty': 4, + 'product_uom': self.productB.uom_id.id, + 'picking_id': picking.id, + 'location_id': loc_1.id, + 'location_dest_id': loc_2.id, + }) + # Check availabilities + picking.action_assign() + self.assertEqual(len(moveA.move_line_ids), 1, "A move line should have been created for the reservation of the package.") + self.assertEqual(moveA.move_line_ids.package_id.id, pack.id, "The package should have been reserved for both products.") + self.assertEqual(moveB.move_line_ids.package_id.id, pack.id, "The package should have been reserved for both products.") + pack_level = moveA.move_line_ids.package_level_id + + # Remove the product A from the package in the destination. + moveA.move_line_ids.result_package_id = False + self.assertEqual(moveA.move_line_ids.result_package_id.id, False, "No package should be linked in the destination.") + self.assertEqual(moveA.move_line_ids.package_level_id.id, False, "Package level should have been unlinked from this move line.") + self.assertEqual(moveB.move_line_ids.result_package_id.id, pack.id, "Package should have stayed the same.") + self.assertEqual(moveB.move_line_ids.package_level_id.id, pack_level.id, "Package level should have stayed the same.") + + # Validate the picking + moveA.move_line_ids.qty_done = 5 + moveB.move_line_ids.qty_done = 4 + picking.button_validate() + + # Check that the quants have their expected location/package/quantities + quantA = self.env['stock.quant'].search([('product_id', '=', self.productA.id), ('location_id', '=', loc_2.id)]) + quantB = self.env['stock.quant'].search([('product_id', '=', self.productB.id), ('location_id', '=', loc_2.id)]) + self.assertEqual(pack.location_id.id, loc_2.id, "Package should have been moved to Location B.") + self.assertEqual(quantA.quantity, 5, "All 5 units of product A should be in location B") + self.assertEqual(quantA.package_id.id, False, "There should be no package for product A as it was removed in the move.") + self.assertEqual(quantB.quantity, 4, "All 4 units of product B should be in location B") + self.assertEqual(quantB.package_id.id, pack.id, "Product B should still be in the initial package.")