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.
This commit is contained in:
Thierry Ducrest
2023-09-01 15:18:35 +02:00
parent c675bb27df
commit 57dec645d0
2 changed files with 77 additions and 1 deletions

View File

@@ -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 @@ class StockReserveRuleRemoval(models.Model):
# 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

View File

@@ -435,6 +435,45 @@ class TestReserveRule(common.SavepointCase):
)
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)