feat: Update gitea_cli_helper.py to use file input and add CLAUDE.md

- Modified gitea_cli_helper.py to read issue/PR body from files instead of inline text
- Added CLAUDE.md with comprehensive development guidelines for Claude Code
- CLAUDE.md includes Odoo 18 specific conventions, Docker commands, and project structure

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Luis Ernesto Portillo Zaldivar 2025-07-14 20:22:31 -06:00
parent 40123969b1
commit 472f88a477
2 changed files with 249 additions and 8 deletions

227
CLAUDE.md Normal file
View File

@ -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 `<list>` instead of `<tree>` - using `<tree>` 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
<!-- 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 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'

View File

@ -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":