diff --git a/addons/stock/models/stock_location.py b/addons/stock/models/stock_location.py index 38847795155c042af55f9d7adbf44607353b8bc0..8550b1c34b7163dc1a54a777820dd08a19c8d58a 100644 --- a/addons/stock/models/stock_location.py +++ b/addons/stock/models/stock_location.py @@ -407,6 +407,12 @@ class Location(models.Model): product = product or self._context.get('products') if (positive_quant and positive_quant.product_id != product) or len(product) > 1: return False + if self.env['stock.move.line'].search([ + ('product_id', '!=', product.id), + ('state', 'not in', ('done', 'cancel')), + ('location_dest_id', '=', self.id), + ], limit=1): + return False return True diff --git a/addons/stock/tests/test_packing.py b/addons/stock/tests/test_packing.py index 86a016e97d2c2cda39ee97f6ad8ac27ec98b72f1..6b0e6a785da226b718c0f2182f3230065de6fdb3 100644 --- a/addons/stock/tests/test_packing.py +++ b/addons/stock/tests/test_packing.py @@ -1229,13 +1229,17 @@ class TestPacking(TestPackingCommon): def test_pack_in_receipt_two_step_multi_putaway_04(self): """ Create a putaway rules for package type T and storage category SC. SC - only allows same products and has a maximum of 2 x T. Two SC locations - L1 and L2. + only allows same products and has a maximum of 2 x T. Four SC locations + L1, L2, L3 and L4. First, move a package that contains two different products: should not redirect to L1/L2 because of the "same products" contraint. - Then, add one T-package (with product P) at L1 and move 2 T-packages - (both with product P): one should be redirected to L1 and the second one - to L2 + Then, add one T-package (with product P01) at L1 and move 2 T-packages + (both with product P01): one should be redirected to L1 and the second + one to L2 + Finally, move 3 T-packages (two with 1xP01, one with 1xP02): one P01 + should be redirected to L2 and the second one to L3 (because of capacity + constraint), then P02 should be redirected to L4 (because of "same + product" policy) """ self.warehouse.reception_steps = "two_steps" supplier_location = self.env.ref('stock.stock_location_suppliers') @@ -1255,12 +1259,12 @@ class TestPacking(TestPackingCommon): })], }) - loc01, loc02 = self.env['stock.location'].create([{ - 'name': n, + loc01, loc02, loc03, loc04 = self.env['stock.location'].create([{ + 'name': 'loc 0%d' % i, 'usage': 'internal', 'location_id': self.stock_location.id, 'storage_category_id': storage_category.id, - } for n in ('loc01', 'loc02')]) + } for i in range(1, 5)]) self.env['stock.putaway.rule'].create({ 'location_in_id': self.stock_location.id, @@ -1288,10 +1292,11 @@ class TestPacking(TestPackingCommon): moves.move_line_ids.qty_done = 1 moves.move_line_ids.result_package_id = self.env['stock.quant.package'].create({'package_type_id': package_type.id}) receipt.button_validate() - dest_moves = moves.move_dest_ids - self.assertEqual(dest_moves.move_line_ids.location_dest_id, self.stock_location, + internal_picking = moves.move_dest_ids.picking_id + self.assertEqual(internal_picking.move_line_ids.location_dest_id, self.stock_location, 'Storage location only accepts one same product. Here the package contains two different ' 'products so it should not be redirected.') + internal_picking.action_cancel() # Second test part package = self.env['stock.quant.package'].create({'package_type_id': package_type.id}) @@ -1325,9 +1330,53 @@ class TestPacking(TestPackingCommon): } for _ in range(2)]) receipt.button_validate() - self.assertEqual(receipt.move_ids.move_dest_ids.move_line_ids.location_dest_id, loc01 | loc02, + internal_transfer = receipt.move_ids.move_dest_ids.picking_id + self.assertEqual(internal_transfer.move_line_ids.location_dest_id, loc01 | loc02, 'There is already one package at L1, so the first SML should be redirected to L1 ' 'and the second one to L2') + internal_transfer.move_line_ids.qty_done = 1 + internal_transfer.button_validate() + + # Third part (move 3 packages, 2 x P01 and 1 x P02) + receipt = self.env['stock.picking'].create({ + 'picking_type_id': self.warehouse.in_type_id.id, + 'location_id': supplier_location.id, + 'location_dest_id': input_location.id, + 'move_ids': [(0, 0, { + 'name': product.name, + 'location_id': supplier_location.id, + 'location_dest_id': input_location.id, + 'product_id': product.id, + 'product_uom': product.uom_id.id, + 'product_uom_qty': qty, + }) for qty, product in [(2.0, self.productA), (1.0, self.productB)]], + }) + receipt.action_confirm() + + receipt.do_unreserve() + moves = receipt.move_ids + self.env['stock.move.line'].create([{ + 'move_id': move.id, + 'qty_done': 1, + 'product_id': product.id, + 'product_uom_id': product.uom_id.id, + 'location_id': supplier_location.id, + 'location_dest_id': input_location.id, + 'result_package_id': self.env['stock.quant.package'].create({'package_type_id': package_type.id}).id, + 'picking_id': receipt.id, + } for product, move in [ + (self.productA, moves[0]), + (self.productA, moves[0]), + (self.productB, moves[1]), + ]]) + receipt.button_validate() + + internal_transfer = receipt.move_ids.move_dest_ids.picking_id + self.assertRecordValues(internal_transfer.move_line_ids, [ + {'product_id': self.productA.id, 'reserved_uom_qty': 1.0, 'location_dest_id': loc02.id}, + {'product_id': self.productA.id, 'reserved_uom_qty': 1.0, 'location_dest_id': loc03.id}, + {'product_id': self.productB.id, 'reserved_uom_qty': 1.0, 'location_dest_id': loc04.id}, + ]) def test_rounding_and_reserved_qty(self): """