# cli-anything-odoo_12

CLI para **Odoo 12 ERP** — consulte dados, gerencie módulos, exporte relatórios e faça perguntas em linguagem natural, tudo pelo terminal.

## Instalação

Em distros Linux modernas (PEP 668), a solução mais simples é usar `pipx`, que instala CLIs em ambientes isolados automaticamente:

```bash
# Instalar pipx (se ainda não tiver)
sudo apt install pipx
pipx ensurepath

# Instalar o CLI
pipx install cli-anything-odoo-12

# Atualizar para nova versão
pipx upgrade cli-anything-odoo-12
```

Alternativa com `pip` (em virtualenv ou sistemas sem PEP 668):

```bash
pip install cli-anything-odoo-12
```

Após instalar, o comando `cli-anything-odoo_12` fica disponível no terminal.

## Pré-requisitos

- **Python 3.10+**
- **Odoo 12 rodando** (acessível via rede, ex: `http://localhost:8069`)

## Quick Start

### 1. Connect to Odoo

The CLI supports two backend protocols:

| Backend | Flag | Auth | Requisitos |
|---------|------|------|------------|
| **REST** (padrão) | `--backend rest` | OAuth2 (client_credentials) | Módulo REST API instalado no Odoo |
| **XML-RPC** | `--backend xmlrpc` | Usuário/senha | Nenhum (nativo do Odoo) |

#### Opção A: REST API (recomendado)

Requer configuração única do OAuth2:

**Passo 1 — Criar Client OAuth2 no Odoo:**

1. Acesse **Configurações > REST API > OAuth2**
2. Crie um novo client:
   - **Nome**: ex. "CLI API"
   - **Tipo OAuth**: `Client Credentials`
   - **Usuário**: selecione o usuário Odoo que o client vai operar
3. Copie o **Client Key** e **Client Secret**

**Passo 2 — Configurar o CLI:**

```bash
cli-anything-odoo_12 config setup-rest \
  --client-id <CLIENT_KEY> \
  --client-secret <CLIENT_SECRET> \
  --url http://localhost:8069 \
  --db mydb
```

**Passo 3 — Verificar:**

```bash
cli-anything-odoo_12 record search res.partner --domain "[('is_company','=',True)]" --limit 5
```

#### Opção B: XML-RPC (sem configuração extra)

```bash
# Connect via XML-RPC and save credentials
cli-anything-odoo_12 connect \
  --url http://localhost:8069 --db mydb --user admin --password admin \
  --backend xmlrpc --save
```

#### Após configurar (ambos os backends)

```bash
# As credenciais são reutilizadas automaticamente nos próximos comandos:
cli-anything-odoo_12 record search res.partner --limit 5
cli-anything-odoo_12 ask "empresas"

# Trocar backend por comando:
cli-anything-odoo_12 --backend xmlrpc record search res.partner --limit 5

# Ou passar credenciais por comando (sobrescreve config salva):
cli-anything-odoo_12 --url http://localhost:8069 --db mydb --user admin --password admin record search res.partner
```

### 2. Record Operations (CRUD)

```bash
# Search partners (companies only)
cli-anything-odoo_12 record search res.partner --domain "[('is_company','=',True)]" --fields name,email --limit 10

# Example output:
#   Found 10 record(s) in res.partner
#   [0]
#     id: 1
#     name: My Company
#     email: info@mycompany.com
#   [1]
#     id: 14
#     name: Azure Interior
#     email: azure@example.com

# Read specific record
cli-anything-odoo_12 record read res.partner 1 --fields name,email,phone

# Create record
cli-anything-odoo_12 record create res.partner --values '{"name":"New Company","is_company":true}'
# Output: Created res.partner record id=42

# Update record
cli-anything-odoo_12 record write res.partner 42 --values '{"phone":"+55 11 99999-0000"}'

# Delete record
cli-anything-odoo_12 record unlink res.partner 42

# Count records
cli-anything-odoo_12 record count res.partner --domain "[('is_company','=',True)]"
# Output: res.partner: 15 record(s)

# List model fields
cli-anything-odoo_12 record fields res.partner
# Output: res.partner: 143 fields
```

### 3. Module Management

```bash
cli-anything-odoo_12 module list --installed
# Output: Found 254 module(s)

cli-anything-odoo_12 module list --search sale
cli-anything-odoo_12 module info sale
cli-anything-odoo_12 module install sale
cli-anything-odoo_12 module update sale
```

### 4. Database Operations

```bash
cli-anything-odoo_12 db list
# Output: Found 3 database(s)
#   - erp-production
#   - erp-staging
#   - test_cli

cli-anything-odoo_12 db create new_db --lang pt_BR
cli-anything-odoo_12 db backup mydb /tmp/mydb_backup.zip
cli-anything-odoo_12 db restore new_db /tmp/mydb_backup.zip
cli-anything-odoo_12 db drop old_db --confirm
```

### 5. Reports

```bash
cli-anything-odoo_12 report list --model account.invoice
# Output: Found 63 report(s)

cli-anything-odoo_12 report render account.report_invoice 42 --output invoice.pdf
# Output: Report saved: invoice.pdf (45,231 bytes)
```

### 6. Data Export

```bash
# Export all partners to CSV
cli-anything-odoo_12 export csv res.partner --output partners.csv --fields name,email,phone
# Output: Exported 920 records to partners.csv

# Export with domain filter
cli-anything-odoo_12 export csv res.partner --output companies.csv --fields name,email --domain "[('is_company','=',True)]"
# Output: Exported 15 records to companies.csv

# Export to JSON with limit
cli-anything-odoo_12 export json res.partner --output partners.json --fields name,email --limit 100
# Output: Exported 100 records to partners.json

# Export to stdout (omit --output)
cli-anything-odoo_12 export json res.partner --fields name --limit 3
```

### 7. Natural Language Query (NLQ)

The `ask` command translates free-text queries (PT-BR, EN, ES) into Odoo search domains using regex patterns. No external dependencies or LLM required.

#### Simple Queries

```bash
# List companies
cli-anything-odoo_12 ask "liste todas as empresas"

# Example output:
#   ━━━ Empresas ━━━
#     Consulta: res.company | 3 registro(s)
#
#     ID   Nome                  E-mail              Telefone
#     ───  ────────────────────  ──────────────────  ──────────
#     1    The Cookiery          info@cookiery.com   (11) 3333-4444
#     3    Filial Centro         centro@cookiery.com —
#     5    Filial Shopping       shop@cookiery.com   —
#
#     Total: 3 registro(s)

# Search with filters
cli-anything-odoo_12 ask "faturas de entrada pagas em janeiro/2024"

# Example output:
#   ━━━ Faturas de Entrada — Paid — Janeiro/2024 ━━━
#     Consulta: account.invoice | 8 registro(s)
#
#     ID    Numero    Parceiro           Data         Total (R$)  Status
#     ────  ────────  ─────────────────  ──────────   ──────────  ──────
#     1042  INV/001   Fornecedor ABC     2024-01-05   15.230,00   paid
#     1043  INV/002   Fornecedor XYZ     2024-01-12    8.500,00   paid
#     ...
#
#     Total: 8 registro(s) | Soma: R$ 87.430,00

# Active customers with limit
cli-anything-odoo_12 ask "clientes ativos" --limit 10
```

#### Cross-Model Grouping (Analytical Queries)

Group orders by product category or individual products — the CLI automatically traverses related models (order > line > product > category).

```bash
# POS orders grouped by product category
cli-anything-odoo_12 ask "vendas do pos de janeiro/2026 agrupado por categoria de produto"

# Example output:
#   ━━━ Vendas do POS — Janeiro/2026 — por Categoria de Produto ━━━
#     Consulta: pos.order | 229 itens de linha
#
#     Categoria de Produto  Itens  Qtd   Total (R$)   %
#     ────────────────────  ─────  ────  ──────────   ────
#     Cookies               98     156   2.376,50     67.4%
#     Bebidas               45     89      534,20     15.1%
#     Salgados              38     62      312,80      8.9%
#     Doces                 28     45      198,34      5.6%
#     Sem Categoria         20     32      106,00      3.0%
#
#     Total: 229 itens | Qtd: 384 | Soma: R$ 3.527,84

# POS orders grouped by individual product
cli-anything-odoo_12 ask "vendas do pos agrupado por produto"

# Invoice grouped by product category
cli-anything-odoo_12 ask "faturas agrupado por categoria de produto"

# Purchase orders grouped by product category
cli-anything-odoo_12 ask "pedidos de compra agrupado por categoria de produto"
```

#### Supported Cross-Model Groupings

| Model | Group Key | Traversal Path |
|-------|-----------|----------------|
| pos.order | `categoria de produto` | pos.order > pos.order.line > product.product > product.category |
| pos.order | `categoria pos` | pos.order > pos.order.line > product.product > pos.category |
| pos.order | `produto` | pos.order > pos.order.line > product.product |
| account.invoice | `categoria de produto` | account.invoice > account.invoice.line > product.product > product.category |
| account.invoice | `produto` | account.invoice > account.invoice.line > product.product |
| sale.order | `categoria de produto` | sale.order > sale.order.line > product.product > product.category |
| sale.order | `produto` | sale.order > sale.order.line > product.product |
| purchase.order | `categoria de produto` | purchase.order > purchase.order.line > product.product > product.category |

#### Simple Field Grouping

```bash
# POS orders grouped by point-of-sale terminal
cli-anything-odoo_12 ask "vendas do pos agrupado por pdv"

# Example output:
#   ━━━ Vendas do POS ━━━
#     Consulta: pos.order | 50 registro(s)
#
#     Grupo              Qtd  Total (R$)   %
#     ─────────────────  ───  ──────────   ────
#     Loja Centro        28   4.520,00     65.2%
#     Loja Shopping      22   2.410,00     34.8%
#
#     Total: 50 registro(s) | Soma: R$ 6.930,00
```

#### With Export

```bash
# Export to CSV
cli-anything-odoo_12 ask "faturas de entrada em janeiro/2024 e exporte para csv em /tmp/faturas.csv"
# Output: ... Exportado para /tmp/faturas.csv

# Export to JSON
cli-anything-odoo_12 ask "empresas e exporte para json em /tmp/empresas.json"

# Complex combined query with export
cli-anything-odoo_12 ask "faturas de entrada pagas em janeiro/2024 acima de R$ 5.000 e exporte para csv em /tmp/fat.csv"
```

#### Other Query Examples

```bash
# Birthdays in a specific month
cli-anything-odoo_12 ask "aniversariantes de marco"

# Date range
cli-anything-odoo_12 ask "faturas de 01/03 a 15/03"

# Relative dates
cli-anything-odoo_12 ask "pedidos de compra este mes"
cli-anything-odoo_12 ask "faturas ultimo mes"

# Amount filters (BRL format)
cli-anything-odoo_12 ask "faturas acima de R$ 10.000"
cli-anything-odoo_12 ask "pedidos de venda abaixo de 5000"

# English queries
cli-anything-odoo_12 ask "invoices above 5000"
cli-anything-odoo_12 ask "active customers"
cli-anything-odoo_12 ask "paid invoices in january/2024"

# Spanish queries
cli-anything-odoo_12 ask "facturas pagadas"
```

#### Supported Aliases (Multilingual)

| PT-BR | EN | ES | Odoo Model |
|-------|----|----|------------|
| empresas | companies | — | res.company |
| parceiros, contatos | partners, contacts | socios | res.partner |
| clientes | customers | — | res.partner (customer=True) |
| fornecedores | suppliers | — | res.partner (supplier=True) |
| faturas, notas fiscais | invoices | facturas | account.invoice |
| pedidos de venda | sale orders | pedido de venta | sale.order |
| pedidos de compra | purchase orders | orden de compra | purchase.order |
| produtos | products | — | product.product |
| pagamentos | payments | — | account.payment |
| vendas do pos, pedidos pos | — | — | pos.order |
| usuarios | users | — | res.users |
| aniversariantes | — | — | res.partner (birthday) |

#### Supported Filters

| Filter | PT-BR | EN |
|--------|-------|----|
| Status | pagas, em aberto, rascunho, canceladas, confirmadas | paid, open, draft, cancelled, done |
| Type | de entrada, de saida | incoming, outgoing |
| Date | em janeiro/2024, este mes, ultimo mes | in january/2024, this month, last month |
| Range | de 01/03 a 15/03 | from 01/03 to 15/03 |
| Amount | acima de R$ 10.000, abaixo de 5000 | above 5000, below 1000 |
| Boolean | ativos, inativos, arquivados | active, inactive, archived |
| Document | CPF 123.456.789-00 | — |
| Group | agrupado por categoria de produto | grouped by product category |
| Export | exporte para csv em /tmp/f.csv | — |
| Limit | top 10, limitar a 5 | limit 10 |

### 8. Configuration Management

```bash
# Show saved config (passwords/tokens are masked)
cli-anything-odoo_12 config show

# Example output:
#   url: http://localhost:8069
#   db: erp-thecookiery
#   user: nexuz@nexuz.com.br
#   password: adm***
#   backend: rest
#   grant_type: client_credentials
#   client_id: vjVaBgp7...
#   client_secret: 4EIwDzsN***
#   access_token: eyJhbGci***
#   token_expiry: 1742060400.0

# Delete a specific key
cli-anything-odoo_12 config delete --key access_token

# Delete all config
cli-anything-odoo_12 config delete
```

### 9. JSON Output (for agents)

```bash
# Any command with --json produces machine-readable output
cli-anything-odoo_12 --json record search res.partner --limit 3

# Example output:
# {
#   "model": "res.partner",
#   "count": 3,
#   "records": [
#     {"id": 1, "name": "My Company", "email": "info@co.com"},
#     ...
#   ]
# }

cli-anything-odoo_12 --json ask "empresas"

# Cross-group JSON output
cli-anything-odoo_12 --json ask "vendas do pos agrupado por categoria de produto"

# Example output:
# {
#   "model": "pos.order",
#   "title": "Vendas do POS",
#   "group_by": "Categoria de Produto",
#   "groups": [
#     {"name": "Cookies", "count": 98, "qty": 156.0, "total": 2376.50, "pct": 67.4},
#     ...
#   ],
#   "grand_count": 229,
#   "grand_qty": 384.0,
#   "grand_total": 3527.84
# }

cli-anything-odoo_12 --json module list --installed
cli-anything-odoo_12 --json db list
```

### 10. Interactive REPL

```bash
# Start REPL (just run without subcommand)
cli-anything-odoo_12 --url http://localhost:8069 --db mydb --user admin --password admin

# Example session:
#   cli-anything-odoo_12  v1.0.0
#   Connected to http://localhost:8069 db=mydb uid=2
#
#   odoo_12 [mydb] > record search res.partner --limit 5
#   Found 5 record(s) in res.partner
#   ...
#
#   odoo_12 [mydb] > ask "faturas pagas em janeiro/2024"
#   ━━━ Faturas — Paid — Janeiro/2024 ━━━
#   ...
#
#   odoo_12 [mydb] > quit
```

## Global Flags

| Flag | Effect |
|------|--------|
| `--json` | Machine-readable JSON output (all commands) |
| `--url` | Odoo server URL (overrides saved config) |
| `--db` | Database name (overrides saved config) |
| `--user` | Username (overrides saved config) |
| `--password` | Password (overrides saved config) |
| `--backend` | Backend protocol: `rest` (default) or `xmlrpc` |

## Architecture

```
cli_anything/odoo_12/
├── odoo_12_cli.py              # Main CLI entry point (Click framework)
├── core/
│   ├── aliases.py              # Multilingual model/status/type alias registry
│   ├── connection.py           # Config management + backend factory (get_rpc)
│   ├── cross_group.py          # Cross-model grouping engine
│   ├── database.py             # Database operations (list, create, backup, etc.)
│   ├── export.py               # CSV/JSON export
│   ├── formatters.py           # Business-friendly output formatting
│   ├── module.py               # Module management
│   ├── nlq.py                  # Natural Language Query parser (regex-based)
│   ├── record.py               # CRUD operations
│   ├── report.py               # Report generation
│   └── session.py              # Undo/redo + command history
├── utils/
│   ├── odoo_backend_base.py    # ABC OdooBackend (21 abstract methods) + config I/O
│   ├── odoo_backend.py         # OdooRPC (XML-RPC) + backward-compat re-exports
│   ├── odoo_rest.py            # OdooREST (REST API, OAuth2, urllib — zero deps)
│   └── repl_skin.py            # Terminal UI (tables, colors, prompts)
└── tests/
    ├── TEST.md                 # Test plan + results
    ├── test_core.py            # Unit tests (82 tests, no Odoo needed)
    ├── test_rest.py            # REST backend unit tests (24 tests, mock HTTP)
    └── test_full_e2e.py        # E2E tests (27 tests, requires running Odoo)
```

### Backend Architecture

All commands work identically regardless of backend. The `OdooBackend` ABC defines 21 methods
implemented by both `OdooRPC` (XML-RPC) and `OdooREST` (REST API):

```
                     OdooBackend (ABC)
                    21 abstract methods
                   /                   \
            OdooRPC                   OdooREST
         (xmlrpc.client)          (urllib — REST API)
         Built-in Odoo API        OAuth2 + JSON/HTTP
```

The `connection.py` factory (`get_rpc()`) selects the backend based on saved config or `--backend` flag.
REST is the default; XML-RPC is available as fallback via `--backend xmlrpc`.

### OAuth2

O CLI utiliza o grant type `client_credentials`. O client OAuth2 é vinculado a um usuário no Odoo,
e a autenticação usa apenas `client_id` + `client_secret`.

O ciclo de vida do token é automático: tokens são persistidos em `config.json`, renovados na expiração
e re-autenticados em caso de falha (retry em 401).

## Troubleshooting

| Problema | Causa | Solucao |
|----------|-------|---------|
| `Connection refused` | Odoo offline ou URL errada | Verificar se Odoo esta rodando: `curl http://localhost:8069/web/login` |
| `Access Denied` | Credenciais invalidas | Verificar usuario/senha: `cli-anything-odoo_12 connect --url ... --save` |
| `OAuth2 authentication failed` | client_id/secret incorretos | Verificar OAuth2 client no Odoo (Settings > REST API > OAuth2) |
| `unauthorized_client` | OAuth Type do client incorreto | No Odoo, verificar se o OAuth Type esta como `Client Credentials` |
| `REST API error 500: AccessError` | Usuario REST sem permissoes suficientes | Alterar tipo do usuario para "Usuario Interno" (Settings > Users) |
| `Modelo nao reconhecido na query` | Alias NLQ nao existe | Usar alias suportado (ver tabela de aliases) ou `record search <model>` |
| `command not found` | CLI nao instalado | Executar `pip install cli-anything-odoo-12` e verificar `which cli-anything-odoo_12` |
| `404 on /api/*` | Modulo REST API nao instalado | Instalar modulo REST API no Odoo ou usar `--backend xmlrpc` |
| NLQ retorna 0 registros | Filtros muito restritivos | Simplificar a query (remover filtro de data) e testar incrementalmente |
| Export CSV vazio | Domain sem resultados | Testar a mesma query sem export primeiro para confirmar resultados |

## Running Tests

```bash
pip install -e ".[dev]"

# Unit tests (no Odoo needed)
python3 -m pytest cli_anything/odoo_12/tests/test_core.py -v

# REST backend unit tests (no Odoo needed — uses mock HTTP)
python3 -m pytest cli_anything/odoo_12/tests/test_rest.py -v

# E2E tests (requires running Odoo)
ODOO_URL=http://localhost:8069 ODOO_DB=mydb ODOO_USER=admin ODOO_PASSWORD=admin \
  python3 -m pytest cli_anything/odoo_12/tests/test_full_e2e.py -v -s

# All tests
CLI_ANYTHING_FORCE_INSTALLED=1 python3 -m pytest cli_anything/odoo_12/tests/ -v -s
```
