feat(#32): Add automatic sample generation - Task 1 completed
- Added generated_sample_ids field to sale.order model - Override action_confirm() to intercept lab order confirmation - Implemented _generate_lab_samples() main logic method - Implemented _group_analyses_by_sample_type() for grouping - Implemented _create_sample_for_group() for sample creation - Added necessary fields to stock.lot model (doctor_id, origin, volume_ml, analysis_names) - Updated state field to include 'pending_collection' state - Added proper error handling and user notifications via message_post - Successful test with ephemeral instance restart
This commit is contained in:
parent
cb0cabf2d2
commit
a9ed1a23bd
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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 = "<ul>"
|
||||
for sample in created_samples:
|
||||
sample_list += f"<li>{sample.name} - {sample.sample_type_product_id.name}</li>"
|
||||
sample_list += "</ul>"
|
||||
|
||||
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))
|
||||
)
|
||||
|
|
|
@ -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'),
|
||||
|
|
37
pr_description_issue44.txt
Normal file
37
pr_description_issue44.txt
Normal file
|
@ -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.
|
Loading…
Reference in New Issue
Block a user