feature/7-sample-management #33

Merged
luis_portillo merged 8 commits from feature/7-sample-management into dev 2025-07-14 18:13:20 +00:00
13 changed files with 252 additions and 4 deletions

View 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.

View File

@ -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)

View File

@ -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,

View 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>

View File

@ -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

View File

@ -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."
)

View 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
)

View File

@ -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

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
3 access_sale_order_receptionist sale.order.receptionist sale.model_sale_order group_lims_receptionist 1 1 1 0
4 access_stock_lot_user stock.lot.user stock.model_stock_lot base.group_user 1 1 1 1

View File

@ -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>

View 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>