# Save this script and run it using the Odoo shell: # odoo-bin shell -c -d < fix_corrupted_move_lines.py import logging _logger = logging.getLogger(__name__) def fix_corrupted_move_lines(env): _logger.info("Scanning for corrupted stock.move.lines with missing product_id...") # Search for all move lines that do not have a product_id # We use a raw SQL query to aggressively find them even if ORM hides them, # but we will try to use ORM first. env.cr.execute(""" SELECT id, move_id FROM stock_move_line WHERE product_id IS NULL AND move_id IS NOT NULL """) corrupted_lines = env.cr.fetchall() if not corrupted_lines: _logger.info("No corrupted move lines found!") print("No corrupted move lines found.") return _logger.info(f"Found {len(corrupted_lines)} corrupted move lines. Fixing...") print(f"Found {len(corrupted_lines)} corrupted move lines. Fixing...") fixed_count = 0 for line_id, move_id in corrupted_lines: try: # Find the parent move to get the correct product_id move = env['stock.move'].browse(move_id) if not move.exists() or not move.product_id: _logger.warning(f"Could not fix move line {line_id}: Parent move {move_id} is missing or has no product.") continue # Perform a hard SQL update to bypass any ORM constraints that might crash env.cr.execute(""" UPDATE stock_move_line SET product_id = %s WHERE id = %s """, (move.product_id.id, line_id)) fixed_count += 1 print(f"Fixed move line {line_id} -> Assigned product_id {move.product_id.id} ({move.product_id.display_name})") except Exception as e: _logger.error(f"Error fixing line {line_id}: {e}") env.cr.commit() _logger.info(f"Successfully fixed {fixed_count} move lines.") print(f"Successfully fixed {fixed_count} move lines. Please refresh your Odoo browser.") # Execute the fix fix_corrupted_move_lines(env)