
- Cambiar 'tree' por 'list' en view_mode de todas las acciones de dashboard - Corregir sintaxis de filtros de fecha usando context_today() y relativedelta - Eliminar campo booleano is_out_of_range como medida en gráfico - Corregir referencia a sample.state en lugar de sample.sample_state - Reemplazar sample.test_ids por búsqueda de tests asociados - Eliminar consulta SQL directa a columna logo inexistente - Corregir método invalidate_cache() por _invalidate_cache() - Agregar sección de notificaciones en CLAUDE.md Los dashboards ahora funcionan correctamente sin errores de JavaScript. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
14 KiB
CLAUDE.md
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.
Key Technologies
- Odoo 18: ERP framework (Python-based)
- PostgreSQL 15: Database
- Docker & Docker Compose: Containerization
- Gitea: Version control and issue tracking
Development Commands
Starting the Environment
# Start all services
docker-compose up -d
# MANDATORY: View initialization logs to check for errors
docker-compose logs odoo_init
# Stop and clean everything (removes volumes)
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:
- Restart the ephemeral instance:
docker-compose down -v && docker-compose up -d
- Check initialization logs for errors:
docker-compose logs odoo_init | grep -i "error\|traceback\|exception"
- Verify successful completion:
docker-compose logs odoo_init | tail -30
- Only proceed to next task if no errors are found
- If errors are found, fix them before continuing
Development Workflow per Task
When implementing issues with multiple tasks, follow this workflow for EACH task:
- Stop instance:
docker-compose down -v
- Implement the task: Make code changes
- Start instance:
docker-compose up -d
(timeout: 300000ms) - Validate logs: Check for errors in initialization
- Commit & Push:
git add -A && git commit -m "feat(#X): Task description" && git push
- Comment on issue: Update issue with task completion
- Mark task completed: Update todo list
- Proceed to next task: Only if no errors found
Database Operations
Direct PostgreSQL Access
# 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:
- Create script (e.g.,
test/verify_products.py
):
import odoo
import json
def verify_lab_order_products(cr):
cr.execute("""SELECT ... FROM sale_order ...""")
return cr.fetchall()
if __name__ == '__main__':
db_name = 'lims_demo'
registry = odoo.registry(db_name)
with registry.cursor() as cr:
results = verify_lab_order_products(cr)
print(json.dumps(results, indent=4))
- Copy to container:
docker cp test/verify_products.py lims_odoo:/tmp/verify_products.py
- Execute:
docker-compose exec odoo python3 /tmp/verify_products.py
Gitea Integration
# Create issue
python utils/gitea_cli_helper.py create-issue --title "Title" --body "Description\nSupports multiple lines"
# Create PR with inline description
python utils/gitea_cli_helper.py create-pr --head "feature-branch" --base "dev" --title "Title" --body "Description"
# Create PR with description from file
python utils/gitea_cli_helper.py create-pr dev --title "feat(#31): Sample lifecycle" --description-file pr_description.txt
# Comment on issue
python utils/gitea_cli_helper.py comment-issue --issue-number 123 --body "Comment text"
# Close issue
python utils/gitea_cli_helper.py close-issue --issue-number 123
# Get issue details and comments
python utils/gitea_cli_helper.py get-issue --issue-number 8
# List all open issues
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 managementproduct.py
: Analysis types and categoriessale_order.py
: Analysis orders and sample managementstock_lot.py
: Sample tracking and lifecycleanalysis_range.py
: Normal ranges for test results
Odoo 18 Specific Conventions
View Definitions
- CRITICAL: Use
<list>
instead of<tree>
in view XML - using<tree>
causes error "El nodo raíz de una vista list debe ser , no " - View mode in actions must be
tree,form
notlist,form
(paradójicamente, el modo se llama "tree" pero el XML debe usar<list>
)
Visibility Attributes
-
Use
invisible
attribute directly instead ofattrs
:<!-- Wrong (Odoo < 17) --> <field name="field" attrs="{'invisible': [('condition', '=', False)]}"/> <!-- Correct (Odoo 18) --> <field name="field" invisible="not condition"/> <field name="field" invisible="condition == False"/>
Context with ref()
-
Use
eval
attribute when usingref()
in action contexts:<!-- Wrong - ref() undefined in client --> <field name="context">{'default_categ_id': ref('module.xml_id')}</field> <!-- Correct - evaluated on server --> <field name="context" eval="{'default_categ_id': ref('module.xml_id')}"/>
XPath in View Inheritance
- Use flexible XPath expressions for robustness:
<!-- More robust - works with list or tree --> <xpath expr="//field[@name='order_line']//field[@name='product_id']" position="attributes"> <attribute name="domain">[('is_analysis', '=', True)]</attribute> </xpath>
Data Management
- Initial Data:
lims_management/data/
- Sequences, categories, basic configuration - 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
- XML files in
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.
Environment Variables
Required in .env
file:
GITEA_API_KEY
: Personal Access Token for GiteaGITEA_API_KEY_URL
: Gitea API base URL (e.g.,https://gitea.grupoconsiti.com/api/v1/
)GITEA_USERNAME
: Gitea username (repository owner)GITEA_REPO_NAME
: Repository name (e.g.,clinical_laboratory
)
Important Patterns
Sample Lifecycle States
STATE_PENDING_COLLECTION = 'pending_collection'
STATE_COLLECTED = 'collected'
STATE_IN_ANALYSIS = 'in_analysis'
STATE_COMPLETED = 'completed'
STATE_CANCELLED = 'cancelled'
Barcode Generation
- 13-digit format: YYMMDDNNNNNNC
- Uses
barcode
Python library for Code-128 generation - Stored as PDF with human-readable text
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
- Create script:
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,
'is_lab_request': True,
'order_line': [(0, 0, {
'product_id': hemograma.product_variant_id.id,
'product_uom_qty': 1
})]
})
if __name__ == '__main__':
db_name = 'lims_demo'
registry = odoo.registry(db_name)
with registry.cursor() as cr:
create_lab_requests(cr)
cr.commit()
- Integrate in initialization or run separately
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
Al agregar archivos al manifest, seguir SIEMPRE este orden:
- security/*.xml (grupos y categorías)
- security/ir.model.access.csv
- data/*.xml (secuencias, categorías, datos base)
- views/*_views.xml en este orden específico:
- Modelos base (sin dependencias)
- Modelos dependientes
- Vistas que referencian acciones
- menus.xml (SIEMPRE al final de views)
- wizards/*.xml
- reports/*.xml
- demo/*.xml
Desarrollo de modelos relacionados
Cuando crees modelos que se relacionan entre sí en el mismo issue:
Fase 1: Modelos base
- Crear modelos SIN campos One2many
- Solo incluir campos básicos y Many2one si el modelo referenciado ya existe
- Probar que la instancia levante
Fase 2: Relaciones
- Agregar campos One2many en los modelos padre
- Verificar que todos los inverse_name existan
- Probar nuevamente
Fase 3: Vistas complejas
- Agregar vistas con referencias a acciones
- Verificar que las acciones referenciadas ya estén definidas
Contextos en vistas XML
- En formularios: usar
id
(NOactive_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?
- ¿Usaste
id
en lugar deactive_id
en contextos de formulario? - ¿Verificaste que todos los campos en las vistas existen en los modelos?
- ¿Los nombres de métodos/acciones coinciden exactamente con los definidos en Python?
- ¿Los widgets utilizados son válidos en Odoo 18?
Desarrollo de vistas - Mejores prácticas
Antes de crear vistas:
- Verificar campos del modelo: SIEMPRE revisar qué campos existen con
grep "fields\." models/archivo.py
- Verificar métodos disponibles: Buscar métodos con
grep "def action_" models/archivo.py
- Verificar campos relacionados: Confirmar que los campos related tienen la ruta correcta
Orden de creación de vistas:
- Primero: Definir todas las acciones (ir.actions.act_window) en un solo lugar
- Segundo: Crear las vistas (form, list, search, etc.)
- Tercero: Crear los menús que referencian las acciones
- Cuarto: Si hay referencias cruzadas entre archivos, considerar consolidar en un solo archivo
Widgets válidos en Odoo 18:
- Numéricos:
float
,integer
,monetary
(NOfloat_time
para datos generales) - Texto:
text
,char
,html
(NOtext_emojis
) - Booleanos:
boolean
,boolean_toggle
,boolean_button
- Selección:
selection
,radio
,selection_badge
- Relaciones:
many2one
,many2many_tags
- Estado:
statusbar
,badge
,progressbar
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
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:
- Leer el error completo en los logs
- Identificar archivo y línea exacta del problema
- Verificar que el elemento referenciado existe y está accesible
- Si es necesario, simplificar la vista temporalmente para aislar el problema
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:
<!-- CORRECTO en Odoo 18 -->
<span t-field="record.barcode_field"
t-options="{'widget': 'barcode', 'type': 'Code128', 'width': 250, 'height': 60, 'humanreadable': 1}"
style="display: block;"/>
Consideraciones importantes:
- NO usar rutas directas como
/report/barcode/Code128/
- esta sintaxis está deprecated - Usar siempre
t-field
con el widget barcode para renderizado correcto - Parámetros disponibles en t-options:
type
: Tipo de código ('Code128', 'EAN13', 'QR', etc.)width
: Ancho en píxelesheight
: Alto en píxeleshumanreadable
: 1 para mostrar texto legible, 0 para ocultarlo
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:
<!-- En lugar de --> <h4>LABORATORIO CLÍNICO</h4> <!-- Usar --> <h4>LABORATORIO CLÍNICO</h4>
- í = í
- Í = Í
- á = á
- Á = Á
- é = é
- É = É
- ó = ó
- Ó = Ó
- ú = ú
- Ú = Ú
- ñ = ñ
- Ñ = Ñ
Layout de etiquetas múltiples por página
<!-- Contenedor principal sin salto de página -->
<div class="page">
<t t-foreach="docs" t-as="o">
<!-- Cada etiqueta como inline-block -->
<div style="display: inline-block; vertical-align: top;
page-break-inside: avoid; overflow: hidden;">
<!-- Contenido de la etiqueta -->
</div>
</t>
</div>