mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
When rules are configured and have been applied, we should not have an implicit fallback on the base location, as it would kind of cancel the benefits of the rules (as it would then take whatever it wants anywhere in all the locations).
119 lines
4.5 KiB
Python
119 lines
4.5 KiB
Python
# Copyright 2019 Camptocamp SA
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
|
from odoo import models
|
|
from odoo.tools.float_utils import float_compare
|
|
|
|
|
|
class StockMove(models.Model):
|
|
_inherit = "stock.move"
|
|
|
|
def _update_reserved_quantity(
|
|
self,
|
|
need,
|
|
available_quantity,
|
|
location_id,
|
|
lot_id=None,
|
|
package_id=None,
|
|
owner_id=None,
|
|
strict=True,
|
|
):
|
|
"""Create or update move lines."""
|
|
if strict:
|
|
# chained moves must take what was reserved by the previous move
|
|
return super()._update_reserved_quantity(
|
|
need,
|
|
available_quantity,
|
|
location_id=location_id,
|
|
lot_id=lot_id,
|
|
package_id=package_id,
|
|
owner_id=owner_id,
|
|
strict=strict,
|
|
)
|
|
rules = self.env["stock.reserve.rule"]._rules_for_location(location_id)
|
|
|
|
forced_package_id = self.package_level_id.package_id or None
|
|
rounding = self.product_id.uom_id.rounding
|
|
|
|
still_need = need
|
|
for rule in rules:
|
|
# 1st check if rule is applicable from the move
|
|
if not rule._is_rule_applicable(self):
|
|
continue
|
|
|
|
for removal_rule in rule.rule_removal_ids:
|
|
# Exclude any rule which does not share the same path as the
|
|
# move's location. Example:
|
|
# Rule location: Stock
|
|
# Removal rule 1: Stock/Zone1
|
|
# Removal rule 2: Stock/Zone2
|
|
# If we have a stock.move with "Stock" as source location,
|
|
# it can use both rules.
|
|
# If we have a stock.move with "Stock/Zone2" as source location,
|
|
# it should never use "Stock/Zone1"
|
|
if not removal_rule.location_id.is_sublocation_of(location_id):
|
|
continue
|
|
|
|
quants = self.env["stock.quant"]._gather(
|
|
self.product_id,
|
|
removal_rule.location_id,
|
|
lot_id=lot_id,
|
|
package_id=forced_package_id,
|
|
owner_id=owner_id,
|
|
strict=strict,
|
|
)
|
|
|
|
# get quants allowed by the rule
|
|
rule_quants = removal_rule._filter_quants(self, quants)
|
|
if not rule_quants:
|
|
continue
|
|
|
|
# Apply the advanced removal strategy, if any. Even within the
|
|
# application of the removal strategy, the original company's
|
|
# one should be respected (eg. if we remove quants that would
|
|
# empty bins first, in case of equality, we should remove the
|
|
# fifo or fefo first depending of the configuration).
|
|
strategy = removal_rule._apply_strategy(rule_quants)
|
|
next(strategy)
|
|
while True:
|
|
try:
|
|
next_quant = strategy.send(still_need)
|
|
if not next_quant:
|
|
continue
|
|
location, location_quantity, to_take = next_quant
|
|
taken_in_loc = super()._update_reserved_quantity(
|
|
# in this strategy, we take as much as we can
|
|
# from this bin
|
|
to_take,
|
|
location_quantity,
|
|
location_id=location,
|
|
lot_id=lot_id,
|
|
package_id=package_id,
|
|
owner_id=owner_id,
|
|
strict=strict,
|
|
)
|
|
still_need -= taken_in_loc
|
|
except StopIteration:
|
|
break
|
|
|
|
need_zero = (
|
|
float_compare(still_need, 0, precision_rounding=rounding) != 1
|
|
)
|
|
if need_zero:
|
|
# useless to eval the other rules when still_need <= 0
|
|
break
|
|
|
|
reserved = need - still_need
|
|
return reserved
|
|
|
|
# We fall here if there is no rule or they have all been
|
|
# excluded by 'rule._is_rule_applicable'
|
|
return super()._update_reserved_quantity(
|
|
need,
|
|
available_quantity,
|
|
location_id=location_id,
|
|
lot_id=lot_id,
|
|
package_id=package_id,
|
|
owner_id=owner_id,
|
|
strict=strict,
|
|
)
|