# -*- 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