# 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, )