diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 02f7faa..ee793ed 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -27,7 +27,8 @@ "Bash(gh pr merge:*)", "Bash(git cherry-pick:*)", "Bash(del comment_issue_15.txt)", - "Bash(cat:*)" + "Bash(cat:*)", + "Bash(powershell.exe:*)" ], "deny": [] } diff --git a/CLAUDE.md b/CLAUDE.md index 28b0df6..97b1e27 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,6 +2,11 @@ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +## Notifications + +When tasks complete, or you need autorizathion for an action notify me using: +powershell.exe -c "[System.Media.SystemSounds]::Beep.Play()" + ## Project Overview This is a Laboratory Information Management System (LIMS) module for Odoo 18 ERP, specifically designed for clinical laboratories. The module manages patients, samples, analyses, and test results. @@ -16,6 +21,7 @@ This is a Laboratory Information Management System (LIMS) module for Odoo 18 ERP ## Development Commands ### Starting the Environment + ```bash # Start all services docker-compose up -d @@ -30,10 +36,13 @@ 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. ### MANDATORY Testing Rule + **CRITICAL**: After EVERY task that modifies code, models, views, or data: + 1. Restart the ephemeral instance: `docker-compose down -v && docker-compose up -d` 2. Check initialization logs for errors: `docker-compose logs odoo_init | grep -i "error\|traceback\|exception"` 3. Verify successful completion: `docker-compose logs odoo_init | tail -30` @@ -41,7 +50,9 @@ After successful installation/update, the instance must remain active for user v 5. If errors are found, fix them before continuing ### Development Workflow per Task + When implementing issues with multiple tasks, follow this workflow for EACH task: + 1. **Stop instance**: `docker-compose down -v` 2. **Implement the task**: Make code changes 3. **Start instance**: `docker-compose up -d` (timeout: 300000ms) @@ -54,15 +65,18 @@ When implementing issues with multiple tasks, follow this workflow for EACH task ### Database Operations #### Direct PostgreSQL Access + ```bash # Connect to PostgreSQL docker exec -it lims_db psql -U odoo -d odoo ``` #### Python Script Method (Recommended) + For complex queries, use Python scripts with Odoo ORM: 1. Create script (e.g., `test/verify_products.py`): + ```python import odoo import json @@ -80,16 +94,19 @@ if __name__ == '__main__': ``` 2. Copy to container: + ```bash docker cp test/verify_products.py lims_odoo:/tmp/verify_products.py ``` 3. Execute: + ```bash docker-compose exec odoo python3 /tmp/verify_products.py ``` ### Gitea Integration + ```bash # Create issue python utils/gitea_cli_helper.py create-issue --title "Title" --body "Description\nSupports multiple lines" @@ -116,12 +133,14 @@ python utils/gitea_cli_helper.py list-open-issues ## Mandatory Reading At the start of each work session, read these documents to understand requirements and technical design: + - `documents/requirements/RequerimientoInicial.md` - `documents/requirements/ToBeDesing.md` ## Code Architecture ### Module Structure + - **lims_management/models/**: Core business logic - `partner.py`: Patient and healthcare provider management - `product.py`: Analysis types and categories @@ -132,31 +151,37 @@ At the start of each work session, read these documents to understand requiremen ### Odoo 18 Specific Conventions #### View Definitions + - **CRITICAL**: Use `` instead of `` in view XML - using `` causes error "El nodo raíz de una vista list debe ser , no " - View mode in actions must be `tree,form` not `list,form` (paradójicamente, el modo se llama "tree" pero el XML debe usar ``) #### Visibility Attributes + - Use `invisible` attribute directly instead of `attrs`: + ```xml - + ``` #### Context with ref() + - Use `eval` attribute when using `ref()` in action contexts: + ```xml {'default_categ_id': ref('module.xml_id')} - + ``` #### XPath in View Inheritance + - Use flexible XPath expressions for robustness: ```xml @@ -166,13 +191,15 @@ At the start of each work session, read these documents to understand requiremen ``` ### Data Management + - **Initial Data**: `lims_management/data/` - Sequences, categories, basic configuration -- **Demo Data**: +- **Demo Data**: - XML files in `lims_management/demo/` - Python scripts in `test/` directory for complex demo data creation - Use `noupdate="1"` for demo data to prevent reloading ### Security Model + - Access rights defined in `security/ir.model.access.csv` - Field-level security in `security/security.xml` - Group-based permissions: Laboratory Technician, Manager, etc. @@ -180,6 +207,7 @@ At the start of each work session, read these documents to understand requiremen ## Environment Variables Required in `.env` file: + - `GITEA_API_KEY`: Personal Access Token for Gitea - `GITEA_API_KEY_URL`: Gitea API base URL (e.g., `https://gitea.grupoconsiti.com/api/v1/`) - `GITEA_USERNAME`: Gitea username (repository owner) @@ -188,6 +216,7 @@ Required in `.env` file: ## Important Patterns ### Sample Lifecycle States + ```python STATE_PENDING_COLLECTION = 'pending_collection' STATE_COLLECTED = 'collected' @@ -197,6 +226,7 @@ STATE_CANCELLED = 'cancelled' ``` ### Barcode Generation + - 13-digit format: YYMMDDNNNNNNC - Uses `barcode` Python library for Code-128 generation - Stored as PDF with human-readable text @@ -204,30 +234,34 @@ STATE_CANCELLED = 'cancelled' ### Demo Data Creation #### XML Files (Simple Data) + - 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 def create_lab_requests(cr): env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {}) - + # Use ref() to get existing records patient1 = env.ref('lims_management.demo_patient_1') hemograma = env.ref('lims_management.analysis_hemograma') - + # Create records with business logic env['sale.order'].create({ 'partner_id': patient1.id, @@ -251,53 +285,64 @@ if __name__ == '__main__': ## Git Workflow ### Pre-commit Hook + Automatically installed via `scripts/install_hooks.sh`: + - Prevents commits to 'main' or 'dev' branches - Enforces feature branch workflow ### Branch Naming + - Feature branches: `feature/XX-description` (where XX is issue number) - Always create PRs to 'dev' branch, not 'main' ## Desarrollo de nuevos modelos y vistas -### Orden de carga en __manifest__.py +### Orden de carga en **manifest**.py + Al agregar archivos al manifest, seguir SIEMPRE este orden: -1. security/*.xml (grupos y categorías) + +1. security/\*.xml (grupos y categorías) 2. security/ir.model.access.csv -3. data/*.xml (secuencias, categorías, datos base) -4. views/*_views.xml en este orden específico: +3. data/\*.xml (secuencias, categorías, datos base) +4. views/\*\_views.xml en este orden específico: - Modelos base (sin dependencias) - - Modelos dependientes + - Modelos dependientes - Vistas que referencian acciones - menus.xml (SIEMPRE al final de views) -5. wizards/*.xml -6. reports/*.xml -7. demo/*.xml +5. wizards/\*.xml +6. reports/\*.xml +7. demo/\*.xml ### Desarrollo de modelos relacionados + Cuando crees modelos que se relacionan entre sí en el mismo issue: #### Fase 1: Modelos base + 1. Crear modelos SIN campos One2many 2. Solo incluir campos básicos y Many2one si el modelo referenciado ya existe 3. Probar que la instancia levante #### Fase 2: Relaciones + 1. Agregar campos One2many en los modelos padre 2. Verificar que todos los inverse_name existan 3. Probar nuevamente #### Fase 3: Vistas complejas + 1. Agregar vistas con referencias a acciones 2. Verificar que las acciones referenciadas ya estén definidas ### Contextos en vistas XML + - En formularios: usar `id` (NO `active_id`) - En acciones de ventana: usar `active_id` - En campos One2many: usar `parent` para referenciar el registro padre ### Checklist antes de reiniciar instancia + - [ ] ¿Los modelos referenciados en relaciones ya existen? - [ ] ¿Las acciones/vistas referenciadas se cargan ANTES? - [ ] ¿Los grupos en ir.model.access.csv coinciden con los de security.xml? @@ -309,17 +354,20 @@ Cuando crees modelos que se relacionan entre sí en el mismo issue: ### Desarrollo de vistas - Mejores prácticas #### Antes de crear vistas: + 1. **Verificar campos del modelo**: SIEMPRE revisar qué campos existen con `grep "fields\." models/archivo.py` 2. **Verificar métodos disponibles**: Buscar métodos con `grep "def action_" models/archivo.py` 3. **Verificar campos relacionados**: Confirmar que los campos related tienen la ruta correcta #### Orden de creación de vistas: + 1. **Primero**: Definir todas las acciones (ir.actions.act_window) en un solo lugar 2. **Segundo**: Crear las vistas (form, list, search, etc.) 3. **Tercero**: Crear los menús que referencian las acciones 4. **Cuarto**: Si hay referencias cruzadas entre archivos, considerar consolidar en un solo archivo #### Widgets válidos en Odoo 18: + - Numéricos: `float`, `integer`, `monetary` (NO `float_time` para datos generales) - Texto: `text`, `char`, `html` (NO `text_emojis`) - Booleanos: `boolean`, `boolean_toggle`, `boolean_button` @@ -330,22 +378,27 @@ Cuando crees modelos que se relacionan entre sí en el mismo issue: #### Errores comunes y soluciones: ##### Error: "External ID not found" + - **Causa**: Referencia a un ID que aún no fue cargado -- **Solución**: Reorganizar orden en __manifest__.py o mover definición al mismo archivo +- **Solución**: Reorganizar orden en **manifest**.py o mover definición al mismo archivo ##### Error: "Field 'X' does not exist" + - **Causa**: Vista referencia campo inexistente en el modelo - **Solución**: Verificar modelo y agregar campo o corregir nombre en vista ##### Error: "action_X is not a valid action" + - **Causa**: Nombre de método incorrecto en botón - **Solución**: Verificar nombre exacto del método en el modelo Python ##### Error: "Invalid widget" + - **Causa**: Uso de widget no existente o deprecated - **Solución**: Usar widgets estándar de Odoo 18 #### Estrategia de depuración: + 1. Leer el error completo en los logs 2. Identificar archivo y línea exacta del problema 3. Verificar que el elemento referenciado existe y está accesible @@ -354,16 +407,18 @@ Cuando crees modelos que se relacionan entre sí en el mismo issue: ### Manejo de códigos de barras en reportes QWeb (Odoo 18) #### Generación de códigos de barras + Para mostrar códigos de barras en reportes PDF, usar el widget nativo de Odoo: ```xml - ``` #### Consideraciones importantes: + 1. **NO usar** rutas directas como `/report/barcode/Code128/` - esta sintaxis está deprecated 2. **Usar siempre** `t-field` con el widget barcode para renderizado correcto 3. **Parámetros disponibles** en t-options: @@ -375,19 +430,23 @@ Para mostrar códigos de barras en reportes PDF, usar el widget nativo de Odoo: #### Problemas comunes y soluciones: ##### Código de barras vacío en PDF + - **Causa**: Campo computed sin store=True o sintaxis incorrecta - **Solución**: Asegurar que el campo esté almacenado y usar widget barcode ##### Caracteres especiales en reportes (tildes, ñ) + - **Problema**: Aparecen como "ñ" o "í" en lugar de "ñ" o "í" - **Solución**: Usar referencias numéricas de caracteres XML: + ```xml

LABORATORIO CLÍNICO

- +

LABORATORIO CLÍNICO

``` + - í = í - Í = Í - á = á @@ -402,15 +461,16 @@ Para mostrar códigos de barras en reportes PDF, usar el widget nativo de Odoo: - Ñ = Ñ ##### Layout de etiquetas múltiples por página + ```xml
-
-``` \ No newline at end of file +``` diff --git a/lims_management/views/dashboard_views.xml b/lims_management/views/dashboard_views.xml index ed6e5e2..c739741 100644 --- a/lims_management/views/dashboard_views.xml +++ b/lims_management/views/dashboard_views.xml @@ -31,7 +31,7 @@ Estado de Órdenes sale.order - graph,pivot,tree,form + graph,pivot,list,form [('is_lab_request', '=', True)] {'search_default_group_by_state': 1} Productividad de Técnicos lims.test - graph,pivot,tree,form + graph,pivot,list,form {'search_default_group_by_technician': 1, 'search_default_this_month': 1} Dashboard de Muestras stock.lot - graph,pivot,tree,form + graph,pivot,list,form [('is_lab_sample', '=', True)] {'search_default_group_by_state': 1} - @@ -174,7 +173,7 @@ Parámetros Fuera de Rango lims.result - graph,pivot,tree,form + graph,pivot,list,form [('test_id.state', '=', 'validated')] {'search_default_out_of_range': 1} Análisis Más Solicitados sale.order.line - graph,pivot,tree + graph,pivot,list [('order_id.is_lab_request', '=', True), ('product_id.is_analysis', '=', True)] {'search_default_group_by_product': 1} Distribución Demográfica de Tests lims.test - graph,pivot,tree + graph,pivot,list [('state', '=', 'validated')] {'search_default_this_year': 1} - - - - + + + + diff --git a/scripts/update_company_logo_odoo18.py b/scripts/update_company_logo_odoo18.py index bf7bbfc..a38c5a4 100644 --- a/scripts/update_company_logo_odoo18.py +++ b/scripts/update_company_logo_odoo18.py @@ -63,28 +63,21 @@ try: env.cr.rollback() continue - # Verificación final con consulta directa a la BD + # Verificación final usando el ORM print("\n" + "="*60) - print("VERIFICACIÓN FINAL (consulta directa):") + print("VERIFICACIÓN FINAL:") print("="*60) - env.cr.execute(""" - SELECT id, name, - CASE WHEN logo IS NOT NULL THEN 'SI' ELSE 'NO' END as tiene_logo, - CASE WHEN logo IS NOT NULL THEN length(logo) ELSE 0 END as logo_size - FROM res_company - ORDER BY id - """) - - for row in env.cr.fetchall(): - print(f"\nEmpresa ID {row[0]}:") - print(f" - Nombre: {row[1]}") - print(f" - Logo presente: {row[2]}") - if row[2] == 'SI': - print(f" - Tamaño del logo (base64): {row[3]:,} caracteres") + # Verificar a través del ORM que es más seguro + for company in env['res.company'].search([], order='id'): + print(f"\nEmpresa ID {company.id}:") + print(f" - Nombre: {company.name}") + print(f" - Logo presente: {'SI' if company.logo else 'NO'}") + if company.logo: + print(f" - Tamaño del logo (base64): {len(company.logo):,} caracteres") # Forzar actualización de caché - env['res.company'].invalidate_cache() + env['res.company']._invalidate_cache() print("\nLogo de la empresa actualizado exitosamente.") print("NOTA: Si el logo no aparece en la interfaz, puede ser necesario:") diff --git a/test/create_demo_data.py b/test/create_demo_data.py index 7d4ff66..35da52e 100644 --- a/test/create_demo_data.py +++ b/test/create_demo_data.py @@ -216,12 +216,13 @@ def create_demo_lab_data(cr): # Procesar cada muestra for sample in order.generated_sample_ids: # Marcar como recolectada - if sample.sample_state == 'pending_collection': + if sample.state == 'pending_collection': sample.action_collect() print(f" ✓ Muestra {sample.name} recolectada") # Procesar pruebas de esta muestra - for test in sample.test_ids: + tests = env['lims.test'].search([('sample_id', '=', sample.id)]) + for test in tests: print(f" - Procesando prueba: {test.product_id.name}") # Iniciar proceso si está en borrador @@ -241,7 +242,7 @@ def create_demo_lab_data(cr): print(f" ✓ Resultados ingresados") # Validar las primeras 2 órdenes completas y algunas pruebas de la tercera - should_validate = (idx < 2) or (idx == 2 and test == sample.test_ids[0]) + should_validate = (idx < 2) or (idx == 2 and test == tests[0]) if should_validate and test.state == 'result_entered': test.action_validate() diff --git a/test/test_notification.py b/test/test_notification.py new file mode 100644 index 0000000..3e4fa4c --- /dev/null +++ b/test/test_notification.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +""" +Script de prueba para demostrar el uso de notificaciones +cuando se completan tareas en el sistema LIMS +""" + +import time +import subprocess +import sys + +def notify_completion(): + """Envía una notificación de sonido cuando se completa una tarea""" + try: + # Ejecutar el comando de PowerShell para el beep + subprocess.run(['powershell.exe', '-c', '[System.Media.SystemSounds]::Beep.Play()'], check=True) + print("[OK] Notificación enviada exitosamente") + except subprocess.CalledProcessError: + print("[ERROR] Error al enviar notificación") + except FileNotFoundError: + print("[ERROR] PowerShell no encontrado en el sistema") + +def simulate_task(): + """Simula una tarea que toma tiempo""" + print("Iniciando tarea de prueba...") + + # Simular trabajo + for i in range(3): + print(f" Procesando... {i+1}/3") + time.sleep(1) + + print("[OK] Tarea completada!") + return True + +def main(): + print("=== Prueba de Sistema de Notificaciones LIMS ===\n") + + # Ejecutar tarea + if simulate_task(): + print("\nEnviando notificación de finalización...") + notify_completion() + + print("\n=== Fin de la prueba ===") + +if __name__ == "__main__": + main() \ No newline at end of file