diff --git a/lims_management/models/__pycache__/stock_lot.cpython-312.pyc b/lims_management/models/__pycache__/stock_lot.cpython-312.pyc
index 49bcfe0..f3a7a03 100644
Binary files a/lims_management/models/__pycache__/stock_lot.cpython-312.pyc and b/lims_management/models/__pycache__/stock_lot.cpython-312.pyc differ
diff --git a/lims_management/models/lims_result.py b/lims_management/models/lims_result.py
index 19d840d..dfaca77 100644
--- a/lims_management/models/lims_result.py
+++ b/lims_management/models/lims_result.py
@@ -93,7 +93,15 @@ class LimsResult(models.Model):
)
value_selection = fields.Char(
- string='Valor de Selección'
+ string='Valor de Selección',
+ help='Ingrese el valor o las primeras letras. Ej: P para Positivo, N para Negativo'
+ )
+
+ # Campo para mostrar las opciones disponibles
+ selection_options_display = fields.Char(
+ string='Opciones disponibles',
+ compute='_compute_selection_options_display',
+ help='Opciones válidas para este parámetro'
)
value_boolean = fields.Boolean(
@@ -275,6 +283,17 @@ class LimsResult(models.Model):
raise ValidationError(
_('Para parámetros de selección solo se debe elegir una opción.')
)
+ # Validar que el valor seleccionado sea válido
+ if has_value and record.parameter_id:
+ valid_options = record.parameter_id.get_selection_list()
+ if valid_options and record.value_selection not in valid_options:
+ # Intentar autocompletar antes de rechazar
+ autocompleted = record._validate_and_autocomplete_selection(record.value_selection)
+ if autocompleted not in valid_options:
+ raise ValidationError(
+ _('El valor "%s" no es una opción válida. Opciones disponibles: %s') %
+ (record.value_selection, ', '.join(valid_options))
+ )
elif value_type == 'boolean':
has_value = True # Boolean siempre tiene valor (True o False)
if (record.value_numeric not in [False, 0.0]) or record.value_text or record.value_selection:
@@ -301,4 +320,119 @@ class LimsResult(models.Model):
# Si es selección, obtener las opciones
if self.parameter_value_type == 'selection' and self.parameter_id.selection_values:
# Esto se usará en las vistas para mostrar las opciones dinámicamente
- pass
\ No newline at end of file
+ pass
+
+ @api.depends('parameter_id', 'parameter_id.selection_values')
+ def _compute_selection_options_display(self):
+ """Calcula las opciones disponibles para mostrar al usuario."""
+ for record in self:
+ if record.parameter_id and record.parameter_value_type == 'selection':
+ options = record.parameter_id.get_selection_list()
+ if options:
+ record.selection_options_display = ' | '.join(options)
+ else:
+ record.selection_options_display = 'Sin opciones definidas'
+ else:
+ record.selection_options_display = False
+
+ @api.onchange('value_selection')
+ def _onchange_value_selection(self):
+ """Autocompleta el valor de selección basado en coincidencia parcial."""
+ if self.value_selection and self.parameter_id and self.parameter_value_type == 'selection':
+ # Obtener las opciones disponibles
+ options = self.parameter_id.get_selection_list()
+ if options:
+ # Convertir el valor ingresado a mayúsculas para comparación
+ input_upper = self.value_selection.upper().strip()
+
+ # Buscar coincidencias
+ matches = []
+ for option in options:
+ option_upper = option.upper()
+ if option_upper.startswith(input_upper):
+ matches.append(option)
+
+ # Si hay exactamente una coincidencia, autocompletar
+ if len(matches) == 1:
+ self.value_selection = matches[0]
+ elif len(matches) == 0:
+ # Si no hay coincidencias directas, buscar coincidencias parciales
+ for option in options:
+ if input_upper in option.upper():
+ matches.append(option)
+
+ # Si hay una sola coincidencia parcial, autocompletar
+ if len(matches) == 1:
+ self.value_selection = matches[0]
+
+ def _validate_and_autocomplete_selection(self, value):
+ """Valida y autocompleta el valor de selección.
+
+ Esta función es llamada antes de guardar para asegurar que el valor
+ sea válido y esté completo.
+ """
+ if not value or not self.parameter_id or self.parameter_value_type != 'selection':
+ return value
+
+ options = self.parameter_id.get_selection_list()
+ if not options:
+ return value
+
+ # Convertir a mayúsculas para comparación
+ value_upper = value.upper().strip()
+
+ # Buscar coincidencias exactas primero
+ for option in options:
+ if option.upper() == value_upper:
+ return option
+
+ # Buscar coincidencias que empiecen con el valor
+ matches = []
+ for option in options:
+ if option.upper().startswith(value_upper):
+ matches.append(option)
+
+ if len(matches) == 1:
+ return matches[0]
+ elif len(matches) > 1:
+ # Si hay múltiples coincidencias, intentar ser más específico
+ # Preferir la coincidencia más corta
+ shortest = min(matches, key=len)
+ return shortest
+
+ # Si no hay coincidencias por inicio, buscar contenido
+ for option in options:
+ if value_upper in option.upper():
+ matches.append(option)
+
+ if len(matches) == 1:
+ return matches[0]
+ elif len(matches) > 1:
+ # Retornar la primera coincidencia
+ return matches[0]
+
+ # Si no hay ninguna coincidencia, retornar el valor original
+ # La validación en @api.constrains se encargará de rechazarlo
+ return value
+
+ @api.model
+ def create(self, vals):
+ """Override create para autocompletar valores de selección."""
+ if 'value_selection' in vals and vals.get('value_selection'):
+ # Necesitamos el parameter_id para validar
+ if 'parameter_id' in vals:
+ parameter = self.env['lims.analysis.parameter'].browse(vals['parameter_id'])
+ if parameter.value_type == 'selection':
+ # Crear un registro temporal para usar el método
+ temp_record = self.new({'parameter_id': parameter.id, 'parameter_value_type': 'selection'})
+ vals['value_selection'] = temp_record._validate_and_autocomplete_selection(vals['value_selection'])
+ return super(LimsResult, self).create(vals)
+
+ def write(self, vals):
+ """Override write para autocompletar valores de selección."""
+ if 'value_selection' in vals and vals.get('value_selection'):
+ for record in self:
+ if record.parameter_value_type == 'selection':
+ vals['value_selection'] = record._validate_and_autocomplete_selection(vals['value_selection'])
+ break # Solo necesitamos procesar una vez
+ return super(LimsResult, self).write(vals)
\ No newline at end of file
diff --git a/lims_management/models/lims_test.py b/lims_management/models/lims_test.py
index cc8dc39..32b503d 100644
--- a/lims_management/models/lims_test.py
+++ b/lims_management/models/lims_test.py
@@ -183,11 +183,40 @@ class LimsTest(models.Model):
# Crear una línea de resultado por cada parámetro
for param_config in template_parameters:
+ # Preparar las notas/instrucciones
+ notes = param_config.instructions or ''
+
+ # Si es un parámetro de tipo selection, agregar instrucciones de autocompletado
+ if param_config.parameter_value_type == 'selection':
+ selection_values = param_config.parameter_id.selection_values
+ if selection_values:
+ options = [v.strip() for v in selection_values.split(',')]
+ if options:
+ # Generar instrucciones automáticas
+ auto_instructions = "Opciones: " + ", ".join(options) + ". "
+ auto_instructions += "Puede escribir las iniciales o parte del texto. "
+
+ # Agregar ejemplos específicos
+ examples = []
+ for opt in options[:3]: # Mostrar ejemplos para las primeras 3 opciones
+ if opt:
+ initial = opt[0].upper()
+ examples.append(f"{initial}={opt}")
+
+ if examples:
+ auto_instructions += "Ej: " + ", ".join(examples)
+
+ # Combinar con instrucciones existentes
+ if notes:
+ notes = auto_instructions + "\n" + notes
+ else:
+ notes = auto_instructions
+
result_vals = {
'test_id': test.id,
'parameter_id': param_config.parameter_id.id,
'sequence': param_config.sequence,
- 'notes': param_config.instructions or ''
+ 'notes': notes
}
# Inicializar valores según el tipo
diff --git a/lims_management/views/lims_test_views.xml b/lims_management/views/lims_test_views.xml
index f038c09..1ce6b1c 100644
--- a/lims_management/views/lims_test_views.xml
+++ b/lims_management/views/lims_test_views.xml
@@ -90,7 +90,7 @@
class="oe_edit_only"/>