feat(#11): Implementar informe PDF de resultados de laboratorio
- Agregar QWeb template para generar PDF profesional con: - Encabezado con datos del laboratorio y logo - Información completa del paciente y orden - Tabla de resultados con indicadores visuales para valores fuera de rango - Sección de observaciones y notas - Información del validador y fecha de validación - Agregar campo computado reference_text en parameter_range para mostrar rangos formateados - Agregar botón "Imprimir Informe de Resultados" en vista de órdenes (solo visible cuando hay pruebas validadas) - Agregar campo lab_notes en sale.order para observaciones generales - Reorganizar vista de lims.test con pestañas para mejor UX - Corregir manejo de employee_ids en el reporte para casos donde no existe el módulo HR - Incluir scripts de prueba para generar datos de demostración El informe resalta valores críticos y fuera de rango con colores distintivos, facilitando la interpretación rápida de los resultados por parte del médico. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
b9cd21b2c2
commit
aa8a0571fc
|
@ -25,7 +25,9 @@
|
|||
"Bash(bash:*)",
|
||||
"Bash(grep:*)",
|
||||
"Bash(gh pr merge:*)",
|
||||
"Bash(git cherry-pick:*)"
|
||||
"Bash(git cherry-pick:*)",
|
||||
"Bash(del comment_issue_15.txt)",
|
||||
"Bash(cat:*)"
|
||||
],
|
||||
"deny": []
|
||||
}
|
||||
|
|
|
@ -48,6 +48,8 @@
|
|||
'views/menus.xml',
|
||||
'views/lims_config_views.xml',
|
||||
'report/sample_label_report.xml',
|
||||
'reports/lab_results_report_data.xml',
|
||||
'reports/lab_results_report.xml',
|
||||
],
|
||||
'demo': [
|
||||
'demo/demo_users.xml',
|
||||
|
|
Binary file not shown.
|
@ -28,6 +28,14 @@ class LimsTest(models.Model):
|
|||
ondelete='restrict'
|
||||
)
|
||||
|
||||
sale_order_id = fields.Many2one(
|
||||
'sale.order',
|
||||
string='Orden de Venta',
|
||||
related='sale_order_line_id.order_id',
|
||||
store=True,
|
||||
readonly=True
|
||||
)
|
||||
|
||||
patient_id = fields.Many2one(
|
||||
'res.partner',
|
||||
string='Paciente',
|
||||
|
|
|
@ -102,6 +102,26 @@ class LimsParameterRange(models.Model):
|
|||
readonly=True
|
||||
)
|
||||
|
||||
reference_text = fields.Char(
|
||||
string='Texto de Referencia',
|
||||
compute='_compute_reference_text',
|
||||
store=False,
|
||||
help='Texto formateado del rango de referencia'
|
||||
)
|
||||
|
||||
@api.depends('normal_min', 'normal_max', 'parameter_unit')
|
||||
def _compute_reference_text(self):
|
||||
"""Computa el texto de referencia basado en los valores min/max y unidad"""
|
||||
for record in self:
|
||||
if record.normal_min is not False and record.normal_max is not False:
|
||||
unit = record.parameter_unit or ''
|
||||
# Formatear los números para evitar decimales innecesarios
|
||||
min_val = f"{record.normal_min:.2f}".rstrip('0').rstrip('.')
|
||||
max_val = f"{record.normal_max:.2f}".rstrip('0').rstrip('.')
|
||||
record.reference_text = f"{min_val} - {max_val} {unit}".strip()
|
||||
else:
|
||||
record.reference_text = "N/A"
|
||||
|
||||
@api.depends('parameter_id', 'gender', 'age_min', 'age_max', 'pregnant')
|
||||
def _compute_name(self):
|
||||
for record in self:
|
||||
|
|
|
@ -339,3 +339,56 @@ class SaleOrder(models.Model):
|
|||
|
||||
# Retornar la acción de imprimir el reporte para las muestras activas
|
||||
return report.report_action(active_samples)
|
||||
|
||||
# Fields for lab results report
|
||||
can_print_results = fields.Boolean(
|
||||
string="Puede Imprimir Resultados",
|
||||
compute='_compute_can_print_results',
|
||||
help="Indica si todas las pruebas están validadas y se puede imprimir el informe"
|
||||
)
|
||||
|
||||
lab_test_ids = fields.One2many(
|
||||
'lims.test',
|
||||
'sale_order_id',
|
||||
string="Pruebas de Laboratorio",
|
||||
readonly=True,
|
||||
help="Todas las pruebas de laboratorio asociadas a esta orden"
|
||||
)
|
||||
|
||||
referring_doctor_id = fields.Many2one(
|
||||
'res.partner',
|
||||
string="Médico Solicitante",
|
||||
related='doctor_id',
|
||||
readonly=True,
|
||||
help="Médico que solicitó los análisis"
|
||||
)
|
||||
|
||||
lab_notes = fields.Text(
|
||||
string="Observaciones del Laboratorio",
|
||||
help="Observaciones generales sobre la orden o los resultados"
|
||||
)
|
||||
|
||||
@api.depends('lab_test_ids.state')
|
||||
def _compute_can_print_results(self):
|
||||
"""Compute if results can be printed (all tests validated)"""
|
||||
for order in self:
|
||||
tests = order.lab_test_ids
|
||||
order.can_print_results = (
|
||||
tests and
|
||||
all(test.state == 'validated' for test in tests)
|
||||
)
|
||||
|
||||
def action_print_lab_results(self):
|
||||
"""Generate and print lab results report"""
|
||||
self.ensure_one()
|
||||
|
||||
# Verify all tests are validated
|
||||
if not self.can_print_results:
|
||||
raise UserError(_("No se puede imprimir el informe: hay pruebas sin validar"))
|
||||
|
||||
# Ensure this is a lab request
|
||||
if not self.is_lab_request:
|
||||
raise UserError(_("Esta no es una orden de laboratorio"))
|
||||
|
||||
# Generate the report
|
||||
return self.env.ref('lims_management.action_report_lab_results').report_action(self)
|
||||
|
|
274
lims_management/reports/lab_results_report.xml
Normal file
274
lims_management/reports/lab_results_report.xml
Normal file
|
@ -0,0 +1,274 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Template principal del reporte -->
|
||||
<template id="report_lab_results">
|
||||
<t t-call="web.html_container">
|
||||
<t t-foreach="docs" t-as="o">
|
||||
<t t-if="o.is_lab_request">
|
||||
<t t-call="lims_management.report_lab_results_document" t-lang="o.partner_id.lang"/>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!-- Documento individual -->
|
||||
<template id="report_lab_results_document">
|
||||
<t t-call="web.external_layout">
|
||||
<div class="page">
|
||||
<!-- Estilos CSS -->
|
||||
<style>
|
||||
.lab-header {
|
||||
border-bottom: 2px solid #337ab7;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.patient-info {
|
||||
background-color: #f8f9fa;
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.results-table {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.results-table th {
|
||||
background-color: #e9ecef;
|
||||
font-weight: bold;
|
||||
padding: 10px;
|
||||
border: 1px solid #dee2e6;
|
||||
}
|
||||
.results-table td {
|
||||
padding: 8px;
|
||||
border: 1px solid #dee2e6;
|
||||
}
|
||||
.result-out-of-range {
|
||||
color: #d9534f;
|
||||
font-weight: bold;
|
||||
}
|
||||
.result-critical {
|
||||
background-color: #f2dede;
|
||||
color: #a94442;
|
||||
font-weight: bold;
|
||||
padding: 2px 5px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.result-normal {
|
||||
color: #5cb85c;
|
||||
}
|
||||
.test-header {
|
||||
background-color: #337ab7;
|
||||
color: white;
|
||||
padding: 10px;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.observations {
|
||||
background-color: #fcf8e3;
|
||||
padding: 10px;
|
||||
margin-top: 10px;
|
||||
border-left: 4px solid #faebcc;
|
||||
}
|
||||
.validation-info {
|
||||
margin-top: 40px;
|
||||
border-top: 1px solid #dee2e6;
|
||||
padding-top: 20px;
|
||||
}
|
||||
.signature-line {
|
||||
border-bottom: 1px solid #000;
|
||||
width: 250px;
|
||||
margin-top: 50px;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Encabezado del laboratorio -->
|
||||
<div class="lab-header">
|
||||
<div class="row">
|
||||
<div class="col-8">
|
||||
<h2>LABORATORIO CLÍNICO</h2>
|
||||
<h3><t t-esc="o.company_id.name"/></h3>
|
||||
<p>
|
||||
<t t-if="o.company_id.street"><t t-esc="o.company_id.street"/><br/></t>
|
||||
<t t-if="o.company_id.city"><t t-esc="o.company_id.city"/>, </t>
|
||||
<t t-if="o.company_id.state_id"><t t-esc="o.company_id.state_id.name"/><br/></t>
|
||||
<t t-if="o.company_id.phone">Tel: <t t-esc="o.company_id.phone"/></t>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-4 text-right">
|
||||
<img t-if="o.company_id.logo" t-att-src="image_data_uri(o.company_id.logo)"
|
||||
style="max-height: 100px; max-width: 200px;"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Información del paciente y orden -->
|
||||
<div class="patient-info">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<h4>DATOS DEL PACIENTE</h4>
|
||||
<table class="table table-sm">
|
||||
<tr>
|
||||
<td><strong>Nombre:</strong></td>
|
||||
<td><t t-esc="o.partner_id.name"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Identificación:</strong></td>
|
||||
<td><t t-esc="o.partner_id.vat or 'N/A'"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Edad:</strong></td>
|
||||
<td>
|
||||
<t t-if="o.partner_id.birthdate_date">
|
||||
<t t-esc="o.partner_id.age"/> años
|
||||
</t>
|
||||
<t t-else="">N/A</t>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Sexo:</strong></td>
|
||||
<td>
|
||||
<t t-if="o.partner_id.gender == 'male'">Masculino</t>
|
||||
<t t-elif="o.partner_id.gender == 'female'">Femenino</t>
|
||||
<t t-else="">No especificado</t>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<h4>DATOS DE LA ORDEN</h4>
|
||||
<table class="table table-sm">
|
||||
<tr>
|
||||
<td><strong>Número de Orden:</strong></td>
|
||||
<td><t t-esc="o.name"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Fecha de Solicitud:</strong></td>
|
||||
<td><t t-esc="o.date_order" t-options='{"widget": "date"}'/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Médico Solicitante:</strong></td>
|
||||
<td><t t-esc="o.referring_doctor_id.name or 'N/A'"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Estado:</strong></td>
|
||||
<td>Resultados Validados</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Resultados de análisis -->
|
||||
<h3 class="text-center" style="margin: 30px 0;">INFORME DE RESULTADOS</h3>
|
||||
|
||||
<!-- Iterar por cada prueba validada -->
|
||||
<t t-set="validated_tests" t-value="o.lab_test_ids.filtered(lambda t: t.state == 'validated')"/>
|
||||
<t t-foreach="validated_tests" t-as="test">
|
||||
<div class="test-section">
|
||||
<!-- Encabezado del análisis -->
|
||||
<h4 class="test-header">
|
||||
<t t-esc="test.product_id.name"/>
|
||||
</h4>
|
||||
|
||||
<!-- Tabla de resultados -->
|
||||
<table class="table results-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="30%">PARÁMETRO</th>
|
||||
<th width="20%" class="text-center">RESULTADO</th>
|
||||
<th width="15%" class="text-center">UNIDAD</th>
|
||||
<th width="35%" class="text-center">VALOR DE REFERENCIA</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<t t-foreach="test.result_ids" t-as="result">
|
||||
<tr>
|
||||
<td><t t-esc="result.parameter_id.name"/></td>
|
||||
<td class="text-center">
|
||||
<span t-attf-class="#{result.is_critical and 'result-critical' or result.is_out_of_range and 'result-out-of-range' or 'result-normal'}">
|
||||
<t t-esc="result.value_display"/>
|
||||
<t t-if="result.is_critical"> **</t>
|
||||
<t t-elif="result.is_out_of_range"> *</t>
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<t t-esc="result.parameter_id.unit or '-'"/>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<t t-if="result.applicable_range_id">
|
||||
<t t-if="result.parameter_id.value_type == 'numeric'">
|
||||
<t t-esc="result.applicable_range_id.normal_min"/> - <t t-esc="result.applicable_range_id.normal_max"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-esc="result.applicable_range_id.reference_text or 'N/A'"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-else="">N/A</t>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Mostrar notas si existen -->
|
||||
<t t-if="result.notes">
|
||||
<tr>
|
||||
<td colspan="4" style="padding-left: 30px; font-style: italic;">
|
||||
<strong>Nota:</strong> <t t-esc="result.notes"/>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
</t>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Comentarios de la prueba -->
|
||||
<t t-if="test.notes">
|
||||
<div class="observations">
|
||||
<strong>Observaciones:</strong> <t t-esc="test.notes"/>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<!-- Leyenda de símbolos -->
|
||||
<div style="margin-top: 30px; font-size: 12px;">
|
||||
<p><strong>*</strong> Valor fuera del rango normal</p>
|
||||
<p><strong>**</strong> Valor crítico que requiere atención inmediata</p>
|
||||
</div>
|
||||
|
||||
<!-- Comentarios generales de la orden -->
|
||||
<t t-if="o.lab_notes">
|
||||
<div class="observations" style="margin-top: 30px;">
|
||||
<h5>OBSERVACIONES GENERALES</h5>
|
||||
<p><t t-esc="o.lab_notes"/></p>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<!-- Información de validación -->
|
||||
<div class="validation-info">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<p><strong>Fecha de Validación:</strong>
|
||||
<t t-if="validated_tests">
|
||||
<t t-esc="validated_tests[0].validation_date" t-options='{"widget": "datetime"}'/>
|
||||
</t>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-6 text-center">
|
||||
<t t-if="validated_tests and validated_tests[0].validator_id">
|
||||
<div class="signature-line"></div>
|
||||
<p style="margin-top: 5px;">
|
||||
<strong><t t-esc="validated_tests[0].validator_id.name"/></strong><br/>
|
||||
Responsable del Laboratorio
|
||||
</p>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Nota al pie -->
|
||||
<div style="margin-top: 50px; font-size: 10px; text-align: center; color: #666;">
|
||||
<p>Este informe es confidencial y está dirigido exclusivamente al paciente y/o médico tratante.</p>
|
||||
<p>Los resultados se relacionan únicamente con las muestras analizadas.</p>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
</odoo>
|
30
lims_management/reports/lab_results_report_data.xml
Normal file
30
lims_management/reports/lab_results_report_data.xml
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Paper Format para el reporte de resultados -->
|
||||
<record id="paperformat_lab_results" model="report.paperformat">
|
||||
<field name="name">Formato Resultados de Laboratorio</field>
|
||||
<field name="format">A4</field>
|
||||
<field name="orientation">Portrait</field>
|
||||
<field name="margin_top">40</field>
|
||||
<field name="margin_bottom">25</field>
|
||||
<field name="margin_left">10</field>
|
||||
<field name="margin_right">10</field>
|
||||
<field name="header_spacing">35</field>
|
||||
<field name="dpi">90</field>
|
||||
</record>
|
||||
|
||||
<!-- Acción del reporte -->
|
||||
<record id="action_report_lab_results" model="ir.actions.report">
|
||||
<field name="name">Informe de Resultados</field>
|
||||
<field name="model">sale.order</field>
|
||||
<field name="report_type">qweb-pdf</field>
|
||||
<field name="report_name">lims_management.report_lab_results</field>
|
||||
<field name="report_file">lims_management.report_lab_results</field>
|
||||
<field name="print_report_name">'Resultados_Lab_' + object.name + '.pdf'</field>
|
||||
<field name="paperformat_id" ref="paperformat_lab_results"/>
|
||||
<field name="attachment">'Resultados_Lab_' + object.name + '.pdf'</field>
|
||||
<field name="attachment_use">True</field>
|
||||
<field name="binding_model_id" ref="sale.model_sale_order"/>
|
||||
<field name="binding_type">report</field>
|
||||
</record>
|
||||
</odoo>
|
|
@ -64,7 +64,7 @@
|
|||
</group>
|
||||
|
||||
<notebook>
|
||||
<page string="Resultados">
|
||||
<page string="Resultados" name="results">
|
||||
<field name="result_ids"
|
||||
readonly="state in ['validated', 'cancelled']"
|
||||
context="{'default_test_id': id, 'default_patient_id': patient_id, 'default_test_date': create_date}"
|
||||
|
@ -118,16 +118,19 @@
|
|||
</list>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Observaciones">
|
||||
<field name="notes" placeholder="Agregar observaciones generales de la prueba..."/>
|
||||
<page string="Observaciones" name="observations">
|
||||
<group>
|
||||
<field name="notes" nolabel="1" placeholder="Agregar observaciones generales de la prueba..."/>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Actividades" name="activities">
|
||||
<field name="activity_ids"/>
|
||||
</page>
|
||||
<page string="Historial" name="history">
|
||||
<field name="message_ids" options="{'no_create': True}"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids"/>
|
||||
<field name="activity_ids"/>
|
||||
<field name="message_ids"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
|
|
@ -16,6 +16,12 @@
|
|||
class="btn-primary"
|
||||
invisible="not is_lab_request or state != 'sale' or not all_sample_ids"
|
||||
icon="fa-print"/>
|
||||
<button name="action_print_lab_results"
|
||||
string="Imprimir Informe de Resultados"
|
||||
type="object"
|
||||
class="btn-success"
|
||||
invisible="not can_print_results or not is_lab_request"
|
||||
icon="fa-file-pdf-o"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='partner_id']" position="after">
|
||||
<field name="doctor_id" invisible="not is_lab_request"/>
|
||||
|
@ -69,6 +75,11 @@
|
|||
</group>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Observaciones Lab" name="lab_notes" invisible="not is_lab_request">
|
||||
<group>
|
||||
<field name="lab_notes" nolabel="1" placeholder="Ingrese observaciones generales sobre la orden o los resultados..."/>
|
||||
</group>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
|
127
test/add_results_to_tests.py
Normal file
127
test/add_results_to_tests.py
Normal file
|
@ -0,0 +1,127 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Script para agregar resultados a las pruebas ya validadas
|
||||
"""
|
||||
|
||||
import odoo
|
||||
import logging
|
||||
import random
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def add_results_to_validated_tests(env):
|
||||
"""Agregar resultados a pruebas ya validadas"""
|
||||
|
||||
# Buscar las órdenes S00029 y S00030
|
||||
orders = env['sale.order'].search([('name', 'in', ['S00029', 'S00030'])], order='name')
|
||||
|
||||
if not orders:
|
||||
print("No se encontraron las órdenes S00029 o S00030")
|
||||
return
|
||||
|
||||
for order in orders:
|
||||
print(f"\n=== Procesando orden {order.name} ===")
|
||||
|
||||
for test in order.lab_test_ids:
|
||||
print(f"\nPrueba: {test.product_id.name}")
|
||||
|
||||
# Cambiar temporalmente a estado draft para poder modificar
|
||||
test.sudo().write({'state': 'draft'})
|
||||
|
||||
# Generar resultados si no existen
|
||||
if not test.result_ids:
|
||||
test.sudo()._generate_test_results()
|
||||
print(f" Generados {len(test.result_ids)} resultados")
|
||||
|
||||
# Asignar valores a los resultados
|
||||
for result in test.result_ids:
|
||||
parameter = result.parameter_id
|
||||
vals = {}
|
||||
|
||||
if parameter.value_type == 'numeric':
|
||||
# Valores específicos por código
|
||||
if parameter.code == 'HGB': # Hemoglobina
|
||||
vals['value_numeric'] = random.uniform(12.0, 16.0)
|
||||
elif parameter.code == 'HCT': # Hematocrito
|
||||
vals['value_numeric'] = random.uniform(36.0, 46.0)
|
||||
elif parameter.code == 'WBC': # Leucocitos
|
||||
vals['value_numeric'] = random.uniform(4.5, 10.0)
|
||||
elif parameter.code == 'PLT': # Plaquetas
|
||||
vals['value_numeric'] = random.uniform(150, 400)
|
||||
elif parameter.code == 'RBC': # Eritrocitos
|
||||
vals['value_numeric'] = random.uniform(4.0, 5.5)
|
||||
elif parameter.code == 'GLU': # Glucosa
|
||||
vals['value_numeric'] = random.uniform(70, 110)
|
||||
elif parameter.code == 'CHOL': # Colesterol
|
||||
vals['value_numeric'] = random.uniform(160, 220)
|
||||
elif parameter.code == 'TRIG': # Triglicéridos
|
||||
vals['value_numeric'] = random.uniform(50, 150)
|
||||
elif parameter.code == 'HDL': # HDL
|
||||
vals['value_numeric'] = random.uniform(40, 60)
|
||||
elif parameter.code == 'LDL': # LDL
|
||||
vals['value_numeric'] = random.uniform(80, 130)
|
||||
else:
|
||||
# Valor genérico
|
||||
vals['value_numeric'] = random.uniform(10, 100)
|
||||
|
||||
print(f" - {parameter.name}: {vals['value_numeric']:.2f}")
|
||||
|
||||
elif parameter.value_type == 'text':
|
||||
vals['value_text'] = "Normal"
|
||||
elif parameter.value_type == 'selection':
|
||||
vals['value_selection'] = "normal"
|
||||
elif parameter.value_type == 'boolean':
|
||||
vals['value_boolean'] = False
|
||||
|
||||
# Escribir con sudo para evitar restricciones
|
||||
result.sudo().write(vals)
|
||||
|
||||
# Volver a estado validated
|
||||
test.sudo().write({
|
||||
'state': 'validated',
|
||||
'validator_id': env.ref('base.user_admin').id,
|
||||
'validation_date': fields.Datetime.now()
|
||||
})
|
||||
|
||||
# Agregar notas a algunas pruebas
|
||||
if 'Hemograma' in test.product_id.name:
|
||||
test.sudo().write({'notes': 'Todos los parámetros dentro de rangos normales.'})
|
||||
elif 'Lipídico' in test.product_id.name:
|
||||
test.sudo().write({'notes': 'Perfil lipídico normal. Se recomienda mantener dieta balanceada.'})
|
||||
|
||||
print("\n✅ Resultados agregados exitosamente a todas las pruebas")
|
||||
return orders
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Importar fields después de configurar Odoo
|
||||
from odoo import fields
|
||||
|
||||
# Configuración
|
||||
db_name = 'lims_demo'
|
||||
|
||||
# Conectar a Odoo
|
||||
odoo.tools.config.parse_config(['--database', db_name])
|
||||
|
||||
# Obtener el registro de la base de datos
|
||||
registry = odoo.registry(db_name)
|
||||
|
||||
# Crear cursor y environment
|
||||
with registry.cursor() as cr:
|
||||
env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
|
||||
|
||||
try:
|
||||
# Agregar resultados a las pruebas
|
||||
orders = add_results_to_validated_tests(env)
|
||||
|
||||
# Confirmar cambios
|
||||
cr.commit()
|
||||
|
||||
print("\n📋 Ahora puedes probar el botón 'Imprimir Informe de Resultados' en las órdenes S00029 y S00030.")
|
||||
|
||||
except Exception as e:
|
||||
cr.rollback()
|
||||
print(f"\n❌ Error: {str(e)}")
|
||||
_logger.error(f"Error agregando resultados: {str(e)}", exc_info=True)
|
67
test/check_order_s00025.py
Normal file
67
test/check_order_s00025.py
Normal file
|
@ -0,0 +1,67 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Script para verificar los datos de la orden S00025
|
||||
"""
|
||||
|
||||
import odoo
|
||||
|
||||
def check_order_s00025(env):
|
||||
"""Verificar datos específicos de la orden S00025"""
|
||||
|
||||
# Buscar la orden S00025
|
||||
order = env['sale.order'].search([
|
||||
('name', '=', 'S00025')
|
||||
], limit=1)
|
||||
|
||||
if not order:
|
||||
print("❌ No se encontró la orden S00025")
|
||||
return
|
||||
|
||||
patient = order.partner_id
|
||||
print(f"Orden: {order.name}")
|
||||
print(f" Es orden de laboratorio: {order.is_lab_request}")
|
||||
print(f" Paciente: {patient.name}")
|
||||
print(f" ID Paciente: {patient.id}")
|
||||
print(f" Fecha de nacimiento: {patient.birthdate_date}")
|
||||
print(f" Edad: {patient.age if patient.birthdate_date else 'N/A'}")
|
||||
print(f" Género: {patient.gender or 'No especificado'}")
|
||||
|
||||
# Verificar estado de las pruebas
|
||||
print(f"\nPruebas de laboratorio ({len(order.lab_test_ids)}):")
|
||||
for test in order.lab_test_ids:
|
||||
print(f" - {test.product_id.name}: {test.state}")
|
||||
|
||||
# Verificar si puede imprimir resultados
|
||||
print(f"\n¿Puede imprimir resultados?: {order.can_print_results}")
|
||||
|
||||
# Si el paciente no tiene fecha de nacimiento, actualizarla
|
||||
if not patient.birthdate_date:
|
||||
print("\n⚠️ El paciente no tiene fecha de nacimiento. Actualizando...")
|
||||
patient.write({'birthdate_date': '1985-01-15'})
|
||||
print(f" Fecha de nacimiento actualizada a: {patient.birthdate_date}")
|
||||
print(f" Edad calculada: {patient.age} años")
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Configuración
|
||||
db_name = 'lims_demo'
|
||||
|
||||
# Conectar a Odoo
|
||||
odoo.tools.config.parse_config(['--database', db_name])
|
||||
|
||||
# Obtener el registro de la base de datos
|
||||
registry = odoo.registry(db_name)
|
||||
|
||||
# Crear cursor y environment
|
||||
with registry.cursor() as cr:
|
||||
env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
|
||||
|
||||
try:
|
||||
# Verificar datos
|
||||
check_order_s00025(env)
|
||||
cr.commit()
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ Error: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
62
test/check_patient_data.py
Normal file
62
test/check_patient_data.py
Normal file
|
@ -0,0 +1,62 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Script para verificar los datos de pacientes en las órdenes de laboratorio
|
||||
"""
|
||||
|
||||
import odoo
|
||||
|
||||
def check_patient_data(env):
|
||||
"""Verificar datos de pacientes en órdenes recientes"""
|
||||
|
||||
# Buscar órdenes de laboratorio recientes
|
||||
orders = env['sale.order'].search([
|
||||
('is_lab_request', '=', True),
|
||||
('name', 'in', ['S00032', 'S00033'])
|
||||
], order='id desc', limit=5)
|
||||
|
||||
if not orders:
|
||||
orders = env['sale.order'].search([
|
||||
('is_lab_request', '=', True)
|
||||
], order='id desc', limit=5)
|
||||
|
||||
print(f"Verificando {len(orders)} órdenes de laboratorio...")
|
||||
|
||||
for order in orders:
|
||||
patient = order.partner_id
|
||||
print(f"\nOrden: {order.name}")
|
||||
print(f" Paciente: {patient.name}")
|
||||
print(f" ID Paciente: {patient.id}")
|
||||
print(f" Fecha de nacimiento: {patient.birthdate_date}")
|
||||
print(f" Edad: {patient.age if patient.birthdate_date else 'N/A'}")
|
||||
print(f" Género: {patient.gender or 'No especificado'}")
|
||||
print(f" ¿Tiene campo age?: {hasattr(patient, 'age')}")
|
||||
print(f" ¿Tiene campo birthdate_date?: {hasattr(patient, 'birthdate_date')}")
|
||||
|
||||
# Verificar si podemos acceder a los campos
|
||||
try:
|
||||
age_value = patient.age
|
||||
print(f" Valor de age: {age_value}")
|
||||
except Exception as e:
|
||||
print(f" Error al acceder a age: {str(e)}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Configuración
|
||||
db_name = 'lims_demo'
|
||||
|
||||
# Conectar a Odoo
|
||||
odoo.tools.config.parse_config(['--database', db_name])
|
||||
|
||||
# Obtener el registro de la base de datos
|
||||
registry = odoo.registry(db_name)
|
||||
|
||||
# Crear cursor y environment
|
||||
with registry.cursor() as cr:
|
||||
env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
|
||||
|
||||
try:
|
||||
# Verificar datos
|
||||
check_patient_data(env)
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ Error: {str(e)}")
|
226
test/create_validated_tests.py
Normal file
226
test/create_validated_tests.py
Normal file
|
@ -0,0 +1,226 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Script para crear órdenes de laboratorio con resultados validados para probar el reporte PDF
|
||||
"""
|
||||
|
||||
import odoo
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
import random
|
||||
from odoo import fields
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def create_validated_lab_order(env):
|
||||
"""Crear una orden de laboratorio con resultados completos y validados"""
|
||||
|
||||
# Obtener o crear paciente y doctor demo
|
||||
patient = env['res.partner'].search([('is_patient', '=', True)], limit=1)
|
||||
if not patient:
|
||||
patient = env['res.partner'].create({
|
||||
'name': 'Juan Pérez (Demo)',
|
||||
'is_patient': True,
|
||||
'birthdate_date': '1980-05-15',
|
||||
'gender': 'male',
|
||||
'vat': '12345678',
|
||||
})
|
||||
|
||||
doctor = env['res.partner'].search([('is_doctor', '=', True)], limit=1)
|
||||
if not doctor:
|
||||
doctor = env['res.partner'].create({
|
||||
'name': 'Dr. María García (Demo)',
|
||||
'is_doctor': True,
|
||||
})
|
||||
|
||||
# Usar usuario admin como técnico y validador
|
||||
admin_user = env.ref('base.user_admin')
|
||||
technician = admin_user
|
||||
validator = admin_user
|
||||
|
||||
# Obtener análisis disponibles
|
||||
hemograma = env.ref('lims_management.analysis_hemograma')
|
||||
glucosa = env.ref('lims_management.analysis_glucosa')
|
||||
perfil_lipidico = env.ref('lims_management.analysis_perfil_lipidico')
|
||||
|
||||
# Crear orden de laboratorio
|
||||
order = env['sale.order'].create({
|
||||
'partner_id': patient.id,
|
||||
'doctor_id': doctor.id,
|
||||
'is_lab_request': True,
|
||||
'lab_notes': 'Paciente en ayunas de 12 horas. Control de rutina anual.',
|
||||
'order_line': [
|
||||
(0, 0, {
|
||||
'product_id': hemograma.product_variant_id.id,
|
||||
'product_uom_qty': 1,
|
||||
'price_unit': hemograma.list_price,
|
||||
}),
|
||||
(0, 0, {
|
||||
'product_id': glucosa.product_variant_id.id,
|
||||
'product_uom_qty': 1,
|
||||
'price_unit': glucosa.list_price,
|
||||
}),
|
||||
(0, 0, {
|
||||
'product_id': perfil_lipidico.product_variant_id.id,
|
||||
'product_uom_qty': 1,
|
||||
'price_unit': perfil_lipidico.list_price,
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
||||
# Confirmar orden (genera muestras y pruebas automáticamente)
|
||||
order.action_confirm()
|
||||
|
||||
_logger.info(f"Orden creada: {order.name}")
|
||||
|
||||
# Primero, marcar todas las muestras como recolectadas
|
||||
for sample in order.generated_sample_ids:
|
||||
sample.write({'state': 'collected'})
|
||||
|
||||
# Procesar cada prueba
|
||||
for test in order.lab_test_ids:
|
||||
# Asignar técnico
|
||||
test.write({
|
||||
'technician_id': technician.id
|
||||
})
|
||||
|
||||
# Generar resultados si no existen
|
||||
if not test.result_ids:
|
||||
# Usar sudo para evitar restricciones de permisos y llamar método interno
|
||||
test.sudo()._generate_test_results()
|
||||
_logger.info(f"Generados {len(test.result_ids)} resultados para prueba {test.name}")
|
||||
|
||||
# Cambiar estado a in_process
|
||||
test.write({'state': 'in_process'})
|
||||
|
||||
# Ingresar resultados según el tipo de análisis
|
||||
for result in test.result_ids:
|
||||
parameter = result.parameter_id
|
||||
vals = {}
|
||||
|
||||
# Solo procesar parámetros numéricos
|
||||
if parameter.value_type == 'numeric':
|
||||
# Generar valores basados en el parámetro
|
||||
if parameter.code == 'HGB': # Hemoglobina
|
||||
vals['value_numeric'] = random.uniform(11.0, 16.5) # Algunos fuera de rango
|
||||
elif parameter.code == 'HCT': # Hematocrito
|
||||
vals['value_numeric'] = random.uniform(35.0, 48.0)
|
||||
elif parameter.code == 'WBC': # Leucocitos
|
||||
vals['value_numeric'] = random.uniform(3.5, 11.0) # Algunos fuera de rango
|
||||
elif parameter.code == 'PLT': # Plaquetas
|
||||
vals['value_numeric'] = random.uniform(140, 450)
|
||||
elif parameter.code == 'RBC': # Eritrocitos
|
||||
vals['value_numeric'] = random.uniform(3.8, 5.8)
|
||||
elif parameter.code == 'GLU': # Glucosa
|
||||
vals['value_numeric'] = random.uniform(65, 125) # Algunos elevados
|
||||
elif parameter.code == 'CHOL': # Colesterol
|
||||
vals['value_numeric'] = random.uniform(150, 240) # Algunos elevados
|
||||
elif parameter.code == 'TRIG': # Triglicéridos
|
||||
vals['value_numeric'] = random.uniform(40, 200) # Algunos elevados
|
||||
elif parameter.code == 'HDL': # HDL
|
||||
vals['value_numeric'] = random.uniform(35, 65)
|
||||
elif parameter.code == 'LDL': # LDL
|
||||
vals['value_numeric'] = random.uniform(70, 160)
|
||||
else:
|
||||
# Valor genérico para otros parámetros numéricos
|
||||
if result.applicable_range_id:
|
||||
# Generar valor cercano al rango normal
|
||||
min_val = result.applicable_range_id.normal_min or 0
|
||||
max_val = result.applicable_range_id.normal_max or 100
|
||||
vals['value_numeric'] = random.uniform(min_val * 0.8, max_val * 1.2)
|
||||
else:
|
||||
# Valor por defecto si no hay rango
|
||||
vals['value_numeric'] = random.uniform(1, 100)
|
||||
|
||||
_logger.info(f"Asignando valor numérico {vals.get('value_numeric', 0):.2f} a {parameter.name} ({parameter.code})")
|
||||
elif parameter.value_type == 'text':
|
||||
vals['value_text'] = "Normal"
|
||||
elif parameter.value_type == 'selection':
|
||||
vals['value_selection'] = "normal"
|
||||
elif parameter.value_type == 'boolean':
|
||||
vals['value_boolean'] = False
|
||||
|
||||
# Escribir valores
|
||||
if vals:
|
||||
result.write(vals)
|
||||
|
||||
# Agregar notas si está fuera de rango
|
||||
if result.is_out_of_range:
|
||||
if result.is_critical:
|
||||
result.notes = "Valor crítico. Se recomienda repetir el análisis y consultar con el médico de inmediato."
|
||||
else:
|
||||
result.notes = "Valor ligeramente alterado. Se sugiere control en 3 meses."
|
||||
|
||||
# Marcar resultados como ingresados
|
||||
test.write({'state': 'result_entered'})
|
||||
|
||||
# Agregar comentarios a algunas pruebas
|
||||
if test.product_id == hemograma:
|
||||
test.notes = "Serie roja dentro de parámetros normales. Serie blanca con ligera leucocitosis."
|
||||
elif test.product_id == perfil_lipidico:
|
||||
test.notes = "Se recomienda dieta baja en grasas y control en 3 meses."
|
||||
|
||||
# Validar todas las pruebas
|
||||
for test in order.lab_test_ids:
|
||||
test.write({
|
||||
'state': 'validated',
|
||||
'validator_id': validator.id,
|
||||
'validation_date': fields.Datetime.now()
|
||||
})
|
||||
|
||||
_logger.info(f"Todas las pruebas validadas para orden {order.name}")
|
||||
|
||||
return order
|
||||
|
||||
|
||||
def create_multiple_test_orders(env, count=3):
|
||||
"""Crear múltiples órdenes con diferentes escenarios"""
|
||||
orders = env['sale.order']
|
||||
|
||||
for i in range(count):
|
||||
_logger.info(f"Creando orden {i+1} de {count}")
|
||||
order = create_validated_lab_order(env)
|
||||
orders |= order
|
||||
|
||||
# Variar las observaciones
|
||||
if i == 1:
|
||||
order.lab_notes = "Paciente diabético tipo 2. Control mensual de glucemia."
|
||||
elif i == 2:
|
||||
order.lab_notes = "Control post-operatorio. Paciente con antecedentes de anemia."
|
||||
|
||||
return orders
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Configuración
|
||||
db_name = 'lims_demo'
|
||||
|
||||
# Conectar a Odoo
|
||||
odoo.tools.config.parse_config(['--database', db_name])
|
||||
|
||||
# Obtener el registro de la base de datos
|
||||
registry = odoo.registry(db_name)
|
||||
|
||||
# Crear cursor y environment
|
||||
with registry.cursor() as cr:
|
||||
env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
|
||||
|
||||
try:
|
||||
# Crear órdenes con resultados validados
|
||||
orders = create_multiple_test_orders(env, count=2)
|
||||
|
||||
# Confirmar cambios
|
||||
cr.commit()
|
||||
|
||||
print(f"\n✅ Se crearon {len(orders)} órdenes de laboratorio con resultados validados:")
|
||||
for order in orders:
|
||||
print(f" - {order.name}: {order.partner_id.name}")
|
||||
print(f" Pruebas: {', '.join(test.product_id.name for test in order.lab_test_ids)}")
|
||||
|
||||
print("\n📋 Ahora puedes probar el botón 'Imprimir Informe de Resultados' en estas órdenes.")
|
||||
|
||||
except Exception as e:
|
||||
cr.rollback()
|
||||
print(f"\n❌ Error: {str(e)}")
|
||||
_logger.error(f"Error creando datos demo: {str(e)}", exc_info=True)
|
65
test/update_patient_birthdate.py
Normal file
65
test/update_patient_birthdate.py
Normal file
|
@ -0,0 +1,65 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Script para actualizar la fecha de nacimiento de los pacientes existentes
|
||||
"""
|
||||
|
||||
import odoo
|
||||
from datetime import date, timedelta
|
||||
import random
|
||||
|
||||
def update_patient_birthdates(env):
|
||||
"""Actualizar fechas de nacimiento de pacientes existentes"""
|
||||
|
||||
# Buscar pacientes sin fecha de nacimiento
|
||||
patients = env['res.partner'].search([
|
||||
('is_patient', '=', True),
|
||||
('birthdate_date', '=', False)
|
||||
])
|
||||
|
||||
if patients:
|
||||
print(f"Actualizando {len(patients)} pacientes sin fecha de nacimiento...")
|
||||
|
||||
for patient in patients:
|
||||
# Generar una edad aleatoria entre 20 y 70 años
|
||||
age_years = random.randint(20, 70)
|
||||
birthdate = date.today() - timedelta(days=age_years * 365 + random.randint(0, 364))
|
||||
|
||||
# Actualizar fecha de nacimiento
|
||||
patient.write({
|
||||
'birthdate_date': birthdate.strftime('%Y-%m-%d')
|
||||
})
|
||||
|
||||
print(f" - {patient.name}: {birthdate.strftime('%Y-%m-%d')} ({age_years} años)")
|
||||
else:
|
||||
print("Todos los pacientes ya tienen fecha de nacimiento.")
|
||||
|
||||
return patients
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Configuración
|
||||
db_name = 'lims_demo'
|
||||
|
||||
# Conectar a Odoo
|
||||
odoo.tools.config.parse_config(['--database', db_name])
|
||||
|
||||
# Obtener el registro de la base de datos
|
||||
registry = odoo.registry(db_name)
|
||||
|
||||
# Crear cursor y environment
|
||||
with registry.cursor() as cr:
|
||||
env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
|
||||
|
||||
try:
|
||||
# Actualizar pacientes
|
||||
patients = update_patient_birthdates(env)
|
||||
|
||||
# Confirmar cambios
|
||||
cr.commit()
|
||||
|
||||
if patients:
|
||||
print(f"\n✅ Se actualizaron {len(patients)} pacientes con fecha de nacimiento.")
|
||||
|
||||
except Exception as e:
|
||||
cr.rollback()
|
||||
print(f"\n❌ Error: {str(e)}")
|
Loading…
Reference in New Issue
Block a user