clinical_laboratory/lims_management/models/lims_test.py
Luis Ernesto Portillo Zaldivar 820c05ffa9 fix: Implementar generación automática de pruebas y mejorar selección de muestras
- Agregar método _generate_lab_tests() en sale.order para crear pruebas automáticamente al confirmar orden
- Agregar método _find_sample_for_analysis() para asociar muestras con análisis según tipo
- Mejorar dominio de sample_id en lims.test para filtrar por paciente y estado (collected/in_analysis)
- Agregar método _onchange_sale_order_line() para actualizar dominio de muestra dinámicamente
- Las pruebas ahora se crean automáticamente con la muestra correcta asignada

Esto resuelve el problema reportado donde las órdenes aprobadas no generaban pruebas
y las muestras no estaban disponibles para selección manual.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-15 01:17:37 -06:00

247 lines
7.8 KiB
Python

# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
from odoo.exceptions import UserError
import logging
_logger = logging.getLogger(__name__)
class LimsTest(models.Model):
_name = 'lims.test'
_description = 'Prueba de Laboratorio'
_inherit = ['mail.thread', 'mail.activity.mixin']
_rec_name = 'name'
_order = 'create_date desc'
name = fields.Char(
string='Código de Prueba',
required=True,
readonly=True,
copy=False,
default='Nuevo'
)
sale_order_line_id = fields.Many2one(
'sale.order.line',
string='Línea de Orden',
required=True,
ondelete='restrict'
)
patient_id = fields.Many2one(
'res.partner',
string='Paciente',
related='sale_order_line_id.order_id.partner_id',
store=True,
readonly=True
)
product_id = fields.Many2one(
'product.product',
string='Análisis',
related='sale_order_line_id.product_id',
store=True,
readonly=True
)
sample_id = fields.Many2one(
'stock.lot',
string='Muestra',
domain="[('is_lab_sample', '=', True), ('patient_id', '=', patient_id), ('state', 'in', ['collected', 'in_analysis'])]",
tracking=True
)
state = fields.Selection([
('draft', 'Borrador'),
('in_process', 'En Proceso'),
('result_entered', 'Resultado Ingresado'),
('validated', 'Validado'),
('cancelled', 'Cancelado')
], string='Estado', default='draft', tracking=True)
validator_id = fields.Many2one(
'res.users',
string='Validador',
readonly=True,
tracking=True
)
validation_date = fields.Datetime(
string='Fecha de Validación',
readonly=True,
tracking=True
)
technician_id = fields.Many2one(
'res.users',
string='Técnico',
default=lambda self: self.env.user,
tracking=True
)
require_validation = fields.Boolean(
string='Requiere Validación',
compute='_compute_require_validation',
store=True
)
result_ids = fields.One2many(
'lims.result',
'test_id',
string='Resultados'
)
notes = fields.Text(
string='Observaciones'
)
company_id = fields.Many2one(
'res.company',
string='Compañía',
required=True,
default=lambda self: self.env.company
)
@api.depends('company_id')
def _compute_require_validation(self):
"""Calcula si la prueba requiere validación basado en configuración."""
IrConfig = self.env['ir.config_parameter'].sudo()
require_validation = IrConfig.get_param('lims_management.require_validation', 'True')
for record in self:
record.require_validation = require_validation == 'True'
@api.onchange('sale_order_line_id')
def _onchange_sale_order_line(self):
"""Update sample domain when order line changes"""
if self.sale_order_line_id:
# Try to find a suitable sample from the order
order = self.sale_order_line_id.order_id
product = self.sale_order_line_id.product_id
if order.is_lab_request and product.required_sample_type_id:
# Find samples for this patient with the required sample type
suitable_samples = self.env['stock.lot'].search([
('is_lab_sample', '=', True),
('patient_id', '=', order.partner_id.id),
('sample_type_product_id', '=', product.required_sample_type_id.id),
('state', 'in', ['collected', 'in_analysis'])
])
if suitable_samples:
# If only one sample, select it automatically
if len(suitable_samples) == 1:
self.sample_id = suitable_samples[0]
# Update domain to show only suitable samples
return {
'domain': {
'sample_id': [
('id', 'in', suitable_samples.ids)
]
}
}
@api.model_create_multi
def create(self, vals_list):
"""Genera código único al crear."""
for vals in vals_list:
if vals.get('name', 'Nuevo') == 'Nuevo':
vals['name'] = self.env['ir.sequence'].next_by_code('lims.test') or 'Nuevo'
return super().create(vals_list)
def action_start_process(self):
"""Inicia el proceso de análisis."""
self.ensure_one()
if self.state != 'draft':
raise UserError(_('Solo se pueden procesar pruebas en estado borrador.'))
if not self.sample_id:
raise UserError(_('Debe asignar una muestra antes de iniciar el proceso.'))
self.write({
'state': 'in_process',
'technician_id': self.env.user.id
})
# Log en el chatter
self.message_post(
body=_('Prueba iniciada por %s') % self.env.user.name,
subject=_('Proceso Iniciado')
)
return True
def action_enter_results(self):
"""Marca como resultados ingresados."""
self.ensure_one()
if self.state != 'in_process':
raise UserError(_('Solo se pueden ingresar resultados en pruebas en proceso.'))
if not self.result_ids:
raise UserError(_('Debe ingresar al menos un resultado.'))
# Si no requiere validación, pasar directamente a validado
if not self.require_validation:
self.write({
'state': 'validated',
'validator_id': self.env.user.id,
'validation_date': fields.Datetime.now()
})
self.message_post(
body=_('Resultados ingresados y auto-validados por %s') % self.env.user.name,
subject=_('Resultados Validados')
)
else:
self.state = 'result_entered'
self.message_post(
body=_('Resultados ingresados por %s') % self.env.user.name,
subject=_('Resultados Ingresados')
)
return True
def action_validate(self):
"""Valida los resultados (solo administradores)."""
self.ensure_one()
if self.state != 'result_entered':
raise UserError(_('Solo se pueden validar pruebas con resultados ingresados.'))
# TODO: Verificar permisos cuando se implemente seguridad
self.write({
'state': 'validated',
'validator_id': self.env.user.id,
'validation_date': fields.Datetime.now()
})
# Log en el chatter
self.message_post(
body=_('Resultados validados por %s') % self.env.user.name,
subject=_('Resultados Validados')
)
return True
def action_cancel(self):
"""Cancela la prueba."""
self.ensure_one()
if self.state == 'validated':
raise UserError(_('No se pueden cancelar pruebas validadas.'))
self.state = 'cancelled'
# Log en el chatter
self.message_post(
body=_('Prueba cancelada por %s') % self.env.user.name,
subject=_('Prueba Cancelada')
)
return True
def action_draft(self):
"""Regresa a borrador."""
self.ensure_one()
if self.state not in ['cancelled']:
raise UserError(_('Solo se pueden regresar a borrador pruebas canceladas.'))
self.state = 'draft'
return True