from odoo import models, api, _ from odoo.tools import float_compare import logging _logger = logging.getLogger(__name__) class MrpProduction(models.Model): _inherit = 'mrp.production' @api.depends('state', 'product_qty', 'qty_producing', 'move_raw_ids.quantity', 'move_raw_ids.product_uom_qty') def _compute_show_produce(self): """ Override to hide produce buttons if proceeding would cause negative stock. """ # First compute standard visibility super()._compute_show_produce() for production in self: _logger.info("DEBUG MO_LOCK: MO %s state=%s qty_producing=%s product_qty=%s", production.name, production.state, production.qty_producing, production.product_qty) _logger.info("DEBUG MO_LOCK: Start - show_produce=%s show_produce_all=%s", production.show_produce, production.show_produce_all) # If standard logic says hidden, no need to check further if not production.show_produce and not production.show_produce_all: continue # Determine which action we are validating # If show_produce_all is True, we are producing the full remaining amount # If show_produce (partial) is True, we are producing 'qty_producing' # This logic needs to mirror what happens when the button is clicked. # However, for visibility, we want to know if *current* configured production is possible. # If "Produce All" is available, it means we plan to produce everything remaining. # If "Produce" (partial) is available, we plan to produce `qty_producing`. # Check if any component would go negative potential_negative = False # We need to check stock availability in the source location for move in production.move_raw_ids: if move.state in ('done', 'cancel'): continue # Skip check if product is not storable (Service, etc.) if not move.product_id.is_storable: continue # If the move is already assigned (Ready), we consider it safe to produce. if move.state == 'assigned': continue # Check availability based on the ACTUAL 'Consumed' quantity # We use forecast_availability as it includes reservations # Use float_compare to avoid precision issues if float_compare(move.forecast_availability, move.quantity, precision_rounding=move.product_uom.rounding) < 0: potential_negative = True break if potential_negative: production.show_produce = False production.show_produce_all = False # Relaxed guard: Do not hide if qty_producing > 0 even if consumption is 0. # This allows users to trigger consumption by clicking the Produce button. # If they really want to block 0-consumption production, it should be a validation error on click, # not a hidden button that prevents them from even trying.