Skip to content

Commit

Permalink
[IMP] stock_reserve_rule: New removal strategy is added.
Browse files Browse the repository at this point in the history
  • Loading branch information
geomer198 committed Oct 25, 2023
1 parent 88bd31a commit 9352c9d
Show file tree
Hide file tree
Showing 9 changed files with 372 additions and 8 deletions.
2 changes: 1 addition & 1 deletion stock_reserve_rule/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"name": "Stock Reservation Rules",
"summary": "Configure reservation rules by location",
"version": "14.0.1.2.0",
"author": "Camptocamp, Odoo Community Association (OCA)",
"author": "Cetmix, Camptocamp, Odoo Community Association (OCA)",
"website": "https://github.com/OCA/stock-logistics-warehouse",
"category": "Stock Management",
"depends": [
Expand Down
8 changes: 8 additions & 0 deletions stock_reserve_rule/demo/stock_location_demo.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
<field name="name">Zone C</field>
<field name="location_id" ref="stock.stock_location_stock" />
</record>
<record id="stock_location_zone_d_demo" model="stock.location">
<field name="name">Zone D</field>
<field name="location_id" ref="stock.stock_location_stock" />
</record>
<record id="stock_location_zone_a_bin_1_demo" model="stock.location">
<field name="name">Bin A1</field>
<field name="location_id" ref="stock_location_zone_a_demo" />
Expand All @@ -24,4 +28,8 @@
<field name="name">Bin C1</field>
<field name="location_id" ref="stock_location_zone_c_demo" />
</record>
<record id="stock_location_zone_d_bin_1_demo" model="stock.location">
<field name="name">Bin D1</field>
<field name="location_id" ref="stock_location_zone_d_demo" />
</record>
</odoo>
6 changes: 6 additions & 0 deletions stock_reserve_rule/demo/stock_reserve_rule_demo.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,10 @@
<field name="location_id" ref="stock_location_zone_c_demo" />
<field name="removal_strategy">default</field>
</record>
<record id="stock_reserve_rule_4_removal_demo" model="stock.reserve.rule.removal">
<field name="rule_id" ref="stock_reserve_rule_1_demo" />
<field name="sequence">4</field>
<field name="location_id" ref="stock_location_zone_d_demo" />
<field name="removal_strategy">single_lot</field>
</record>
</odoo>
123 changes: 122 additions & 1 deletion stock_reserve_rule/models/stock_reserve_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ class StockReserveRuleRemoval(models.Model):
("default", "Default Removal Strategy"),
("empty_bin", "Empty Bins"),
("packaging", "Full Packaging"),
("single_lot", "Single lot"),
],
required=True,
default="default",
Expand All @@ -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)."
"By lot: ",
)

packaging_type_ids = fields.Many2many(
Expand All @@ -152,6 +154,62 @@ class StockReserveRuleRemoval(models.Model):
"When empty, any packaging can be removed.",
)

TOLERANCE_LIMIT = [
("upper_limit", "Upper Limit"),
("lower_limit", "Lower Limit"),
]

tolerance_requested_limit = fields.Selection(
selection=TOLERANCE_LIMIT,
string="Tolerance on",
)

tolerance_requested_computation = fields.Selection(
selection=[
("percentage", "Percentage (%)"),
("absolute", "Absolute Value"),
],
string="Tolerance computation",
)

tolerance_requested_value = fields.Float(string="Tolerance value", default=0.0)

tolerance_display = fields.Char(
compute="_compute_tolerance_display", store=True, string="Tolerance"
)

@api.depends(
"tolerance_requested_limit",
"tolerance_requested_computation",
"tolerance_requested_value",
)
def _compute_tolerance_display(self):
for rec in self:
tolerance_on = rec.tolerance_requested_limit
tolerance_computation = rec.tolerance_requested_computation
if not tolerance_computation or not tolerance_on:
rec.tolerance_display = ""
continue
value = rec.tolerance_requested_value
if value == 0.0:
rec.tolerance_display = "Requested Qty = Lot Qty"
continue
limit = "-" if tolerance_on == "lower_limit" else ""
computation = "%" if tolerance_computation == "percentage" else ""
tolerance_on_dict = dict(self.TOLERANCE_LIMIT)
rec.tolerance_display = "{} ({}{}{})".format(
tolerance_on_dict.get(tolerance_on), limit, value, computation
)

@api.onchange("tolerance_requested_value")
def _onchange_tolerance_limit(self):
if self.tolerance_requested_value < 0.0:
raise models.UserError(
_(
"Tolerance from requested qty value must be more than or equal to 0.0"
)
)

@api.constrains("location_id")
def _constraint_location_id(self):
"""The location has to be a child of the rule location."""
Expand Down Expand Up @@ -296,3 +354,66 @@ 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

@api.model
def _get_requested_comparisons(self, rounding):
value = self.tolerance_requested_value
if value == 0.0:
return lambda product_qty, need: product_qty == need
computation = self.tolerance_requested_computation
if self.tolerance_requested_limit == "upper_limit":

def wrap(product_qty, need):
if computation == "percentage":
return need + rounding < product_qty <= need * (100 + value) / 100
# computation == "absolute"
else:
return need + rounding < product_qty <= need + value

return wrap

if self.tolerance_requested_limit == "lower_limit":

def wrap(product_qty, need):
if computation == "percentage":
return need * (100 - value) / 100 <= product_qty < need - rounding
# computation == "absolute"
else:
return need - value <= product_qty < need - rounding

Check warning on line 382 in stock_reserve_rule/models/stock_reserve_rule.py

View check run for this annotation

Codecov / codecov/patch

stock_reserve_rule/models/stock_reserve_rule.py#L382

Added line #L382 was not covered by tests

return wrap

def _apply_strategy_single_lot(self, quants):
need = yield
# 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
comparison = self._get_requested_comparisons(rounding)
if product.tracking == "lot" or comparison is not None:
for location, location_quants in quants._group_by_location():
grouped_quants = self.env["stock.quant"].read_group(
[
("lot_id", "!=", False),
("location_id", "in", location.ids),
("product_id", "=", product.id),
("quantity", ">", 0),
],
["lot_id", "quantity"],
["lot_id"],
orderby="id",
)
lot_ids_with_quantity = {
group["lot_id"][0]: group["quantity"] for group in grouped_quants
}
location_quantity = sum(location_quants.mapped("quantity")) - sum(
location_quants.mapped("reserved_quantity")
)
lot_id = False
for rec_id, product_qty in lot_ids_with_quantity.items():
if comparison(product_qty, need):
lot_id = rec_id
break
if location_quantity > 0 and lot_id:
lot_id = self.env["stock.production.lot"].browse(lot_id)
need = yield location, location_quantity, need, lot_id, None
1 change: 1 addition & 0 deletions stock_reserve_rule/readme/CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
* Guewen Baconnier <[email protected]>
* Jacques-Etienne Baudoux (BCIM) <[email protected]>
* Cetmix <https://cetmix.com>
3 changes: 3 additions & 0 deletions stock_reserve_rule/readme/DESCRIPTION.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ The included advanced removal strategies are:
* Full Packaging: tries to remove full packaging (configured on the products)
first, by largest to smallest package or based on a pre-selected package
(default removal strategy is then applied for equal quantities).
* By lot: tries to remove a lot. A specific field allows to set whether to reserve
a lot with qty matching requested qty, or a lot with qty >= requested quantity,
or only lots with qty > requested qty.

Examples of scenario:

Expand Down
3 changes: 1 addition & 2 deletions stock_reserve_rule/readme/USAGE.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ Scenario:
and see the rules (by default in demo, the rules are created inactive)
* Open Transfer: Outgoing shipment (reservation rules demo 1)
* Check availability: it has 150 units, as it will not empty Zone A, it will not
take products there, it should take 100 in B and 50 in C (following the rules
order)
take products there, it should take 100 in B and 50 in C (following the rules order)
* Unreserve this transfer (to test the second case)
* Open Transfer: Outgoing shipment (reservation rules demo 2)
* Check availability: it has 250 units, it can empty Zone A, it will take 200 in
Expand Down
Loading

0 comments on commit 9352c9d

Please sign in to comment.