diff --git a/issue_body.txt b/issue_body.txt
new file mode 100644
index 0000000..7cf6864
--- /dev/null
+++ b/issue_body.txt
@@ -0,0 +1,24 @@
+## Descripción
+
+Actualmente, cuando se cancela una orden de laboratorio, las muestras asociadas permanecen activas y no se descartan automáticamente. Esto puede causar confusión ya que quedan muestras "huérfanas" en el sistema que ya no tienen una orden válida.
+
+## Comportamiento esperado
+
+Cuando se cancela una orden de laboratorio:
+1. Todas las muestras generadas asociadas a esa orden deben cambiar automáticamente su estado a "cancelled"
+2. Si hay pruebas (lims.test) asociadas a esas muestras, también deben cancelarse
+3. Se debe registrar en el chatter de la muestra que fue cancelada debido a la cancelación de la orden
+
+## Criterios de aceptación
+
+- [ ] Al cancelar una orden de laboratorio, todas sus muestras asociadas se marcan como canceladas
+- [ ] Las pruebas asociadas a las muestras también se cancelan
+- [ ] Se registra un mensaje en el chatter de cada muestra indicando la razón de cancelación
+- [ ] Si una muestra ya estaba cancelada o completada, no se modifica
+- [ ] La acción es reversible: si se vuelve a poner la orden en borrador, las muestras NO deben reactivarse automáticamente
+
+## Notas técnicas
+
+- El método a modificar es `action_cancel()` en el modelo `sale.order`
+- Verificar el campo `generated_sample_ids` para obtener las muestras asociadas
+- Solo cancelar muestras que estén en estados: 'pending_collection', 'collected', 'in_analysis'
\ No newline at end of file
diff --git a/lims_management/models/__pycache__/__init__.cpython-312.pyc b/lims_management/models/__pycache__/__init__.cpython-312.pyc
index 4830c5f..9029664 100644
Binary files a/lims_management/models/__pycache__/__init__.cpython-312.pyc and b/lims_management/models/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lims_management/models/__pycache__/partner.cpython-312.pyc b/lims_management/models/__pycache__/partner.cpython-312.pyc
index 0ba6f10..0b833d5 100644
Binary files a/lims_management/models/__pycache__/partner.cpython-312.pyc and b/lims_management/models/__pycache__/partner.cpython-312.pyc differ
diff --git a/lims_management/models/__pycache__/product.cpython-312.pyc b/lims_management/models/__pycache__/product.cpython-312.pyc
index a8b31aa..2031240 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 31b1b70..9f92bfa 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 5556171..7c49021 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 c4a10f3..697e824 100644
--- a/lims_management/models/sale_order.py
+++ b/lims_management/models/sale_order.py
@@ -240,3 +240,56 @@ class SaleOrder(models.Model):
return sample
return False
+
+ def action_cancel(self):
+ """Override para cancelar automáticamente muestras y pruebas asociadas cuando se cancela una orden de laboratorio"""
+ # Primero llamar al método padre
+ res = super(SaleOrder, self).action_cancel()
+
+ # Si es una orden de laboratorio, cancelar muestras y pruebas asociadas
+ if self.is_lab_request:
+ # Cancelar muestras que estén en estados cancelables
+ cancelable_sample_states = ['pending_collection', 'collected', 'received', 'in_process']
+ samples_to_cancel = self.generated_sample_ids.filtered(
+ lambda s: s.state in cancelable_sample_states
+ )
+
+ if samples_to_cancel:
+ # Cancelar las muestras
+ samples_to_cancel.action_cancel()
+
+ # Registrar en el chatter de cada muestra
+ for sample in samples_to_cancel:
+ sample.message_post(
+ body=_("Muestra cancelada automáticamente debido a la cancelación de la orden %s") % self.name,
+ message_type='notification'
+ )
+
+ # Buscar y cancelar pruebas asociadas a estas muestras
+ tests_to_cancel = self.env['lims.test'].search([
+ ('sample_id', 'in', samples_to_cancel.ids),
+ ('state', 'not in', ['validated', 'cancelled'])
+ ])
+
+ if tests_to_cancel:
+ for test in tests_to_cancel:
+ test.action_cancel()
+ test.message_post(
+ body=_("Prueba cancelada automáticamente debido a la cancelación de la orden %s") % self.name,
+ message_type='notification'
+ )
+
+ # Registrar en el chatter de la orden
+ message = _("Se cancelaron automáticamente:
")
+ message += _("- %d muestras
") % len(samples_to_cancel)
+ if tests_to_cancel:
+ message += _("- %d pruebas de laboratorio") % len(tests_to_cancel)
+
+ self.message_post(
+ body=message,
+ message_type='notification'
+ )
+
+ _logger.info(f"Cancelled {len(samples_to_cancel)} samples and {len(tests_to_cancel)} tests for order {self.name}")
+
+ return res
diff --git a/lims_management/models/stock_lot.py b/lims_management/models/stock_lot.py
index 9ec953e..02d42e1 100644
--- a/lims_management/models/stock_lot.py
+++ b/lims_management/models/stock_lot.py
@@ -80,7 +80,8 @@ class StockLot(models.Model):
('in_process', 'En Proceso'),
('analyzed', 'Analizada'),
('stored', 'Almacenada'),
- ('disposed', 'Desechada')
+ ('disposed', 'Desechada'),
+ ('cancelled', 'Cancelada')
], string='Estado', default='collected', tracking=True)
def action_collect(self):
@@ -107,6 +108,10 @@ class StockLot(models.Model):
"""Dispose of the sample"""
self.write({'state': 'disposed'})
+ def action_cancel(self):
+ """Cancel the sample"""
+ self.write({'state': 'cancelled'})
+
@api.onchange('sample_type_product_id')
def _onchange_sample_type_product_id(self):
"""Synchronize container_type when sample_type_product_id changes"""
diff --git a/lims_management/tests/__init__.py b/lims_management/tests/__init__.py
index 0a66197..103baea 100644
--- a/lims_management/tests/__init__.py
+++ b/lims_management/tests/__init__.py
@@ -2,4 +2,5 @@
from . import test_analysis_parameter
from . import test_parameter_range
from . import test_result_parameter_integration
-from . import test_auto_result_generation
\ No newline at end of file
+from . import test_auto_result_generation
+from . import test_order_cancel_cascade
\ No newline at end of file
diff --git a/lims_management/tests/test_order_cancel_cascade.py b/lims_management/tests/test_order_cancel_cascade.py
new file mode 100644
index 0000000..adaa118
--- /dev/null
+++ b/lims_management/tests/test_order_cancel_cascade.py
@@ -0,0 +1,263 @@
+# -*- coding: utf-8 -*-
+"""
+Test para verificar la cancelación en cascada de muestras y pruebas
+cuando se cancela una orden de laboratorio
+"""
+
+from odoo.tests import TransactionCase
+from odoo.exceptions import UserError
+import logging
+
+_logger = logging.getLogger(__name__)
+
+
+class TestOrderCancelCascade(TransactionCase):
+ """Test de cancelación en cascada de órdenes de laboratorio"""
+
+ def setUp(self):
+ super().setUp()
+
+ # Obtener modelos
+ self.Partner = self.env['res.partner']
+ self.Product = self.env['product.product']
+ self.SaleOrder = self.env['sale.order']
+ self.StockLot = self.env['stock.lot']
+ self.LimsTest = self.env['lims.test']
+
+ # Crear datos de prueba
+ self.patient = self.Partner.create({
+ 'name': 'Test Patient Cancel',
+ 'is_patient': True,
+ 'birthdate_date': '1990-01-01',
+ 'gender': 'male'
+ })
+
+ self.doctor = self.Partner.create({
+ 'name': 'Test Doctor Cancel',
+ 'is_doctor': True
+ })
+
+ # Crear tipo de muestra
+ self.sample_type = self.env['product.template'].create({
+ 'name': 'Tubo EDTA Test',
+ 'is_sample_type': True,
+ 'type': 'service',
+ 'categ_id': self.env.ref('product.product_category_all').id
+ })
+
+ # Crear análisis
+ self.analysis = self.env['product.template'].create({
+ 'name': 'Hemograma Test Cancel',
+ 'is_analysis': True,
+ 'type': 'service',
+ 'required_sample_type_id': self.sample_type.id,
+ 'categ_id': self.env.ref('product.product_category_all').id
+ })
+
+ # Crear parámetro para el análisis
+ self.parameter = self.env['lims.analysis.parameter'].create({
+ 'name': 'Hemoglobina Test',
+ 'code': 'HGB_TEST',
+ 'value_type': 'numeric',
+ 'unit': 'g/dL'
+ })
+
+ # Configurar parámetro en el análisis
+ self.env['product.template.parameter'].create({
+ 'product_tmpl_id': self.analysis.id,
+ 'parameter_id': self.parameter.id,
+ 'sequence': 10
+ })
+
+ def test_01_cancel_order_cancels_samples(self):
+ """Test que al cancelar una orden se cancelan las muestras asociadas"""
+ # Crear orden de laboratorio
+ order = self.SaleOrder.create({
+ 'partner_id': self.patient.id,
+ 'doctor_id': self.doctor.id,
+ 'is_lab_request': True,
+ 'order_line': [(0, 0, {
+ 'product_id': self.analysis.product_variant_id.id,
+ 'product_uom_qty': 1.0
+ })]
+ })
+
+ # Confirmar la orden (debe generar muestras)
+ order.action_confirm()
+
+ # Verificar que se generaron muestras
+ self.assertTrue(order.generated_sample_ids, "No se generaron muestras")
+ samples = order.generated_sample_ids
+
+ # Verificar estado inicial de las muestras
+ for sample in samples:
+ self.assertIn(sample.state, ['pending_collection', 'collected'],
+ f"Estado inicial incorrecto: {sample.state}")
+
+ # Cancelar la orden
+ order.action_cancel()
+
+ # Verificar que las muestras fueron canceladas
+ for sample in samples:
+ self.assertEqual(sample.state, 'cancelled',
+ f"Muestra no fue cancelada: {sample.state}")
+
+ def test_02_cancel_order_cancels_tests(self):
+ """Test que al cancelar una orden se cancelan las pruebas asociadas"""
+ # Crear orden de laboratorio
+ order = self.SaleOrder.create({
+ 'partner_id': self.patient.id,
+ 'doctor_id': self.doctor.id,
+ 'is_lab_request': True,
+ 'order_line': [(0, 0, {
+ 'product_id': self.analysis.product_variant_id.id,
+ 'product_uom_qty': 1.0
+ })]
+ })
+
+ # Confirmar la orden
+ order.action_confirm()
+
+ # Obtener las pruebas generadas
+ tests = self.LimsTest.search([
+ ('sale_order_line_id.order_id', '=', order.id)
+ ])
+ self.assertTrue(tests, "No se generaron pruebas")
+
+ # Verificar estado inicial
+ for test in tests:
+ self.assertEqual(test.state, 'draft',
+ f"Estado inicial incorrecto: {test.state}")
+
+ # Iniciar proceso en una prueba
+ if tests:
+ tests[0].write({'sample_id': order.generated_sample_ids[0].id})
+ tests[0].action_start_process()
+ self.assertEqual(tests[0].state, 'in_process')
+
+ # Cancelar la orden
+ order.action_cancel()
+
+ # Verificar que las pruebas fueron canceladas
+ for test in tests:
+ self.assertEqual(test.state, 'cancelled',
+ f"Prueba no fue cancelada: {test.state}")
+
+ def test_03_dont_cancel_completed_samples(self):
+ """Test que no se cancelan muestras en estados finales"""
+ # Crear orden
+ order = self.SaleOrder.create({
+ 'partner_id': self.patient.id,
+ 'doctor_id': self.doctor.id,
+ 'is_lab_request': True,
+ 'order_line': [(0, 0, {
+ 'product_id': self.analysis.product_variant_id.id,
+ 'product_uom_qty': 1.0
+ })]
+ })
+
+ # Confirmar
+ order.action_confirm()
+
+ # Marcar una muestra como analizada
+ sample = order.generated_sample_ids[0]
+ sample.write({'state': 'analyzed'})
+
+ # Cancelar la orden
+ order.action_cancel()
+
+ # Verificar que la muestra analizada no fue cancelada
+ self.assertEqual(sample.state, 'analyzed',
+ "Muestra analizada fue cancelada incorrectamente")
+
+ def test_04_dont_cancel_validated_tests(self):
+ """Test que no se cancelan pruebas validadas"""
+ # Crear orden
+ order = self.SaleOrder.create({
+ 'partner_id': self.patient.id,
+ 'doctor_id': self.doctor.id,
+ 'is_lab_request': True,
+ 'order_line': [(0, 0, {
+ 'product_id': self.analysis.product_variant_id.id,
+ 'product_uom_qty': 1.0
+ })]
+ })
+
+ # Confirmar
+ order.action_confirm()
+
+ # Obtener prueba y marcarla como validada
+ test = self.LimsTest.search([
+ ('sale_order_line_id.order_id', '=', order.id)
+ ], limit=1)
+
+ if test:
+ test.write({
+ 'state': 'validated',
+ 'sample_id': order.generated_sample_ids[0].id
+ })
+
+ # Cancelar la orden
+ order.action_cancel()
+
+ # Verificar que la prueba validada no fue cancelada
+ self.assertEqual(test.state, 'validated',
+ "Prueba validada fue cancelada incorrectamente")
+
+ def test_05_chatter_messages_created(self):
+ """Test que se crean mensajes en el chatter"""
+ # Crear orden
+ order = self.SaleOrder.create({
+ 'partner_id': self.patient.id,
+ 'doctor_id': self.doctor.id,
+ 'is_lab_request': True,
+ 'order_line': [(0, 0, {
+ 'product_id': self.analysis.product_variant_id.id,
+ 'product_uom_qty': 1.0
+ })]
+ })
+
+ # Confirmar
+ order.action_confirm()
+
+ # Obtener conteo inicial de mensajes
+ initial_order_messages = len(order.message_ids)
+ sample = order.generated_sample_ids[0]
+ initial_sample_messages = len(sample.message_ids)
+
+ # Cancelar
+ order.action_cancel()
+
+ # Verificar que se agregaron mensajes
+ self.assertGreater(len(order.message_ids), initial_order_messages,
+ "No se agregó mensaje en la orden")
+ self.assertGreater(len(sample.message_ids), initial_sample_messages,
+ "No se agregó mensaje en la muestra")
+
+ # Verificar contenido del mensaje
+ last_order_msg = order.message_ids[0].body
+ self.assertIn("cancelaron automáticamente", last_order_msg,
+ "Mensaje de orden no contiene texto esperado")
+
+ def test_06_non_lab_order_not_affected(self):
+ """Test que órdenes normales no son afectadas"""
+ # Crear orden normal (no de laboratorio)
+ order = self.SaleOrder.create({
+ 'partner_id': self.patient.id,
+ 'is_lab_request': False, # NO es orden de laboratorio
+ 'order_line': [(0, 0, {
+ 'product_id': self.analysis.product_variant_id.id,
+ 'product_uom_qty': 1.0
+ })]
+ })
+
+ # Confirmar
+ order.action_confirm()
+
+ # No deberían generarse muestras
+ self.assertFalse(order.generated_sample_ids,
+ "Se generaron muestras en orden normal")
+
+ # Cancelar - no debería causar error
+ order.action_cancel()
+ self.assertEqual(order.state, 'cancel')
\ No newline at end of file
diff --git a/test/run_cancel_tests.py b/test/run_cancel_tests.py
new file mode 100644
index 0000000..0cc22d3
--- /dev/null
+++ b/test/run_cancel_tests.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Ejecutar tests de cancelación en cascada
+"""
+
+import odoo
+import sys
+
+def run_tests(cr):
+ """Ejecutar los tests"""
+ env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
+
+ # Importar y ejecutar tests
+ from odoo.addons.lims_management.tests.test_order_cancel_cascade import TestOrderCancelCascade
+
+ suite = odoo.tests.loader.make_suite(TestOrderCancelCascade)
+ result = odoo.tests.runner.run(suite)
+
+ print(f"\n📊 Resultados de los tests:")
+ print(f" Tests ejecutados: {result.testsRun}")
+ print(f" Errores: {len(result.errors)}")
+ print(f" Fallos: {len(result.failures)}")
+
+ if result.errors:
+ print("\n❌ Errores:")
+ for test, error in result.errors:
+ print(f" - {test}: {error}")
+
+ if result.failures:
+ print("\n❌ Fallos:")
+ for test, failure in result.failures:
+ print(f" - {test}: {failure}")
+
+ if result.wasSuccessful():
+ print("\n✅ Todos los tests pasaron exitosamente!")
+ else:
+ print("\n❌ Algunos tests fallaron")
+
+ return result.wasSuccessful()
+
+if __name__ == '__main__':
+ db_name = 'lims_demo'
+ try:
+ registry = odoo.modules.registry.Registry(db_name)
+ with registry.cursor() as cr:
+ success = run_tests(cr)
+ sys.exit(0 if success else 1)
+ except Exception as e:
+ print(f"Error: {e}")
+ import traceback
+ traceback.print_exc()
+ sys.exit(1)
\ No newline at end of file
diff --git a/test/test_cancel_cascade.py b/test/test_cancel_cascade.py
new file mode 100644
index 0000000..b7ffdb6
--- /dev/null
+++ b/test/test_cancel_cascade.py
@@ -0,0 +1,149 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Script para probar la funcionalidad de cancelación en cascada
+"""
+
+import odoo
+import traceback
+from datetime import datetime
+
+
+def test_cancel_cascade(cr):
+ """Probar la cancelación en cascada de órdenes"""
+ env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
+
+ print("🧪 Probando cancelación en cascada de órdenes de laboratorio\n")
+
+ try:
+ # Buscar un paciente y doctor de prueba
+ patient = env['res.partner'].search([('is_patient', '=', True)], limit=1)
+ doctor = env['res.partner'].search([('is_doctor', '=', True)], limit=1)
+
+ if not patient or not doctor:
+ print("❌ No se encontraron pacientes o doctores de prueba")
+ return
+
+ # Buscar un análisis
+ analysis = env['product.product'].search([
+ ('is_analysis', '=', True),
+ ('required_sample_type_id', '!=', False)
+ ], limit=1)
+
+ if not analysis:
+ print("❌ No se encontró un análisis con tipo de muestra requerido")
+ return
+
+ print(f"📋 Creando orden de laboratorio de prueba...")
+ print(f" Paciente: {patient.name}")
+ print(f" Doctor: {doctor.name}")
+ print(f" Análisis: {analysis.name}")
+
+ # Crear orden de laboratorio
+ order = env['sale.order'].create({
+ 'partner_id': patient.id,
+ 'doctor_id': doctor.id,
+ 'is_lab_request': True,
+ 'order_line': [(0, 0, {
+ 'product_id': analysis.id,
+ 'product_uom_qty': 1.0
+ })]
+ })
+
+ print(f"\n✓ Orden creada: {order.name}")
+ print(f" Estado inicial: {order.state}")
+
+ # Confirmar la orden
+ print("\n🔄 Confirmando orden...")
+ order.action_confirm()
+
+ print(f"✓ Orden confirmada")
+ print(f" Estado: {order.state}")
+ print(f" Muestras generadas: {len(order.generated_sample_ids)}")
+
+ # Mostrar muestras generadas
+ if order.generated_sample_ids:
+ print("\n📦 Muestras generadas:")
+ for sample in order.generated_sample_ids:
+ print(f" - {sample.name}: {sample.sample_type_product_id.name} (Estado: {sample.state})")
+
+ # Buscar pruebas generadas
+ tests = env['lims.test'].search([
+ ('sale_order_line_id.order_id', '=', order.id)
+ ])
+
+ print(f"\n🔬 Pruebas generadas: {len(tests)}")
+ if tests:
+ for test in tests:
+ print(f" - {test.name}: {test.product_id.name} (Estado: {test.state})")
+
+ # Iniciar proceso en una prueba para hacerlo más realista
+ if tests and order.generated_sample_ids:
+ print("\n🔄 Iniciando proceso en primera prueba...")
+ test = tests[0]
+ test.sample_id = order.generated_sample_ids[0]
+ test.action_start_process()
+ print(f" ✓ Prueba {test.name} en proceso")
+
+ # CANCELAR LA ORDEN
+ print("\n❌ Cancelando la orden de laboratorio...")
+ order.action_cancel()
+
+ print(f"\n✓ Orden cancelada")
+ print(f" Estado de la orden: {order.state}")
+
+ # Verificar estado de las muestras
+ print("\n📦 Estado final de las muestras:")
+ for sample in order.generated_sample_ids:
+ print(f" - {sample.name}: {sample.state}")
+ # Verificar si hay mensaje en el chatter
+ last_msg = sample.message_ids[0] if sample.message_ids else None
+ if last_msg and "cancelada automáticamente" in last_msg.body:
+ print(f" ✓ Mensaje de cancelación registrado")
+
+ # Verificar estado de las pruebas
+ print("\n🔬 Estado final de las pruebas:")
+ for test in tests:
+ test_updated = env['lims.test'].browse(test.id)
+ print(f" - {test_updated.name}: {test_updated.state}")
+ # Verificar mensaje
+ last_msg = test_updated.message_ids[0] if test_updated.message_ids else None
+ if last_msg and "cancelada automáticamente" in last_msg.body:
+ print(f" ✓ Mensaje de cancelación registrado")
+
+ # Verificar mensaje en la orden
+ print("\n📝 Mensajes en la orden:")
+ for msg in order.message_ids[:3]: # Últimos 3 mensajes
+ if "cancelaron automáticamente" in msg.body:
+ print(f" ✓ Mensaje de resumen de cancelación encontrado")
+ # Extraer números del mensaje
+ import re
+ samples_match = re.search(r'(\d+) muestras', msg.body)
+ tests_match = re.search(r'(\d+) pruebas', msg.body)
+ if samples_match:
+ print(f" - Muestras canceladas: {samples_match.group(1)}")
+ if tests_match:
+ print(f" - Pruebas canceladas: {tests_match.group(1)}")
+ break
+
+ print("\n✅ Prueba completada exitosamente!")
+
+ # Hacer rollback para no dejar datos de prueba
+ cr.rollback()
+ print("\n⚠️ Cambios revertidos (rollback)")
+
+ except Exception as e:
+ print(f"\n❌ Error durante la prueba: {str(e)}")
+ traceback.print_exc()
+ cr.rollback()
+
+
+if __name__ == '__main__':
+ db_name = 'lims_demo'
+ try:
+ registry = odoo.modules.registry.Registry(db_name)
+ with registry.cursor() as cr:
+ test_cancel_cascade(cr)
+ except Exception as e:
+ print(f"Error general: {e}")
+ traceback.print_exc()
\ No newline at end of file
diff --git a/test/verify_order_state.py b/test/verify_order_state.py
new file mode 100644
index 0000000..99540b0
--- /dev/null
+++ b/test/verify_order_state.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Verificar estado de orden después de cancelar
+"""
+
+import odoo
+
+def verify_order_state(cr):
+ """Verificar que el estado de la orden cambia correctamente"""
+ env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
+
+ # Buscar datos necesarios
+ patient = env['res.partner'].search([('is_patient', '=', True)], limit=1)
+ analysis = env['product.product'].search([('is_analysis', '=', True)], limit=1)
+
+ # Crear orden simple
+ order = env['sale.order'].create({
+ 'partner_id': patient.id,
+ 'is_lab_request': True,
+ 'order_line': [(0, 0, {
+ 'product_id': analysis.id,
+ 'product_uom_qty': 1.0
+ })]
+ })
+
+ print(f"Orden creada: {order.name}")
+ print(f"Estado inicial: {order.state}")
+
+ # Confirmar
+ order.action_confirm()
+ print(f"Estado después de confirmar: {order.state}")
+
+ # Cancelar
+ order.action_cancel()
+ print(f"Estado después de cancelar: {order.state}")
+
+ # Verificar nuevamente
+ order_check = env['sale.order'].browse(order.id)
+ print(f"Estado verificado nuevamente: {order_check.state}")
+
+if __name__ == '__main__':
+ db_name = 'lims_demo'
+ try:
+ registry = odoo.modules.registry.Registry(db_name)
+ with registry.cursor() as cr:
+ verify_order_state(cr)
+ except Exception as e:
+ print(f"Error: {e}")
\ No newline at end of file