diff --git a/lims_management/models/__pycache__/sale_order.cpython-312.pyc b/lims_management/models/__pycache__/sale_order.cpython-312.pyc index c909f78..d4ff865 100644 Binary files a/lims_management/models/__pycache__/sale_order.cpython-312.pyc and b/lims_management/models/__pycache__/sale_order.cpython-312.pyc differ diff --git a/lims_management/models/stock_lot.py b/lims_management/models/stock_lot.py index f18c1d1..0cd362a 100644 --- a/lims_management/models/stock_lot.py +++ b/lims_management/models/stock_lot.py @@ -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 diff --git a/lims_management/views/stock_lot_views.xml b/lims_management/views/stock_lot_views.xml index ec33607..511b425 100644 --- a/lims_management/views/stock_lot_views.xml +++ b/lims_management/views/stock_lot_views.xml @@ -91,10 +91,12 @@ - + + + @@ -103,9 +105,16 @@ + + + + diff --git a/lims_management/wizards/sample_rejection_wizard.py b/lims_management/wizards/sample_rejection_wizard.py index 9bea34b..dc2802e 100644 --- a/lims_management/wizards/sample_rejection_wizard.py +++ b/lims_management/wizards/sample_rejection_wizard.py @@ -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'}