101 lines
5.1 KiB
Python
101 lines
5.1 KiB
Python
import logging
|
|
from odoo import api, fields, models, _
|
|
from odoo.osv import expression
|
|
from odoo.tools.float_utils import float_is_zero
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
class StockMoveLine(models.Model):
|
|
_inherit = 'stock.move.line'
|
|
|
|
def unlink(self):
|
|
"""
|
|
Safety Patch (Attempt 20): Prevent 'Missing product_id' crash on stock.quant.
|
|
This targets the native Odoo crash at stock_move_line.py line 570.
|
|
"""
|
|
precision = self.env['decimal.precision'].precision_get('Product Unit')
|
|
for ml in self:
|
|
# CRITICAL FIX: If product_id is missing (virtual records), skip reservation update.
|
|
if not ml.product_id:
|
|
_logger.info(f"DEBUG_RESTRICT: Skipping reservation update for product-less move line {ml.id}")
|
|
continue
|
|
|
|
# Replicate standard Odoo check before calling _update_reserved_quantity
|
|
if not float_is_zero(ml.quantity_product_uom, precision_digits=precision) and ml.move_id and not ml.move_id._should_bypass_reservation(ml.location_id):
|
|
try:
|
|
self.env['stock.quant']._update_reserved_quantity(
|
|
ml.product_id, ml.location_id, -ml.quantity_product_uom,
|
|
lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id, strict=True
|
|
)
|
|
except Exception as e:
|
|
_logger.error(f"DEBUG_RESTRICT: Failed to update reservation for line {ml.id}: {e}")
|
|
|
|
# Call super WITHOUT original logic to avoid double-processing or errors
|
|
return super(models.Model, self).unlink()
|
|
|
|
class StockLocation(models.Model):
|
|
_inherit = 'stock.location'
|
|
|
|
@api.model
|
|
def get_allowed_locations_for_mo(self, mo_id=None, picking_type_id=None):
|
|
"""
|
|
Public helper for JS to fetch allowed locations.
|
|
"""
|
|
allowed_ids = []
|
|
source_name = "None"
|
|
|
|
if mo_id:
|
|
mo = self.env['mrp.production'].sudo().browse(mo_id)
|
|
if mo.exists() and mo.allowed_source_location_ids:
|
|
allowed_ids = mo.allowed_source_location_ids.ids
|
|
source_name = f"MO {mo.name}"
|
|
|
|
if not allowed_ids and picking_type_id:
|
|
pt = self.env['stock.picking.type'].sudo().browse(picking_type_id)
|
|
if pt.exists():
|
|
if pt.default_location_src_ids:
|
|
allowed_ids = pt.default_location_src_ids.ids
|
|
source_name = f"PT {pt.display_name}"
|
|
elif pt.default_location_src_id:
|
|
allowed_ids = [pt.default_location_src_id.id]
|
|
source_name = f"PT {pt.display_name} (M21)"
|
|
|
|
if allowed_ids:
|
|
_logger.debug(f"DEBUG_RESTRICT: Identified {len(allowed_ids)} Allowed Locations for {source_name}: {allowed_ids}")
|
|
return allowed_ids
|
|
|
|
return []
|
|
|
|
class StockLot(models.Model):
|
|
_inherit = 'stock.lot'
|
|
|
|
@api.model
|
|
def name_search(self, name='', args=None, operator='ilike', limit=100):
|
|
ctx = self.env.context
|
|
if not ctx.get('skip_location_restriction') and ctx.get('uid'):
|
|
mo_id = (ctx.get('active_mo_id') or ctx.get('default_production_id') or ctx.get('production_id'))
|
|
allowed_ids = self.env['stock.location'].sudo().get_allowed_locations_for_mo(mo_id=mo_id, picking_type_id=ctx.get('default_picking_type_id'))
|
|
if allowed_ids:
|
|
quant_domain = [('location_id', 'child_of', allowed_ids), ('quantity', '>', 0), ('lot_id', '!=', False)]
|
|
if ctx.get('default_product_id'):
|
|
quant_domain.append(('product_id', '=', ctx.get('default_product_id')))
|
|
|
|
quants = self.env['stock.quant'].with_context(skip_location_restriction=True).sudo().search(quant_domain)
|
|
args = expression.AND([args or [], [('id', 'in', quants.mapped('lot_id').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):
|
|
ctx = self.env.context
|
|
if not ctx.get('skip_location_restriction') and ctx.get('uid'):
|
|
mo_id = (ctx.get('active_mo_id') or ctx.get('default_production_id') or ctx.get('production_id'))
|
|
allowed_ids = self.env['stock.location'].sudo().get_allowed_locations_for_mo(mo_id=mo_id, picking_type_id=ctx.get('default_picking_type_id'))
|
|
if allowed_ids:
|
|
quant_domain = [('location_id', 'child_of', allowed_ids), ('quantity', '>', 0), ('lot_id', '!=', False)]
|
|
if ctx.get('default_product_id'):
|
|
quant_domain.append(('product_id', '=', ctx.get('default_product_id')))
|
|
|
|
quants = self.env['stock.quant'].with_context(skip_location_restriction=True).sudo().search(quant_domain)
|
|
domain = expression.AND([domain, [('id', 'in', quants.mapped('lot_id').ids)]])
|
|
return super().web_search_read(domain, specification, offset=offset, limit=limit, order=order, count_limit=count_limit)
|