# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## 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
```bash
# 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:
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`
4. Only proceed to next task if no errors are found
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)
4. **Validate logs**: Check for errors in initialization
5. **Commit & Push**: `git add -A && git commit -m "feat(#X): Task description" && git push`
6. **Comment on issue**: Update issue with task completion
7. **Mark task completed**: Update todo list
8. **Proceed to next task**: Only if no errors found

### 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

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))
```

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"

# 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 management
  - `product.py`: Analysis types and categories
  - `sale_order.py`: Analysis orders and sample management
  - `stock_lot.py`: Sample tracking and lifecycle
  - `analysis_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 <list>, no <tree>"
- View mode in actions must be `tree,form` not `list,form` (paradójicamente, el modo se llama "tree" pero el XML debe usar `<list>`)

#### Visibility Attributes
- Use `invisible` attribute directly instead of `attrs`:
  ```xml
  <!-- 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 using `ref()` in action contexts:
  ```xml
  <!-- 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:
  ```xml
  <!-- 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

### 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 Gitea
- `GITEA_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
```python
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

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,
        '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()
```

2. 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:
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:
   - Modelos base (sin dependencias)
   - Modelos dependientes  
   - Vistas que referencian acciones
   - menus.xml (SIEMPRE al final de views)
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?
- [ ] ¿Usaste `id` en lugar de `active_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:
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`
- 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:
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
4. 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:

```xml
<!-- 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:
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:
   - `type`: Tipo de código ('Code128', 'EAN13', 'QR', etc.)
   - `width`: Ancho en píxeles
   - `height`: Alto en píxeles
   - `humanreadable`: 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:
  ```xml
  <!-- En lugar de -->
  <h4>LABORATORIO CLÍNICO</h4>
  
  <!-- Usar -->
  <h4>LABORATORIO CL&#205;NICO</h4>
  ```
  - í = &#237;
  - Í = &#205;
  - á = &#225;
  - Á = &#193;
  - é = &#233;
  - É = &#201;
  - ó = &#243;
  - Ó = &#211;
  - ú = &#250;
  - Ú = &#218;
  - ñ = &#241;
  - Ñ = &#209;

##### Layout de etiquetas múltiples por página
```xml
<!-- 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>
```