Merge pull request 'feat(#11): Implementar informe PDF de resultados de laboratorio' (#66) from feature/11-informe-resultados-pdf into dev

Reviewed-on: #66
This commit is contained in:
luis_portillo 2025-07-17 00:59:21 +00:00
commit ac427ff778
15 changed files with 959 additions and 9 deletions

View File

@ -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": []
}

View File

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

View File

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

View File

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

View File

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

View 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&#205;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&#243;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&#241;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&#250;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&#233;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&#193;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&#237;tico que requiere atenci&#243;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&#243;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&#225; dirigido exclusivamente al paciente y/o m&#233;dico tratante.</p>
<p>Los resultados se relacionan &#250;nicamente con las muestras analizadas.</p>
</div>
</div>
</t>
</template>
</odoo>

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

View File

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

View File

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

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

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

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

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

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