From 726034dcbfc05b772ba8a798862d4974c677ad44 Mon Sep 17 00:00:00 2001 From: Suherdy Yacob Date: Fri, 3 Apr 2026 20:16:14 +0700 Subject: [PATCH] feat: improve location restriction logic by expanding context key support and adding fallback to default source location --- models/stock_location.py | 47 +++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/models/stock_location.py b/models/stock_location.py index 72cbaf0..6ec0297 100644 --- a/models/stock_location.py +++ b/models/stock_location.py @@ -12,26 +12,45 @@ class StockLocation(models.Model): """ Helper to retrieve allowed locations based on the current context (picking type). Used by UI-level overrides in StockQuant and StockLot. + Returns: + list: IDs of allowed locations. + False: If no restriction should be applied (e.g. no picking type found). """ ctx = self.env.context # 1. Identify the Picking Type (Operation Type) - picking_type_id = ctx.get('default_picking_type_id') or ctx.get('picking_type_id') + # Check multiple common context keys used by stock/mrp models + picking_type_id = (ctx.get('default_picking_type_id') or + ctx.get('picking_type_id') or + ctx.get('active_picking_type_id')) - # 2. Support for Manufacturing Orders: if we have an MO ID but no picking type, find it. - if not picking_type_id and ctx.get('active_mo_id'): - mo = self.env['mrp.production'].browse(ctx.get('active_mo_id')) + # 2. Advanced MO Support: find picking type from MO ID if needed + mo_id = (ctx.get('active_mo_id') or + ctx.get('default_mo_id') or + ctx.get('default_production_id') or + ctx.get('mo_id') or + ctx.get('production_id')) + + if not picking_type_id and mo_id: + mo = self.env['mrp.production'].browse(mo_id) if mo.exists(): picking_type_id = mo.picking_type_id.id if not picking_type_id: - return [] + # NO identification of operation type = NO restriction (safe fallback) + return False # 3. Retrieve allowed locations from the picking type picking_type = self.env['stock.picking.type'].browse(picking_type_id) - if picking_type.exists() and picking_type.default_location_src_ids: - return picking_type.default_location_src_ids.ids + if picking_type.exists(): + # Priority 1: Use the M2M "Allowed Source Locations" if set + if picking_type.default_location_src_ids: + return picking_type.default_location_src_ids.ids - return [] + # Priority 2: Fallback to the primary "Default Source Location" (M2O) + if picking_type.default_location_src_id: + return [picking_type.default_location_src_id.id] + + return False class StockQuant(models.Model): _inherit = 'stock.quant' @@ -40,14 +59,12 @@ class StockQuant(models.Model): def web_search_read(self, domain, specification, offset=0, limit=None, order=None, count_limit=None): """ UI-SURFACE OVERRIDE: Applies location filtering ONLY for the web interface. - This method is called by the M2M Catalog and list view searches. - Internal search() and _gather() will NOT be affected. """ ctx = self.env.context if not ctx.get('skip_location_restriction') and ctx.get('uid'): allowed_location_ids = self.env['stock.location']._get_allowed_locations() if allowed_location_ids: - # Add location filter to the domain + # Add location filter to the domain ONLY if we have an explicit list domain = expression.AND([domain, [('location_id', 'in', allowed_location_ids)]]) return super().web_search_read(domain, specification, offset=offset, limit=limit, order=order, count_limit=count_limit) @@ -59,19 +76,17 @@ class StockLot(models.Model): def name_search(self, name='', args=None, operator='ilike', limit=100): """ UI-SURFACE OVERRIDE: Filters the many2one lot selection dropdown. - This is ONLY used by the web client for autocomplete/dropdown lookups. """ ctx = self.env.context if not ctx.get('skip_location_restriction') and ctx.get('uid'): allowed_location_ids = self.env['stock.location']._get_allowed_locations() - # If no explicit mapping found on the picking type, - # check if a default_location_id was passed to the view context. + # If our custom helper failed to find a picking-type restriction, + # we check if a simple default_location_id filter was passed directly. if not allowed_location_ids and ctx.get('default_location_id'): allowed_location_ids = [ctx.get('default_location_id')] if allowed_location_ids: - # Find quants in the allowed locations that have positive stock for this product quant_domain = [ ('location_id', 'in', allowed_location_ids), ('quantity', '>', 0), @@ -81,8 +96,6 @@ class StockLot(models.Model): if product_id: quant_domain.append(('product_id', '=', product_id)) - # We sudo() the quant search to ensure we find quants even if there are record rules, - # as this is strictly for filtering the UI dropdown. quants = self.env['stock.quant'].with_context(skip_location_restriction=True).sudo().search(quant_domain) lot_ids = quants.mapped('lot_id').ids