quality_check_lot_preserve/models/quality_check.py

229 lines
9.7 KiB
Python
Executable File

# -*- 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)