diff --git a/lims_management/__manifest__.py b/lims_management/__manifest__.py index 16f9d1f..b19986e 100644 --- a/lims_management/__manifest__.py +++ b/lims_management/__manifest__.py @@ -15,7 +15,7 @@ 'author': "Gemini", 'website': "https://gitea.grupoconsiti.com/luis_portillo/clinical_laboratory", 'category': 'Industries', - 'version': '18.0.1.0.0', + 'version': '18.0.1.0.1', 'depends': ['base', 'product', 'sale', 'base_setup'], 'assets': { 'web.assets_backend': [ diff --git a/lims_management/migrations/18.0.1.0.1/post-migrate.py b/lims_management/migrations/18.0.1.0.1/post-migrate.py new file mode 100644 index 0000000..fed9202 --- /dev/null +++ b/lims_management/migrations/18.0.1.0.1/post-migrate.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +from odoo import api, SUPERUSER_ID + +def migrate(cr, version): + """Migrate existing selection values from char field to selection field.""" + env = api.Environment(cr, SUPERUSER_ID, {}) + + # Find all results with selection parameters that have values + results = env['lims.result'].search([ + ('parameter_value_type', '=', 'selection'), + ('value_selection', '!=', False), + ('value_selection', '!=', '') + ]) + + if results: + cr.execute(""" + UPDATE lims_result + SET value_selection_field = value_selection + WHERE id IN %s + AND parameter_value_type = 'selection' + AND value_selection IS NOT NULL + AND value_selection != '' + """, (tuple(results.ids),)) + + # Log migration + print(f"Migrated {len(results)} selection values from value_selection to value_selection_field") \ No newline at end of file diff --git a/lims_management/models/lims_result.py b/lims_management/models/lims_result.py index 46878bd..9e2da51 100644 --- a/lims_management/models/lims_result.py +++ b/lims_management/models/lims_result.py @@ -97,6 +97,12 @@ class LimsResult(models.Model): string='Valor de Selección' ) + # Add Selection field with dynamic options + value_selection_field = fields.Selection( + selection='_get_selection_options', + string='Valor de Selección' + ) + # Campo para mostrar las opciones disponibles selection_options_display = fields.Char( string='Opciones disponibles', @@ -167,7 +173,7 @@ class LimsResult(models.Model): else: record.display_name = record.parameter_name or _('Nuevo') - @api.depends('value_numeric', 'value_text', 'value_selection', 'value_boolean', 'parameter_value_type') + @api.depends('value_numeric', 'value_text', 'value_selection', 'value_selection_field', 'value_boolean', 'parameter_value_type') def _compute_value_display(self): """Calcula el valor a mostrar según el tipo de dato.""" for record in self: @@ -179,7 +185,8 @@ class LimsResult(models.Model): elif record.parameter_value_type == 'text': record.value_display = record.value_text or '' elif record.parameter_value_type == 'selection': - record.value_display = record.value_selection or '' + # Use the new selection field value + record.value_display = record.value_selection_field or record.value_selection or '' elif record.parameter_value_type == 'boolean': record.value_display = 'Sí' if record.value_boolean else 'No' else: @@ -278,18 +285,20 @@ class LimsResult(models.Model): _('Para parámetros de texto solo se debe ingresar el valor de texto.') ) elif value_type == 'selection': - has_value = bool(record.value_selection) + # Check both fields for backward compatibility + has_value = bool(record.value_selection_field or record.value_selection) if (record.value_numeric not in [False, 0.0]) or record.value_text or record.value_boolean: raise ValidationError( _('Para parámetros de selección solo se debe elegir una opción.') ) # Validar que el valor seleccionado sea válido + actual_value = record.value_selection_field or record.value_selection 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: + if valid_options and actual_value 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)) + (actual_value, ', '.join(valid_options)) ) elif value_type == 'boolean': has_value = True # Boolean siempre tiene valor (True o False) @@ -312,6 +321,7 @@ class LimsResult(models.Model): self.value_numeric = False self.value_text = False self.value_selection = False + self.value_selection_field = False self.value_boolean = False # Si es selección, obtener las opciones @@ -319,6 +329,12 @@ class LimsResult(models.Model): # Esto se usará en las vistas para mostrar las opciones dinámicamente pass + @api.onchange('value_selection_field') + def _onchange_value_selection_field(self): + """Sincroniza el campo de selección con el campo char.""" + if self.parameter_value_type == 'selection': + self.value_selection = self.value_selection_field + def _get_selection_options(self): """Retorna las opciones de selección basadas en el parámetro.""" options = [] diff --git a/lims_management/views/lims_result_bulk_entry_views.xml b/lims_management/views/lims_result_bulk_entry_views.xml index ece7fd5..65035c1 100644 --- a/lims_management/views/lims_result_bulk_entry_views.xml +++ b/lims_management/views/lims_result_bulk_entry_views.xml @@ -65,9 +65,8 @@ decoration-warning="is_out_of_range and not is_critical"/> - + diff --git a/lims_management/views/lims_result_views.xml b/lims_management/views/lims_result_views.xml index d7af9be..9022f41 100644 --- a/lims_management/views/lims_result_views.xml +++ b/lims_management/views/lims_result_views.xml @@ -34,9 +34,8 @@ decoration-warning="is_critical"/> - + diff --git a/test/diagnose_selection_issue.py b/test/diagnose_selection_issue.py new file mode 100644 index 0000000..298858c --- /dev/null +++ b/test/diagnose_selection_issue.py @@ -0,0 +1,132 @@ +import odoo +import json + +def diagnose_selection_issue(cr): + """Diagnose why selection widget shows 'Sin Opciones'""" + env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {}) + + print("="*80) + print("DIAGNOSING SELECTION WIDGET ISSUE") + print("="*80) + + # 1. Check the specific test and parameter + test_id = 33 + result_id = 43 + + # Get the test + test = env['lims.test'].browse(test_id) + if not test: + print(f"\nERROR: Test with ID {test_id} not found!") + return + + print(f"\nTest: {test.name} (ID: {test.id})") + print(f"Category: {test.category_id.name}") + + # Check parameters with selection type + selection_params = test.parameter_ids.filtered(lambda p: p.value_type == 'selection') + print(f"\nParameters with selection type: {len(selection_params)}") + + for param in selection_params: + print(f"\n--- Parameter: {param.name} (ID: {param.id}) ---") + print(f"Value Type: {param.value_type}") + print(f"Selection Values (raw field): '{param.selection_values}'") + print(f"Selection Values (type): {type(param.selection_values)}") + print(f"Selection Values (bool): {bool(param.selection_values)}") + + # Test get_selection_list method + try: + selection_list = param.get_selection_list() + print(f"\nget_selection_list() result:") + print(f" Type: {type(selection_list)}") + print(f" Content: {selection_list}") + print(f" Length: {len(selection_list) if isinstance(selection_list, list) else 'N/A'}") + except Exception as e: + print(f"\nERROR calling get_selection_list(): {str(e)}") + + # 2. Check the specific result + result = env['lims.result'].browse(result_id) + if not result: + print(f"\n\nERROR: Result with ID {result_id} not found!") + return + + print(f"\n\n--- Result Analysis (ID: {result_id}) ---") + print(f"Test: {result.test_id.name}") + print(f"Parameter: {result.parameter_id.name}") + print(f"Parameter Value Type: {result.parameter_id.value_type}") + + # Check computed field + print(f"\nComputed field 'selection_options': {result.selection_options}") + + # Manually test _get_selection_options + try: + # Clear cache to force recomputation + result.invalidate_recordset(['selection_options']) + + # Access the field to trigger computation + options = result.selection_options + print(f"\nAfter invalidating cache:") + print(f" selection_options: {options}") + print(f" Type: {type(options)}") + except Exception as e: + print(f"\nERROR accessing selection_options: {str(e)}") + + # 3. Direct method test + print(f"\n\n--- Direct Method Tests ---") + + # Test parameter's get_selection_list directly + if result.parameter_id.value_type == 'selection': + param = result.parameter_id + print(f"\nParameter: {param.name}") + print(f"Selection Values: '{param.selection_values}'") + + # Test parsing + if param.selection_values: + lines = param.selection_values.strip().split('\n') + print(f"\nParsing test:") + print(f" Lines after split: {lines}") + print(f" Number of lines: {len(lines)}") + + parsed_options = [] + for line in lines: + line = line.strip() + if line: + if '|' in line: + parts = line.split('|', 1) + if len(parts) == 2: + parsed_options.append((parts[0].strip(), parts[1].strip())) + else: + parsed_options.append((line, line)) + + print(f"\n Parsed options: {parsed_options}") + + # 4. Check other results for the same test + print(f"\n\n--- Other Results for Test {test_id} ---") + other_results = env['lims.result'].search([('test_id', '=', test_id)], limit=5) + for res in other_results: + if res.parameter_id.value_type == 'selection': + print(f"\nResult ID: {res.id}") + print(f" Parameter: {res.parameter_id.name}") + print(f" Selection Options: {res.selection_options}") + print(f" Value: {res.value_text}") + + # 5. Database check + print(f"\n\n--- Direct Database Check ---") + cr.execute(""" + SELECT id, name, value_type, selection_values + FROM lims_parameter + WHERE value_type = 'selection' + AND id IN (SELECT parameter_id FROM lims_result WHERE id = %s) + """, (result_id,)) + + for row in cr.fetchall(): + print(f"\nParameter ID: {row[0]}") + print(f" Name: {row[1]}") + print(f" Value Type: {row[2]}") + print(f" Selection Values: '{row[3]}'") + print(f" Selection Values (repr): {repr(row[3])}") + +if __name__ == '__main__': + db_name = 'odoo' + registry = odoo.registry(db_name) + with registry.cursor() as cr: + diagnose_selection_issue(cr) \ No newline at end of file