Merge pull request 'feat(#71): Implementar dashboards para administrador del laboratorio' (#74) from feature/71-laboratory-dashboards into dev

Reviewed-on: #74
This commit is contained in:
luis_portillo 2025-07-21 22:53:31 +00:00
commit 73e3014036
9 changed files with 927 additions and 273 deletions

View File

@ -1,24 +0,0 @@
## Descripción
Actualmente, cuando se cancela una orden de laboratorio, las muestras asociadas permanecen activas y no se descartan automáticamente. Esto puede causar confusión ya que quedan muestras "huérfanas" en el sistema que ya no tienen una orden válida.
## Comportamiento esperado
Cuando se cancela una orden de laboratorio:
1. Todas las muestras generadas asociadas a esa orden deben cambiar automáticamente su estado a "cancelled"
2. Si hay pruebas (lims.test) asociadas a esas muestras, también deben cancelarse
3. Se debe registrar en el chatter de la muestra que fue cancelada debido a la cancelación de la orden
## Criterios de aceptación
- [ ] Al cancelar una orden de laboratorio, todas sus muestras asociadas se marcan como canceladas
- [ ] Las pruebas asociadas a las muestras también se cancelan
- [ ] Se registra un mensaje en el chatter de cada muestra indicando la razón de cancelación
- [ ] Si una muestra ya estaba cancelada o completada, no se modifica
- [ ] La acción es reversible: si se vuelve a poner la orden en borrador, las muestras NO deben reactivarse automáticamente
## Notas técnicas
- El método a modificar es `action_cancel()` en el modelo `sale.order`
- Verificar el campo `generated_sample_ids` para obtener las muestras asociadas
- Solo cancelar muestras que estén en estados: 'pending_collection', 'collected', 'in_analysis'

View File

@ -1,38 +0,0 @@
**Contexto:**
Para poder implementar la automatización de generación de muestras (Issue #32), es necesario establecer una relación entre los productos tipo análisis y los tipos de muestra que requieren.
**Problema Actual:**
- Los productos tipo test (is_analysis=True) no tienen campo que indique qué tipo de muestra requieren
- Los productos tipo muestra (is_sample_type=True) no están relacionados con los tests
- El modelo stock.lot tiene container_type como Selection hardcodeado, no como relación
**Tareas Requeridas:**
1. **Modificar product.template:**
- Agregar campo Many2one 'required_sample_type_id' que relacione análisis con tipo de muestra
- Domain: [('is_sample_type', '=', True)]
2. **Actualizar stock.lot:**
- Opción A: Cambiar container_type de Selection a Many2one hacia product.template
- Opción B: Agregar nuevo campo sample_type_product_id
- Mantener compatibilidad con datos existentes
3. **Actualizar vistas:**
- Agregar campo en formulario de productos cuando is_analysis=True
- Mostrar tipo de muestra requerida en vistas de análisis
4. **Migración de datos:**
- Mapear valores actuales de container_type a productos tipo muestra
- Actualizar registros existentes
5. **Actualizar demo data:**
- Asignar tipos de muestra correctos a cada análisis
- Ejemplo: Hemograma → Tubo EDTA, Glucosa → Tubo Suero
**Beneficios:**
- Permitirá automatizar la generación de muestras al confirmar órdenes
- Evitará errores al saber exactamente qué contenedor usar para cada test
- Facilitará la agrupación de análisis que usan el mismo tipo de muestra
**Dependencia:**
Este issue es prerequisito para poder implementar el Issue #32

View File

@ -11,5 +11,14 @@
<field name="company_id" eval="False"/>
</record>
<!-- Secuencia para muestras de laboratorio -->
<record id="seq_stock_lot_serial" model="ir.sequence">
<field name="name">Secuencia de Muestras de Laboratorio</field>
<field name="code">stock.lot.serial</field>
<field name="prefix">M-%(year)s%(month)s%(day)s-</field>
<field name="padding">6</field>
<field name="company_id" eval="False"/>
</record>
</data>
</odoo>

View File

@ -10,8 +10,9 @@
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1985-05-15</field>
<field name="gender">female</field>
<field name="phone">+1-202-555-0174</field>
<field name="phone">+503 7234-5678</field>
<field name="email">ana.torres@example.com</field>
<field name="vat">03245678-9</field>
</record>
<record id="demo_patient_2" model="res.partner">
@ -21,8 +22,9 @@
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1992-11-20</field>
<field name="gender">male</field>
<field name="phone">+1-202-555-0192</field>
<field name="phone">+503 7892-3456</field>
<field name="email">carlos.ruiz@example.com</field>
<field name="vat">04567890-1</field>
</record>
<record id="demo_patient_3" model="res.partner">
@ -32,8 +34,9 @@
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1978-03-10</field>
<field name="gender">female</field>
<field name="phone">+1-202-555-0201</field>
<field name="phone">+503 7345-6789</field>
<field name="email">maria.gonzalez@example.com</field>
<field name="vat">01234567-8</field>
</record>
<!-- Datos de Demostración para Médicos -->
@ -41,7 +44,7 @@
<field name="name">Dr. Luis Herrera</field>
<field name="is_doctor" eval="True"/>
<field name="doctor_license">L-98765</field>
<field name="phone">+1-202-555-0145</field>
<field name="phone">+503 2234-5678</field>
<field name="email">luis.herrera@hospital.com</field>
</record>
@ -49,14 +52,14 @@
<field name="name">Dra. Sofia Vargas</field>
<field name="is_doctor" eval="True"/>
<field name="doctor_license">L-54321</field>
<field name="phone">+1-202-555-0133</field>
<field name="phone">+503 2345-6789</field>
<field name="email">sofia.vargas@clinic.com</field>
</record>
<!-- Datos de Demostración para Tutor y Paciente Menor de Edad -->
<record id="demo_tutor_1" model="res.partner">
<field name="name">Laura Mendoza</field>
<field name="phone">+1-202-555-0188</field>
<field name="phone">+503 7456-7890</field>
<field name="email">laura.mendoza@example.com</field>
</record>
@ -70,5 +73,562 @@
<field name="parent_id" ref="demo_tutor_1"/>
</record>
<!-- Pacientes adicionales - Niños (0-12 años) -->
<record id="demo_patient_4" model="res.partner">
<field name="name">Sofía Jiménez</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-S45F04</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date" eval="(datetime.now() - relativedelta(years=8)).strftime('%Y-%m-%d')"/>
<field name="gender">female</field>
<field name="phone">+503 7567-8901</field>
</record>
<record id="demo_patient_5" model="res.partner">
<field name="name">Diego Morales</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-D78M05</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date" eval="(datetime.now() - relativedelta(years=3)).strftime('%Y-%m-%d')"/>
<field name="gender">male</field>
<field name="phone">+503 7678-9012</field>
</record>
<record id="demo_patient_6" model="res.partner">
<field name="name">Valentina Castro</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-V23F06</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date" eval="(datetime.now() - relativedelta(years=10)).strftime('%Y-%m-%d')"/>
<field name="gender">female</field>
<field name="phone">+503 7789-0123</field>
</record>
<!-- Adolescentes (13-17 años) -->
<record id="demo_patient_7" model="res.partner">
<field name="name">Santiago Pérez</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-S90M07</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date" eval="(datetime.now() - relativedelta(years=15)).strftime('%Y-%m-%d')"/>
<field name="gender">male</field>
<field name="phone">+503 7890-1234</field>
</record>
<record id="demo_patient_8" model="res.partner">
<field name="name">Isabella Rodríguez</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-I34F08</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date" eval="(datetime.now() - relativedelta(years=16)).strftime('%Y-%m-%d')"/>
<field name="gender">female</field>
<field name="phone">+503 7901-2345</field>
</record>
<!-- Adultos jóvenes (18-35 años) - Incluye embarazadas -->
<record id="demo_patient_9" model="res.partner">
<field name="name">Camila Fernández</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-C67F09</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1995-07-22</field>
<field name="gender">female</field>
<field name="is_pregnant" eval="True"/>
<field name="phone">+503 7012-3456</field>
<field name="email">camila.fernandez@example.com</field>
<field name="vat">05678901-2</field>
</record>
<record id="demo_patient_10" model="res.partner">
<field name="name">Alejandro Gutiérrez</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-A12M10</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1990-02-14</field>
<field name="gender">male</field>
<field name="phone">+503 7123-4567</field>
<field name="email">alejandro.gutierrez@example.com</field>
<field name="vat">06789012-3</field>
</record>
<record id="demo_patient_11" model="res.partner">
<field name="name">Lucía Mendoza</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-L89F11</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1992-09-30</field>
<field name="gender">female</field>
<field name="is_pregnant" eval="True"/>
<field name="phone">+503 7234-5678</field>
<field name="vat">07890123-4</field>
</record>
<record id="demo_patient_12" model="res.partner">
<field name="name">Miguel Ángel Silva</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-M45M12</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1988-11-05</field>
<field name="gender">male</field>
<field name="phone">+503 7345-6789</field>
<field name="vat">08901234-5</field>
</record>
<record id="demo_patient_13" model="res.partner">
<field name="name">Natalia Vargas</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-N78F13</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1996-04-18</field>
<field name="gender">female</field>
<field name="phone">+503 7456-7890</field>
<field name="vat">09012345-6</field>
</record>
<!-- Adultos (36-55 años) -->
<record id="demo_patient_14" model="res.partner">
<field name="name">Roberto Martínez</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-R23M14</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1975-06-12</field>
<field name="gender">male</field>
<field name="phone">+503 7567-8901</field>
<field name="vat">00123456-7</field>
</record>
<record id="demo_patient_15" model="res.partner">
<field name="name">Patricia López</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-P56F15</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1972-12-25</field>
<field name="gender">female</field>
<field name="phone">+503 7678-9012</field>
<field name="vat">01234567-8</field>
</record>
<record id="demo_patient_16" model="res.partner">
<field name="name">Fernando Díaz</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-F90M16</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1980-03-08</field>
<field name="gender">male</field>
<field name="phone">+503 7789-0123</field>
<field name="vat">02345678-9</field>
</record>
<record id="demo_patient_17" model="res.partner">
<field name="name">Andrea Herrera</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-A34F17</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1978-08-17</field>
<field name="gender">female</field>
<field name="is_pregnant" eval="True"/>
<field name="phone">+503 7890-1234</field>
<field name="vat">03456789-0</field>
</record>
<!-- Adultos mayores (56-75 años) -->
<record id="demo_patient_18" model="res.partner">
<field name="name">José Luis Ramírez</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-J67M18</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1965-01-20</field>
<field name="gender">male</field>
<field name="phone">+503 7901-2345</field>
<field name="vat">04567890-1</field>
</record>
<record id="demo_patient_19" model="res.partner">
<field name="name">Carmen Sánchez</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-C12F19</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1958-10-15</field>
<field name="gender">female</field>
<field name="phone">+503 7012-3456</field>
<field name="vat">05678901-2</field>
</record>
<record id="demo_patient_20" model="res.partner">
<field name="name">Ricardo Flores</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-R89M20</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1960-05-28</field>
<field name="gender">male</field>
<field name="phone">+503 7123-4567</field>
<field name="vat">06789012-3</field>
</record>
<!-- Ancianos (76+ años) -->
<record id="demo_patient_21" model="res.partner">
<field name="name">Esperanza Romero</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-E45F21</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1945-12-03</field>
<field name="gender">female</field>
<field name="phone">+503 7234-5678</field>
<field name="vat">07890123-4</field>
</record>
<record id="demo_patient_22" model="res.partner">
<field name="name">Francisco Aguilar</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-F78M22</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1943-07-19</field>
<field name="gender">male</field>
<field name="phone">+503 7345-6789</field>
<field name="vat">08901234-5</field>
</record>
<!-- Más pacientes diversos -->
<record id="demo_patient_23" model="res.partner">
<field name="name">Daniela Cortés</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-D23F23</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1998-02-11</field>
<field name="gender">female</field>
<field name="phone">+503 7456-7890</field>
<field name="vat">09012345-6</field>
</record>
<record id="demo_patient_24" model="res.partner">
<field name="name">Gabriel Moreno</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-G56M24</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date" eval="(datetime.now() - relativedelta(years=6)).strftime('%Y-%m-%d')"/>
<field name="gender">male</field>
<field name="phone">+503 7567-8901</field>
</record>
<record id="demo_patient_25" model="res.partner">
<field name="name">Valeria Ruiz</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-V90F25</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1987-09-24</field>
<field name="gender">female</field>
<field name="is_pregnant" eval="True"/>
<field name="phone">+503 7678-9012</field>
<field name="vat">00123456-7</field>
</record>
<record id="demo_patient_26" model="res.partner">
<field name="name">Eduardo Navarro</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-E34M26</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1970-11-30</field>
<field name="gender">male</field>
<field name="phone">+503 7789-0123</field>
<field name="vat">01234568-8</field>
</record>
<record id="demo_patient_27" model="res.partner">
<field name="name">Mariana Delgado</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-M67F27</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1999-06-07</field>
<field name="gender">female</field>
<field name="phone">+503 7890-1234</field>
<field name="vat">02345679-9</field>
</record>
<record id="demo_patient_28" model="res.partner">
<field name="name">Andrés Jiménez</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-A12M28</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1955-08-21</field>
<field name="gender">male</field>
<field name="phone">+503 7901-2345</field>
<field name="vat">03456780-0</field>
</record>
<record id="demo_patient_29" model="res.partner">
<field name="name">Paola Méndez</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-P89F29</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1991-03-16</field>
<field name="gender">female</field>
<field name="is_pregnant" eval="True"/>
<field name="phone">+503 7012-3456</field>
<field name="vat">04567891-1</field>
</record>
<record id="demo_patient_30" model="res.partner">
<field name="name">Sebastián Vega</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-S45M30</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date" eval="(datetime.now() - relativedelta(years=14)).strftime('%Y-%m-%d')"/>
<field name="gender">male</field>
<field name="phone">+503 7123-4567</field>
</record>
<record id="demo_patient_31" model="res.partner">
<field name="name">Claudia Paredes</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-C78F31</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1982-10-09</field>
<field name="gender">female</field>
<field name="phone">+503 7234-5678</field>
<field name="vat">05678902-2</field>
</record>
<record id="demo_patient_32" model="res.partner">
<field name="name">Raúl Castro</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-R23M32</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1948-04-27</field>
<field name="gender">male</field>
<field name="phone">+503 7345-6789</field>
<field name="vat">06789013-3</field>
</record>
<record id="demo_patient_33" model="res.partner">
<field name="name">Adriana Guerrero</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-A56F33</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1994-12-13</field>
<field name="gender">female</field>
<field name="is_pregnant" eval="True"/>
<field name="phone">+503 7456-7890</field>
<field name="vat">07890124-4</field>
</record>
<record id="demo_patient_34" model="res.partner">
<field name="name">Javier Molina</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-J90M34</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date" eval="(datetime.now() - relativedelta(years=9)).strftime('%Y-%m-%d')"/>
<field name="gender">male</field>
<field name="phone">+503 7567-8901</field>
</record>
<record id="demo_patient_35" model="res.partner">
<field name="name">Rosa María Ochoa</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-R34F35</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1962-01-05</field>
<field name="gender">female</field>
<field name="phone">+503 7678-9012</field>
<field name="vat">08901235-5</field>
</record>
<record id="demo_patient_36" model="res.partner">
<field name="name">Manuel Reyes</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-M67M36</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1976-07-31</field>
<field name="gender">male</field>
<field name="phone">+503 7789-0123</field>
<field name="vat">09012346-6</field>
</record>
<record id="demo_patient_37" model="res.partner">
<field name="name">Teresa Campos</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-T12F37</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1940-09-18</field>
<field name="gender">female</field>
<field name="phone">+503 7890-1234</field>
<field name="vat">00123457-7</field>
</record>
<record id="demo_patient_38" model="res.partner">
<field name="name">Pablo Espinoza</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-P89M38</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1989-05-03</field>
<field name="gender">male</field>
<field name="phone">+503 7901-2345</field>
<field name="vat">01234569-8</field>
</record>
<record id="demo_patient_39" model="res.partner">
<field name="name">Mónica Villanueva</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-M45F39</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1985-11-26</field>
<field name="gender">female</field>
<field name="is_pregnant" eval="True"/>
<field name="phone">+503 7012-3456</field>
<field name="vat">02345670-9</field>
</record>
<record id="demo_patient_40" model="res.partner">
<field name="name">Diego Alejandro Luna</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-D78M40</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date" eval="(datetime.now() - relativedelta(years=2)).strftime('%Y-%m-%d')"/>
<field name="gender">male</field>
<field name="phone">+503 7123-4567</field>
</record>
<record id="demo_patient_41" model="res.partner">
<field name="name">Beatriz Salazar</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-B23F41</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1968-02-14</field>
<field name="gender">female</field>
<field name="phone">+503 7234-5678</field>
<field name="vat">03456781-0</field>
</record>
<record id="demo_patient_42" model="res.partner">
<field name="name">Héctor Valdés</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-H56M42</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1973-06-29</field>
<field name="gender">male</field>
<field name="phone">+503 7345-6789</field>
<field name="vat">04567892-1</field>
</record>
<record id="demo_patient_43" model="res.partner">
<field name="name">Silvia Peña</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-S90F43</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1997-08-11</field>
<field name="gender">female</field>
<field name="phone">+503 7456-7890</field>
<field name="vat">05678903-2</field>
</record>
<record id="demo_patient_44" model="res.partner">
<field name="name">Arturo Domínguez</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-A34M44</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1951-12-07</field>
<field name="gender">male</field>
<field name="phone">+503 7567-8901</field>
<field name="vat">06789014-3</field>
</record>
<record id="demo_patient_45" model="res.partner">
<field name="name">Gloria Ríos</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-G67F45</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1983-04-22</field>
<field name="gender">female</field>
<field name="is_pregnant" eval="True"/>
<field name="phone">+503 7678-9012</field>
<field name="vat">07890125-4</field>
</record>
<record id="demo_patient_46" model="res.partner">
<field name="name">Emilio Núñez</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-E12M46</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date" eval="(datetime.now() - relativedelta(years=11)).strftime('%Y-%m-%d')"/>
<field name="gender">male</field>
<field name="phone">+503 7789-0123</field>
</record>
<record id="demo_patient_47" model="res.partner">
<field name="name">Laura Patricia Ibarra</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-L89F47</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1979-10-16</field>
<field name="gender">female</field>
<field name="phone">+503 7890-1234</field>
<field name="vat">08901236-5</field>
</record>
<record id="demo_patient_48" model="res.partner">
<field name="name">Óscar Medina</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-O45M48</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1966-03-25</field>
<field name="gender">male</field>
<field name="phone">+503 7901-2345</field>
<field name="vat">09012347-6</field>
</record>
<record id="demo_patient_49" model="res.partner">
<field name="name">Verónica Soto</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-V78F49</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1993-07-08</field>
<field name="gender">female</field>
<field name="is_pregnant" eval="True"/>
<field name="phone">+503 7012-3456</field>
<field name="vat">00123458-7</field>
</record>
<record id="demo_patient_50" model="res.partner">
<field name="name">Rubén Contreras</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-R23M50</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1937-11-14</field>
<field name="gender">male</field>
<field name="phone">+503 7123-4567</field>
<field name="vat">01234560-8</field>
</record>
<record id="demo_patient_51" model="res.partner">
<field name="name">Alejandra Fuentes</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-A56F51</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date" eval="(datetime.now() - relativedelta(years=7)).strftime('%Y-%m-%d')"/>
<field name="gender">female</field>
<field name="phone">+503 7234-5678</field>
</record>
<record id="demo_patient_52" model="res.partner">
<field name="name">Nicolás Ramos</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-N90M52</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1986-01-19</field>
<field name="gender">male</field>
<field name="phone">+503 7345-6789</field>
<field name="vat">02345671-9</field>
</record>
<record id="demo_patient_53" model="res.partner">
<field name="name">Fernanda Acosta</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-F34F53</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">2000-05-12</field>
<field name="gender">female</field>
<field name="phone">+503 7456-7890</field>
<field name="vat">03456782-0</field>
</record>
</data>
</odoo>

View File

@ -1,32 +0,0 @@
## Descripción
Implementación del sistema de etiquetas con código de barras para las muestras de laboratorio.
## Cambios realizados
### Funcionalidad principal
- Creado reporte QWeb para imprimir etiquetas de muestras (100x50mm)
- Implementado botón 'Imprimir Etiquetas' en órdenes de laboratorio
- Las etiquetas incluyen:
- Información del paciente
- Código de muestra y orden
- Tipo de contenedor
- Fecha de recolección
- Código de barras Code128
- Lista de análisis a realizar
### Correcciones técnicas
- **Código de barras**: Corregido problema de visualización usando widget nativo de Odoo 18
- **Caracteres especiales**: Solucionado problema de codificación UTF-8 con referencias numéricas
- **Layout**: Ajustado diseño para mostrar múltiples etiquetas por página sin solapamiento
- **Espaciado**: Optimizado el tamaño y posición del código de barras
## Testing
- Probado con órdenes que tienen múltiples muestras
- Verificado que los códigos de barras se generen y visualicen correctamente
- Confirmado que los caracteres en español (tildes, ñ) se muestren bien
- Validado que no hay solapamiento entre etiquetas
## Capturas
- Los códigos de barras ahora se visualizan correctamente
- Las etiquetas respetan el formato 100x50mm
- Múltiples etiquetas por página sin problemas de diseño

View File

@ -1,74 +0,0 @@
## Descripción
Implementación de la funcionalidad para cancelar automáticamente muestras y pruebas cuando se cancela una orden de laboratorio, evitando que queden elementos "huérfanos" en el sistema.
## 🎯 Objetivo
Resolver el issue #54: Las muestras y pruebas asociadas a una orden de laboratorio deben cancelarse automáticamente cuando se cancela la orden.
## 🔧 Cambios implementados
### 1. Modelo `stock.lot` (Muestras)
- Agregado nuevo estado `'cancelled'` a la selección de estados
- Implementado método `action_cancel()` para cambiar el estado a cancelado
### 2. Modelo `sale.order` (Órdenes)
- Override del método `action_cancel()` que:
- Llama primero al método padre para mantener el comportamiento estándar
- Si es una orden de laboratorio (`is_lab_request = True`):
- Cancela muestras en estados: `pending_collection`, `collected`, `received`, `in_process`
- Cancela pruebas asociadas que no estén en estado `validated` o `cancelled`
- Registra mensajes en el chatter de cada elemento cancelado
- Muestra un resumen en la orden con la cantidad de elementos cancelados
### 3. Tests unitarios
- Creado `test_order_cancel_cascade.py` con 6 tests que verifican:
- ✅ Cancelación correcta de muestras
- ✅ Cancelación correcta de pruebas
- ✅ No cancelación de muestras en estados finales (analyzed, stored, disposed)
- ✅ No cancelación de pruebas validadas
- ✅ Generación de mensajes en chatter
- ✅ Órdenes normales (no laboratorio) no afectadas
## 🧪 Pruebas realizadas
### Test manual exitoso:
```
📦 Muestras generadas:
- 0000012: Contenedor de Heces (Estado: pending_collection)
🔬 Pruebas generadas:
- LAB-2025-00014: Coprocultivo (Estado: draft)
❌ Cancelando la orden de laboratorio...
📦 Estado final de las muestras:
- 0000012: cancelled ✓
🔬 Estado final de las pruebas:
- LAB-2025-00014: cancelled ✓
✅ Mensajes de cancelación registrados en todos los elementos
```
## 📋 Checklist
- [x] Código implementado y probado
- [x] Tests unitarios creados
- [x] Pruebas manuales exitosas
- [x] Mensajes en chatter funcionando
- [x] Sin errores o warnings
- [x] Documentación en código
## 🔍 Cómo probar
1. Crear una orden de laboratorio con análisis
2. Confirmar la orden (se generan muestras y pruebas)
3. Opcionalmente iniciar proceso en alguna prueba
4. Cancelar la orden
5. Verificar que:
- Las muestras cambiaron a estado "Cancelada"
- Las pruebas cambiaron a estado "Cancelado"
- Hay mensajes en el chatter explicando la cancelación
Resuelve #54

View File

@ -1,48 +0,0 @@
## Implementación del flujo de validación y seguridad
### Cambios realizados
#### 1. Ajuste de permisos base (ir.model.access.csv)
- Recepcionista: Solo lectura en lims.test y lims.result
- Técnico: Lectura/escritura pero sin crear/eliminar
- Administrador: Permisos completos
#### 2. Reglas de registro implementadas (lims_security.xml)
- Recepcionistas no pueden editar pruebas
- Técnicos solo pueden editar pruebas no validadas
- Administradores tienen acceso completo
#### 3. Validación de permisos en transiciones (lims_test.py)
- `action_start_process()`: Solo técnicos y administradores
- `action_enter_results()`: Solo técnicos y administradores
- `action_validate()`: Solo administradores
- `action_cancel()`: Técnicos (excepto validadas) y administradores
- `action_draft()`: Solo administradores
#### 4. Trazabilidad mejorada
- stock.lot ahora hereda de mail.thread
- Todos los cambios de estado se registran en el chatter
- Mensajes más descriptivos con contexto
#### 5. Validaciones adicionales
- Control de transiciones de estado válidas
- Verificación del estado de la muestra
- Validación de resultados críticos fuera de rango
- No se puede crear pruebas en estado != draft sin ser admin
#### 6. Vistas actualizadas
- Botones visibles solo para roles apropiados
- Campos de resultados editables solo por técnicos/admin
#### 7. Usuarios demo para pruebas
- Usuario: `recepcionista` / Contraseña: `demo`
- Usuario: `tecnico` / Contraseña: `demo`
- Usuario: `administrador` / Contraseña: `demo`
### Pruebas realizadas
- Verificación de permisos por rol
- Validación de transiciones de estado
- Trazabilidad en chatter
- Restricciones visuales en formularios
Closes #9

View File

@ -1,5 +1,242 @@
import odoo
import json
import random
from datetime import datetime
# Diccionario de notas médicas para parámetros críticos
CRITICAL_NOTES = {
'glucosa': {
'high': 'Valor elevado de glucosa. Posible prediabetes o diabetes. Se recomienda repetir la prueba en ayunas y consultar con endocrinología.',
'low': 'Hipoglucemia detectada. Riesgo de síntomas neuroglucogénicos. Evaluar causas: medicamentos, insuficiencia hepática o endocrinopatías.'
},
'hemoglobina': {
'high': 'Policitemia. Evaluar posibles causas: deshidratación, tabaquismo, cardiopatía o policitemia vera.',
'low': 'Anemia severa. Investigar origen: deficiencia de hierro, pérdida sanguínea, hemólisis o enfermedad crónica.'
},
'hematocrito': {
'high': 'Hemoconcentración. Correlacionar con hemoglobina. Descartar deshidratación o policitemia.',
'low': 'Valor compatible con anemia. Evaluar junto con hemoglobina e índices eritrocitarios.'
},
'leucocitos': {
'high': 'Leucocitosis marcada. Descartar proceso infeccioso, inflamatorio o hematológico.',
'low': 'Leucopenia severa. Riesgo de infecciones. Evaluar causas: viral, medicamentosa o hematológica.'
},
'plaquetas': {
'high': 'Trombocitosis. Riesgo trombótico. Descartar causa primaria vs reactiva.',
'low': 'Trombocitopenia severa. Riesgo de sangrado. Evaluar PTI, hiperesplenismo o supresión medular.'
},
'neutrofilos': {
'high': 'Neutrofilia. Sugiere infección bacteriana o proceso inflamatorio agudo.',
'low': 'Neutropenia. Alto riesgo de infección bacteriana. Evaluar urgentemente.'
},
'linfocitos': {
'high': 'Linfocitosis. Considerar infección viral o proceso linfoproliferativo.',
'low': 'Linfopenia. Evaluar inmunodeficiencia o efecto de corticoides.'
},
'colesterol total': {
'high': 'Hipercolesterolemia. Riesgo cardiovascular elevado. Iniciar medidas dietéticas y evaluar tratamiento con estatinas.',
'low': 'Hipocolesterolemia. Evaluar malnutrición, hipertiroidismo o enfermedad hepática.'
},
'trigliceridos': {
'high': 'Hipertrigliceridemia severa. Riesgo de pancreatitis aguda. Considerar tratamiento farmacológico urgente.',
'low': 'Valor bajo, generalmente sin significado patológico.'
},
'hdl': {
'high': 'HDL elevado, factor protector cardiovascular.',
'low': 'HDL bajo. Factor de riesgo cardiovascular. Recomendar ejercicio y cambios en estilo de vida.'
},
'ldl': {
'high': 'LDL elevado. Alto riesgo aterogénico. Evaluar inicio de estatinas según riesgo global.',
'low': 'LDL bajo, generalmente favorable.'
},
'glucosa en sangre': {
'high': 'Hiperglucemia. Si en ayunas >126 mg/dL sugiere diabetes. Confirmar con segunda muestra.',
'low': 'Hipoglucemia. Evaluar síntomas y causas. Riesgo neurológico si <50 mg/dL.'
}
}
def get_critical_note(param_name, value, normal_min=None, normal_max=None):
"""Obtiene la nota apropiada para un resultado crítico"""
param_lower = param_name.lower()
# Buscar el parámetro en el diccionario
for key in CRITICAL_NOTES:
if key in param_lower:
if normal_max and value > normal_max:
return CRITICAL_NOTES[key].get('high', f'Valor crítico alto para {param_name}. Requiere evaluación médica inmediata.')
elif normal_min and value < normal_min:
return CRITICAL_NOTES[key].get('low', f'Valor crítico bajo para {param_name}. Requiere evaluación médica inmediata.')
# Nota genérica si no se encuentra el parámetro
if normal_max and value > normal_max:
return f'Valor significativamente elevado. Rango normal: {normal_min}-{normal_max}. Se recomienda evaluación médica.'
elif normal_min and value < normal_min:
return f'Valor significativamente bajo. Rango normal: {normal_min}-{normal_max}. Se recomienda evaluación médica.'
return 'Valor fuera de rango normal. Requiere interpretación clínica.'
def process_order_tests(env, order):
"""Process all tests for a given order: regenerate results, fill values, and validate"""
print(f"\nProcessing tests for order {order.name}...")
# First, update sample states to allow processing
samples = order.generated_sample_ids.sudo()
for sample in samples:
if sample.state == 'pending_collection':
sample.action_collect()
print(f" - Sample {sample.name} collected")
if sample.state == 'collected':
sample.action_receive()
print(f" - Sample {sample.name} received")
if sample.state == 'received':
sample.action_start_analysis()
print(f" - Sample {sample.name} analysis started")
# Find all tests associated with this order
tests = env['lims.test'].search([('sale_order_id', '=', order.id)])
print(f"Found {len(tests)} tests for order {order.name}")
# Ensure we have the right permissions by using sudo()
tests = tests.sudo()
for test in tests:
try:
print(f"\nProcessing test {test.name} - {test.product_id.name}")
# First, mark the test as in_process if it's in draft state
if test.state == 'draft':
test.write({'state': 'in_process'})
# Manually create results if they don't exist
if not test.result_ids:
# Get analysis parameters from product template
product_tmpl = test.product_id.product_tmpl_id
for param_link in product_tmpl.parameter_ids:
param = param_link.parameter_id
# Prepare result data with values
result_data = {
'test_id': test.id,
'parameter_id': param.id,
}
# Set value based on parameter type
try:
if param.value_type == 'numeric':
# Generar valor que a veces esté fuera de rango
if random.random() < 0.3: # 30% de valores críticos
# Obtener rangos normales del parámetro
normal_min = param_link.normal_min if hasattr(param_link, 'normal_min') and param_link.normal_min else 10
normal_max = param_link.normal_max if hasattr(param_link, 'normal_max') and param_link.normal_max else 100
# Decidir si será alto o bajo
if random.random() < 0.5:
# Valor alto
value = round(random.uniform(normal_max * 1.2, normal_max * 1.5), 2)
else:
# Valor bajo
value = round(random.uniform(normal_min * 0.5, normal_min * 0.8), 2)
result_data['value_numeric'] = value
else:
result_data['value_numeric'] = 50.0
elif param.value_type == 'text':
# Handle different text parameters appropriately
param_lower = param.name.lower()
if 'cultivo' in param_lower:
result_data['value_text'] = "No se observa crecimiento bacteriano"
elif 'observacion' in param_lower:
result_data['value_text'] = "Sin observaciones particulares"
elif 'color' in param_lower:
result_data['value_text'] = "Amarillo claro"
elif 'aspecto' in param_lower:
result_data['value_text'] = "Transparente"
elif 'olor' in param_lower:
result_data['value_text'] = "Sui generis"
else:
result_data['value_text'] = "Normal"
elif param.value_type == 'boolean':
result_data['value_boolean'] = False
elif param.value_type == 'selection':
if param.selection_values:
options = param.selection_values.split(',')
# For pregnancy tests, randomly assign positive/negative
if 'beta-hcg' in param.name.lower() or 'embarazo' in param.name.lower():
# 30% chance of positive for pregnant patients, 5% for others
is_positive = random.random() < (0.3 if hasattr(test, 'patient_id') and test.patient_id.is_pregnant else 0.05)
result_data['value_selection'] = 'Positivo' if is_positive else 'Negativo'
else:
result_data['value_selection'] = options[0].strip()
else:
# No selection values defined, use default
if 'embarazo' in param.name.lower():
result_data['value_selection'] = 'Negativo'
else:
result_data['value_selection'] = 'Normal'
except Exception as e:
print(f" - Error preparing value for parameter {param.name}: {str(e)}")
# Set a default value to avoid validation errors
if param.value_type == 'numeric':
result_data['value_numeric'] = 50.0
elif param.value_type == 'text':
result_data['value_text'] = "Pendiente"
elif param.value_type == 'boolean':
result_data['value_boolean'] = False
elif param.value_type == 'selection':
result_data['value_selection'] = "Normal"
# Create result with values
result = env['lims.result'].create(result_data)
print(f" - Created {len(product_tmpl.parameter_ids)} result fields")
# Evaluar resultados críticos y agregar notas
for result in test.result_ids:
# Leer el registro para actualizar campos computados
result.read(['is_critical'])
# Si el resultado es crítico, agregar nota
if result.is_critical and result.parameter_id.value_type == 'numeric':
value = result.value_numeric
param_name = result.parameter_id.name
# Obtener rangos del rango aplicable si existe
normal_min = normal_max = None
if result.applicable_range_id:
normal_min = result.applicable_range_id.normal_min
normal_max = result.applicable_range_id.normal_max
# Obtener la nota apropiada
note = get_critical_note(param_name, value, normal_min, normal_max)
result.write({'notes': note})
print(f" - Agregada nota crítica para {param_name}: valor {value}")
print(f" - Results ready with values and critical notes")
# Update test state directly to bypass permission checks
if test.state == 'in_process':
# Mark as results entered
test.write({
'state': 'result_entered'
})
print(f" - Results entered")
# Then validate the test
if test.state == 'result_entered':
test.write({
'state': 'validated',
'validator_id': env.user.id,
'validation_date': datetime.now()
})
print(f" - Test validated successfully")
except Exception as e:
print(f" - Error processing test {test.name}: {str(e)}")
import traceback
traceback.print_exc()
print(f" - Test state: {test.state}")
print(f" - Product template: {test.product_id.product_tmpl_id.name}")
print(f" - Parameters: {len(test.product_id.product_tmpl_id.parameter_ids)}")
print(f"\nCompleted processing tests for order {order.name}")
def create_lab_requests(cr):
env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
@ -16,59 +253,123 @@ def create_lab_requests(cr):
except Exception:
pass
try:
# Get patients and doctors - using search instead of ref to be more robust
patient1 = env['res.partner'].search([('patient_identifier', '=', 'P-A87B01'), ('is_patient', '=', True)], limit=1)
patient2 = env['res.partner'].search([('patient_identifier', '=', 'P-C45D02'), ('is_patient', '=', True)], limit=1)
doctor1 = env['res.partner'].search([('doctor_license', '=', 'L-98765'), ('is_doctor', '=', True)], limit=1)
# Get all available analysis products
all_analyses = env['product.template'].search([('is_analysis', '=', True)])
# Find or create pregnancy test
pregnancy_test = env['product.template'].search([('name', '=', 'Prueba de Embarazo'), ('is_analysis', '=', True)], limit=1)
if not pregnancy_test:
# Create pregnancy test if it doesn't exist
pregnancy_test = env['product.template'].create({
'name': 'Prueba de Embarazo',
'is_analysis': True,
'analysis_type': 'immunology',
'list_price': 15.00,
'standard_price': 8.00,
'type': 'service',
'categ_id': env.ref('lims_management.lims_category_immunology').id if env.ref('lims_management.lims_category_immunology', False) else env['product.category'].search([], limit=1).id,
})
print("Created Pregnancy Test product")
if not patient1:
print("Warning: Patient 1 not found, skipping lab requests creation")
return
# Get analysis products - using search instead of ref
hemograma = env['product.template'].search([('name', '=', 'Hemograma Completo'), ('is_analysis', '=', True)], limit=1)
perfil_lipidico = env['product.template'].search([('name', '=', 'Perfil Lipídico'), ('is_analysis', '=', True)], limit=1)
glucosa = env['product.template'].search([('name', '=', 'Glucosa en Sangre'), ('is_analysis', '=', True)], limit=1)
urocultivo = env['product.template'].search([('name', '=', 'Urocultivo'), ('is_analysis', '=', True)], limit=1)
# Create parameter for pregnancy test
preg_param = env['lims.analysis.parameter'].search([('name', '=', 'Beta-hCG')], limit=1)
if not preg_param:
preg_param = env['lims.analysis.parameter'].create({
'name': 'Beta-hCG',
'code': 'BHCG',
'value_type': 'selection',
'selection_values': 'Positivo,Negativo,Indeterminado',
'description': 'Hormona gonadotropina coriónica humana beta'
})
# Create Lab Request 1 - Multiple analyses with same sample type
if patient1 and hemograma and perfil_lipidico:
order1 = env['sale.order'].create({
'partner_id': patient1.id,
'doctor_id': doctor1.id if doctor1 else False,
'is_lab_request': True,
'order_line': [
(0, 0, {'product_id': hemograma.product_variant_id.id, 'product_uom_qty': 1}),
(0, 0, {'product_id': perfil_lipidico.product_variant_id.id, 'product_uom_qty': 1})
]
})
print(f"Created Lab Order 1: {order1.name}")
# Confirm the order to test automatic sample generation
order1.action_confirm()
print(f"Confirmed Lab Order 1. Generated samples: {len(order1.generated_sample_ids)}")
# Create Lab Request 2 - Different sample types
if patient2 and glucosa and urocultivo:
order2 = env['sale.order'].create({
'partner_id': patient2.id,
'is_lab_request': True,
'order_line': [
(0, 0, {'product_id': glucosa.product_variant_id.id, 'product_uom_qty': 1}),
(0, 0, {'product_id': urocultivo.product_variant_id.id, 'product_uom_qty': 1})
]
})
print(f"Created Lab Order 2: {order2.name}")
# Confirm to test automatic sample generation with different types
order2.action_confirm()
print(f"Confirmed Lab Order 2. Generated samples: {len(order2.generated_sample_ids)}")
except Exception as e:
print(f"Error creating lab requests: {str(e)}")
import traceback
traceback.print_exc()
# Link parameter to pregnancy test
env['product.template.parameter'].create({
'product_tmpl_id': pregnancy_test.id,
'parameter_id': preg_param.id,
'sequence': 10,
'required': True
})
# Separate analyses for different purposes
routine_analyses = [a for a in all_analyses if a.name not in ['Prueba de Embarazo']]
# Get all patients
all_patients = env['res.partner'].search([('is_patient', '=', True)], order='id')
# Get available doctors
doctors = env['res.partner'].search([('is_doctor', '=', True)])
print(f"\n=== Starting creation of lab orders for {len(all_patients)} patients ===")
print(f"Available analyses: {len(all_analyses)}")
print(f"Available doctors: {len(doctors)}")
orders_created = 0
failed_orders = []
for idx, patient in enumerate(all_patients):
print(f"\n--- Processing patient {idx+1}/{len(all_patients)}: {patient.name} ---")
# Randomly assign a doctor
doctor = random.choice(doctors) if doctors else False
# Create 2 orders per patient
for order_num in range(1, 3):
try:
order_lines = []
# For pregnant patients, include pregnancy test in one of the orders
if patient.is_pregnant and order_num == 1:
order_lines.append((0, 0, {
'product_id': pregnancy_test.product_variant_id.id,
'product_uom_qty': 1
}))
print(f" - Added pregnancy test for pregnant patient")
# Select random analyses (minimum 2 per order)
num_analyses = random.randint(2, 4)
selected_analyses = random.sample(routine_analyses, min(num_analyses, len(routine_analyses)))
for analysis in selected_analyses:
order_lines.append((0, 0, {
'product_id': analysis.product_variant_id.id,
'product_uom_qty': 1
}))
# Create the order
order = env['sale.order'].create({
'partner_id': patient.id,
'doctor_id': doctor.id if doctor else False,
'is_lab_request': True,
'order_line': order_lines
})
print(f" Order {order_num}: Created {order.name} with {len(order_lines)} analyses")
# Confirm the order
order.action_confirm()
print(f" Order {order_num}: Confirmed. Generated samples: {len(order.generated_sample_ids)}")
# Process tests
process_order_tests(env, order)
orders_created += 1
except Exception as e:
error_msg = f"Patient: {patient.name}, Order {order_num}, Error: {str(e)}"
failed_orders.append(error_msg)
print(f" ERROR creating order {order_num} for {patient.name}: {str(e)}")
import traceback
traceback.print_exc()
# Final summary
print("\n=== SUMMARY ===")
print(f"Total orders created: {orders_created}")
print(f"Failed orders: {len(failed_orders)}")
if failed_orders:
print("\n=== FAILED ORDERS ===")
for idx, error in enumerate(failed_orders, 1):
print(f"{idx}. {error}")
if __name__ == '__main__':
db_name = 'lims_demo'