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",
|
'author': "Gemini",
|
||||||
'website': "https://gitea.grupoconsiti.com/luis_portillo/clinical_laboratory",
|
'website': "https://gitea.grupoconsiti.com/luis_portillo/clinical_laboratory",
|
||||||
'category': 'Industries',
|
'category': 'Industries',
|
||||||
'version': '18.0.1.0.0',
|
'version': '18.0.1.0.1',
|
||||||
'depends': ['base', 'product', 'sale', 'base_setup'],
|
'depends': ['base', 'product', 'sale', 'base_setup'],
|
||||||
'assets': {
|
'assets': {
|
||||||
'web.assets_backend': [
|
'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'
|
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
|
# Campo para mostrar las opciones disponibles
|
||||||
selection_options_display = fields.Char(
|
selection_options_display = fields.Char(
|
||||||
string='Opciones disponibles',
|
string='Opciones disponibles',
|
||||||
|
@ -167,7 +173,7 @@ class LimsResult(models.Model):
|
||||||
else:
|
else:
|
||||||
record.display_name = record.parameter_name or _('Nuevo')
|
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):
|
def _compute_value_display(self):
|
||||||
"""Calcula el valor a mostrar según el tipo de dato."""
|
"""Calcula el valor a mostrar según el tipo de dato."""
|
||||||
for record in self:
|
for record in self:
|
||||||
|
@ -179,7 +185,8 @@ class LimsResult(models.Model):
|
||||||
elif record.parameter_value_type == 'text':
|
elif record.parameter_value_type == 'text':
|
||||||
record.value_display = record.value_text or ''
|
record.value_display = record.value_text or ''
|
||||||
elif record.parameter_value_type == 'selection':
|
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':
|
elif record.parameter_value_type == 'boolean':
|
||||||
record.value_display = 'Sí' if record.value_boolean else 'No'
|
record.value_display = 'Sí' if record.value_boolean else 'No'
|
||||||
else:
|
else:
|
||||||
|
@ -278,18 +285,20 @@ class LimsResult(models.Model):
|
||||||
_('Para parámetros de texto solo se debe ingresar el valor de texto.')
|
_('Para parámetros de texto solo se debe ingresar el valor de texto.')
|
||||||
)
|
)
|
||||||
elif value_type == 'selection':
|
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:
|
if (record.value_numeric not in [False, 0.0]) or record.value_text or record.value_boolean:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
_('Para parámetros de selección solo se debe elegir una opción.')
|
_('Para parámetros de selección solo se debe elegir una opción.')
|
||||||
)
|
)
|
||||||
# Validar que el valor seleccionado sea válido
|
# 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:
|
if has_value and record.parameter_id:
|
||||||
valid_options = record.parameter_id.get_selection_list()
|
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(
|
raise ValidationError(
|
||||||
_('El valor "%s" no es una opción válida. Opciones disponibles: %s') %
|
_('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':
|
elif value_type == 'boolean':
|
||||||
has_value = True # Boolean siempre tiene valor (True o False)
|
has_value = True # Boolean siempre tiene valor (True o False)
|
||||||
|
@ -312,6 +321,7 @@ class LimsResult(models.Model):
|
||||||
self.value_numeric = False
|
self.value_numeric = False
|
||||||
self.value_text = False
|
self.value_text = False
|
||||||
self.value_selection = False
|
self.value_selection = False
|
||||||
|
self.value_selection_field = False
|
||||||
self.value_boolean = False
|
self.value_boolean = False
|
||||||
|
|
||||||
# Si es selección, obtener las opciones
|
# 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
|
# Esto se usará en las vistas para mostrar las opciones dinámicamente
|
||||||
pass
|
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):
|
def _get_selection_options(self):
|
||||||
"""Retorna las opciones de selección basadas en el parámetro."""
|
"""Retorna las opciones de selección basadas en el parámetro."""
|
||||||
options = []
|
options = []
|
||||||
|
|
|
@ -65,9 +65,8 @@
|
||||||
decoration-warning="is_out_of_range and not is_critical"/>
|
decoration-warning="is_out_of_range and not is_critical"/>
|
||||||
<field name="value_text"
|
<field name="value_text"
|
||||||
invisible="parameter_value_type != 'text'"/>
|
invisible="parameter_value_type != 'text'"/>
|
||||||
<field name="value_selection"
|
<field name="value_selection_field"
|
||||||
invisible="parameter_value_type != 'selection'"
|
invisible="parameter_value_type != 'selection'"/>
|
||||||
widget="selection"/>
|
|
||||||
<field name="value_boolean"
|
<field name="value_boolean"
|
||||||
invisible="parameter_value_type != 'boolean'"
|
invisible="parameter_value_type != 'boolean'"
|
||||||
widget="boolean_toggle"/>
|
widget="boolean_toggle"/>
|
||||||
|
|
|
@ -34,9 +34,8 @@
|
||||||
decoration-warning="is_critical"/>
|
decoration-warning="is_critical"/>
|
||||||
<field name="value_text"
|
<field name="value_text"
|
||||||
invisible="parameter_value_type != 'text'"/>
|
invisible="parameter_value_type != 'text'"/>
|
||||||
<field name="value_selection"
|
<field name="value_selection_field"
|
||||||
invisible="parameter_value_type != 'selection'"
|
invisible="parameter_value_type != 'selection'"/>
|
||||||
widget="selection"/>
|
|
||||||
<field name="value_boolean"
|
<field name="value_boolean"
|
||||||
invisible="parameter_value_type != 'boolean'"
|
invisible="parameter_value_type != 'boolean'"
|
||||||
widget="boolean_toggle"/>
|
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