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:
parent
6137a004a2
commit
0eaaaef98d
73
GEMINI.md
73
GEMINI.md
|
@ -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`).
|
- **Errores de sintaxis:** Problemas en archivos Python (`.py`) o XML (`.views`, `.xml`).
|
||||||
- **Permisos incorrectos:** Problemas de acceso a archivos o directorios.
|
- **Permisos incorrectos:** Problemas de acceso a archivos o directorios.
|
||||||
- **Datos incorrectos:** Errores en los archivos de datos de demostración o iniciales.
|
- **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"/>
|
||||||
|
```
|
||||||
|
```
|
||||||
|
|
|
@ -2,39 +2,40 @@
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
- [ ] **Extender el Modelo de Productos (`product.template`):**
|
- [x] **Extender el Modelo de Productos (`product.template`):**
|
||||||
- [ ] Crear `lims_management/models/product.py`.
|
- [x] Crear `lims_management/models/product.py`.
|
||||||
- [ ] Heredar de `product.template`.
|
- [x] Heredar de `product.template`.
|
||||||
- [ ] Añadir campo booleano `is_analysis`.
|
- [x] Añadir campo booleano `is_analysis`.
|
||||||
- [ ] Añadir campo de selección `analysis_type`.
|
- [x] Añadir campo de selección `analysis_type`.
|
||||||
- [ ] Añadir campo de texto `technical_specifications`.
|
- [x] Añadir campo de texto `technical_specifications`.
|
||||||
- [ ] Crear campo `value_range` (One2many) que enlace al nuevo modelo `lims.analysis.range`.
|
- [x] Crear campo `value_range` (One2many) que enlace al nuevo modelo `lims.analysis.range`.
|
||||||
|
|
||||||
- [ ] **Crear el Modelo para Rangos de Referencia (`lims.analysis.range`):**
|
- [x] **Crear el Modelo para Rangos de Referencia (`lims.analysis.range`):**
|
||||||
- [ ] Crear `lims_management/models/analysis_range.py`.
|
- [x] 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] Definir campos: `analysis_id` (Many2one), `gender`, `age_min`, `age_max`, `min_value`, `max_value`, `unit_of_measure`.
|
||||||
|
|
||||||
- [ ] **Definir Permisos de Seguridad:**
|
- [x] **Definir Permisos de Seguridad:**
|
||||||
- [ ] Modificar `lims_management/security/ir.model.access.csv`.
|
- [x] Modificar `lims_management/security/ir.model.access.csv`.
|
||||||
- [ ] Añadir permisos para el modelo `lims.analysis.range`.
|
- [x] Añadir permisos para el modelo `lims.analysis.range`.
|
||||||
|
|
||||||
- [ ] **Crear las Vistas para el Catálogo de Análisis:**
|
- [x] **Crear las Vistas para el Catálogo de Análisis:**
|
||||||
- [ ] Crear `lims_management/views/analysis_views.xml`.
|
- [x] Crear `lims_management/views/analysis_views.xml`.
|
||||||
- [ ] Crear vista de lista/Kanban para análisis clínicos.
|
- [x] 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".
|
- [x] 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`).
|
- [x] Mostrar campos condicionalmente (`is_analysis = True`).
|
||||||
- [ ] Añadir tabla editable para `value_range`.
|
- [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":**
|
- [x] **Crear el Menú "Catálogo de Análisis":**
|
||||||
- [ ] Modificar `lims_management/views/menus.xml`.
|
- [x] Modificar `lims_management/views/menus.xml`.
|
||||||
- [ ] Crear una nueva acción de ventana (`ir.actions.act_window`).
|
- [x] Crear una nueva acción de ventana (`ir.actions.act_window`).
|
||||||
- [ ] Crear un `menuitem` para "Catálogo de Análisis".
|
- [x] Crear un `menuitem` para "Catálogo de Análisis".
|
||||||
|
|
||||||
- [ ] **Actualizar el Manifiesto (`__manifest__.py`):**
|
- [x] **Actualizar el Manifiesto (`__manifest__.py`):**
|
||||||
- [ ] Añadir los nuevos modelos al `__init__.py` de la carpeta `models`.
|
- [x] 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] Añadir el nuevo archivo de vistas a la lista `data` en `__manifest__.py`.
|
||||||
|
|
||||||
- [ ] **Verificación Final:**
|
- [x] **Verificación Final:**
|
||||||
- [ ] Reiniciar la instancia de Odoo (`docker-compose down -v` y `docker-compose up -d`).
|
- [x] Reiniciar la instancia de Odoo (`docker-compose down -v` y `docker-compose up -d`).
|
||||||
- [ ] Revisar logs de `odoo_init`.
|
- [x] Revisar logs de `odoo_init`.
|
||||||
- [ ] Verificar la funcionalidad en la interfaz de Odoo.
|
- [x] Verificar la funcionalidad en la interfaz de Odoo.
|
||||||
|
|
|
@ -16,12 +16,13 @@
|
||||||
'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.0',
|
||||||
'depends': ['base'],
|
'depends': ['base', 'product'],
|
||||||
'data': [
|
'data': [
|
||||||
'security/lims_security.xml',
|
'security/lims_security.xml',
|
||||||
'security/ir.model.access.csv',
|
'security/ir.model.access.csv',
|
||||||
'data/ir_sequence.xml',
|
'data/ir_sequence.xml',
|
||||||
'views/partner_views.xml',
|
'views/partner_views.xml',
|
||||||
|
'views/analysis_views.xml',
|
||||||
'views/menus.xml',
|
'views/menus.xml',
|
||||||
],
|
],
|
||||||
'demo': [
|
'demo': [
|
||||||
|
|
Binary file not shown.
|
@ -1,2 +1,4 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from . import partner
|
from . import partner
|
||||||
|
from . import product
|
||||||
|
from . import analysis_range
|
BIN
lims_management/models/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
lims_management/models/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
lims_management/models/__pycache__/partner.cpython-312.pyc
Normal file
BIN
lims_management/models/__pycache__/partner.cpython-312.pyc
Normal file
Binary file not shown.
BIN
lims_management/models/__pycache__/product.cpython-312.pyc
Normal file
BIN
lims_management/models/__pycache__/product.cpython-312.pyc
Normal file
Binary file not shown.
26
lims_management/models/analysis_range.py
Normal file
26
lims_management/models/analysis_range.py
Normal 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")
|
28
lims_management/models/product.py
Normal file
28
lims_management/models/product.py
Normal 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"
|
||||||
|
)
|
|
@ -1 +1,2 @@
|
||||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
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
|
||||||
|
|
|
49
lims_management/views/analysis_views.xml
Normal file
49
lims_management/views/analysis_views.xml
Normal 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>
|
|
@ -52,5 +52,34 @@
|
||||||
parent="lims_menu_root"
|
parent="lims_menu_root"
|
||||||
action="action_lims_doctor"
|
action="action_lims_doctor"
|
||||||
sequence="30"/>
|
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>
|
</data>
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user