feat(#5): Implementar catálogo de análisis clínicos

- Se extiende el modelo product.template para incluir análisis.
- Se crea el modelo para rangos de referencia (lims.analysis.range).
- Se definen permisos de seguridad para el nuevo modelo.
- Se crean las vistas de formulario y lista necesarias.
- Se añade el menú 'Catálogo de Análisis' en Configuración.
- Se actualiza la guía de desarrollo en GEMINI.md con las nuevas convenciones de Odoo 18.
This commit is contained in:
Luis Ernesto Portillo Zaldivar 2025-07-13 23:45:32 -06:00
parent 6137a004a2
commit 0eaaaef98d
14 changed files with 242 additions and 32 deletions

View File

@ -140,3 +140,76 @@ Busca errores en la salida. Si encuentras alguno, debes presentar un resumen del
- **Errores de sintaxis:** Problemas en archivos Python (`.py`) o XML (`.views`, `.xml`).
- **Permisos incorrectos:** Problemas de acceso a archivos o directorios.
- **Datos incorrectos:** Errores en los archivos de datos de demostración o iniciales.
---
## Convenciones de Desarrollo en Odoo 18
Para evitar errores recurrentes, es **mandatorio** seguir las siguientes convenciones específicas para Odoo 18, especialmente en lo que respecta a la definición de vistas.
### Uso de Vistas de Lista (Tree Views)
En Odoo 18, la etiqueta `<tree>` ha sido **reemplazada por `<list>`**. El uso de `<tree>` provocará un error de validación (`ValueError: Wrong value for ir.ui.view.type: 'tree'`).
**Forma Incorrecta (Odoo < 18):**
```xml
<record id="view_example_tree" model="ir.ui.view">
<field name="arch" type="xml">
<tree string="Ejemplo">
<field name="name"/>
</tree>
</field>
</record>
```
**Forma Correcta (Odoo 18):**
```xml
<record id="view_example_list" model="ir.ui.view">
<field name="arch" type="xml">
<list string="Ejemplo">
<field name="name"/>
</list>
</field>
</record>
```
### Definición de Acciones de Ventana (`view_mode`)
Consecuente con el cambio anterior, al definir una acción de ventana (`ir.actions.act_window`) que deba mostrar una vista de lista, el `view_mode` debe ser `'list,form'` en lugar de `'tree,form'`.
**Forma Incorrecta:**
```xml
<record id="action_example" model="ir.actions.act_window">
<field name="name">Ejemplo</field>
<field name="res_model">example.model</field>
<field name="view_mode">tree,form</field>
</record>
```
**Forma Correcta:**
```xml
<record id="action_example" model="ir.actions.act_window">
<field name="name">Ejemplo</field>
<field name="res_model">example.model</field>
<field name="view_mode">list,form</field>
</record>
### Atributos de Visibilidad (`attrs`)
A partir de Odoo 17, el atributo `attrs` para controlar la visibilidad de los elementos ha sido **reemplazado por el uso directo de `invisible`**.
**Forma Incorrecta (Odoo < 17):**
```xml
<field name="special_field" attrs="{'invisible': [('is_special', '=', False)]}"/>
```
**Forma Correcta (Odoo 18):**
Se utiliza el atributo `invisible` con una expresión de dominio simplificada. La expresión se evalúa como verdadera para ocultar el campo.
```xml
<field name="special_field" invisible="not is_special"/>
```
O, de forma equivalente:
```xml
<field name="special_field" invisible="is_special == False"/>
```
```

View File

@ -2,39 +2,40 @@
## TODO
- [ ] **Extender el Modelo de Productos (`product.template`):**
- [ ] Crear `lims_management/models/product.py`.
- [ ] Heredar de `product.template`.
- [ ] Añadir campo booleano `is_analysis`.
- [ ] Añadir campo de selección `analysis_type`.
- [ ] Añadir campo de texto `technical_specifications`.
- [ ] Crear campo `value_range` (One2many) que enlace al nuevo modelo `lims.analysis.range`.
- [x] **Extender el Modelo de Productos (`product.template`):**
- [x] Crear `lims_management/models/product.py`.
- [x] Heredar de `product.template`.
- [x] Añadir campo booleano `is_analysis`.
- [x] Añadir campo de selección `analysis_type`.
- [x] Añadir campo de texto `technical_specifications`.
- [x] Crear campo `value_range` (One2many) que enlace al nuevo modelo `lims.analysis.range`.
- [ ] **Crear el Modelo para Rangos de Referencia (`lims.analysis.range`):**
- [ ] Crear `lims_management/models/analysis_range.py`.
- [ ] Definir campos: `analysis_id` (Many2one), `gender`, `age_min`, `age_max`, `min_value`, `max_value`, `unit_of_measure`.
- [x] **Crear el Modelo para Rangos de Referencia (`lims.analysis.range`):**
- [x] Crear `lims_management/models/analysis_range.py`.
- [x] Definir campos: `analysis_id` (Many2one), `gender`, `age_min`, `age_max`, `min_value`, `max_value`, `unit_of_measure`.
- [ ] **Definir Permisos de Seguridad:**
- [ ] Modificar `lims_management/security/ir.model.access.csv`.
- [ ] Añadir permisos para el modelo `lims.analysis.range`.
- [x] **Definir Permisos de Seguridad:**
- [x] Modificar `lims_management/security/ir.model.access.csv`.
- [x] Añadir permisos para el modelo `lims.analysis.range`.
- [ ] **Crear las Vistas para el Catálogo de Análisis:**
- [ ] Crear `lims_management/views/analysis_views.xml`.
- [ ] Crear vista de lista/Kanban para análisis clínicos.
- [ ] Heredar de la vista de formulario de productos para añadir la pestaña "Configuración de Análisis".
- [ ] Mostrar campos condicionalmente (`is_analysis = True`).
- [ ] Añadir tabla editable para `value_range`.
- [x] **Crear las Vistas para el Catálogo de Análisis:**
- [x] Crear `lims_management/views/analysis_views.xml`.
- [x] Crear vista de lista/Kanban para análisis clínicos.
- [x] Heredar de la vista de formulario de productos para añadir la pestaña "Configuración de Análisis".
- [x] Mostrar campos condicionalmente (`is_analysis = True`).
- [x] Crear una vista de árbol independiente para los rangos de referencia (`lims.analysis.range`).
- [x] En la vista de formulario del producto, referenciar la nueva vista de árbol para el campo `value_range_ids`.
- [ ] **Crear el Menú "Catálogo de Análisis":**
- [ ] Modificar `lims_management/views/menus.xml`.
- [ ] Crear una nueva acción de ventana (`ir.actions.act_window`).
- [ ] Crear un `menuitem` para "Catálogo de Análisis".
- [x] **Crear el Menú "Catálogo de Análisis":**
- [x] Modificar `lims_management/views/menus.xml`.
- [x] Crear una nueva acción de ventana (`ir.actions.act_window`).
- [x] Crear un `menuitem` para "Catálogo de Análisis".
- [ ] **Actualizar el Manifiesto (`__manifest__.py`):**
- [ ] Añadir los nuevos modelos al `__init__.py` de la carpeta `models`.
- [ ] Añadir el nuevo archivo de vistas a la lista `data` en `__manifest__.py`.
- [x] **Actualizar el Manifiesto (`__manifest__.py`):**
- [x] Añadir los nuevos modelos al `__init__.py` de la carpeta `models`.
- [x] Añadir el nuevo archivo de vistas a la lista `data` en `__manifest__.py`.
- [ ] **Verificación Final:**
- [ ] Reiniciar la instancia de Odoo (`docker-compose down -v` y `docker-compose up -d`).
- [ ] Revisar logs de `odoo_init`.
- [ ] Verificar la funcionalidad en la interfaz de Odoo.
- [x] **Verificación Final:**
- [x] Reiniciar la instancia de Odoo (`docker-compose down -v` y `docker-compose up -d`).
- [x] Revisar logs de `odoo_init`.
- [x] Verificar la funcionalidad en la interfaz de Odoo.

View File

@ -16,12 +16,13 @@
'website': "https://gitea.grupoconsiti.com/luis_portillo/clinical_laboratory",
'category': 'Industries',
'version': '18.0.1.0.0',
'depends': ['base'],
'depends': ['base', 'product'],
'data': [
'security/lims_security.xml',
'security/ir.model.access.csv',
'data/ir_sequence.xml',
'views/partner_views.xml',
'views/analysis_views.xml',
'views/menus.xml',
],
'demo': [

View File

@ -1,2 +1,4 @@
# -*- coding: utf-8 -*-
from . import partner
from . import partner
from . import product
from . import analysis_range

View File

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
from odoo import models, fields
class LimsAnalysisRange(models.Model):
_name = 'lims.analysis.range'
_description = 'Rangos de Referencia para Análisis Clínicos'
analysis_id = fields.Many2one(
'product.template',
string="Análisis",
required=True,
ondelete='cascade'
)
gender = fields.Selection([
('male', 'Masculino'),
('female', 'Femenino'),
('both', 'Ambos')
], string="Género", default='both')
age_min = fields.Integer(string="Edad Mínima", default=0)
age_max = fields.Integer(string="Edad Máxima", default=99)
min_value = fields.Float(string="Valor Mínimo")
max_value = fields.Float(string="Valor Máximo")
unit_of_measure = fields.Char(string="Unidad de Medida")

View File

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
from odoo import models, fields
class ProductTemplate(models.Model):
_inherit = 'product.template'
is_analysis = fields.Boolean(
string="Es un Análisis Clínico",
help="Marcar si este producto es un análisis clínico."
)
analysis_type = fields.Selection([
('hematology', 'Hematología'),
('chemistry', 'Química Clínica'),
('microbiology', 'Microbiología'),
('immunology', 'Inmunología'),
('endocrinology', 'Endocrinología'),
('other', 'Otro')
], string="Tipo de Análisis")
technical_specifications = fields.Text(
string="Especificaciones Técnicas"
)
value_range_ids = fields.One2many(
'lims.analysis.range',
'analysis_id',
string="Rangos de Referencia"
)

View File

@ -1 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_lims_analysis_range_user,lims.analysis.range.user,model_lims_analysis_range,base.group_user,1,1,1,1

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_lims_analysis_range_user lims.analysis.range.user model_lims_analysis_range base.group_user 1 1 1 1

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- Vista de Lista para Rangos de Referencia -->
<record id="view_lims_analysis_range_tree" model="ir.ui.view">
<field name="name">lims.analysis.range.tree</field>
<field name="model">lims.analysis.range</field>
<field name="arch" type="xml">
<list string="Rangos de Referencia" editable="bottom">
<field name="gender"/>
<field name="age_min"/>
<field name="age_max"/>
<field name="min_value"/>
<field name="max_value"/>
<field name="unit_of_measure"/>
</list>
</field>
</record>
<!-- Hereda la vista de formulario de producto para añadir la pestaña de Análisis -->
<record id="view_product_template_form_lims" model="ir.ui.view">
<field name="name">product.template.form.lims</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_form_view"/>
<field name="arch" type="xml">
<xpath expr="//notebook" position="inside">
<page string="Configuración de Análisis" name="analysis_config"
invisible="not is_analysis">
<group>
<group>
<field name="analysis_type"/>
</group>
<group>
<field name="technical_specifications"/>
</group>
</group>
<separator string="Rangos de Referencia"/>
<field name="value_range_ids"
view_id="lims_management.view_lims_analysis_range_tree"/>
</page>
</xpath>
<!-- Añade el campo is_analysis cerca del nombre del producto para fácil acceso -->
<xpath expr="//field[@name='name']" position="after">
<field name="is_analysis"/>
</xpath>
</field>
</record>
</data>
</odoo>

View File

@ -52,5 +52,34 @@
parent="lims_menu_root"
action="action_lims_doctor"
sequence="30"/>
<!-- Submenú de Configuración -->
<menuitem
id="lims_menu_config"
name="Configuración"
parent="lims_menu_root"
sequence="100"/>
<!-- Acción de Ventana para Catálogo de Análisis -->
<record id="action_lims_analysis_catalog" model="ir.actions.act_window">
<field name="name">Catálogo de Análisis</field>
<field name="res_model">product.template</field>
<field name="view_mode">list,form</field>
<field name="domain">[('is_analysis', '=', True)]</field>
<field name="context">{'default_is_analysis': True}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Crea un nuevo análisis clínico
</p>
</field>
</record>
<!-- Menú para Catálogo de Análisis -->
<menuitem
id="lims_menu_analysis_catalog"
name="Catálogo de Análisis"
parent="lims_menu_config"
action="action_lims_analysis_catalog"
sequence="10"/>
</data>
</odoo>