From 7b4b8354651cdec870d9bc5bb4e42f36dfcbca7b Mon Sep 17 00:00:00 2001 From: "Adrien Widart (awt)" Date: Tue, 3 Jan 2023 08:48:15 +0000 Subject: [PATCH] [FIX] stock: apply putaway on package with one product category When applying the putaway rules, if we redirect a package (with a specific type), we don't check the category of the package products. To reproduce the issue: 1. In Settings, enable - Packages - Multi-Step Routes - Storage Categories 2. Edit the warehouse: - Receipt: 2 steps 3. Create three categories C1, C2, C3 - Parent: All 4. Create three locations L1, L2, L3 - Parent: WH/Stock 5. For each location Lx, create a putaway rules PRx: - From: WH/Stock - Category: Cx - To: Lx 6. Create a product P - Type: Storable - Category: C2 7. Create a planned receipt R with 1 x P 8. Mark it as todo, set the done quantity and put it in pack 9. Set a type on the package 10. Mark R as done 11. Open the associated internal transfer Error: the destination location of the package is not L2 -> an incorrect putaway rule has been applied When we try to apply the PR on the SML "input to stock", because this is a package with a type, we don't provide the list of the products. Therefore, we have no idea about the product category. This is a shame in case of a package that contains some products of the same category. We should be able to apply the putaway rules related to that category. Moreover, this commit also fixes two other issues explained in [1] and [2]. The FW of these commits have been stopped for the current one because they didn't fix the above use case. Moreover, this commit simplifies/clarifies the filtering and sorting of all putaway rules [1] 03e47b165e7f6c59b41509d9229e8e2074aa3f34 [2] e0098eb281d886797a888a6f8fac474bbacb1b09 OPW-3098452 X-original-commit: c99523bc3a4a5a335f1928cf9bb53bb8483b492e Part-of: odoo/odoo#109669 --- addons/stock/models/stock_location.py | 26 +++-- addons/stock/models/stock_move_line.py | 2 +- addons/stock/tests/test_move.py | 138 +++++++++++++++++++++++++ 3 files changed, 157 insertions(+), 9 deletions(-) diff --git a/addons/stock/models/stock_location.py b/addons/stock/models/stock_location.py index 471626eec98..457353425e8 100644 --- a/addons/stock/models/stock_location.py +++ b/addons/stock/models/stock_location.py @@ -256,6 +256,8 @@ class Location(models.Model): no package is specified. """ self = self._check_access_putaway() + products = self.env.context.get('products', self.env['product.product']) + products |= product # find package type on package or packaging package_type = self.env['stock.package.type'] if package: @@ -263,14 +265,22 @@ class Location(models.Model): elif packaging: package_type = packaging.package_type_id - putaway_rules = self.env['stock.putaway.rule'] - putaway_rules |= self.putaway_rule_ids.filtered(lambda x: x.product_id == product and (package_type in x.package_type_ids or package_type == x.package_type_ids)) - categ = product.categ_id - while categ: - putaway_rules |= self.putaway_rule_ids.filtered(lambda x: x.category_id == categ and (package_type in x.package_type_ids or package_type == x.package_type_ids)) - categ = categ.sudo().parent_id - if package_type: - putaway_rules |= self.putaway_rule_ids.filtered(lambda x: not x.product_id and (package_type in x.package_type_ids or package_type == x.package_type_ids)) + categ = products.categ_id if len(products.categ_id) == 1 else self.env['product.category'] + categs = categ + while categ.parent_id: + categ = categ.parent_id + categs |= categ + + putaway_rules = self.putaway_rule_ids.filtered(lambda rule: + (not rule.product_id or rule.product_id in products) and + (not rule.category_id or rule.category_id in categs) and + (not rule.package_type_ids or package_type in rule.package_type_ids)) + + putaway_rules = putaway_rules.sorted(lambda rule: (rule.package_type_ids, + rule.product_id, + rule.category_id == categs[:1], # same categ, not a parent + rule.category_id), + reverse=True) putaway_location = None locations = self.child_internal_location_ids diff --git a/addons/stock/models/stock_move_line.py b/addons/stock/models/stock_move_line.py index 9a8e7d80ae4..3f1b9d1ec41 100644 --- a/addons/stock/models/stock_move_line.py +++ b/addons/stock/models/stock_move_line.py @@ -234,7 +234,7 @@ class StockMoveLine(models.Model): smls = self.env['stock.move.line'].concat(*smls) excluded_smls = smls if package.package_type_id: - best_loc = smls.move_id.location_dest_id.with_context(exclude_sml_ids=excluded_smls.ids)._get_putaway_strategy(self.env['product.product'], package=package) + best_loc = smls.move_id.location_dest_id.with_context(exclude_sml_ids=excluded_smls.ids, products=smls.product_id)._get_putaway_strategy(self.env['product.product'], package=package) smls.location_dest_id = smls.package_level_id.location_dest_id = best_loc elif package: used_locations = set() diff --git a/addons/stock/tests/test_move.py b/addons/stock/tests/test_move.py index 7f31c06fe4f..81ca2541f79 100644 --- a/addons/stock/tests/test_move.py +++ b/addons/stock/tests/test_move.py @@ -939,6 +939,144 @@ class StockMove(TransactionCase): # check if the putaway was rightly applied self.assertEqual(move1.move_line_ids.location_dest_id.id, shelf2_location.id) + def test_putaway_7(self): + """ + Putaway with one package type and one product + """ + warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1) + warehouse.reception_steps = 'two_steps' + child_loc = self.stock_location.child_ids[0] + + package_type = self.env['stock.package.type'].create({ + 'name': 'Super Package Type', + }) + + package = self.env['stock.quant.package'].create({'package_type_id': package_type.id}) + + self.env['stock.putaway.rule'].create({ + 'product_id': self.product.id, + 'package_type_ids': [(6, 0, package_type.ids)], + 'location_in_id': self.stock_location.id, + 'location_out_id': child_loc.id, + }) + + move_input = self.env['stock.move'].create({ + 'name': self.product.name, + 'location_id': self.supplier_location.id, + 'location_dest_id': warehouse.wh_input_stock_loc_id.id, + 'product_id': self.product.id, + 'product_uom': self.uom_unit.id, + 'product_uom_qty': 1.0, + 'warehouse_id': warehouse.id, + }) + move_input._action_confirm() + move_input.move_line_ids.qty_done = 1 + move_input.move_line_ids.result_package_id = package + move_input._action_done() + + move_stock = move_input.move_dest_ids + self.assertEqual(move_stock.move_line_ids.location_dest_id, child_loc) + + def test_putaway_8(self): + """ + Putaway with product P + Receive 1 x P in a package with a specific type + """ + warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1) + warehouse.reception_steps = 'two_steps' + child_loc = self.stock_location.child_ids[0] + + package_type = self.env['stock.package.type'].create({ + 'name': 'Super Package Type', + }) + + package = self.env['stock.quant.package'].create({'package_type_id': package_type.id}) + + self.env['stock.putaway.rule'].create({ + 'product_id': self.product.id, + 'location_in_id': self.stock_location.id, + 'location_out_id': child_loc.id, + }) + + move_input = self.env['stock.move'].create({ + 'name': self.product.name, + 'location_id': self.supplier_location.id, + 'location_dest_id': warehouse.wh_input_stock_loc_id.id, + 'product_id': self.product.id, + 'product_uom': self.uom_unit.id, + 'product_uom_qty': 1.0, + 'warehouse_id': warehouse.id, + }) + move_input._action_confirm() + move_input.move_line_ids.qty_done = 1 + move_input.move_line_ids.result_package_id = package + move_input._action_done() + + move_stock = move_input.move_dest_ids + self.assertEqual(move_stock.move_line_ids.location_dest_id, child_loc) + + def test_putaway_9(self): + """ + Putaway with one category C + 2 steps receive + Receive one C-type product in a package with a specific type + The putaway should be selected + """ + warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1) + warehouse.reception_steps = 'two_steps' + + basic_category = self.env.ref('product.product_category_all') + child_locations = self.env['stock.location'] + categs = self.env['product.category'] + + for i in range(3): + loc = self.env['stock.location'].create({ + 'name': 'shelf %s' % i, + 'usage': 'internal', + 'location_id': self.stock_location.id, + }) + child_locations |= loc + + categ = self.env['product.category'].create({ + 'name': 'Category %s' % i, + 'parent_id': basic_category.id + }) + categs |= categ + + self.env['stock.putaway.rule'].create({ + 'category_id': categ.id, + 'location_in_id': self.stock_location.id, + 'location_out_id': loc.id, + }) + + second_child_location = child_locations[1] + second_categ = categs[1] + self.product.categ_id = second_categ + + package_type = self.env['stock.package.type'].create({ + 'name': 'Super Package Type', + }) + package = self.env['stock.quant.package'].create({ + 'package_type_id': package_type.id, + }) + + move_input = self.env['stock.move'].create({ + 'name': self.product.name, + 'location_id': self.supplier_location.id, + 'location_dest_id': warehouse.wh_input_stock_loc_id.id, + 'product_id': self.product.id, + 'product_uom': self.uom_unit.id, + 'product_uom_qty': 1.0, + 'warehouse_id': warehouse.id, + }) + move_input._action_confirm() + move_input.move_line_ids.qty_done = 1 + move_input.move_line_ids.result_package_id = package + move_input._action_done() + + move_stock = move_input.move_dest_ids + self.assertEqual(move_stock.move_line_ids.location_dest_id, second_child_location) + def test_putaway_with_storage_category_1(self): """Receive a product. Test the product will be move to a child location with correct storage category.