fix: add bypass for ID-based searches and system operations to prevent validation errors in stock location and lot filtering
This commit is contained in:
parent
9a9ce14ccf
commit
12b8a72a89
@ -16,9 +16,12 @@ class StockQuant(models.Model):
|
|||||||
"""Helper to extract allowed location IDs from context"""
|
"""Helper to extract allowed location IDs from context"""
|
||||||
ctx = self.env.context
|
ctx = self.env.context
|
||||||
|
|
||||||
# FIX: If we are picking from a non-internal location (like Transit, Supplier, or Customer),
|
# 1. Skip restrictions if we are performing a bypass or internal system operation
|
||||||
# we should NOT apply strict internal location restrictions, because the stock MUST come
|
if ctx.get('skip_location_restriction') or ctx.get('prefetch_fields'):
|
||||||
# from that exact external/transit location.
|
return []
|
||||||
|
|
||||||
|
# 2. FIX: If we are picking from a non-internal location (like Transit, Supplier, or Customer),
|
||||||
|
# we should NOT apply strict internal location restrictions.
|
||||||
loc_id = ctx.get('default_location_id')
|
loc_id = ctx.get('default_location_id')
|
||||||
if loc_id:
|
if loc_id:
|
||||||
loc = self.env['stock.location'].sudo().browse(loc_id)
|
loc = self.env['stock.location'].sudo().browse(loc_id)
|
||||||
@ -27,7 +30,7 @@ class StockQuant(models.Model):
|
|||||||
|
|
||||||
allowed_location_ids = []
|
allowed_location_ids = []
|
||||||
|
|
||||||
# 1. Try from explicit keys often passed by UI or patches
|
# 3. Try from explicit keys often passed by UI or patches
|
||||||
raw_ids = (ctx.get('allowed_source_location_ids') or
|
raw_ids = (ctx.get('allowed_source_location_ids') or
|
||||||
ctx.get('default_allowed_source_location_ids'))
|
ctx.get('default_allowed_source_location_ids'))
|
||||||
if raw_ids:
|
if raw_ids:
|
||||||
@ -39,7 +42,7 @@ class StockQuant(models.Model):
|
|||||||
elif isinstance(raw_ids, int):
|
elif isinstance(raw_ids, int):
|
||||||
allowed_location_ids = [raw_ids]
|
allowed_location_ids = [raw_ids]
|
||||||
|
|
||||||
# 2. Try from active move
|
# 4. Try from active move
|
||||||
if not allowed_location_ids:
|
if not allowed_location_ids:
|
||||||
active_move_id = ctx.get('active_move_id') or ctx.get('default_move_id')
|
active_move_id = ctx.get('active_move_id') or ctx.get('default_move_id')
|
||||||
if active_move_id:
|
if active_move_id:
|
||||||
@ -47,7 +50,7 @@ class StockQuant(models.Model):
|
|||||||
if move.exists() and move.allowed_source_location_ids:
|
if move.exists() and move.allowed_source_location_ids:
|
||||||
allowed_location_ids = move.allowed_source_location_ids.ids
|
allowed_location_ids = move.allowed_source_location_ids.ids
|
||||||
|
|
||||||
# 3. Try from active MO
|
# 5. Try from active MO
|
||||||
if not allowed_location_ids:
|
if not allowed_location_ids:
|
||||||
active_mo_id = ctx.get('active_mo_id') or ctx.get('default_raw_material_production_id')
|
active_mo_id = ctx.get('active_mo_id') or ctx.get('default_raw_material_production_id')
|
||||||
if active_mo_id:
|
if active_mo_id:
|
||||||
@ -55,8 +58,7 @@ class StockQuant(models.Model):
|
|||||||
if mo.exists() and mo.allowed_source_location_ids:
|
if mo.exists() and mo.allowed_source_location_ids:
|
||||||
allowed_location_ids = mo.allowed_source_location_ids.ids
|
allowed_location_ids = mo.allowed_source_location_ids.ids
|
||||||
|
|
||||||
# 4. Fallback to picking type in context
|
# 6. Fallback to picking type in context
|
||||||
# 4. Fallback to picking type in context
|
|
||||||
if not allowed_location_ids:
|
if not allowed_location_ids:
|
||||||
picking_type_id = ctx.get('default_picking_type_id')
|
picking_type_id = ctx.get('default_picking_type_id')
|
||||||
if picking_type_id:
|
if picking_type_id:
|
||||||
@ -82,12 +84,16 @@ class StockQuant(models.Model):
|
|||||||
|
|
||||||
def _search(self, domain, offset=0, limit=None, order=None, *args, **kwargs):
|
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)"""
|
"""Override to apply location restrictions during search (e.g. catalog or list selection)"""
|
||||||
allowed_location_ids = self._get_allowed_locations()
|
# CRITICAL FIX: If we are searching for specific IDs (e.g., Odoo is fetching selected records for save),
|
||||||
|
# we must NOT restrict the search results or we trigger 'Missing Product' validation errors
|
||||||
|
# on backend fallback create logic.
|
||||||
|
search_by_id = any(isinstance(leaf, (list, tuple)) and leaf[0] == 'id' for leaf in domain)
|
||||||
|
if search_by_id:
|
||||||
|
return super()._search(domain, offset=offset, limit=limit, order=order, *args, **kwargs)
|
||||||
|
|
||||||
|
allowed_location_ids = self._get_allowed_locations()
|
||||||
if allowed_location_ids:
|
if allowed_location_ids:
|
||||||
# 1. Smart Domain Swap: find if there's already a location_id restriction (like child_of)
|
# 1. Smart Domain Swap: find if there's already a location_id restriction (like child_of)
|
||||||
# and REPLACE it instead of AND-ing it. This prevents collisions with Odoo's
|
|
||||||
# default 'child_of location_src_id' behavior.
|
|
||||||
found_collision = False
|
found_collision = False
|
||||||
new_domain = []
|
new_domain = []
|
||||||
for leaf in domain:
|
for leaf in domain:
|
||||||
@ -104,7 +110,6 @@ class StockQuant(models.Model):
|
|||||||
if found_collision:
|
if found_collision:
|
||||||
domain = new_domain
|
domain = new_domain
|
||||||
else:
|
else:
|
||||||
# 2. Standard AND merge if no collision found
|
|
||||||
domain = Domain.AND([domain, [('location_id', 'in', allowed_location_ids)]])
|
domain = Domain.AND([domain, [('location_id', 'in', allowed_location_ids)]])
|
||||||
|
|
||||||
return super()._search(domain, offset=offset, limit=limit, order=order, *args, **kwargs)
|
return super()._search(domain, offset=offset, limit=limit, order=order, *args, **kwargs)
|
||||||
@ -133,23 +138,22 @@ class StockLot(models.Model):
|
|||||||
def _search(self, domain, offset=0, limit=None, order=None, *args, **kwargs):
|
def _search(self, domain, offset=0, limit=None, order=None, *args, **kwargs):
|
||||||
ctx = self.env.context
|
ctx = self.env.context
|
||||||
|
|
||||||
# We only want to filter if the user is in a picking using lot_id directly (like Receive operations)
|
# FIX: If we are searching for specific IDs, bypass custom filtering to avoid backend failures
|
||||||
|
search_by_id = any(isinstance(leaf, (list, tuple)) and leaf[0] == 'id' for leaf in domain)
|
||||||
|
if search_by_id:
|
||||||
|
return super()._search(domain, offset=offset, limit=limit, order=order, *args, **kwargs)
|
||||||
|
|
||||||
active_picking_id = ctx.get('active_picking_id')
|
active_picking_id = ctx.get('active_picking_id')
|
||||||
loc_id = ctx.get('default_location_id')
|
loc_id = ctx.get('default_location_id')
|
||||||
|
|
||||||
if active_picking_id and loc_id:
|
if active_picking_id and loc_id:
|
||||||
loc = self.env['stock.location'].sudo().browse(loc_id)
|
loc = self.env['stock.location'].sudo().browse(loc_id)
|
||||||
|
|
||||||
# If the source is an internal or transit location, restrict the dropdown to lots actually present there.
|
|
||||||
# If the source is a supplier, we do not filter (they could be receiving brand new lots).
|
|
||||||
if loc.exists() and loc.usage != 'supplier':
|
if loc.exists() and loc.usage != 'supplier':
|
||||||
quant_domain = [
|
quant_domain = [
|
||||||
('location_id', 'child_of', loc.id),
|
('location_id', 'child_of', loc.id),
|
||||||
('quantity', '>', 0),
|
('quantity', '>', 0),
|
||||||
('lot_id', '!=', False)
|
('lot_id', '!=', False)
|
||||||
]
|
]
|
||||||
|
|
||||||
# Highly optimized query: only search quants for the specific product
|
|
||||||
product_id = ctx.get('default_product_id')
|
product_id = ctx.get('default_product_id')
|
||||||
if product_id:
|
if product_id:
|
||||||
quant_domain.append(('product_id', '=', product_id))
|
quant_domain.append(('product_id', '=', product_id))
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user