# -*- coding: utf-8 -*- from odoo import models, api, fields, Command 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() @api.model def default_get(self, fields_list): defaults = super(QualityCheck, self).default_get(fields_list) if 'company_id' not in defaults: picking_id = defaults.get('picking_id') or self.env.context.get('default_picking_id') if picking_id: picking = self.env['stock.picking'].browse(picking_id) if picking.company_id: defaults['company_id'] = picking.company_id.id return defaults @api.model_create_multi def create(self, vals_list): """ Override create to ensure company_id is set. Prioritizes: 1. Explicitly passed value (in vals) 2. Picking's company (if picking_id is available) 3. Context default_company_id 4. Current user's company (env.company) """ for vals in vals_list: if not vals.get('company_id'): # Try to get from picking picking_id = vals.get('picking_id') or self.env.context.get('default_picking_id') if picking_id: picking = self.env['stock.picking'].browse(picking_id) if picking.company_id: vals['company_id'] = picking.company_id.id # If still missing, check context or use current environment company if not vals.get('company_id'): vals['company_id'] = self.env.context.get('default_company_id') or self.env.company.id return super(QualityCheck, self).create(vals_list) def write(self, vals): """ Override write to prevent state reset when lot_ids 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 # Using formatted SQL for safety 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_ids if qc.lot_line_id and qc._update_lot_from_lot_line(): # Update lot_ids (ORM handles Many2many) qc.lot_ids = [Command.set([qc.lot_line_id.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_ids 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() # Update lot_ids. The write override will handle state preservation. vals = {'lot_ids': [Command.set([lot_id])] if lot_id else [Command.clear()]} # Use context flag to preserve state during write (though write logic checks _should_preserve_state) self.with_context(quality_check_preserve_state=True).sudo().write(vals) # Redundant SQL restore removed as write() already handles it. def _is_to_do(self, checkable_products, check_picked=False): """ Override to allow quality checks on receipt operations even if lot is not yet assigned. """ self.ensure_one() # If this is a receipt operation, we want to allow the check even if lot is missing # because our module supports assigning the lot later or during the check if self._is_receipt_operation(): # Standard logic from quality_control.quality_check._is_to_do # but skipping the lot requirement for tracked products on receipt if self.quality_state != 'none': return False if self.measure_on != 'operation': if self.product_id not in checkable_products: return False if self.move_line_id: if not self.move_line_id._is_checkable(check_picked): return False # This is the part we're effectively skipping for receipt operations: # "Only process qc related to tracked product if its lot is set" # We skip this check because we want to allow it. return True # For non-receipt operations, fall back to standard behavior return super(QualityCheck, self)._is_to_do(checkable_products, check_picked)