diff --git a/.claude/settings.local.json b/.claude/settings.local.json index b8988b6..e9b9f67 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -10,7 +10,11 @@ "Bash(git stash:*)", "Bash(git commit:*)", "Bash(docker-compose up:*)", - "Bash(docker:*)" + "Bash(docker:*)", + "Bash(curl:*)", + "Bash(mkdir:*)", + "Bash(mv:*)", + "Bash(rm:*)" ], "deny": [] } diff --git a/CLAUDE.md b/CLAUDE.md index c735b10..064b638 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -27,6 +27,8 @@ docker-compose logs odoo_init docker-compose down -v ``` +**IMPORTANT**: Odoo initialization takes approximately 5 minutes. When using docker-compose commands, set timeout to 5 minutes (300000ms) to avoid premature timeouts. + ### Instance Persistence Policy After successful installation/update, the instance must remain active for user validation. Do NOT stop the instance until user explicitly confirms testing is complete. @@ -188,10 +190,16 @@ STATE_CANCELLED = 'cancelled' - Use for basic records without complex dependencies - Place in `lims_management/demo/` - Use `noupdate="1"` to prevent reloading +- **IMPORTANT**: Do NOT create sale.order records in XML demo files - use Python scripts instead #### Python Scripts (Complex Data) For data with dependencies or business logic: +#### Test Scripts +- **IMPORTANT**: Always create test scripts inside the `test/` folder within the project directory +- Example: `test/test_sample_generation.py` +- This ensures scripts are properly organized and accessible + 1. Create script: ```python import odoo diff --git a/create_lab_requests.py b/create_lab_requests.py index 227645e..226bc25 100644 --- a/create_lab_requests.py +++ b/create_lab_requests.py @@ -16,34 +16,59 @@ def create_lab_requests(cr): except Exception: pass - # Get patient and doctor - patient1 = env.ref('lims_management.demo_patient_1') - doctor1 = env.ref('lims_management.demo_doctor_1') - patient2 = env.ref('lims_management.demo_patient_2') + try: + # Get patients and doctors - using search instead of ref to be more robust + patient1 = env['res.partner'].search([('patient_identifier', '=', 'P-A87B01'), ('is_patient', '=', True)], limit=1) + patient2 = env['res.partner'].search([('patient_identifier', '=', 'P-C45D02'), ('is_patient', '=', True)], limit=1) + doctor1 = env['res.partner'].search([('doctor_license', '=', 'L-98765'), ('is_doctor', '=', True)], limit=1) + + if not patient1: + print("Warning: Patient 1 not found, skipping lab requests creation") + return + + # Get analysis products - using search instead of ref + hemograma = env['product.template'].search([('name', '=', 'Hemograma Completo'), ('is_analysis', '=', True)], limit=1) + perfil_lipidico = env['product.template'].search([('name', '=', 'Perfil Lipídico'), ('is_analysis', '=', True)], limit=1) + glucosa = env['product.template'].search([('name', '=', 'Glucosa en Sangre'), ('is_analysis', '=', True)], limit=1) + urocultivo = env['product.template'].search([('name', '=', 'Urocultivo'), ('is_analysis', '=', True)], limit=1) + + # Create Lab Request 1 - Multiple analyses with same sample type + if patient1 and hemograma and perfil_lipidico: + order1 = env['sale.order'].create({ + 'partner_id': patient1.id, + 'doctor_id': doctor1.id if doctor1 else False, + 'is_lab_request': True, + 'order_line': [ + (0, 0, {'product_id': hemograma.product_variant_id.id, 'product_uom_qty': 1}), + (0, 0, {'product_id': perfil_lipidico.product_variant_id.id, 'product_uom_qty': 1}) + ] + }) + print(f"Created Lab Order 1: {order1.name}") + + # Confirm the order to test automatic sample generation + order1.action_confirm() + print(f"Confirmed Lab Order 1. Generated samples: {len(order1.generated_sample_ids)}") - # Get analysis products - hemograma = env.ref('lims_management.analysis_hemograma') - perfil_lipidico = env.ref('lims_management.analysis_perfil_lipidico') - - # Create Lab Request 1 - env['sale.order'].create({ - 'partner_id': patient1.id, - 'doctor_id': doctor1.id, - 'is_lab_request': True, - 'order_line': [ - (0, 0, {'product_id': hemograma.product_variant_id.id, 'product_uom_qty': 1}), - (0, 0, {'product_id': perfil_lipidico.product_variant_id.id, 'product_uom_qty': 1}) - ] - }) - - # Create Lab Request 2 - env['sale.order'].create({ - 'partner_id': patient2.id, - 'is_lab_request': True, - 'order_line': [ - (0, 0, {'product_id': hemograma.product_variant_id.id, 'product_uom_qty': 1}) - ] - }) + # Create Lab Request 2 - Different sample types + if patient2 and glucosa and urocultivo: + order2 = env['sale.order'].create({ + 'partner_id': patient2.id, + 'is_lab_request': True, + 'order_line': [ + (0, 0, {'product_id': glucosa.product_variant_id.id, 'product_uom_qty': 1}), + (0, 0, {'product_id': urocultivo.product_variant_id.id, 'product_uom_qty': 1}) + ] + }) + print(f"Created Lab Order 2: {order2.name}") + + # Confirm to test automatic sample generation with different types + order2.action_confirm() + print(f"Confirmed Lab Order 2. Generated samples: {len(order2.generated_sample_ids)}") + + except Exception as e: + print(f"Error creating lab requests: {str(e)}") + import traceback + traceback.print_exc() if __name__ == '__main__': db_name = 'lims_demo' diff --git a/documents/plans/ISSUE32_PLAN.md b/documents/plans/ISSUE32_PLAN.md index a3a1e03..dbf1845 100644 --- a/documents/plans/ISSUE32_PLAN.md +++ b/documents/plans/ISSUE32_PLAN.md @@ -153,14 +153,14 @@ graph TD ## Criterios de Aceptación -1. [ ] Al confirmar una orden de laboratorio, se generan automáticamente las muestras necesarias -2. [ ] Los análisis que requieren el mismo tipo de muestra se agrupan en un solo contenedor -3. [ ] Cada muestra tiene un código de barras único -4. [ ] Se muestra claramente qué muestras fueron generadas para cada orden -5. [ ] Se manejan adecuadamente los análisis sin tipo de muestra definido -6. [ ] El sistema registra un log de la generación para auditoría -7. [ ] La funcionalidad se puede deshabilitar si es necesario -8. [ ] No afecta el rendimiento de confirmación de órdenes regulares +1. [x] Al confirmar una orden de laboratorio, se generan automáticamente las muestras necesarias +2. [x] Los análisis que requieren el mismo tipo de muestra se agrupan en un solo contenedor +3. [x] Cada muestra tiene un código de barras único +4. [x] Se muestra claramente qué muestras fueron generadas para cada orden +5. [x] Se manejan adecuadamente los análisis sin tipo de muestra definido +6. [x] El sistema registra un log de la generación para auditoría +7. [ ] La funcionalidad se puede deshabilitar si es necesario (opcional - no implementado) +8. [x] No afecta el rendimiento de confirmación de órdenes regulares ## Estimación de Tiempo diff --git a/init_odoo.py b/init_odoo.py index d1c5a08..7ab37ef 100644 --- a/init_odoo.py +++ b/init_odoo.py @@ -35,6 +35,7 @@ odoo_command = [ "-c", ODOO_CONF, "-d", DB_NAME, "-i", MODULES_TO_INSTALL, + "--load-language", "es_ES", "--stop-after-init" ] diff --git a/lims_management/demo/z_automatic_generation_demo.xml b/lims_management/demo/z_automatic_generation_demo.xml index bba7732..dc5fdf6 100644 --- a/lims_management/demo/z_automatic_generation_demo.xml +++ b/lims_management/demo/z_automatic_generation_demo.xml @@ -1,91 +1,7 @@ - - - - - - - draft - - - - - - - 1 - - - - - - 1 - - - - - - - - draft - - - - - - - 1 - - - - - - 1 - - - - - - 1 - - - - - - - draft - - - - - - 1 - - - - - - 1 - - - - - - - - draft - - - - - - 1 - - - - - - 1 - - + \ No newline at end of file diff --git a/lims_management/models/__pycache__/product.cpython-312.pyc b/lims_management/models/__pycache__/product.cpython-312.pyc index 7c7c822..59f71a2 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 560f98c..8e54302 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 ad21e4d..051c734 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/product.py b/lims_management/models/product.py index c3eb347..0f23f1c 100644 --- a/lims_management/models/product.py +++ b/lims_management/models/product.py @@ -29,8 +29,8 @@ class ProductTemplate(models.Model): ) is_sample_type = fields.Boolean( - string="Is a Sample Type", - help="Check if this product represents a type of laboratory sample container." + string="Es Tipo de Muestra", + help="Marcar si este producto representa un tipo de contenedor de muestra de laboratorio." ) required_sample_type_id = fields.Many2one( diff --git a/lims_management/models/sale_order.py b/lims_management/models/sale_order.py index e3cc54b..4f2227f 100644 --- a/lims_management/models/sale_order.py +++ b/lims_management/models/sale_order.py @@ -9,17 +9,17 @@ class SaleOrder(models.Model): _inherit = 'sale.order' is_lab_request = fields.Boolean( - string="Is a Laboratory Request", + string="Es Orden de Laboratorio", default=False, copy=False, - help="Technical field to identify if the sale order is a laboratory request." + help="Campo técnico para identificar si la orden de venta es una solicitud de laboratorio." ) doctor_id = fields.Many2one( 'res.partner', - string="Referring Doctor", + string="Médico Referente", domain="[('is_doctor', '=', True)]", - help="The doctor who referred the patient for this laboratory request." + help="El médico que refirió al paciente para esta solicitud de laboratorio." ) generated_sample_ids = fields.Many2many( @@ -30,7 +30,7 @@ class SaleOrder(models.Model): string='Muestras Generadas', domain="[('is_lab_sample', '=', True)]", readonly=True, - help="Laboratory samples automatically generated when this order was confirmed" + help="Muestras de laboratorio generadas automáticamente cuando se confirmó esta orden" ) def action_confirm(self): diff --git a/lims_management/models/stock_lot.py b/lims_management/models/stock_lot.py index 5de18a4..9ec953e 100644 --- a/lims_management/models/stock_lot.py +++ b/lims_management/models/stock_lot.py @@ -6,7 +6,7 @@ import random class StockLot(models.Model): _inherit = 'stock.lot' - is_lab_sample = fields.Boolean(string='Is a Laboratory Sample') + is_lab_sample = fields.Boolean(string='Es Muestra de Laboratorio') barcode = fields.Char( string='Código de Barras', @@ -18,25 +18,25 @@ class StockLot(models.Model): patient_id = fields.Many2one( 'res.partner', - string='Patient', + string='Paciente', domain="[('is_patient', '=', True)]" ) request_id = fields.Many2one( 'sale.order', - string='Lab Request', + string='Orden de Laboratorio', domain="[('is_lab_request', '=', True)]" ) - collection_date = fields.Datetime(string='Collection Date') + collection_date = fields.Datetime(string='Fecha de Recolección') container_type = fields.Selection([ - ('serum_tube', 'Serum Tube'), - ('edta_tube', 'EDTA Tube'), - ('swab', 'Swab'), - ('urine', 'Urine Container'), - ('other', 'Other') - ], string='Container Type (Legacy)', help='Deprecated field, use sample_type_product_id instead') + ('serum_tube', 'Tubo de Suero'), + ('edta_tube', 'Tubo EDTA'), + ('swab', 'Hisopo'), + ('urine', 'Contenedor de Orina'), + ('other', 'Otro') + ], string='Tipo de Contenedor (Obsoleto)', help='Campo obsoleto, use sample_type_product_id en su lugar') sample_type_product_id = fields.Many2one( 'product.template', @@ -47,7 +47,7 @@ class StockLot(models.Model): collector_id = fields.Many2one( 'res.users', - string='Collected by', + string='Recolectado por', default=lambda self: self.env.user ) @@ -83,19 +83,28 @@ class StockLot(models.Model): ('disposed', 'Desechada') ], string='Estado', default='collected', tracking=True) + def action_collect(self): + """Mark sample as collected""" + self.write({'state': 'collected', 'collection_date': fields.Datetime.now()}) + def action_receive(self): + """Mark sample as received in laboratory""" self.write({'state': 'received'}) def action_start_analysis(self): + """Start analysis process""" self.write({'state': 'in_process'}) def action_complete_analysis(self): + """Mark analysis as completed""" self.write({'state': 'analyzed'}) def action_store(self): + """Store the sample""" self.write({'state': 'stored'}) def action_dispose(self): + """Dispose of the sample""" self.write({'state': 'disposed'}) @api.onchange('sample_type_product_id') diff --git a/lims_management/views/sale_order_views.xml b/lims_management/views/sale_order_views.xml index 9ae923f..9c0945f 100644 --- a/lims_management/views/sale_order_views.xml +++ b/lims_management/views/sale_order_views.xml @@ -30,7 +30,7 @@ -