Merge pull request 'feature/7-sample-management' (#33) from feature/7-sample-management into dev
Reviewed-on: luis_portillo/clinical_laboratory#33
This commit is contained in:
commit
f2a8b541d8
57
documents/plans/ISSUE7_PLAN.md
Normal file
57
documents/plans/ISSUE7_PLAN.md
Normal file
|
@ -0,0 +1,57 @@
|
|||
# Plan de Actividades: Issue #7 - Gestión de Muestras de Laboratorio
|
||||
|
||||
## Objetivo
|
||||
|
||||
Extender el modelo de Lotes/Números de Serie de Odoo (`stock.lot`) para representar y gestionar las **Muestras de Laboratorio**. Esto permitirá la trazabilidad completa de la muestra desde su recolección hasta el análisis.
|
||||
|
||||
## TODO
|
||||
|
||||
- [x] **Extender el Modelo de Lote/Número de Serie (`stock.lot`):**
|
||||
- [x] Crear el archivo `lims_management/models/stock_lot.py`.
|
||||
- [x] Heredar del modelo `stock.lot`.
|
||||
- [x] Añadir campos: `is_lab_sample`, `patient_id`, `request_id`, `collection_date`, `container_type`.
|
||||
- [ ] **(Nuevo)** Añadir campo `collector_id` (Many2one a `res.users`) para registrar quién tomó la muestra.
|
||||
|
||||
- [x] **Adaptar las Vistas de Lote/Número de Serie:**
|
||||
- [x] Crear el archivo `lims_management/views/stock_lot_views.xml`.
|
||||
- [x] Crear vistas de lista y formulario para las muestras.
|
||||
- [x] Crear un producto de servicio por defecto para las muestras.
|
||||
- [ ] **(Nuevo)** Añadir el campo `collector_id` a las vistas de lista y formulario.
|
||||
|
||||
- [x] **Crear el Menú "Gestión de Muestras":**
|
||||
- [x] Modificar `lims_management/views/menus.xml`.
|
||||
- [x] Crear acción de ventana y `menuitem` para `stock.lot` con el dominio y contexto adecuados.
|
||||
|
||||
- [x] **Establecer Permisos y Reglas de Dominio:**
|
||||
- [x] Modificar `lims_management/security/ir.model.access.csv` para dar permisos sobre `stock.lot`.
|
||||
- [x] Añadir dominios en las vistas para los campos relacionales.
|
||||
|
||||
- [x] **Actualizar el Manifiesto (`__manifest__.py`):**
|
||||
- [x] Añadir nuevos archivos de modelos, vistas y datos al manifiesto.
|
||||
|
||||
- [x] **Verificación Final:**
|
||||
- [x] Reiniciar y verificar la instancia de Odoo.
|
||||
|
||||
- [x] **Mejorar Modelo de Productos para Tipos de Muestra:**
|
||||
- [x] Añadir un campo booleano `is_sample_type` al modelo `product.template`.
|
||||
|
||||
- [x] **Crear Menú para "Tipos de Muestra":**
|
||||
- [x] Añadir acción de ventana y `menuitem` para los tipos de muestra.
|
||||
|
||||
- [x] **Actualizar Vista de Muestras (`stock.lot`):**
|
||||
- [x] Hacer visible y aplicar dominio al campo `product_id` (Tipo de Muestra).
|
||||
- [x] Eliminar el producto genérico y su referencia en el contexto.
|
||||
|
||||
- [x] **Crear Datos de Demostración:**
|
||||
- [x] Crear archivo `demo/z_sample_demo.xml` con tipos de muestra y muestras de ejemplo.
|
||||
- [x] Añadir el archivo de demostración al manifiesto.
|
||||
- [ ] **(Nuevo)** Actualizar los datos de demostración para incluir el `collector_id`.
|
||||
|
||||
- [x] **Verificación Final (con Demo):**
|
||||
- [x] Validar la funcionalidad completa con los datos de demostración.
|
||||
|
||||
---
|
||||
## Consideraciones Futuras (Siguientes Issues)
|
||||
|
||||
- **Ciclo de Vida de la Muestra:** Implementar un campo de estado (`state`) con su lógica de transiciones (ej. 'Recolectada' -> 'Recibida' -> 'En Proceso' -> 'Completada' -> 'Almacenada').
|
||||
- **Informes de Muestras:** Crear informes en PDF o vistas dinámicas sobre el estado y trazabilidad de las muestras.
|
|
@ -104,4 +104,4 @@ except FileNotFoundError:
|
|||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"Ocurrió un error inesperado al ejecutar Odoo: {e}")
|
||||
sys.exit(1)
|
||||
sys.exit(1)
|
|
@ -25,11 +25,13 @@
|
|||
'views/partner_views.xml',
|
||||
'views/analysis_views.xml',
|
||||
'views/sale_order_views.xml',
|
||||
'views/stock_lot_views.xml',
|
||||
'views/menus.xml',
|
||||
],
|
||||
'demo': [
|
||||
'demo/z_lims_demo.xml',
|
||||
'demo/z_analysis_demo.xml',
|
||||
'demo/z_sample_demo.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'application': True,
|
||||
|
|
41
lims_management/demo/z_sample_demo.xml
Normal file
41
lims_management/demo/z_sample_demo.xml
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<!-- Tipos de Muestra (Productos) -->
|
||||
<record id="sample_type_serum" model="product.template">
|
||||
<field name="name">Tubo de Suero (Tapa Roja)</field>
|
||||
<field name="is_sample_type" eval="True"/>
|
||||
<field name="type">service</field>
|
||||
</record>
|
||||
<record id="sample_type_edta" model="product.template">
|
||||
<field name="name">Tubo EDTA (Tapa Morada)</field>
|
||||
<field name="is_sample_type" eval="True"/>
|
||||
<field name="type">service</field>
|
||||
</record>
|
||||
<record id="sample_type_urine" model="product.template">
|
||||
<field name="name">Contenedor de Orina</field>
|
||||
<field name="is_sample_type" eval="True"/>
|
||||
<field name="type">service</field>
|
||||
</record>
|
||||
|
||||
<!-- Muestras de Laboratorio (Lotes) -->
|
||||
<record id="lab_sample_01" model="stock.lot">
|
||||
<field name="name">SAM-2025-00001</field>
|
||||
<field name="product_id" model="product.product" eval="obj().env.ref('lims_management.sample_type_serum').product_variant_id.id"/>
|
||||
<field name="is_lab_sample" eval="True"/>
|
||||
<field name="patient_id" ref="lims_management.demo_patient_1"/>
|
||||
<field name="collector_id" ref="base.user_admin"/>
|
||||
<field name="collection_date" eval="(DateTime.now() - timedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="container_type">serum_tube</field>
|
||||
</record>
|
||||
<record id="lab_sample_02" model="stock.lot">
|
||||
<field name="name">SAM-2025-00002</field>
|
||||
<field name="product_id" model="product.product" eval="obj().env.ref('lims_management.sample_type_edta').product_variant_id.id"/>
|
||||
<field name="is_lab_sample" eval="True"/>
|
||||
<field name="patient_id" ref="lims_management.demo_patient_2"/>
|
||||
<field name="collector_id" ref="base.user_admin"/>
|
||||
<field name="collection_date" eval="(DateTime.now() - timedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="container_type">edta_tube</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
|
@ -1,5 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from . import partner
|
||||
from . import product
|
||||
from . import analysis_range
|
||||
from . import sale_order
|
||||
from . import product
|
||||
from . import partner
|
||||
from . import sale_order
|
||||
from . import stock_lot
|
||||
|
|
Binary file not shown.
Binary file not shown.
BIN
lims_management/models/__pycache__/stock_lot.cpython-312.pyc
Normal file
BIN
lims_management/models/__pycache__/stock_lot.cpython-312.pyc
Normal file
Binary file not shown.
|
@ -26,3 +26,8 @@ class ProductTemplate(models.Model):
|
|||
'analysis_id',
|
||||
string="Rangos de Referencia"
|
||||
)
|
||||
|
||||
is_sample_type = fields.Boolean(
|
||||
string="Is a Sample Type",
|
||||
help="Check if this product represents a type of laboratory sample container."
|
||||
)
|
||||
|
|
35
lims_management/models/stock_lot.py
Normal file
35
lims_management/models/stock_lot.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from odoo import models, fields
|
||||
|
||||
class StockLot(models.Model):
|
||||
_inherit = 'stock.lot'
|
||||
|
||||
is_lab_sample = fields.Boolean(string='Is a Laboratory Sample')
|
||||
|
||||
patient_id = fields.Many2one(
|
||||
'res.partner',
|
||||
string='Patient',
|
||||
domain="[('is_patient', '=', True)]"
|
||||
)
|
||||
|
||||
request_id = fields.Many2one(
|
||||
'sale.order',
|
||||
string='Lab Request',
|
||||
domain="[('is_lab_request', '=', True)]"
|
||||
)
|
||||
|
||||
collection_date = fields.Datetime(string='Collection Date')
|
||||
|
||||
container_type = fields.Selection([
|
||||
('serum_tube', 'Serum Tube'),
|
||||
('edta_tube', 'EDTA Tube'),
|
||||
('swab', 'Swab'),
|
||||
('urine', 'Urine Container'),
|
||||
('other', 'Other')
|
||||
], string='Container Type')
|
||||
|
||||
collector_id = fields.Many2one(
|
||||
'res.users',
|
||||
string='Collected by',
|
||||
default=lambda self: self.env.user
|
||||
)
|
|
@ -1,3 +1,4 @@
|
|||
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
|
||||
access_sale_order_receptionist,sale.order.receptionist,sale.model_sale_order,group_lims_receptionist,1,1,1,0
|
||||
access_stock_lot_user,stock.lot.user,stock.model_stock_lot,base.group_user,1,1,1,1
|
||||
|
|
|
|
@ -75,6 +75,33 @@
|
|||
action="action_lims_lab_request"
|
||||
sequence="15"/>
|
||||
|
||||
<!-- Acción de Ventana para Muestras de Laboratorio -->
|
||||
<record id="action_lims_lab_sample" model="ir.actions.act_window">
|
||||
<field name="name">Muestras de Laboratorio</field>
|
||||
<field name="res_model">stock.lot</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="view_ids" eval="[(5, 0, 0),
|
||||
(0, 0, {'view_mode': 'list', 'view_id': ref('view_lab_sample_list')}),
|
||||
(0, 0, {'view_mode': 'form', 'view_id': ref('view_lab_sample_form')})]"/>
|
||||
<field name="domain">[('is_lab_sample', '=', True)]</field>
|
||||
<field name="context" eval="{
|
||||
'default_is_lab_sample': True
|
||||
}"/>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Crea una nueva muestra de laboratorio
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Menú para Muestras de Laboratorio -->
|
||||
<menuitem
|
||||
id="lims_menu_lab_samples"
|
||||
name="Muestras"
|
||||
parent="lims_menu_root"
|
||||
action="action_lims_lab_sample"
|
||||
sequence="16"/>
|
||||
|
||||
<!-- Submenú de Configuración -->
|
||||
<menuitem
|
||||
id="lims_menu_config"
|
||||
|
@ -108,5 +135,30 @@
|
|||
parent="lims_menu_config"
|
||||
action="action_lims_analysis_catalog"
|
||||
sequence="10"/>
|
||||
|
||||
<!-- Acción de Ventana para Tipos de Muestra -->
|
||||
<record id="action_lims_sample_type_catalog" model="ir.actions.act_window">
|
||||
<field name="name">Tipos de Muestra</field>
|
||||
<field name="res_model">product.template</field>
|
||||
<field name="view_mode">kanban,form</field>
|
||||
<field name="domain">[('is_sample_type', '=', True)]</field>
|
||||
<field name="context" eval="{
|
||||
'default_is_sample_type': True,
|
||||
'default_type': 'service'
|
||||
}"/>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Crea un nuevo tipo de muestra
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Menú para Tipos de Muestra -->
|
||||
<menuitem
|
||||
id="lims_menu_sample_type_catalog"
|
||||
name="Tipos de Muestra"
|
||||
parent="lims_menu_config"
|
||||
action="action_lims_sample_type_catalog"
|
||||
sequence="20"/>
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
54
lims_management/views/stock_lot_views.xml
Normal file
54
lims_management/views/stock_lot_views.xml
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<!-- Vista de Lista para Muestras de Laboratorio -->
|
||||
<record id="view_lab_sample_list" model="ir.ui.view">
|
||||
<field name="name">lab.sample.list</field>
|
||||
<field name="model">stock.lot</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Lab Samples">
|
||||
<field name="name"/>
|
||||
<field name="patient_id"/>
|
||||
<field name="product_id" string="Sample Type"/>
|
||||
<field name="collection_date"/>
|
||||
<field name="collector_id"/>
|
||||
<field name="container_type"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Vista de Formulario para Muestras de Laboratorio -->
|
||||
<record id="view_lab_sample_form" model="ir.ui.view">
|
||||
<field name="name">lab.sample.form</field>
|
||||
<field name="model">stock.lot</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Lab Sample">
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<h1>
|
||||
<field name="name"/>
|
||||
</h1>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="patient_id"/>
|
||||
<field name="request_id"/>
|
||||
<field name="product_id"
|
||||
string="Sample Type"
|
||||
domain="[('is_sample_type', '=', True)]"
|
||||
options="{'no_create': True, 'no_create_edit': True}"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="collection_date"/>
|
||||
<field name="collector_id"/>
|
||||
<field name="container_type"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
Loading…
Reference in New Issue
Block a user