# Copyright 2015-2017 Akretion (http://www.akretion.com) # @author Alexis de Lattre # 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, ) )