diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index f15c95c..645c6df 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -23,7 +23,8 @@
"Bash(find:*)",
"Bash(true)",
"Bash(bash:*)",
- "Bash(grep:*)"
+ "Bash(grep:*)",
+ "Bash(gh pr merge:*)"
],
"deny": []
}
diff --git a/documents/logs/Etiquetas de Muestras (4).pdf b/documents/logs/Etiquetas de Muestras (4).pdf
new file mode 100644
index 0000000..483a575
Binary files /dev/null and b/documents/logs/Etiquetas de Muestras (4).pdf differ
diff --git a/documents/logs/Screenshot_1.png b/documents/logs/Screenshot_1.png
new file mode 100644
index 0000000..67faf23
Binary files /dev/null and b/documents/logs/Screenshot_1.png differ
diff --git a/documents/logs/Screenshot_2.png b/documents/logs/Screenshot_2.png
new file mode 100644
index 0000000..8cd6d3f
Binary files /dev/null and b/documents/logs/Screenshot_2.png differ
diff --git a/lims_management/__manifest__.py b/lims_management/__manifest__.py
index 95e14e7..3895a7a 100644
--- a/lims_management/__manifest__.py
+++ b/lims_management/__manifest__.py
@@ -43,6 +43,7 @@
'views/product_template_parameter_config_views.xml',
'views/parameter_dashboard_views.xml',
'views/menus.xml',
+ 'report/sample_label_report.xml',
],
'demo': [
'demo/demo_users.xml',
diff --git a/lims_management/models/__pycache__/sale_order.cpython-312.pyc b/lims_management/models/__pycache__/sale_order.cpython-312.pyc
index 9f92bfa..4a05057 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 7c49021..e690aed 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 697e824..61c6558 100644
--- a/lims_management/models/sale_order.py
+++ b/lims_management/models/sale_order.py
@@ -293,3 +293,19 @@ class SaleOrder(models.Model):
_logger.info(f"Cancelled {len(samples_to_cancel)} samples and {len(tests_to_cancel)} tests for order {self.name}")
return res
+
+ def action_print_sample_labels(self):
+ """Imprimir etiquetas de todas las muestras generadas para esta orden"""
+ self.ensure_one()
+
+ if not self.generated_sample_ids:
+ raise UserError(_('No hay muestras generadas para esta orden. Por favor, confirme la orden primero.'))
+
+ # Asegurar que todas las muestras tengan código de barras
+ self.generated_sample_ids._ensure_barcode()
+
+ # Obtener el reporte
+ report = self.env.ref('lims_management.action_report_sample_label')
+
+ # Retornar la acción de imprimir el reporte para todas las muestras
+ return report.report_action(self.generated_sample_ids)
diff --git a/lims_management/models/stock_lot.py b/lims_management/models/stock_lot.py
index de0e31b..6d8e313 100644
--- a/lims_management/models/stock_lot.py
+++ b/lims_management/models/stock_lot.py
@@ -272,3 +272,10 @@ class StockLot(models.Model):
even_sum = sum([sum(divmod(2 * d, 10)) for d in digits[-2::-2]])
total = odd_sum + even_sum
return (10 - (total % 10)) % 10
+
+ def _ensure_barcode(self):
+ """Ensure all lab samples have a barcode"""
+ for record in self:
+ if record.is_lab_sample and not record.barcode:
+ record.barcode = record._generate_unique_barcode()
+ return True
diff --git a/lims_management/report/__init__.py b/lims_management/report/__init__.py
new file mode 100644
index 0000000..7c68785
--- /dev/null
+++ b/lims_management/report/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
\ No newline at end of file
diff --git a/lims_management/report/sample_label_report.xml b/lims_management/report/sample_label_report.xml
new file mode 100644
index 0000000..23755d5
--- /dev/null
+++ b/lims_management/report/sample_label_report.xml
@@ -0,0 +1,89 @@
+
+
+
+
+
+ Formato Etiqueta Muestra
+
+ custom
+ 50
+ 100
+ Landscape
+ 2
+ 2
+ 2
+ 2
+
+ 0
+ 200
+
+
+
+
+ Etiquetas de Muestras
+ stock.lot
+ qweb-pdf
+ lims_management.report_sample_label
+ lims_management.report_sample_label
+ 'Etiquetas - ' + object.name
+ report
+
+
+
+
+
+
+
+ o_report_qweb_pdf
+
+
+
+
+
+
LABORATORIO CLÍNICO
+
+
+
+
+
Paciente:
+
ID:
+ Sin ID
+
+
+
+
+
+
Orden:
+
Tipo:
+
Fecha:
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Sin código de barras
+
+
+
+
+
+
+
Análisis:
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lims_management/views/sale_order_views.xml b/lims_management/views/sale_order_views.xml
index 9c0945f..b2b5020 100644
--- a/lims_management/views/sale_order_views.xml
+++ b/lims_management/views/sale_order_views.xml
@@ -8,6 +8,15 @@
sale.order
+
+
+
+
diff --git a/pr_body_9.txt b/pr_body_9.txt
new file mode 100644
index 0000000..fa45958
--- /dev/null
+++ b/pr_body_9.txt
@@ -0,0 +1,48 @@
+## Implementación del flujo de validación y seguridad
+
+### Cambios realizados
+
+#### 1. Ajuste de permisos base (ir.model.access.csv)
+- Recepcionista: Solo lectura en lims.test y lims.result
+- Técnico: Lectura/escritura pero sin crear/eliminar
+- Administrador: Permisos completos
+
+#### 2. Reglas de registro implementadas (lims_security.xml)
+- Recepcionistas no pueden editar pruebas
+- Técnicos solo pueden editar pruebas no validadas
+- Administradores tienen acceso completo
+
+#### 3. Validación de permisos en transiciones (lims_test.py)
+- `action_start_process()`: Solo técnicos y administradores
+- `action_enter_results()`: Solo técnicos y administradores
+- `action_validate()`: Solo administradores
+- `action_cancel()`: Técnicos (excepto validadas) y administradores
+- `action_draft()`: Solo administradores
+
+#### 4. Trazabilidad mejorada
+- stock.lot ahora hereda de mail.thread
+- Todos los cambios de estado se registran en el chatter
+- Mensajes más descriptivos con contexto
+
+#### 5. Validaciones adicionales
+- Control de transiciones de estado válidas
+- Verificación del estado de la muestra
+- Validación de resultados críticos fuera de rango
+- No se puede crear pruebas en estado != draft sin ser admin
+
+#### 6. Vistas actualizadas
+- Botones visibles solo para roles apropiados
+- Campos de resultados editables solo por técnicos/admin
+
+#### 7. Usuarios demo para pruebas
+- Usuario: `recepcionista` / Contraseña: `demo`
+- Usuario: `tecnico` / Contraseña: `demo`
+- Usuario: `administrador` / Contraseña: `demo`
+
+### Pruebas realizadas
+- Verificación de permisos por rol
+- Validación de transiciones de estado
+- Trazabilidad en chatter
+- Restricciones visuales en formularios
+
+Closes #9
\ No newline at end of file
diff --git a/test/check_sample_barcodes.py b/test/check_sample_barcodes.py
new file mode 100644
index 0000000..5ad5ada
--- /dev/null
+++ b/test/check_sample_barcodes.py
@@ -0,0 +1,128 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Script para verificar los códigos de barras de las muestras en la orden S00025
+"""
+import odoo
+import json
+from datetime import datetime
+
+def check_order_samples(cr, order_name='S00025'):
+ """Verificar las muestras y sus códigos de barras para una orden específica"""
+
+ # Buscar la orden
+ cr.execute("""
+ SELECT id, name, state, is_lab_request
+ FROM sale_order
+ WHERE name = %s
+ """, (order_name,))
+
+ order = cr.fetchone()
+ if not order:
+ print(f"❌ No se encontró la orden {order_name}")
+ return
+
+ print(f"✅ Orden encontrada: {order[1]}")
+ print(f" - ID: {order[0]}")
+ print(f" - Estado: {order[2]}")
+ print(f" - Es orden de lab: {order[3]}")
+ print("")
+
+ # Buscar las muestras asociadas a la orden
+ cr.execute("""
+ SELECT
+ sl.id,
+ sl.name,
+ sl.barcode,
+ sl.is_lab_sample,
+ sl.patient_id,
+ sl.collection_date,
+ sl.state,
+ sl.sample_type_product_id,
+ rp.name as patient_name
+ FROM stock_lot sl
+ LEFT JOIN res_partner rp ON sl.patient_id = rp.id
+ WHERE sl.id IN (
+ SELECT lot_id
+ FROM sale_order_stock_lot_rel
+ WHERE order_id = %s
+ )
+ """, (order[0],))
+
+ samples = cr.fetchall()
+
+ if not samples:
+ print(f"❌ No se encontraron muestras para la orden {order_name}")
+
+ # Verificar si hay relación en la tabla intermedia
+ cr.execute("""
+ SELECT COUNT(*)
+ FROM sale_order_stock_lot_rel
+ WHERE order_id = %s
+ """, (order[0],))
+ count = cr.fetchone()[0]
+ print(f" Registros en sale_order_stock_lot_rel: {count}")
+ return
+
+ print(f"📋 Muestras encontradas: {len(samples)}")
+ print("-" * 80)
+
+ for sample in samples:
+ print(f"Muestra ID: {sample[0]}")
+ print(f" - Nombre: {sample[1]}")
+ print(f" - Código de barras: {sample[2] or '❌ VACÍO'}")
+ print(f" - Es muestra de lab: {sample[3]}")
+ print(f" - Paciente: {sample[8]} (ID: {sample[4]})")
+ print(f" - Fecha recolección: {sample[5]}")
+ print(f" - Estado: {sample[6]}")
+ print(f" - Tipo muestra ID: {sample[7]}")
+
+ # Si no tiene código de barras, generar uno de ejemplo
+ if not sample[2]:
+ print(f" ⚠️ FALTA CÓDIGO DE BARRAS - Ejemplo generado: {datetime.now().strftime('%y%m%d')}000001")
+
+ print("-" * 40)
+
+ # Verificar el campo generated_sample_ids
+ cr.execute("""
+ SELECT COUNT(*)
+ FROM sale_order_stock_lot_rel
+ WHERE order_id = %s
+ """, (order[0],))
+
+ rel_count = cr.fetchone()[0]
+ print(f"\n📊 Resumen:")
+ print(f" - Total muestras en relación: {rel_count}")
+ print(f" - Muestras sin código de barras: {sum(1 for s in samples if not s[2])}")
+
+ # Verificar si el campo barcode es calculado o almacenado
+ cr.execute("""
+ SELECT
+ column_name,
+ data_type,
+ is_nullable,
+ column_default
+ FROM information_schema.columns
+ WHERE table_name = 'stock_lot'
+ AND column_name = 'barcode'
+ """)
+
+ col_info = cr.fetchone()
+ if col_info:
+ print(f"\n🔍 Información del campo 'barcode':")
+ print(f" - Tipo de dato: {col_info[1]}")
+ print(f" - Permite NULL: {col_info[2]}")
+ print(f" - Valor por defecto: {col_info[3]}")
+
+if __name__ == '__main__':
+ print("🔍 Verificando códigos de barras para orden S00025...")
+ print("=" * 80)
+
+ db_name = 'lims_demo'
+ try:
+ registry = odoo.registry(db_name)
+ with registry.cursor() as cr:
+ check_order_samples(cr, 'S00025')
+ except Exception as e:
+ print(f"❌ Error: {str(e)}")
+ print(" Asegúrate de ejecutar este script dentro del contenedor de Odoo")
\ No newline at end of file