[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
This commit is contained in:
Adrien Widart (awt)
2023-01-12 08:02:47 +01:00
parent cface47e4e
commit 7b4b835465
3 changed files with 157 additions and 9 deletions
+18 -8
View File
@@ -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
+1 -1
View File
@@ -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()
+138
View File
@@ -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.