diff --git a/lims_management/models/__pycache__/product.cpython-312.pyc b/lims_management/models/__pycache__/product.cpython-312.pyc index 945c75b..7c7c822 100644 Binary files a/lims_management/models/__pycache__/product.cpython-312.pyc and b/lims_management/models/__pycache__/product.cpython-312.pyc differ diff --git a/lims_management/models/__pycache__/sale_order.cpython-312.pyc b/lims_management/models/__pycache__/sale_order.cpython-312.pyc index deaa781..7ad7754 100644 Binary files a/lims_management/models/__pycache__/sale_order.cpython-312.pyc and b/lims_management/models/__pycache__/sale_order.cpython-312.pyc differ diff --git a/lims_management/models/__pycache__/stock_lot.cpython-312.pyc b/lims_management/models/__pycache__/stock_lot.cpython-312.pyc index 35ea7ee..484c66e 100644 Binary files a/lims_management/models/__pycache__/stock_lot.cpython-312.pyc and b/lims_management/models/__pycache__/stock_lot.cpython-312.pyc differ diff --git a/lims_management/models/sale_order.py b/lims_management/models/sale_order.py index a9217b4..e3cc54b 100644 --- a/lims_management/models/sale_order.py +++ b/lims_management/models/sale_order.py @@ -1,5 +1,9 @@ # -*- coding: utf-8 -*- -from odoo import models, fields +from odoo import models, fields, api, _ +from odoo.exceptions import UserError +import logging + +_logger = logging.getLogger(__name__) class SaleOrder(models.Model): _inherit = 'sale.order' @@ -17,3 +21,145 @@ class SaleOrder(models.Model): domain="[('is_doctor', '=', True)]", help="The doctor who referred the patient for this laboratory request." ) + + generated_sample_ids = fields.Many2many( + 'stock.lot', + 'sale_order_stock_lot_rel', + 'order_id', + 'lot_id', + string='Muestras Generadas', + domain="[('is_lab_sample', '=', True)]", + readonly=True, + help="Laboratory samples automatically generated when this order was confirmed" + ) + + def action_confirm(self): + """Override to generate laboratory samples automatically""" + res = super(SaleOrder, self).action_confirm() + + # Generate samples only for laboratory requests + for order in self.filtered('is_lab_request'): + try: + order._generate_lab_samples() + except Exception as e: + _logger.error(f"Error generating samples for order {order.name}: {str(e)}") + # Continue with order confirmation even if sample generation fails + # But notify the user + order.message_post( + body=_("Error al generar muestras automáticamente: %s. " + "Por favor, genere las muestras manualmente.") % str(e), + message_type='notification' + ) + + return res + + def _generate_lab_samples(self): + """Generate laboratory samples based on the analyses in the order""" + self.ensure_one() + _logger.info(f"Generating laboratory samples for order {self.name}") + + # Group analyses by sample type + sample_groups = self._group_analyses_by_sample_type() + + if not sample_groups: + _logger.warning(f"No analyses with sample types found in order {self.name}") + return + + # Create samples for each group + created_samples = self.env['stock.lot'] + + for sample_type_id, group_data in sample_groups.items(): + sample = self._create_sample_for_group(group_data) + if sample: + created_samples |= sample + + # Link created samples to the order + if created_samples: + self.generated_sample_ids = [(6, 0, created_samples.ids)] + _logger.info(f"Created {len(created_samples)} samples for order {self.name}") + + # Post message with created samples + sample_list = "" + + self.message_post( + body=_("Muestras generadas automáticamente: %s") % sample_list, + message_type='notification' + ) + + def _group_analyses_by_sample_type(self): + """Group order lines by required sample type""" + groups = {} + + for line in self.order_line: + product = line.product_id + + # Skip non-analysis products + if not product.is_analysis: + continue + + # Check if analysis has a required sample type + if not product.required_sample_type_id: + _logger.warning( + f"Analysis {product.name} has no required sample type defined" + ) + # Post warning message + self.message_post( + body=_("Advertencia: El análisis '%s' no tiene tipo de muestra definido") % product.name, + message_type='notification' + ) + continue + + sample_type = product.required_sample_type_id + + # Initialize group if not exists + if sample_type.id not in groups: + groups[sample_type.id] = { + 'sample_type': sample_type, + 'lines': [], + 'total_volume': 0.0, + 'analyses': [] + } + + # Add line to group + groups[sample_type.id]['lines'].append(line) + groups[sample_type.id]['analyses'].append(product.name) + groups[sample_type.id]['total_volume'] += (product.sample_volume_ml or 0.0) * line.product_uom_qty + + return groups + + def _create_sample_for_group(self, group_data): + """Create a single sample for a group of analyses""" + try: + sample_type = group_data['sample_type'] + + # Prepare sample values + vals = { + 'product_id': sample_type.product_variant_id.id, + 'patient_id': self.partner_id.id, + 'doctor_id': self.doctor_id.id if self.doctor_id else False, + 'origin': self.name, + 'sample_type_product_id': sample_type.id, + 'volume_ml': group_data['total_volume'], + 'is_lab_sample': True, + 'state': 'pending_collection', + 'analysis_names': ', '.join(group_data['analyses'][:3]) + + ('...' if len(group_data['analyses']) > 3 else '') + } + + # Create the sample + sample = self.env['stock.lot'].create(vals) + + _logger.info( + f"Created sample {sample.name} for {len(group_data['analyses'])} analyses" + ) + + return sample + + except Exception as e: + _logger.error(f"Error creating sample: {str(e)}") + raise UserError( + _("Error al crear muestra para %s: %s") % (sample_type.name, str(e)) + ) diff --git a/lims_management/models/stock_lot.py b/lims_management/models/stock_lot.py index e24cbf6..1cbb2a7 100644 --- a/lims_management/models/stock_lot.py +++ b/lims_management/models/stock_lot.py @@ -40,8 +40,31 @@ class StockLot(models.Model): string='Collected by', default=lambda self: self.env.user ) + + doctor_id = fields.Many2one( + 'res.partner', + string='Médico Referente', + domain="[('is_doctor', '=', True)]", + help="Médico que ordenó los análisis" + ) + + origin = fields.Char( + string='Origen', + help="Referencia a la orden de laboratorio que generó esta muestra" + ) + + volume_ml = fields.Float( + string='Volumen (ml)', + help="Volumen total de muestra requerido" + ) + + analysis_names = fields.Char( + string='Análisis', + help="Lista de análisis que se realizarán con esta muestra" + ) state = fields.Selection([ + ('pending_collection', 'Pendiente de Recolección'), ('collected', 'Recolectada'), ('received', 'Recibida en Laboratorio'), ('in_process', 'En Proceso'), diff --git a/pr_description_issue44.txt b/pr_description_issue44.txt new file mode 100644 index 0000000..a1d9983 --- /dev/null +++ b/pr_description_issue44.txt @@ -0,0 +1,37 @@ +## Resumen + +Este Pull Request implementa la relación entre análisis y tipos de muestra (Issue #44), estableciendo la base necesaria para la automatización de generación de muestras (Issue #32). + +## Cambios principales + +### 1. Modelos +- **ProductTemplate**: Añadidos campos `required_sample_type_id` y `sample_volume_ml` para definir requisitos de muestra en análisis +- **StockLot**: Añadido campo `sample_type_product_id` manteniendo compatibilidad con `container_type` + +### 2. Vistas +- Actualización de vistas de análisis para mostrar campos de tipo de muestra +- Actualización de vistas de stock.lot con nuevo campo de tipo de muestra +- Visualización de relaciones test-muestra en listas y formularios + +### 3. Datos +- Creación de 10 tipos de muestra comunes (Tubo Suero, EDTA, Orina, etc.) +- Actualización de análisis demo con tipos de muestra requeridos +- Actualización de muestras demo con referencias a productos tipo muestra + +### 4. Herramientas +- Script de verificación `verify_sample_relationships.py` para validar la implementación +- Documentación completa en `ISSUE44_IMPLEMENTATION.md` + +## Compatibilidad + +- Mantiene compatibilidad total con el campo legacy `container_type` +- Sincronización automática entre campos viejos y nuevos +- Sin ruptura de funcionalidad existente + +## Pruebas + +Todas las tareas fueron probadas individualmente con reinicio de instancia efímera y verificación de logs sin errores. + +## Próximos pasos + +Con esta base implementada, el Issue #32 puede proceder con la automatización de generación de muestras al confirmar órdenes de laboratorio. \ No newline at end of file