Merge pull request 'feature/5-analysis-catalog' (#25) from feature/5-analysis-catalog into dev
Reviewed-on: luis_portillo/clinical_laboratory#25
This commit is contained in:
commit
0dbf546908
101
GEMINI.md
101
GEMINI.md
|
@ -140,3 +140,104 @@ 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.
|
||||
|
||||
### Política de Persistencia de la Instancia
|
||||
|
||||
Después de una instalación o actualización exitosa, la instancia de Odoo **debe permanecer activa** para permitir la validación manual por parte del usuario. **No se debe detener la instancia** (`docker-compose down -v`) hasta que el usuario confirme explícitamente que ha finalizado sus pruebas.
|
||||
|
||||
---
|
||||
|
||||
## 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"/>
|
||||
```
|
||||
|
||||
### Uso de `ref()` en el Contexto de Acciones de Ventana
|
||||
|
||||
La función `ref('module.xml_id')` se utiliza para obtener el ID de base de datos de un registro a partir de su ID XML. Sin embargo, esta función **solo existe en el servidor**.
|
||||
|
||||
Cuando se define el `context` de una acción de ventana (`ir.actions.act_window`), este se evalúa en el cliente (navegador), donde `ref()` no está definido, causando un error `Name 'ref' is not defined`.
|
||||
|
||||
Para solucionar esto, el `context` debe ser evaluado en el servidor utilizando el atributo `eval`.
|
||||
|
||||
**Forma Incorrecta:**
|
||||
```xml
|
||||
<field name="context">{
|
||||
'default_categ_id': ref('lims_management.product_category_analysis')
|
||||
}</field>
|
||||
```
|
||||
Esto envía la cadena `"{'default_categ_id': ref(...)}"` al cliente, que no puede procesarla.
|
||||
|
||||
**Forma Correcta:**
|
||||
```xml
|
||||
<field name="context" eval="{
|
||||
'default_categ_id': ref('lims_management.product_category_analysis')
|
||||
}"/>
|
||||
```
|
||||
Al usar `eval`, Odoo ejecuta la expresión en el servidor, reemplaza `ref(...)` por el ID numérico correspondiente, y envía un diccionario JSON válido al cliente.
|
||||
```
|
||||
|
|
84
documents/plans/ISSUE5_PLAN.md
Normal file
84
documents/plans/ISSUE5_PLAN.md
Normal file
|
@ -0,0 +1,84 @@
|
|||
# Plan de Actividades: Issue #5 - Catálogo de Análisis Clínicos
|
||||
|
||||
## TODO
|
||||
|
||||
- [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`.
|
||||
|
||||
- [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`.
|
||||
|
||||
- [x] **Definir Permisos de Seguridad:**
|
||||
|
||||
- [x] Modificar `lims_management/security/ir.model.access.csv`.
|
||||
- [x] Añadir permisos para el modelo `lims.analysis.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`.
|
||||
- [x] **(Nuevo)** Modificar las etiquetas en la acción de ventana para que se muestre "Análisis Clinico" en lugar de "Producto".
|
||||
|
||||
- [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] **(Nuevo)** Definir valores por defecto en el `context` de la acción para: `type`, `purchase_ok`, `categ_id`.
|
||||
- [x] **(Nuevo)** Crear la categoría de producto "Análisis Clínico" mediante un archivo de datos.
|
||||
- [x] Crear un `menuitem` para "Catálogo de Análisis".
|
||||
|
||||
- [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`.
|
||||
|
||||
- [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.
|
||||
|
||||
- [x] **(Nuevo) Crear Datos de Demostración:**
|
||||
- [x] Crear el archivo `demo/analysis_demo.xml`.
|
||||
- [x] Definir registros de ejemplo para análisis clínicos (Hemograma, Perfil Lipídico, etc.).
|
||||
- [x] Asegurarse de que los datos de demostración incluyan la configuración de los rangos de referencia.
|
||||
- [x] Añadir el archivo `analysis_demo.xml` a la clave `demo` en `__manifest__.py`.
|
||||
|
||||
---
|
||||
|
||||
## Consideraciones Adicionales para Valores por Defecto
|
||||
|
||||
### 1. Análisis y Justificación
|
||||
|
||||
Para mejorar la experiencia de usuario y asegurar la consistencia de los datos, se realizó un análisis para determinar qué campos del modelo `product.template` deberían tener valores por defecto al crear un nuevo "Análisis Clínico". Esta decisión se basa en el estudio del código fuente de Odoo 18 (`product.template.py`) y los documentos de requerimientos y diseño del proyecto.
|
||||
|
||||
- **Análisis del Modelo `product.template`:** La revisión del modelo base de productos en Odoo 18 revel<65><6C> campos clave que definen el comportamiento de un producto en el sistema, tales como `type`, `sale_ok`, `purchase_ok`, y `categ_id`. Estos campos son fundamentales para que un producto se integre correctamente con los módulos de Ventas, Compras e Inventario.
|
||||
|
||||
- **Relación con los Requerimientos y Diseño Técnico:**
|
||||
- El documento `RequerimientoInicial.md` especifica que los análisis clínicos deben ser considerados **servicios facturables**. Esto justifica la necesidad de configurar los análisis como productos de tipo "Servicio" (`type='service'`) que se puedan vender (`sale_ok=True`).
|
||||
- El documento `ToBeDesing.md` enfatiza la importancia de **categorizar** la información para mantener el sistema organizado y facilitar los filtros. Esto respalda la creación de una categoría de producto específica, "Análisis Clínico", para agrupar todos estos servicios y separarlos de otros productos que la empresa pueda manejar.
|
||||
- Ambos documentos dejan claro que el laboratorio presta estos servicios, pero no los compra. Por lo tanto, deshabilitar la opción de compra (`purchase_ok=False`) es coherente con el flujo de negocio, evitando que los análisis aparezcan en contextos de compra.
|
||||
|
||||
### 2. Propuesta de Valores por Defecto
|
||||
|
||||
Basado en el análisis anterior, se implementarán los siguientes valores por defecto al crear un nuevo análisis clínico desde su menú correspondiente:
|
||||
|
||||
| Campo en `product.template` | Valor por Defecto Recomendado | Justificación |
|
||||
| :------------------------------------- | :---------------------------- | :------------------------------------------------------------------------------------------------------ |
|
||||
| **`type` (Tipo de Producto)** | `'service'` (Servicio) | Un análisis es un servicio prestado, no un bien físico. Esto evita que Odoo intente gestionar su stock. |
|
||||
| **`purchase_ok` (Se puede Comprar)** | `False` (Falso) | El laboratorio vende análisis, no los compra. Esto limpia la interfaz en los flujos de compra. |
|
||||
| **`categ_id` (Categoría de Producto)** | `Análisis Clínico` | Permite agrupar, filtrar y aplicar reglas contables específicas a todos los análisis clínicos. |
|
||||
| **`sale_ok` (Se puede Vender)** | `True` (Verdadero) | Esencial para que los análisis puedan ser añadidos a las órdenes de laboratorio (órdenes de venta). |
|
||||
|
||||
Estos valores se configurarán en el `context` de la acción de ventana (`ir.actions.act_window`) que gestiona la creación de nuevos análisis, asegurando que cada nuevo registro se cree con la configuración correcta de forma automática.
|
|
@ -16,16 +16,19 @@
|
|||
'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',
|
||||
'data/product_category.xml',
|
||||
'views/partner_views.xml',
|
||||
'views/analysis_views.xml',
|
||||
'views/menus.xml',
|
||||
],
|
||||
'demo': [
|
||||
'data/lims_demo.xml',
|
||||
'demo/lims_demo.xml',
|
||||
'demo/analysis_demo.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'application': True,
|
||||
|
|
Binary file not shown.
8
lims_management/data/product_category.xml
Normal file
8
lims_management/data/product_category.xml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="product_category_analysis" model="product.category">
|
||||
<field name="name">Análisis Clínico</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
71
lims_management/demo/analysis_demo.xml
Normal file
71
lims_management/demo/analysis_demo.xml
Normal file
|
@ -0,0 +1,71 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<!-- Datos de Demostración para Análisis Clínicos -->
|
||||
|
||||
<!-- Análisis: Hemograma Completo -->
|
||||
<record id="analysis_hemograma" model="product.template">
|
||||
<field name="name">Hemograma Completo</field>
|
||||
<field name="is_analysis">True</field>
|
||||
<field name="analysis_type">hematology</field>
|
||||
<field name="categ_id" ref="lims_management.product_category_analysis"/>
|
||||
<field name="type">service</field>
|
||||
<field name="purchase_ok" eval="False"/>
|
||||
<field name="sale_ok" eval="True"/>
|
||||
<field name="technical_specifications">
|
||||
El hemograma completo es un análisis de sangre que mide los niveles de los principales componentes sanguíneos: glóbulos rojos, glóbulos blancos y plaquetas.
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Rangos de Referencia para Hemograma -->
|
||||
<record id="range_hemograma_globulos_rojos_m" model="lims.analysis.range">
|
||||
<field name="analysis_id" ref="analysis_hemograma"/>
|
||||
<field name="gender">male</field>
|
||||
<field name="age_min">18</field>
|
||||
<field name="age_max">99</field>
|
||||
<field name="min_value">4.5</field>
|
||||
<field name="max_value">5.9</field>
|
||||
<field name="unit_of_measure">millones/µL</field>
|
||||
</record>
|
||||
<record id="range_hemograma_globulos_rojos_f" model="lims.analysis.range">
|
||||
<field name="analysis_id" ref="analysis_hemograma"/>
|
||||
<field name="gender">female</field>
|
||||
<field name="age_min">18</field>
|
||||
<field name="age_max">99</field>
|
||||
<field name="min_value">4.0</field>
|
||||
<field name="max_value">5.2</field>
|
||||
<field name="unit_of_measure">millones/µL</field>
|
||||
</record>
|
||||
|
||||
<!-- Análisis: Perfil Lipídico -->
|
||||
<record id="analysis_perfil_lipidico" model="product.template">
|
||||
<field name="name">Perfil Lipídico</field>
|
||||
<field name="is_analysis">True</field>
|
||||
<field name="analysis_type">chemistry</field>
|
||||
<field name="categ_id" ref="lims_management.product_category_analysis"/>
|
||||
<field name="type">service</field>
|
||||
<field name="purchase_ok" eval="False"/>
|
||||
<field name="sale_ok" eval="True"/>
|
||||
<field name="technical_specifications">
|
||||
Mide los niveles de colesterol y otros lípidos en la sangre. Incluye Colesterol Total, LDL, HDL y Triglicéridos.
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Rangos para Colesterol Total -->
|
||||
<record id="range_colesterol_total" model="lims.analysis.range">
|
||||
<field name="analysis_id" ref="analysis_perfil_lipidico"/>
|
||||
<field name="min_value">0</field>
|
||||
<field name="max_value">200</field>
|
||||
<field name="unit_of_measure">mg/dL</field>
|
||||
</record>
|
||||
|
||||
<!-- Rangos para Colesterol LDL -->
|
||||
<record id="range_colesterol_ldl" model="lims.analysis.range">
|
||||
<field name="analysis_id" ref="analysis_perfil_lipidico"/>
|
||||
<field name="min_value">0</field>
|
||||
<field name="max_value">100</field>
|
||||
<field name="unit_of_measure">mg/dL</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
|
@ -1,2 +1,4 @@
|
|||
# -*- 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
|
||||
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,39 @@
|
|||
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">Análisis Clínicos</field>
|
||||
<field name="res_model">product.template</field>
|
||||
<field name="view_mode">kanban,form</field>
|
||||
<field name="domain">[('is_analysis', '=', True)]</field>
|
||||
<field name="context" eval="{
|
||||
'default_is_analysis': True,
|
||||
'default_type': 'service',
|
||||
'default_purchase_ok': False,
|
||||
'default_categ_id': ref('lims_management.product_category_analysis')
|
||||
}"/>
|
||||
<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="Análisis Clínicos"
|
||||
parent="lims_menu_config"
|
||||
action="action_lims_analysis_catalog"
|
||||
sequence="10"/>
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
Loading…
Reference in New Issue
Block a user