From 57dec645d05c6718e18d141def9b5054413383c1 Mon Sep 17 00:00:00 2001 From: Thierry Ducrest Date: Fri, 1 Sep 2023 15:18:35 +0200 Subject: [PATCH] stock_reserve_rule: add full empty bin removal strategy The full empty bin rule is like the empty bin rule, but it should take everything. That means there can not be any pre existing reservations. --- .../models/stock_reserve_rule.py | 39 ++++++++++++++++++- stock_reserve_rule/tests/test_reserve_rule.py | 39 +++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/stock_reserve_rule/models/stock_reserve_rule.py b/stock_reserve_rule/models/stock_reserve_rule.py index a7c39e5595d9..8494d690d17e 100644 --- a/stock_reserve_rule/models/stock_reserve_rule.py +++ b/stock_reserve_rule/models/stock_reserve_rule.py @@ -133,6 +133,7 @@ class StockReserveRuleRemoval(models.Model): ("default", "Default Removal Strategy"), ("empty_bin", "Empty Bins"), ("packaging", "Full Packaging"), + ("full_bin", "Full Bin"), ], required=True, default="default", @@ -142,7 +143,8 @@ class StockReserveRuleRemoval(models.Model): "Empty Bins: take goods from a location only if the bin is" " empty afterwards.\n" "Full Packaging: take goods from a location only if the location " - "quantity matches a packaging quantity (do not open boxes).", + "quantity matches a packaging quantity (do not open boxes).\n" + "Full Bin: take goods from a location if it reserves all its content", ) packaging_type_ids = fields.Many2many( @@ -296,3 +298,38 @@ def is_greater_eq(value, other): # compute how much packaging we can get take = (need // pack_quantity) * pack_quantity need = yield location, location_quantity, take, None, None + + def _apply_strategy_full_bin(self, quants): + need = yield + # Only location with nothing reserved can be fully emptied + quants = quants.filtered(lambda q: q.reserved_quantity == 0) + # Group by location (in this removal strategies, we want to consider + # the total quantity held in a location). + quants_per_bin = quants._group_by_location() + # We take goods only if we empty the bin. + # The original ordering (fefo, fifo, ...) must be kept. + product = fields.first(quants).product_id + rounding = product.uom_id.rounding + locations_with_other_quants = [ + group["location_id"][0] + for group in quants.read_group( + [ + ("location_id", "in", quants.location_id.ids), + ("product_id", "not in", quants.product_id.ids), + ("quantity", ">", 0), + ], + ["location_id"], + "location_id", + ) + ] + for location, location_quants in quants_per_bin: + if location.id in locations_with_other_quants: + continue + + location_quantity = sum(location_quants.mapped("quantity")) + + if location_quantity <= 0: + continue + + if float_compare(need, location_quantity, rounding) != -1: + need = yield location, location_quantity, need, None, None diff --git a/stock_reserve_rule/tests/test_reserve_rule.py b/stock_reserve_rule/tests/test_reserve_rule.py index 7df3c6d9274d..e23db91fa7e6 100644 --- a/stock_reserve_rule/tests/test_reserve_rule.py +++ b/stock_reserve_rule/tests/test_reserve_rule.py @@ -435,6 +435,45 @@ def test_quant_domain_lot_and_owner(self): ) self.assertEqual(move.state, "assigned") + def test_rule_full_bin(self): + self._create_rule( + {}, + [ + { + "location_id": self.loc_zone1.id, + "sequence": 1, + "removal_strategy": "full_bin", + }, + {"location_id": self.loc_zone2.id, "sequence": 2}, + ], + ) + # 100 on location and reserving it with picking 1 + self._update_qty_in_location(self.loc_zone1_bin1, self.product1, 100) + picking1 = self._create_picking(self.wh, [(self.product1, 100)]) + picking1.action_assign() + self.assertEqual(picking1.state, "assigned") + # Add 300 on location + # There is 400 but 100 is reserved + self._update_qty_in_location(self.loc_zone1_bin1, self.product1, 300) + # A move for 300 will no be allowed (not fully empty) + picking2 = self._create_picking(self.wh, [(self.product1, 300)]) + picking2.action_assign() + self.assertEqual(picking2.state, "confirmed") + # But when picking 1 is done, no more reserved quantity + picking1.move_line_ids.qty_done = picking1.move_line_ids.product_uom_qty + picking1._action_done() + # Bin is fully emptied + picking2.action_assign() + move = picking2.move_lines + ml = move.move_line_ids + self.assertRecordValues( + ml, + [ + {"location_id": self.loc_zone1_bin1.id, "product_qty": 300.0}, + ], + ) + self.assertEqual(move.state, "assigned") + def test_rule_empty_bin(self): self._update_qty_in_location(self.loc_zone1_bin1, self.product1, 300) self._update_qty_in_location(self.loc_zone1_bin2, self.product1, 150)