import logging from odoo import api, fields, models, _ from odoo.osv import expression from odoo.exceptions import UserError, ValidationError _logger = logging.getLogger(__name__) class StockLocation(models.Model): _inherit = 'stock.location' def _get_allowed_locations(self): """ Helper to retrieve allowed locations based on the current context (picking type). Used by UI-level overrides in StockQuant and StockLot. """ 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') # 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')) if mo.exists(): picking_type_id = mo.picking_type_id.id if not picking_type_id: return [] # 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.allowed_source_location_ids: return picking_type.allowed_source_location_ids.ids return [] class StockQuant(models.Model): _inherit = 'stock.quant' @api.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 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) class StockLot(models.Model): _inherit = 'stock.lot' @api.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 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), ('lot_id', '!=', False) ] product_id = ctx.get('default_product_id') 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 args = expression.AND([args or [], [('id', 'in', lot_ids)]]) return super().name_search(name, args=args, operator=operator, limit=limit) @api.model def web_search_read(self, domain, specification, offset=0, limit=None, order=None, count_limit=None): """ UI-SURFACE OVERRIDE: Applies filtering for the Lot Catalog and list views. """ 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 not allowed_location_ids and ctx.get('default_location_id'): allowed_location_ids = [ctx.get('default_location_id')] if allowed_location_ids: quant_domain = [ ('location_id', 'in', allowed_location_ids), ('quantity', '>', 0), ('lot_id', '!=', False) ] product_id = ctx.get('default_product_id') if product_id: quant_domain.append(('product_id', '=', product_id)) quants = self.env['stock.quant'].with_context(skip_location_restriction=True).sudo().search(quant_domain) lot_ids = quants.mapped('lot_id').ids domain = expression.AND([domain, [('id', 'in', lot_ids)]]) return super().web_search_read(domain, specification, offset=offset, limit=limit, order=order, count_limit=count_limit)