Skip to content

Instantly share code, notes, and snippets.

@gdgellatly
Last active June 23, 2022 08:50
Show Gist options
  • Save gdgellatly/3894ef4637877bd61991f99c6870fdb9 to your computer and use it in GitHub Desktop.
Save gdgellatly/3894ef4637877bd61991f99c6870fdb9 to your computer and use it in GitHub Desktop.
Unreserve during update_reserved_quantity
# Copyright 2022 Graeme Gellatly
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import api, models
from odoo.tools.float_utils import float_compare
class StockQuant(models.Model):
_inherit = "stock.quant"
@api.model
def _update_reserved_quantity(
self,
product_id,
location_id,
quantity,
lot_id=None,
package_id=None,
owner_id=None,
strict=False,
):
"""Override to fix reservation errors before updating reserved quantity"""
self = self.sudo()
rounding = product_id.uom_id.rounding
quants = self._gather(
product_id,
location_id,
lot_id=lot_id,
package_id=package_id,
owner_id=owner_id,
strict=strict,
)
if float_compare(quantity, 0, precision_rounding=rounding) < 0:
# if we want to unreserve
available_quantity = sum(quants.mapped("reserved_quantity"))
if (
float_compare(
abs(quantity), available_quantity, precision_rounding=rounding
)
> 0
):
if quants:
quantity = 0 if quants._fix_unreserve() else quantity
else:
# Handle the case of quant already cycled out
move_lines = self.env["stock.move.line"].search(
[
("product_id", "=", product_id),
("location_id", "=", location_id),
("lot_id", "=", lot_id),
("package_id", "=", package_id),
("owner_id", "=", owner_id),
("product_qty", "!=", 0),
]
)
move_lines.write({"product_uom_qty": 0})
quantity = 0
return super()._update_reserved_quantity(
product_id,
location_id,
quantity,
lot_id=lot_id,
package_id=package_id,
owner_id=owner_id,
strict=strict,
)
def _fix_unreserve(self):
all_log = ""
line_qty_updated = False
def gen_log_message(msg):
logging = (
"Problematic quant found %s in %s: %s "
"(quantity: %s, reserved_quantity: %s)\n"
% (
quant.product_id.name,
quant.location_id.complete_name,
quant.id,
quant.quantity,
quant.reserved_quantity,
)
)
logging += msg
logging += "******************\n"
return logging
move_line_to_recompute_ids = []
for quant in self:
move_lines = self.env["stock.move.line"].search(
[
("product_id", "=", quant.product_id.id),
("location_id", "=", quant.location_id.id),
("lot_id", "=", quant.lot_id.id),
("package_id", "=", quant.package_id.id),
("owner_id", "=", quant.owner_id.id),
("product_qty", "!=", 0),
]
)
reserved_on_move_lines = sum(move_lines.mapped("product_uom_qty"))
move_line_str = str.join(
", ", [str(move_line_id) for move_line_id in move_lines.ids]
)
if quant.location_id.should_bypass_reservation():
# If a quant is in a location that should bypass
# the reservation, its `reserved_quantity` field
# should be 0.
if quant.reserved_quantity != 0:
msg = (
"its `reserved_quantity` field is not 0 while its "
"location should bypass the reservation\n"
)
if move_lines:
msg += (
"These move lines are reserved on it: "
"%s (sum of the reservation: %s)\n"
% (move_line_str, reserved_on_move_lines)
)
else:
msg += (
"no move lines are reserved on it, you can "
"safely reset its `reserved_quantity` to 0\n"
)
all_log += gen_log_message(msg)
quant.write({"reserved_quantity": 0})
else:
# If a quant is in a reservable location, its `reserved_quantity`
# should be exactly the sum of the `product_qty` of all the
# partially_available / assigned move lines with the same
# characteristics.
if quant.reserved_quantity == 0:
if move_lines:
all_log += gen_log_message(
"its `reserved_quantity` field is 0 while these move lines "
"are reserved on it: %s (sum of the reservation: %s)\n"
% (move_line_str, reserved_on_move_lines)
)
move_lines.with_context(
bypass_reservation_update=True
).sudo().write({"product_uom_qty": 0})
line_qty_updated = True
move_line_to_recompute_ids += move_lines.ids
elif quant.reserved_quantity < 0:
msg = "reserved_quantity field is negative\n"
quant.write({"reserved_quantity": 0})
if move_lines:
msg += (
"These move lines are reserved on it: "
"%s (sum of the reservation: %s)\n"
% (move_line_str, reserved_on_move_lines)
)
move_lines.with_context(
bypass_reservation_update=True
).sudo().write({"product_uom_qty": 0})
line_qty_updated = True
move_line_to_recompute_ids += move_lines.ids
all_log += gen_log_message(msg)
else:
if reserved_on_move_lines != quant.reserved_quantity:
msg = "reserved_quantity does not reflect the reservations\n"
msg += (
"These move lines are reserved on it: "
"%s (sum of the reservation: %s)\n"
% (move_line_str, reserved_on_move_lines)
)
all_log += gen_log_message(msg)
move_lines.with_context(
bypass_reservation_update=True
).sudo().write({"product_uom_qty": 0})
line_qty_updated = True
move_line_to_recompute_ids += move_lines.ids
quant.write({"reserved_quantity": 0})
else:
if any(move_line.product_qty < 0 for move_line in move_lines):
msg = (
"reserved_quantity correctly reflects the move lines "
"reservation but some are negatives\n"
)
msg += (
"These move lines are reserved on it: "
"%s (sum of the reservation: %s)\n"
% (move_line_str, reserved_on_move_lines)
)
all_log += gen_log_message(msg)
move_lines.with_context(
bypass_reservation_update=True
).sudo().write({"product_uom_qty": 0})
line_qty_updated = True
move_line_to_recompute_ids += move_lines.ids
quant.write({"reserved_quantity": 0})
self._create_unreserve_log_entry(all_log)
if move_line_to_recompute_ids:
self.env["stock.move.line"].browse(
move_line_to_recompute_ids
).move_id._recompute_state()
return line_qty_updated
def _create_unreserve_log_entry(self, logging):
if logging:
self.env["ir.logging"].sudo().create(
{
"name": "Unreserve stock.quant and stock.move.line",
"type": "server",
"level": "INFO",
"dbname": self.env.cr.dbname,
"message": logging,
"func": "_update_reserved_quantity",
"path": "addons/stock/models/stock_quant.py",
"line": "0",
}
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment