stock_no_negative/models/stock_quant.py
2026-01-12 16:43:00 +07:00

68 lines
3.0 KiB
Python
Executable File

# Copyright 2015-2017 Akretion (http://www.akretion.com)
# @author Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, api, models
from odoo.exceptions import ValidationError
from odoo.tools import config, float_compare
class StockQuant(models.Model):
_inherit = "stock.quant"
@api.constrains("product_id", "quantity")
def check_negative_qty(self):
# To provide an option to skip the check when necessary.
# e.g. mrp_subcontracting_skip_no_negative - passes the context
# for subcontracting receipts.
# Also allows negative stock for subcontracting locations as they
# are expected to have negative stock in certain Odoo 18 workflows.
if self.env.context.get("skip_negative_qty_check"):
return
p = self.env["decimal.precision"].precision_get("Product Unit of Measure")
check_negative_qty = (
config["test_enable"] and self.env.context.get("test_stock_no_negative")
) or not config["test_enable"]
if not check_negative_qty:
return
for quant in self:
disallowed_by_product = (
not quant.product_id.allow_negative_stock
and not quant.product_id.categ_id.allow_negative_stock
)
disallowed_by_location = not quant.location_id.allow_negative_stock
# Allow negative stock for subcontracting locations
# Check if mrp_subcontracting is installed and location has is_subcontracting_location field
is_subcontracting_location = False
if hasattr(quant.location_id, 'is_subcontracting_location'):
is_subcontracting_location = quant.location_id.is_subcontracting_location
if (
float_compare(quant.quantity, 0, precision_digits=p) == -1
and quant.product_id.is_storable
and quant.location_id.usage in ["internal", "transit"]
and disallowed_by_product
and disallowed_by_location
and not is_subcontracting_location
):
msg_add = ""
if quant.lot_id:
msg_add = _(" lot %(name)s", name=quant.lot_id.display_name)
raise ValidationError(
_(
"You cannot validate this stock operation because the "
"stock level of the product '{name}'{name_lot} would "
"become negative "
"({q_quantity}) on the stock location '{complete_name}' "
"and negative stock is "
"not allowed for this product and/or location."
).format(
name=quant.product_id.display_name,
name_lot=msg_add,
q_quantity=quant.quantity,
complete_name=quant.location_id.complete_name,
)
)