From c036b7cb9c942d1ccdff963dad81558ec668e225 Mon Sep 17 00:00:00 2001 From: Suherdy Yacob Date: Fri, 3 Apr 2026 17:20:32 +0700 Subject: [PATCH] feat: implement backend operation bypass for location restrictions and add diagnostic logging to stock quant creation --- models/stock_location.py | 47 +++++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/models/stock_location.py b/models/stock_location.py index b22d8c6..fbbe8e2 100644 --- a/models/stock_location.py +++ b/models/stock_location.py @@ -66,16 +66,45 @@ class StockQuant(models.Model): if picking_type.exists() and picking_type.default_location_src_ids: allowed_location_ids = picking_type.default_location_src_ids.ids + # 7. NEW: Detect Backend Operations (Force Bypass) + # If we are in a background synchronization or an ORM command (not triggered by UI search) + # return empty so we do not restrict. + is_ui_search = (ctx.get('params') or + ctx.get('bin_size') or + ctx.get('search_view_ref') or + ctx.get('list_view_ref')) + + # If it's NOT a UI search (i.e. it's a Save, Post, Validate background call), bypass. + if not is_ui_search: + # Check for common background flags + if (ctx.get('mail_create_nolog') or + ctx.get('tracking_disable') or + not ctx.get('uid')): # System user + return [] + return allowed_location_ids + @api.model_create_multi + def create(self, vals_list): + """Diagnostic override to catch the 'Missing Product' error root cause""" + for vals in vals_list: + if not vals.get('product_id'): + # LOG THE CALLER for debugging + _logger.error("DEBUG_RESTRICT: Creating StockQuant WITHOUT product_id!") + _logger.error(f"DEBUG_RESTRICT: Vals: {vals}") + _logger.error(f"DEBUG_RESTRICT: Context: {self.env.context}") + # We do NOT raise here to avoid breaking Odoo's original error UX, + # but this will appear in logs or help us see the issue. + return super().create(vals_list) + @api.model def _get_gather_domain(self, product_id, location_id, lot_id=None, package_id=None, owner_id=None, strict=False): """Override to apply location restrictions during reservation (gather)""" result_domain = super()._get_gather_domain(product_id, location_id, lot_id, package_id, owner_id, strict) ctx = self.env.context - # 0. Bypass if skip flag is set - if ctx.get('skip_location_restriction'): + # 0. Bypass if skip flag is set OR we are in a system operation + if ctx.get('skip_location_restriction') or not ctx.get('uid'): return result_domain # 1. FIX: If reserving from a non-internal location, DO NOT apply internal restrictions. @@ -107,9 +136,11 @@ class StockQuant(models.Model): def _search(self, domain, offset=0, limit=None, order=None, *args, **kwargs): """Override to apply location restrictions during search (e.g. catalog or list selection)""" ctx = self.env.context - # 0. CRITICAL FIX: If searching for specific IDs or skip flag set, bypass custom filtering + # 0. CRITICAL FIX: If searching for specific IDs OR skip flag set OR NOT UI Search, bypass custom filtering search_by_id = any(isinstance(leaf, (list, tuple)) and leaf[0] == 'id' for leaf in domain) - if search_by_id or ctx.get('skip_location_restriction'): + is_ui_search = ctx.get('params') or ctx.get('search_view_ref') or ctx.get('list_view_ref') + + if search_by_id or ctx.get('skip_location_restriction') or not is_ui_search: return super()._search(domain, offset=offset, limit=limit, order=order, *args, **kwargs) allowed_location_ids = self._get_allowed_locations() @@ -158,9 +189,12 @@ class StockLot(models.Model): def _search(self, domain, offset=0, limit=None, order=None, *args, **kwargs): ctx = self.env.context - # FIX: If we are searching for specific IDs, bypass custom filtering to avoid backend failures + # 0. NEW: Detect UI Search vs Backend Sync search_by_id = any(isinstance(leaf, (list, tuple)) and leaf[0] == 'id' for leaf in domain) - if search_by_id or ctx.get('skip_location_restriction'): + is_ui_search = ctx.get('params') or ctx.get('search_view_ref') or ctx.get('list_view_ref') + + # FIX: If we are searching for specific IDs OR skip flag set OR NOT UI search, bypass + if search_by_id or ctx.get('skip_location_restriction') or not is_ui_search: return super()._search(domain, offset=offset, limit=limit, order=order, *args, **kwargs) # 1. Identify which locations we should look into for quants @@ -183,6 +217,7 @@ class StockLot(models.Model): quant_domain.append(('product_id', '=', product_id)) # Use internal bypass when searching quants to filter lots + # We use sudo() and skip_location_restriction to ensure we always find the quants quants = self.env['stock.quant'].with_context(skip_location_restriction=True).sudo().search(quant_domain) lot_ids = list(set(quants.mapped('lot_id').ids))