
- Eliminar método create duplicado que sobrescribía la lógica de secuencias - Consolidar la generación de secuencias en un único método create - Agregar contexto especial para evitar validaciones durante la inicialización - Ahora todos los tests se crean con códigos secuenciales (LAB-YYYY-NNNNN) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
379 lines
18 KiB
Python
379 lines
18 KiB
Python
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 con contexto especial
|
|
result.with_context(skip_value_validation=True).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, {})
|
|
|
|
# Delete unwanted demo sale orders
|
|
unwanted_orders = env['sale.order'].search([('name', 'in', ['S00001', 'S00002', 'S00003', 'S00004', 'S00005', 'S00006', 'S00007', 'S00008', 'S00009', 'S00010', 'S00011', 'S00012', 'S00013', 'S00014', 'S00015', 'S00016', 'S00017', 'S00018', 'S00019', 'S00020', 'S00021', 'S00022'])])
|
|
for order in unwanted_orders:
|
|
try:
|
|
order.action_cancel()
|
|
except Exception:
|
|
pass
|
|
try:
|
|
unwanted_orders.unlink()
|
|
except Exception:
|
|
pass
|
|
|
|
# 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")
|
|
|
|
# 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'
|
|
})
|
|
|
|
# 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'
|
|
registry = odoo.registry(db_name)
|
|
with registry.cursor() as cr:
|
|
create_lab_requests(cr)
|
|
cr.commit() |