# -*- coding: utf-8 -*- from odoo import models, api, fields class QualityCheck(models.Model): _inherit = 'quality.check' # Add a context flag to track when we're preserving state _preserve_state_context_key = 'quality_check_preserve_state' def _is_receipt_operation(self): """ Check if the current quality check is part of a receipt operation. Receipt operations are identified by picking_type_code == 'incoming'. This method implements robust operation type identification with multiple fallback paths to ensure reliable detection across different scenarios. Returns: bool: True if this is a receipt operation, False otherwise """ self.ensure_one() # Primary check: picking_type_code through the picking if self.picking_id and self.picking_id.picking_type_id: return self.picking_id.picking_type_id.code == 'incoming' # Fallback 1: check through move_line_id if picking_id is not available if self.move_line_id: if self.move_line_id.picking_id and self.move_line_id.picking_id.picking_type_id: return self.move_line_id.picking_id.picking_type_id.code == 'incoming' # Fallback 2: check through move_id.picking_type_id if self.move_line_id.move_id and self.move_line_id.move_id.picking_type_id: return self.move_line_id.move_id.picking_type_id.code == 'incoming' # Fallback 3: check through move_id if available if hasattr(self, 'move_id') and self.move_id and self.move_id.picking_type_id: return self.move_id.picking_type_id.code == 'incoming' # Default: not a receipt operation if we can't determine the type return False def _should_preserve_state(self): """ Determine if the quality check state should be preserved during lot assignment. State preservation applies only to receipt operations. Returns: bool: True if state should be preserved, False otherwise """ self.ensure_one() return self._is_receipt_operation() def write(self, vals): """ Override write to prevent state reset when lot_id is updated on receipt operations. """ # Store current states before write for receipt operations state_data = {} for record in self: if record._should_preserve_state() and record.quality_state != 'none': # Only preserve if we're not explicitly changing the quality_state if 'quality_state' not in vals: state_data[record.id] = { 'quality_state': record.quality_state, 'user_id': record.user_id.id if record.user_id else False, 'control_date': record.control_date, } # Perform the write result = super(QualityCheck, self).write(vals) # Restore states if they were changed if state_data: for record in self: if record.id in state_data: stored = state_data[record.id] if record.quality_state != stored['quality_state']: # Use SQL to restore state without triggering write again self.env.cr.execute( """ UPDATE quality_check SET quality_state = %s, user_id = %s, control_date = %s WHERE id = %s """, (stored['quality_state'], stored['user_id'], stored['control_date'], record.id) ) record.invalidate_recordset(['quality_state', 'user_id', 'control_date']) return result @api.depends('move_line_id.lot_id') def _compute_lot_line_id(self): """ Override the compute method to preserve quality check state during lot updates. This prevents the state from being reset when lot numbers are assigned. """ for qc in self: # Always update lot_line_id qc.lot_line_id = qc.move_line_id.lot_id # Check if we should update lot_id if qc.lot_line_id and qc._update_lot_from_lot_line(): # For receipt operations, preserve the state if qc._should_preserve_state(): # Store current state before updating lot_id current_state = qc.quality_state # Directly update lot_id field in database without triggering ORM if current_state != 'none': # Use SQL to update lot_id while preserving state self.env.cr.execute( """ UPDATE quality_check SET lot_id = %s WHERE id = %s AND quality_state = %s """, (qc.lot_line_id.id if qc.lot_line_id else None, qc.id, current_state) ) # Invalidate cache for lot_id only qc.invalidate_recordset(['lot_id']) else: # If state is 'none', use normal assignment qc.lot_id = qc.lot_line_id else: # For non-receipt operations, use standard behavior qc.lot_id = qc.lot_line_id def _update_lot_from_lot_line(self): """ Override the standard method from quality_control module. This method is called by _compute_lot_line_id to determine if the lot_id should be automatically updated from the move_line_id.lot_id. For receipt operations, we return True to allow the update, but we handle state preservation in the overridden _compute_lot_line_id method. For non-receipt operations, we preserve standard Odoo behavior. Returns: bool: True to allow lot update (state preservation handled separately) """ self.ensure_one() # Always return True to allow lot updates # State preservation is handled in _compute_lot_line_id return super(QualityCheck, self)._update_lot_from_lot_line() def _check_to_unlink(self): """ Override to prevent deletion of completed quality checks on receipt operations. """ self.ensure_one() # For receipt operations with completed quality checks, prevent deletion if self._should_preserve_state() and self.quality_state != 'none': return False # For other cases, use standard behavior return super(QualityCheck, self)._check_to_unlink() def _update_lot_from_move_line_manual(self, lot_id): """ Manually update the lot_id field while preserving the current quality check state. This method is called from stock.move.line when a lot number is assigned or changed on receipt operations. Args: lot_id: The ID of the lot to assign (or False to clear) """ self.ensure_one() # Store the current quality state before updating lot_id current_state = self.quality_state current_user = self.user_id current_control_date = self.control_date # Use context flag to preserve state during write self.with_context(quality_check_preserve_state=True).sudo().write({'lot_id': lot_id}) # Force restore the quality state if it was changed during the write if current_state != 'none': # Use direct SQL update to avoid triggering any compute methods self.env.cr.execute( """ UPDATE quality_check SET quality_state = %s, user_id = %s, control_date = %s WHERE id = %s """, (current_state, current_user.id if current_user else None, current_control_date, self.id) ) # Invalidate cache to ensure the updated values are reflected self.invalidate_recordset(['quality_state', 'user_id', 'control_date'])