Metadata-Version: 2.4
Name: clisitef-odoo
Version: 1.0.2
Summary: Cliente Python para o AgenteCliSiTef (TEF), para uso em integracoes com Odoo
Author-email: Sadson Diego <sadsondiego@gmail.com>
License: MIT
Project-URL: Homepage, https://github.com/GrupoZenir/clisitef_odoo
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Requires-Dist: requests>=2.20
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"

# clisitef_odoo

Cliente Python para o **AgenteCliSiTef** (a API REST local que expõe o TEF
CliSiTef/Software Express), construído a partir da lógica de
`agenteCliSiTef.js` deste projeto. O objetivo é permitir que um backend em
Python — em particular o Odoo — conduza transações de TEF (crédito, débito
etc.) sem depender de um navegador/DOM.

> O código em `agenteCliSiTef.js` e os HTMLs de exemplo (`index.html`,
> `venda_com_sessao.html`, `sessao.html`) foram usados como especificação do
> protocolo. Esta lib reimplementa o mesmo fluxo (`/state`, `/session`,
> `/startTransaction`, `/continueTransaction`, `/finishTransaction`,
> `/pinpad/*`) em Python puro, trocando os `onclick`/DOM por *callbacks*.

## Instalação

```bash
pip install -e /caminho/para/clisitef_odoo
# ou, publicando em um índice interno:
pip install clisitef-odoo
```

Dependência única: `requests`.

Para rodar os testes:

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

## Conceitos e mapeamento com o AgenteCliSiTef

| Endpoint AgenteCliSiTef | Método em `AgenteCliSiTefClient` |
|---|---|
| `GET /state` | `get_state()` |
| `POST /getVersion` | `get_version()` |
| `POST /session` | `create_session()` |
| `GET /session` | `get_session()` |
| `DELETE /session` | `delete_session()` |
| `POST /startTransaction` | `start_transaction()` |
| `POST /continueTransaction` | `continue_transaction()` |
| `POST /finishTransaction` | `finish_transaction()` |
| `POST /pinpad/open` | `pinpad_open()` |
| `POST /pinpad/close` | `pinpad_close()` |
| `POST /pinpad/isPresent` | `pinpad_is_present()` |
| `POST /pinpad/setDisplayMessage` | `pinpad_set_display_message()` |
| `POST /pinpad/readYesNo` | `pinpad_read_yes_no()` |

`AgenteCliSiTefClient` é *stateless* em relação ao fluxo: cada chamada apenas
faz o HTTP e valida `serviceStatus`. Quem interpreta o fluxo interativo da
CliSiTef (`clisitefStatus`/`commandId`) é a classe `TefTransaction`.

### Dois modos de sessão (igual ao JS original)

- **Sessão efêmera** (`inicio(1, funcao)` no JS): passe `sitef_ip`,
  `store_id`, `terminal_id` diretamente para `start_transaction`/`run` — o
  agente cria e destrói a sessão internamente a cada transação.
- **Sessão persistente** (`inicio(2, funcao)` no JS): crie a sessão uma vez
  com `create_session(...)` e reaproveite `sessionId` em várias
  transações (útil para caixas que ficam o turno todo conectados),
  encerrando com `delete_session()` ao final.

### O loop interativo (`TefTransaction`)

A CliSiTef conduz a transação em passos: a cada `continueTransaction`, ela
retorna um `commandId` que diz o que fazer a seguir. `TefTransaction.run()`
implementa esse loop (equivalente à função `continua()` do JS) e traduz cada
`commandId` em uma chamada de callback:

| commandId | Significado | Callback |
|---|---|---|
| `0` | Campo capturado (ex.: cupom, NSU, etc.) | acumulado em `result.fields`; `on_receipt` para os fieldId 121/122 (cupom estabelecimento/cliente) |
| `1,2,3,4,15` | Atualiza mensagem de status | `on_message(texto)` |
| `11,12,13,14,16` | Limpa mensagem de status | `on_message("")` |
| `22` | Alerta | `on_message(texto)` |
| `23` | Aguardando pinpad/cliente (pode cancelar) | `on_cancel_check()` a cada `poll_interval` segundos |
| `20` | Pergunta Sim/Não | `on_confirm(pergunta) -> bool` |
| `21,30-35,38` | Coleta de dado (CPF, senha, valor, etc.) | `on_input(prompt, field_id, min_len, max_len) -> str \| None` (`None` cancela a coleta) |

Todos os callbacks têm um padrão conservador (perguntas são respondidas
"Não", coletas de dado são canceladas) — **implemente `on_confirm`/`on_input`
de acordo com a regra de negócio real antes de usar em produção**, já que a
resposta correta depende do que está configurado na CliSiTef para aquele
terminal/loja.

Ao final do loop, `run()` chama automaticamente `finishTransaction`
(`confirm=1` se `clisitefStatus == 0`, `confirm=0` caso contrário). Isso é
mais seguro que o HTML de exemplo original, que só finaliza no caminho de
sucesso — use `auto_finish=False` e o método `finish()` se você quiser
controlar isso manualmente (ex.: pedir confirmação do operador antes de
efetivar).

## Exemplo básico

```python
from clisitef_odoo import AgenteCliSiTefClient, TefTransaction, constants

client = AgenteCliSiTefClient(base_url="https://127.0.0.1/agente/clisitef")

def responder_pergunta(pergunta):
    print("CliSiTef pergunta:", pergunta)
    return True  # confirma

def coletar_dado(prompt, field_id, min_len, max_len):
    return input(f"{prompt} ({min_len}-{max_len} caracteres): ")

tef = TefTransaction(
    client,
    on_message=lambda msg: print("Status:", msg),
    on_confirm=responder_pergunta,
    on_input=coletar_dado,
)

result = tef.run(
    function_id=constants.FUNCTION_VENDA_CREDITO,
    trn_amount=1000,  # R$ 10,00 em centavos
    tax_invoice_number="1234",
    tax_invoice_date="20260714",
    tax_invoice_time="153000",
    cashier_operator="CAIXA",
    sitef_ip="127.0.0.1",
    store_id="00000000",
    terminal_id="REST0001",
)

if result.success:
    print("Aprovado! NSU/campos:", result.fields)
    print("Cupom estabelecimento:\n", result.merchant_receipt)
    print("Cupom cliente:\n", result.customer_receipt)
else:
    print("Transação não aprovada, status CliSiTef:", result.clisitef_status)
```

### Usando sessão persistente

```python
session = client.create_session(sitef_ip="127.0.0.1", store_id="00000000", terminal_id="REST0001")
session_id = session["sessionId"]

result = tef.run(function_id=constants.FUNCTION_VENDA_CREDITO, trn_amount=2500, session_id=session_id)

# ... outras transações reaproveitando session_id ...

client.delete_session()
```

### Cancelando fora do fluxo normal

Equivalente ao botão "Finaliza estornando" dos exemplos HTML — útil quando a
aplicação perdeu o estado em memória (ex.: crash) mas precisa desfazer uma
transação pendente na CliSiTef:

```python
tef.finish_out_of_band(
    confirm=0,
    sitef_ip="127.0.0.1", store_id="00000000", terminal_id="REST0001",
    tax_invoice_number="1234", tax_invoice_date="20260714", tax_invoice_time="153000",
)
```

### Funções de pinpad

```python
client.pinpad_open(session_id)
client.pinpad_set_display_message(session_id, "Aguarde...", persistent=True)
presente = client.pinpad_is_present(session_id)["clisitefStatus"] == 1
resposta = client.pinpad_read_yes_no(session_id, "Aceita o valor?")  # clisitefStatus: 0=Anula, 1=Entra
client.pinpad_close(session_id)
```

## Tratamento de erros

- `AgentServiceError`: o **agente** (não a CliSiTef) reportou um problema —
  `serviceStatus != 0` (agente ocupado, sessão inválida, parâmetros
  incorretos). Contém `.service_status` e `.service_message`.
- `ClisitefProtocolError`: resposta em formato inesperado (ex.: sem
  `commandId` quando deveria ter).
- Falhas de rede (`requests.RequestException`) propagam normalmente — trate
  como indisponibilidade do agente (ex.: serviço não iniciado na máquina do
  caixa).

`result.success` (`clisitef_status == 0`) indica que a transação foi
aprovada; qualquer outro valor de `clisitef_status` (inclusive cancelamento
pelo operador/cliente) é considerado não sucesso e `run()` desfaz
automaticamente (`confirm=0`) quando `auto_finish=True`.

## Códigos de função (`functionId`)

`FUNCTION_VENDA_CREDITO = 3` está confirmado pelos exemplos deste projeto
(comentário "Inicia uma venda crédito" nos HTMLs). `FUNCTION_VENDA_DEBITO = 2`
foi confirmado no manual/contrato SiTef do terminal em uso neste projeto. Os
demais códigos de função (voucher, cheque, funções administrativas etc.)
variam conforme a versão/contrato da CliSiTef instalada — confirme com o
manual do fornecedor (Software Express) ou com quem configurou o SiTef antes
de utilizá-los em produção.

## Integração com Odoo

Veja [`examples/odoo_addon_example/`](examples/odoo_addon_example/) para um
esqueleto de addon do Odoo (Point of Sale) que:

1. Adiciona campos de configuração (`clisitef_base_url`, `sitef_ip`,
   `store_id`, `terminal_id`, `function_id`) em `pos.payment.method`.
2. Expõe um método `clisitef_run_payment()` que roda `TefTransaction.run()`
   inteiramente no servidor e devolve um dict serializável em JSON (sucesso,
   status, cupons, campos capturados).
3. Expõe esse método via um controller JSON (`/clisitef_tef/pay`) para ser
   chamado pela UI do PDV.
4. Traz um esqueleto de `PaymentInterface` em JS que apenas chama o backend
   e trata o retorno — **a API exata do `PaymentInterface` muda entre
   versões do Odoo** (14/16/17/18 têm assinaturas diferentes), então adapte
   os nomes de método conforme a sua versão.

Pontos importantes para adaptar aos seus módulos:

- **Onde roda o agente**: o AgenteCliSiTef roda na máquina física do caixa
  (pinpad conectado nela). Se o servidor Odoo for centralizado (não é a
  mesma máquina do caixa), o controller do exemplo não vai alcançar
  `127.0.0.1` do caixa — nesse caso, rode a lógica de pagamento em um
  processo local no caixa (ex.: um pequeno serviço Python que expõe uma rota
  para a UI do PDV) ao invés de no controller do servidor Odoo central.
- **Callbacks de negócio**: `on_confirm`/`on_input` no exemplo usam os
  padrões conservadores da lib (recusam/cancelam). Decida como isso deve se
  comportar no seu fluxo antes de ir para produção — por exemplo, se sua
  CliSiTef está configurada para pedir CPF do cliente, você precisa buscar
  esse dado do próprio pedido do Odoo e devolvê-lo via `on_input`.
- Este addon de exemplo **não foi testado contra uma instância real do
  Odoo** (não há um Odoo disponível no ambiente onde esta lib foi escrita) —
  trate-o como ponto de partida, não como módulo pronto para produção.

## Estrutura do pacote

```
clisitef_odoo/
  client.py       # AgenteCliSiTefClient - chamadas HTTP 1:1 com o agente
  transaction.py  # TefTransaction - loop interativo orientado a callbacks
  constants.py    # clisitefStatus, commandId, fieldId, functionId conhecidos
  models.py       # FieldCapture, TransactionResult
  exceptions.py   # AgentServiceError, ClisitefProtocolError, ...
examples/
  odoo_addon_example/  # esqueleto de addon Odoo (POS payment)
tests/
  test_client.py       # testes do cliente HTTP com mocks
  test_transaction.py  # testes do loop interativo com mocks
```
