fix(#67): Fix selection widget showing 'Sin Opciones' by using proper Selection field
- Add new Selection field 'value_selection_field' with dynamic options - Update views to use the new field instead of char field with selection widget - Add migration script to copy existing data from old field to new field - Update field synchronization and validation logic - Increment module version to trigger migration The issue was that Odoo doesn't support using widget='selection' on Char fields. The solution implements a proper Selection field with dynamic options based on the parameter configuration.
This commit is contained in:
parent
875a90a6aa
commit
22082965d0
|
@ -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': [
|
||||
|
|
26
lims_management/migrations/18.0.1.0.1/post-migrate.py
Normal file
26
lims_management/migrations/18.0.1.0.1/post-migrate.py
Normal file
|
@ -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")
|
|
@ -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 = []
|
||||
|
|
|
@ -65,9 +65,8 @@
|
|||
decoration-warning="is_out_of_range and not is_critical"/>
|
||||
<field name="value_text"
|
||||
invisible="parameter_value_type != 'text'"/>
|
||||
<field name="value_selection"
|
||||
invisible="parameter_value_type != 'selection'"
|
||||
widget="selection"/>
|
||||
<field name="value_selection_field"
|
||||
invisible="parameter_value_type != 'selection'"/>
|
||||
<field name="value_boolean"
|
||||
invisible="parameter_value_type != 'boolean'"
|
||||
widget="boolean_toggle"/>
|
||||
|
|
|
@ -34,9 +34,8 @@
|
|||
decoration-warning="is_critical"/>
|
||||
<field name="value_text"
|
||||
invisible="parameter_value_type != 'text'"/>
|
||||
<field name="value_selection"
|
||||
invisible="parameter_value_type != 'selection'"
|
||||
widget="selection"/>
|
||||
<field name="value_selection_field"
|
||||
invisible="parameter_value_type != 'selection'"/>
|
||||
<field name="value_boolean"
|
||||
invisible="parameter_value_type != 'boolean'"
|
||||
widget="boolean_toggle"/>
|
||||
|
|
132
test/diagnose_selection_issue.py
Normal file
132
test/diagnose_selection_issue.py
Normal file
|
@ -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)
|
Loading…
Reference in New Issue
Block a user