Merge pull request 'feature/31-sample-lifecycle' (#35) from feature/31-sample-lifecycle into dev
Reviewed-on: luis_portillo/clinical_laboratory#35
This commit is contained in:
commit
c46908b66f
5
.env
5
.env
|
@ -9,3 +9,8 @@ POSTGRES_PASSWORD=supersegura
|
|||
ODOO_DB_NAME=lims_demo
|
||||
ODOO_MASTER_PASSWORD=admin
|
||||
ODOO_WEB_PORT=8069
|
||||
|
||||
GITEA_API_KEY=1ad57b1e553ee0b092d51f061e78c5c3df9f8107
|
||||
GITEA_API_KEY_URL=https://gitea.grupoconsiti.com/api/v1/
|
||||
GITEA_USERNAME=luis_portillo
|
||||
GITEA_REPO_NAME=clinical_laboratory
|
BIN
.gitignore
vendored
BIN
.gitignore
vendored
Binary file not shown.
227
CLAUDE.md
Normal file
227
CLAUDE.md
Normal 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'
|
140
GEMINI.md
140
GEMINI.md
|
@ -2,106 +2,62 @@
|
|||
|
||||
Este proyecto utiliza `tea` para interactuar con el repositorio de Gitea.
|
||||
|
||||
## Crear un Issue (Modo no Interactivo)
|
||||
## Gestión de Gitea con `gitea_cli_helper.py`
|
||||
|
||||
Para crear un nuevo issue de forma no interactiva, se utiliza el siguiente comando, proporcionando todos los datos necesarios mediante flags:
|
||||
Para interactuar con el repositorio de Gitea (crear issues, pull requests, comentar y cerrar issues) de forma robusta y con soporte para contenido multilínea, se recomienda utilizar el script de Python `gitea_cli_helper.py`. Este script lee la configuración sensible directamente desde el archivo `.env`.
|
||||
|
||||
### Configuración
|
||||
|
||||
Asegúrate de que las siguientes variables estén definidas en tu archivo `.env`:
|
||||
|
||||
- `GITEA_API_KEY`: Tu Token de Acceso Personal (PAT) de Gitea.
|
||||
- `GITEA_API_KEY_URL`: La URL base de la API de tu instancia de Gitea (ej. `https://gitea.grupoconsiti.com/api/v1/`).
|
||||
- `GITEA_USERNAME`: Tu nombre de usuario de Gitea (propietario del repositorio).
|
||||
- `GITEA_REPO_NAME`: El nombre del repositorio (ej. `clinical_laboratory`).
|
||||
|
||||
### Uso
|
||||
|
||||
El script `gitea_cli_helper.py` utiliza `argparse` para diferentes comandos:
|
||||
|
||||
#### 1. Crear un Issue
|
||||
|
||||
```bash
|
||||
tea issue create --title "Título del Issue" --description "Descripción detallada del issue." --labels "etiqueta1,etiqueta2"
|
||||
python gitea_cli_helper.py create-issue --title "Título del Issue" --body "Descripción detallada del issue.\nSoporta múltiples líneas."
|
||||
```
|
||||
|
||||
- `--title`: Especifica el título del issue.
|
||||
- `--description`: Especifica la descripción o cuerpo del issue.
|
||||
- `--labels`: Especifica una o más etiquetas separadas por comas.
|
||||
- `--title`: Título del issue.
|
||||
- `--body`: Cuerpo o descripción del issue. Los saltos de línea (`\n`) se interpretarán correctamente.
|
||||
|
||||
#### 2. Crear un Pull Request
|
||||
|
||||
```bash
|
||||
python gitea_cli_helper.py create-pr --head "tu-rama" --base "rama-destino" --title "Título del PR" --body "Descripción del Pull Request.\nSoporta múltiples líneas."
|
||||
```
|
||||
|
||||
- `--head`: Rama de origen (tu rama actual).
|
||||
- `--base`: Rama de destino (ej. `dev`, `main`).
|
||||
- `--title`: Título del Pull Request.
|
||||
- `--body`: Cuerpo o descripción del Pull Request.
|
||||
|
||||
#### 3. Comentar en un Issue
|
||||
|
||||
```bash
|
||||
python gitea_cli_helper.py comment-issue --issue-number 123 --body "Este es un nuevo comentario.\nTambién soporta múltiples líneas."
|
||||
```
|
||||
|
||||
- `--issue-number`: Número del issue al que se desea añadir el comentario.
|
||||
- `--body`: Contenido del comentario.
|
||||
|
||||
#### 4. Cerrar un Issue
|
||||
|
||||
```bash
|
||||
python gitea_cli_helper.py close-issue --issue-number 123
|
||||
```
|
||||
|
||||
- `--issue-number`: Número del issue a cerrar.
|
||||
|
||||
---
|
||||
|
||||
## Comentar en un Issue
|
||||
|
||||
Para agregar un comentario a un issue existente, se utiliza el comando `comment` seguido del número del issue y el texto del comentario entre comillas.
|
||||
|
||||
**Formato correcto:**
|
||||
|
||||
```bash
|
||||
tea comment <NÚMERO_ISSUE> "Tu comentario aquí"
|
||||
```
|
||||
|
||||
**Ejemplo:**
|
||||
|
||||
```bash
|
||||
tea comment 3 "Comentario de prueba"
|
||||
```
|
||||
|
||||
**Nota:** No se deben utilizar flags como `-i` o `--message`. El formato es directo.
|
||||
|
||||
---
|
||||
|
||||
## Realizar Commits
|
||||
|
||||
Debido a problemas de interpretación de comillas en el shell de ejecución, el uso de `git commit -m "mensaje"` puede fallar. Para evitar estos problemas, se debe pasar el mensaje del commit a través de la entrada estándar (`stdin`).
|
||||
|
||||
### Política de Mensajes de Commit
|
||||
|
||||
**Es mandatorio que el título de cada commit referencie el número del issue que resuelve.** Esto se hace para mantener una trazabilidad clara entre el código y las tareas.
|
||||
|
||||
**Formato del Título:**
|
||||
```
|
||||
<tipo>(#<issue_id>): <descripción breve>
|
||||
```
|
||||
- **`<tipo>`:** `feat` (nueva funcionalidad), `fix` (corrección de bug), `docs` (cambios en documentación), `style` (formato), `refactor`, `test`, `chore` (otras tareas).
|
||||
- **`(<issue_id>)`:** El número del issue entre paréntesis y precedido de `#`.
|
||||
|
||||
**Ejemplo:**
|
||||
```
|
||||
feat(#4): Agregar campos de género y fecha de nacimiento al paciente
|
||||
```
|
||||
|
||||
### Método Recomendado
|
||||
|
||||
Utiliza el comando `echo` y una tubería (`|`) para enviar el mensaje a `git commit -F -`.
|
||||
|
||||
**Commit de una sola línea:**
|
||||
|
||||
```bash
|
||||
echo "feat(#4): Tu mensaje de commit conciso" | git commit -F -
|
||||
```
|
||||
|
||||
**Commit multilínea:**
|
||||
Para mensajes de commit multilínea, la forma más segura es usar `printf` que maneja mejor los saltos de línea (`
|
||||
`):
|
||||
|
||||
```bash
|
||||
printf "feat(#4): Título del commit
|
||||
|
||||
Cuerpo del mensaje con descripción detallada." | git commit -F -
|
||||
```
|
||||
|
||||
Esto asegura que el formato del mensaje del commit se preserve correctamente.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Crear un Pull Request
|
||||
|
||||
Para crear un pull request (PR), se utiliza el comando `tea pulls create`. Debes especificar la rama base (hacia donde van los cambios) y la rama `head` (tu rama actual), junto con un título que referencie el issue que resuelve.
|
||||
|
||||
**Formato del comando:**
|
||||
|
||||
```bash
|
||||
tea pulls create --base "<rama_base>" --head "<tu_rama>" --title "<Tipo>(#issue): Título descriptivo"
|
||||
```
|
||||
|
||||
**Ejemplo:**
|
||||
|
||||
```bash
|
||||
tea pulls create --base "dev" --head "feature/3-core-setup" --title "feat(#3): Actualiza instrucciones en GEMINI.md"
|
||||
```
|
||||
|
||||
- `--base`: La rama de destino (ej. `dev`, `main`).
|
||||
- `--head`: Tu rama de trabajo actual.
|
||||
- `--title`: Un título claro que incluya el tipo de cambio (`feat`, `fix`, `docs`) y el número de issue.
|
||||
|
||||
---
|
||||
|
||||
## Contexto del Proyecto
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ Implementar una máquina de estados completa para el modelo `stock.lot` con el f
|
|||
|
||||
- **Archivo:** `lims_management/models/stock_lot.py`
|
||||
- **Tareas:**
|
||||
- [ ] **Añadir campo `state`:**
|
||||
- [x] **Añadir campo `state`:**
|
||||
- Tipo: `Selection`
|
||||
- Nombre técnico: `state`
|
||||
- String: "Estado"
|
||||
|
@ -24,7 +24,7 @@ Implementar una máquina de estados completa para el modelo `stock.lot` con el f
|
|||
- `stored`: 'Almacenada'
|
||||
- `disposed`: 'Desechada'
|
||||
- Atributos: `tracking=True` para registrar cambios en el chatter.
|
||||
- [ ] **Definir métodos para transiciones:**
|
||||
- [x] **Definir métodos para transiciones:**
|
||||
- `action_receive()`: Cambia el estado a `received`.
|
||||
- `action_start_analysis()`: Cambia el estado a `in_process`.
|
||||
- `action_complete_analysis()`: Cambia el estado a `analyzed`.
|
||||
|
@ -36,35 +36,35 @@ Implementar una máquina de estados completa para el modelo `stock.lot` con el f
|
|||
|
||||
- **Archivo:** `lims_management/views/stock_lot_views.xml`
|
||||
- **Tareas:**
|
||||
- [ ] **Vista de Formulario:**
|
||||
- [ ] **Añadir `header`:**
|
||||
- [x] **Vista de Formulario:**
|
||||
- [x] **Añadir `header`:**
|
||||
- Incorporar botones para las acciones (`action_receive`, `action_start_analysis`, etc.).
|
||||
- Controlar la visibilidad de los botones según el estado actual (ej. el botón "Recibir" solo debe ser visible si el estado es 'Recolectada').
|
||||
- [ ] **Añadir `statusbar`:**
|
||||
- [x] **Añadir `statusbar`:**
|
||||
- Visualizar el campo `state` usando el widget `statusbar`.
|
||||
- Definir el `statusbar_visible` para mostrar los estados clave del flujo principal.
|
||||
- [ ] **Hacer campos `readonly`:**
|
||||
- [x] **Hacer campos `readonly`:**
|
||||
- Campos como `patient_id`, `request_id`, `collection_date` deben volverse de solo lectura después de que la muestra es recibida para asegurar la integridad de los datos. Se usará el atributo `attrs` con el nuevo formato `invisible` o `readonly` basado en el campo `state`.
|
||||
- [ ] **Vista de Lista:**
|
||||
- [ ] Añadir el campo `state` para que sea visible.
|
||||
- [ ] Añadir el campo `state` a los filtros por defecto en el `search` para poder agrupar por estado fácilmente.
|
||||
- [x] **Vista de Lista:**
|
||||
- [x] Añadir el campo `state` para que sea visible.
|
||||
- [x] Añadir el campo `state` a los filtros por defecto en el `search` para poder agrupar por estado fácilmente.
|
||||
|
||||
### 3. Seguridad (Opcional, si es necesario)
|
||||
|
||||
- **Archivo:** `lims_management/security/lims_security.xml` o `ir.model.access.csv`
|
||||
- **Tareas:**
|
||||
- [ ] Evaluar si se necesitan reglas de seguridad específicas para controlar quién puede ejecutar las transiciones de estado. Por ahora, se asumirá que los grupos existentes (`group_lims_technician`, `group_lims_admin`) tienen los permisos.
|
||||
- [x] Evaluar si se necesitan reglas de seguridad específicas para controlar quién puede ejecutar las transiciones de estado. Por ahora, se asumirá que los grupos existentes (`group_lims_technician`, `group_lims_admin`) tienen los permisos.
|
||||
|
||||
### 4. Verificación y Pruebas
|
||||
|
||||
- **Pasos:**
|
||||
- [ ] Reiniciar la instancia de Odoo con el módulo actualizado.
|
||||
- [ ] Crear una nueva muestra de laboratorio manualmente.
|
||||
- [ ] Verificar que el estado por defecto sea 'Recolectada'.
|
||||
- [ ] Probar cada uno de los botones de transición de estado en la vista de formulario.
|
||||
- [ ] Confirmar que el `statusbar` se actualiza correctamente.
|
||||
- [ ] Revisar el chatter para asegurarse de que los cambios de estado se están registrando.
|
||||
- [ ] Verificar la visibilidad condicional de los botones y el modo de solo lectura de los campos.
|
||||
- [ ] Filtrar y agrupar por estado en la vista de lista.
|
||||
- [x] Reiniciar la instancia de Odoo con el módulo actualizado.
|
||||
- [x] Crear una nueva muestra de laboratorio manualmente.
|
||||
- [x] Verificar que el estado por defecto sea 'Recolectada'.
|
||||
- [x] Probar cada uno de los botones de transición de estado en la vista de formulario.
|
||||
- [x] Confirmar que el `statusbar` se actualiza correctamente.
|
||||
- [x] Revisar el chatter para asegurarse de que los cambios de estado se están registrando.
|
||||
- [x] Verificar la visibilidad condicional de los botones y el modo de solo lectura de los campos.
|
||||
- [x] Filtrar y agrupar por estado en la vista de lista.
|
||||
|
||||
---
|
||||
|
|
147
gitea_cli_helper.py
Normal file
147
gitea_cli_helper.py
Normal file
|
@ -0,0 +1,147 @@
|
|||
import requests
|
||||
import json
|
||||
import os
|
||||
import argparse
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Cargar variables del archivo .env
|
||||
load_dotenv()
|
||||
|
||||
# --- Configuración (obtenida de variables de entorno) ---
|
||||
GITEA_API_KEY = os.getenv("GITEA_API_KEY")
|
||||
GITEA_API_KEY_URL = os.getenv("GITEA_API_KEY_URL")
|
||||
GITEA_USERNAME = os.getenv("GITEA_USERNAME")
|
||||
GITEA_REPO_NAME = os.getenv("GITEA_REPO_NAME")
|
||||
|
||||
# Extraer la URL base de Gitea de GITEA_API_KEY_URL
|
||||
GITEA_BASE_URL = GITEA_API_KEY_URL.split('/api/v1/')[0]
|
||||
|
||||
def _make_gitea_request(method, endpoint, payload=None):
|
||||
"""Helper function to make authenticated requests to Gitea API."""
|
||||
api_url = f"{GITEA_BASE_URL}/api/v1/{endpoint}"
|
||||
headers = {
|
||||
"Accept": "application/json",
|
||||
"Authorization": f"token {GITEA_API_KEY}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
try:
|
||||
if method == "POST":
|
||||
response = requests.post(api_url, headers=headers, data=json.dumps(payload))
|
||||
elif method == "PATCH":
|
||||
response = requests.patch(api_url, headers=headers, data=json.dumps(payload))
|
||||
else:
|
||||
raise ValueError(f"Unsupported HTTP method: {method}")
|
||||
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Error en la solicitud {method} a {api_url}: {e}")
|
||||
if hasattr(e, 'response') and e.response is not None:
|
||||
print(f"Respuesta del servidor: {e.response.text}")
|
||||
raise
|
||||
|
||||
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}' 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_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,
|
||||
"base": base_branch,
|
||||
"title": title,
|
||||
"body": body
|
||||
}
|
||||
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']}")
|
||||
print(f" Título: {pr_data['title']}")
|
||||
print(f" URL: {pr_data['html_url']}")
|
||||
|
||||
def comment_issue(issue_number, body):
|
||||
"""Adds a comment to an existing issue."""
|
||||
endpoint = f"repos/{GITEA_USERNAME}/{GITEA_REPO_NAME}/issues/{issue_number}/comments"
|
||||
payload = {
|
||||
"body": body
|
||||
}
|
||||
print(f"Añadiendo comentario al issue #{issue_number}...")
|
||||
_make_gitea_request("POST", endpoint, payload)
|
||||
print(f"Comentario añadido al issue #{issue_number} exitosamente.")
|
||||
|
||||
def close_issue(issue_number):
|
||||
"""Closes an existing issue."""
|
||||
endpoint = f"repos/{GITEA_USERNAME}/{GITEA_REPO_NAME}/issues/{issue_number}"
|
||||
payload = {
|
||||
"state": "closed"
|
||||
}
|
||||
print(f"Cerrando issue #{issue_number}...")
|
||||
_make_gitea_request("PATCH", endpoint, payload)
|
||||
print(f"Issue #{issue_number} cerrado exitosamente.")
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Helper CLI para interactuar con la API de Gitea.")
|
||||
subparsers = parser.add_subparsers(dest="command", help="Comandos disponibles")
|
||||
|
||||
# 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-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-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.")
|
||||
comment_issue_parser.add_argument("--issue-number", type=int, required=True, help="Número del issue.")
|
||||
comment_issue_parser.add_argument("--body", required=True, help="Cuerpo del comentario (soporta multilínea con \n).")
|
||||
|
||||
# Subparser para cerrar issue
|
||||
close_issue_parser = subparsers.add_parser("close-issue", help="Cierra un issue existente.")
|
||||
close_issue_parser.add_argument("--issue-number", type=int, required=True, help="Número del issue a cerrar.")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.command == "create-issue":
|
||||
create_issue(args.title, args.body_file)
|
||||
elif args.command == "create-pr":
|
||||
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":
|
||||
close_issue(args.issue_number)
|
||||
else:
|
||||
parser.print_help()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Reference in New Issue
Block a user