diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..92228a6 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,227 @@ +# 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 +``` + +### 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. + +### 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., `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 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 gitea_cli_helper.py create-issue --title "Title" --body "Description\nSupports multiple lines" + +# Create PR with inline description +python gitea_cli_helper.py create-pr --head "feature-branch" --base "dev" --title "Title" --body "Description" + +# Create PR with description from file +python gitea_cli_helper.py create-pr dev --title "feat(#31): Sample lifecycle" --description-file pr_description.txt + +# Comment on issue +python gitea_cli_helper.py comment-issue --issue-number 123 --body "Comment text" + +# Close issue +python gitea_cli_helper.py close-issue --issue-number 123 +``` + +## 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 `` instead of `` - using `` causes `ValueError: Wrong value for ir.ui.view.type: 'tree'` +- View mode in actions must be `list,form` not `tree,form` + +#### 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 + + + [('is_analysis', '=', True)] + + ``` + +### Data Management +- **Initial Data**: `lims_management/data/` - Sequences, categories, basic configuration +- **Demo Data**: + - XML files in `lims_management/demo/` + - Python scripts in root 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 + +#### Python Scripts (Complex Data) +For data with dependencies or business logic: + +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' \ No newline at end of file diff --git a/gitea_cli_helper.py b/gitea_cli_helper.py index bf1ac9e..2a2bb6c 100644 --- a/gitea_cli_helper.py +++ b/gitea_cli_helper.py @@ -40,23 +40,37 @@ def _make_gitea_request(method, endpoint, payload=None): print(f"Respuesta del servidor: {e.response.text}") raise -def create_issue(title, body): +def create_issue(title, body_file): """Creates a new issue in the Gitea repository.""" + try: + with open(body_file, 'r', encoding='utf-8') as f: + body = f.read() + except FileNotFoundError: + print(f"Error: El archivo de cuerpo '{body_file}' no fue encontrado.") + return + endpoint = f"repos/{GITEA_USERNAME}/{GITEA_REPO_NAME}/issues" payload = { "title": title, "body": body, "closed": False } - print(f"Creando issue: '{title}'...") + print(f"Creando issue: '{title}' desde archivo '{body_file}'...") issue_data = _make_gitea_request("POST", endpoint, payload) print(f"Issue creado exitosamente:") print(f" Número: {issue_data['number']}") print(f" Título: {issue_data['title']}") print(f" URL: {issue_data['html_url']}") -def create_pull_request(head_branch, base_branch, title, body): +def create_pull_request(head_branch, base_branch, title, body_file): """Creates a new pull request in the Gitea repository.""" + try: + with open(body_file, 'r', encoding='utf-8') as f: + body = f.read() + except FileNotFoundError: + print(f"Error: El archivo de cuerpo '{body_file}' no fue encontrado.") + return + endpoint = f"repos/{GITEA_USERNAME}/{GITEA_REPO_NAME}/pulls" payload = { "head": head_branch, @@ -64,7 +78,7 @@ def create_pull_request(head_branch, base_branch, title, body): "title": title, "body": body } - print(f"Creando Pull Request: '{title}' de '{head_branch}' a '{base_branch}'...") + print(f"Creando Pull Request: '{title}' de '{head_branch}' a '{base_branch}' desde archivo '{body_file}'...") pr_data = _make_gitea_request("POST", endpoint, payload) print(f"Pull Request creado exitosamente:") print(f" Número: {pr_data['number']}") @@ -98,14 +112,14 @@ def main(): # Subparser para crear issue create_issue_parser = subparsers.add_parser("create-issue", help="Crea un nuevo issue.") create_issue_parser.add_argument("--title", required=True, help="Título del issue.") - create_issue_parser.add_argument("--body", required=True, help="Cuerpo/descripción del issue (soporta multilínea con \n).") + create_issue_parser.add_argument("--body-file", required=True, help="Ruta al archivo de texto con el cuerpo/descripción del issue.") # Subparser para crear pull request create_pr_parser = subparsers.add_parser("create-pr", help="Crea un nuevo pull request.") create_pr_parser.add_argument("--head", required=True, help="Rama de origen (head branch).") create_pr_parser.add_argument("--base", required=True, help="Rama de destino (base branch).") create_pr_parser.add_argument("--title", required=True, help="Título del pull request.") - create_pr_parser.add_argument("--body", required=True, help="Cuerpo/descripción del pull request (soporta multilínea con \n).") + create_pr_parser.add_argument("--body-file", required=True, help="Ruta al archivo de texto con el cuerpo/descripción del pull request.") # Subparser para comentar issue comment_issue_parser = subparsers.add_parser("comment-issue", help="Añade un comentario a un issue existente.") @@ -119,9 +133,9 @@ def main(): args = parser.parse_args() if args.command == "create-issue": - create_issue(args.title, args.body) + create_issue(args.title, args.body_file) elif args.command == "create-pr": - create_pull_request(args.head, args.base, args.title, args.body) + create_pull_request(args.head, args.base, args.title, args.body_file) elif args.command == "comment-issue": comment_issue(args.issue_number, args.body) elif args.command == "close-issue":