feat(#60): Mejorar control y trazabilidad de re-muestreos
- Respetar configuración del wizard (checkbox crear re-muestra) - Prevenir creación de múltiples re-muestras activas - Agregar campos para trazabilidad completa: - root_sample_id: muestra original de la cadena - resample_chain_count: total de re-muestreos en cadena - Validar límite de re-muestreos por cadena completa - Mejorar vista con información de trazabilidad - Método auxiliar para contar re-muestreos recursivamente 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
be6c97cfad
commit
3e97c9f418
Binary file not shown.
|
@ -131,6 +131,18 @@ class StockLot(models.Model):
|
|||
compute='_compute_is_resample',
|
||||
store=True
|
||||
)
|
||||
root_sample_id = fields.Many2one(
|
||||
'stock.lot',
|
||||
string='Muestra Original (Raíz)',
|
||||
compute='_compute_root_sample',
|
||||
store=True,
|
||||
help='Muestra original de la cadena de re-muestreos'
|
||||
)
|
||||
resample_chain_count = fields.Integer(
|
||||
string='Re-muestreos en Cadena',
|
||||
compute='_compute_resample_chain_count',
|
||||
help='Número total de re-muestreos en toda la cadena'
|
||||
)
|
||||
|
||||
def action_collect(self):
|
||||
"""Mark sample as collected"""
|
||||
|
@ -216,8 +228,12 @@ class StockLot(models.Model):
|
|||
}
|
||||
}
|
||||
|
||||
def action_reject(self):
|
||||
"""Reject the sample - to be called from wizard"""
|
||||
def action_reject(self, create_resample=None):
|
||||
"""Reject the sample - to be called from wizard
|
||||
|
||||
Args:
|
||||
create_resample: Boolean to force resample creation. If None, uses system config
|
||||
"""
|
||||
self.ensure_one()
|
||||
if self.state == 'completed':
|
||||
raise ValueError('No se puede rechazar una muestra ya completada')
|
||||
|
@ -250,11 +266,19 @@ class StockLot(models.Model):
|
|||
message_type='notification'
|
||||
)
|
||||
|
||||
# Check if automatic resample is enabled
|
||||
IrConfig = self.env['ir.config_parameter'].sudo()
|
||||
auto_resample = IrConfig.get_param('lims_management.auto_resample_on_rejection', 'True') == 'True'
|
||||
# Determine if we should create a resample
|
||||
should_create_resample = False
|
||||
|
||||
if auto_resample:
|
||||
if create_resample is not None:
|
||||
# Explicit value from wizard
|
||||
should_create_resample = create_resample
|
||||
else:
|
||||
# Check system configuration
|
||||
IrConfig = self.env['ir.config_parameter'].sudo()
|
||||
auto_resample = IrConfig.get_param('lims_management.auto_resample_on_rejection', 'True') == 'True'
|
||||
should_create_resample = auto_resample
|
||||
|
||||
if should_create_resample:
|
||||
try:
|
||||
# Create resample automatically
|
||||
resample_action = self.action_create_resample()
|
||||
|
@ -408,10 +432,38 @@ class StockLot(models.Model):
|
|||
for record in self:
|
||||
record.resample_count = len(record.child_sample_ids)
|
||||
|
||||
@api.depends('parent_sample_id')
|
||||
def _compute_root_sample(self):
|
||||
"""Compute the root sample of the resample chain"""
|
||||
for record in self:
|
||||
root = record
|
||||
while root.parent_sample_id:
|
||||
root = root.parent_sample_id
|
||||
record.root_sample_id = root if root != record else False
|
||||
|
||||
@api.depends('parent_sample_id', 'child_sample_ids')
|
||||
def _compute_resample_chain_count(self):
|
||||
"""Compute total resamples in the entire chain"""
|
||||
for record in self:
|
||||
# Find root sample
|
||||
root = record
|
||||
while root.parent_sample_id:
|
||||
root = root.parent_sample_id
|
||||
# Count all resamples from root
|
||||
record.resample_chain_count = self._count_all_resamples_in_chain(root)
|
||||
|
||||
def action_create_resample(self):
|
||||
"""Create a new sample as a resample of the current one"""
|
||||
self.ensure_one()
|
||||
|
||||
# Check if there's already an active resample
|
||||
active_resamples = self.child_sample_ids.filtered(
|
||||
lambda s: s.state not in ['rejected', 'cancelled', 'disposed']
|
||||
)
|
||||
if active_resamples:
|
||||
raise UserError(_('Esta muestra ya tiene una re-muestra activa (%s). No se puede crear otra hasta que se procese o rechace la existente.') %
|
||||
', '.join(active_resamples.mapped('name')))
|
||||
|
||||
# Get configuration
|
||||
IrConfig = self.env['ir.config_parameter'].sudo()
|
||||
auto_resample = IrConfig.get_param('lims_management.auto_resample_on_rejection', 'True') == 'True'
|
||||
|
@ -419,9 +471,17 @@ class StockLot(models.Model):
|
|||
prefix = IrConfig.get_param('lims_management.resample_prefix', 'RE-')
|
||||
max_attempts = int(IrConfig.get_param('lims_management.max_resample_attempts', '3'))
|
||||
|
||||
# Check maximum resample attempts
|
||||
if max_attempts > 0 and self.resample_count >= max_attempts:
|
||||
raise UserError(_('Se ha alcanzado el número máximo de re-muestreos (%d) para esta muestra.') % max_attempts)
|
||||
# Find the original sample (root of the resample chain)
|
||||
original_sample = self
|
||||
while original_sample.parent_sample_id:
|
||||
original_sample = original_sample.parent_sample_id
|
||||
|
||||
# Count all resamples in the chain
|
||||
total_resamples = self._count_all_resamples_in_chain(original_sample)
|
||||
|
||||
# Check maximum resample attempts based on the entire chain
|
||||
if max_attempts > 0 and total_resamples >= max_attempts:
|
||||
raise UserError(_('Se ha alcanzado el número máximo de re-muestreos (%d) para esta cadena de muestras.') % max_attempts)
|
||||
|
||||
# Calculate resample number for naming
|
||||
resample_number = self.resample_count + 1
|
||||
|
@ -483,6 +543,20 @@ class StockLot(models.Model):
|
|||
'target': 'current',
|
||||
}
|
||||
|
||||
def _count_all_resamples_in_chain(self, root_sample):
|
||||
"""Count all resamples in the entire chain starting from root"""
|
||||
count = 0
|
||||
samples_to_check = [root_sample]
|
||||
|
||||
while samples_to_check:
|
||||
sample = samples_to_check.pop(0)
|
||||
# Add all child samples to the check list
|
||||
for child in sample.child_sample_ids:
|
||||
count += 1
|
||||
samples_to_check.append(child)
|
||||
|
||||
return count
|
||||
|
||||
def _notify_resample_created(self, resample):
|
||||
"""Notify receptionist users about the created resample"""
|
||||
# Find receptionist users
|
||||
|
|
|
@ -91,10 +91,12 @@
|
|||
</group>
|
||||
<notebook>
|
||||
<page string="Re-muestreo" invisible="not is_resample and resample_count == 0">
|
||||
<group>
|
||||
<group col="4">
|
||||
<field name="is_resample" invisible="1"/>
|
||||
<field name="resample_count" invisible="1"/>
|
||||
<field name="parent_sample_id" readonly="1" invisible="not is_resample"/>
|
||||
<field name="root_sample_id" readonly="1" invisible="not is_resample"/>
|
||||
<field name="resample_chain_count" readonly="1" invisible="resample_chain_count == 0"/>
|
||||
</group>
|
||||
<group string="Re-muestras Generadas" invisible="resample_count == 0">
|
||||
<field name="child_sample_ids" nolabel="1">
|
||||
|
@ -103,9 +105,16 @@
|
|||
<field name="state" widget="badge"/>
|
||||
<field name="collection_date"/>
|
||||
<field name="rejection_reason_id"/>
|
||||
<field name="resample_count" string="Re-muestras propias"/>
|
||||
</list>
|
||||
</field>
|
||||
</group>
|
||||
<group string="Información de Trazabilidad" invisible="not is_resample">
|
||||
<div class="alert alert-info" role="alert">
|
||||
<p><i class="fa fa-info-circle"/> Esta muestra es parte de una cadena de re-muestreo.</p>
|
||||
<p>Total de re-muestreos en la cadena: <field name="resample_chain_count" readonly="1" nolabel="1" class="oe_inline"/></p>
|
||||
</div>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
|
|
|
@ -67,12 +67,8 @@ class SampleRejectionWizard(models.TransientModel):
|
|||
'rejection_notes': self.rejection_notes
|
||||
})
|
||||
|
||||
# Call the rejection method on the sample
|
||||
self.sample_id.action_reject()
|
||||
|
||||
# Create new sample request if needed
|
||||
if self.create_new_sample and self.sample_id.request_id:
|
||||
self._create_new_sample_request()
|
||||
# Call the rejection method on the sample with explicit resample creation preference
|
||||
self.sample_id.action_reject(create_resample=self.create_new_sample)
|
||||
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user