clisitef-odoo 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,262 @@
1
+ Metadata-Version: 2.4
2
+ Name: clisitef-odoo
3
+ Version: 0.1.0
4
+ Summary: Cliente Python para o AgenteCliSiTef (TEF), para uso em integracoes com Odoo
5
+ Author-email: Sadson Diego <sadsondiego@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/GrupoZenir/clisitef_odoo
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.8
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: requests>=2.20
14
+ Provides-Extra: dev
15
+ Requires-Dist: pytest>=7.0; extra == "dev"
16
+
17
+ # clisitef_odoo
18
+
19
+ Cliente Python para o **AgenteCliSiTef** (a API REST local que expõe o TEF
20
+ CliSiTef/Software Express), construído a partir da lógica de
21
+ `agenteCliSiTef.js` deste projeto. O objetivo é permitir que um backend em
22
+ Python — em particular o Odoo — conduza transações de TEF (crédito, débito
23
+ etc.) sem depender de um navegador/DOM.
24
+
25
+ > O código em `agenteCliSiTef.js` e os HTMLs de exemplo (`index.html`,
26
+ > `venda_com_sessao.html`, `sessao.html`) foram usados como especificação do
27
+ > protocolo. Esta lib reimplementa o mesmo fluxo (`/state`, `/session`,
28
+ > `/startTransaction`, `/continueTransaction`, `/finishTransaction`,
29
+ > `/pinpad/*`) em Python puro, trocando os `onclick`/DOM por *callbacks*.
30
+
31
+ ## Instalação
32
+
33
+ ```bash
34
+ pip install -e /caminho/para/clisitef_odoo
35
+ # ou, publicando em um índice interno:
36
+ pip install clisitef-odoo
37
+ ```
38
+
39
+ Dependência única: `requests`.
40
+
41
+ Para rodar os testes:
42
+
43
+ ```bash
44
+ pip install -e ".[dev]"
45
+ pytest
46
+ ```
47
+
48
+ ## Conceitos e mapeamento com o AgenteCliSiTef
49
+
50
+ | Endpoint AgenteCliSiTef | Método em `AgenteCliSiTefClient` |
51
+ |---|---|
52
+ | `GET /state` | `get_state()` |
53
+ | `POST /getVersion` | `get_version()` |
54
+ | `POST /session` | `create_session()` |
55
+ | `GET /session` | `get_session()` |
56
+ | `DELETE /session` | `delete_session()` |
57
+ | `POST /startTransaction` | `start_transaction()` |
58
+ | `POST /continueTransaction` | `continue_transaction()` |
59
+ | `POST /finishTransaction` | `finish_transaction()` |
60
+ | `POST /pinpad/open` | `pinpad_open()` |
61
+ | `POST /pinpad/close` | `pinpad_close()` |
62
+ | `POST /pinpad/isPresent` | `pinpad_is_present()` |
63
+ | `POST /pinpad/setDisplayMessage` | `pinpad_set_display_message()` |
64
+ | `POST /pinpad/readYesNo` | `pinpad_read_yes_no()` |
65
+
66
+ `AgenteCliSiTefClient` é *stateless* em relação ao fluxo: cada chamada apenas
67
+ faz o HTTP e valida `serviceStatus`. Quem interpreta o fluxo interativo da
68
+ CliSiTef (`clisitefStatus`/`commandId`) é a classe `TefTransaction`.
69
+
70
+ ### Dois modos de sessão (igual ao JS original)
71
+
72
+ - **Sessão efêmera** (`inicio(1, funcao)` no JS): passe `sitef_ip`,
73
+ `store_id`, `terminal_id` diretamente para `start_transaction`/`run` — o
74
+ agente cria e destrói a sessão internamente a cada transação.
75
+ - **Sessão persistente** (`inicio(2, funcao)` no JS): crie a sessão uma vez
76
+ com `create_session(...)` e reaproveite `sessionId` em várias
77
+ transações (útil para caixas que ficam o turno todo conectados),
78
+ encerrando com `delete_session()` ao final.
79
+
80
+ ### O loop interativo (`TefTransaction`)
81
+
82
+ A CliSiTef conduz a transação em passos: a cada `continueTransaction`, ela
83
+ retorna um `commandId` que diz o que fazer a seguir. `TefTransaction.run()`
84
+ implementa esse loop (equivalente à função `continua()` do JS) e traduz cada
85
+ `commandId` em uma chamada de callback:
86
+
87
+ | commandId | Significado | Callback |
88
+ |---|---|---|
89
+ | `0` | Campo capturado (ex.: cupom, NSU, etc.) | acumulado em `result.fields`; `on_receipt` para os fieldId 121/122 (cupom estabelecimento/cliente) |
90
+ | `1,2,3,4,15` | Atualiza mensagem de status | `on_message(texto)` |
91
+ | `11,12,13,14,16` | Limpa mensagem de status | `on_message("")` |
92
+ | `22` | Alerta | `on_message(texto)` |
93
+ | `23` | Aguardando pinpad/cliente (pode cancelar) | `on_cancel_check()` a cada `poll_interval` segundos |
94
+ | `20` | Pergunta Sim/Não | `on_confirm(pergunta) -> bool` |
95
+ | `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) |
96
+
97
+ Todos os callbacks têm um padrão conservador (perguntas são respondidas
98
+ "Não", coletas de dado são canceladas) — **implemente `on_confirm`/`on_input`
99
+ de acordo com a regra de negócio real antes de usar em produção**, já que a
100
+ resposta correta depende do que está configurado na CliSiTef para aquele
101
+ terminal/loja.
102
+
103
+ Ao final do loop, `run()` chama automaticamente `finishTransaction`
104
+ (`confirm=1` se `clisitefStatus == 0`, `confirm=0` caso contrário). Isso é
105
+ mais seguro que o HTML de exemplo original, que só finaliza no caminho de
106
+ sucesso — use `auto_finish=False` e o método `finish()` se você quiser
107
+ controlar isso manualmente (ex.: pedir confirmação do operador antes de
108
+ efetivar).
109
+
110
+ ## Exemplo básico
111
+
112
+ ```python
113
+ from clisitef_odoo import AgenteCliSiTefClient, TefTransaction, constants
114
+
115
+ client = AgenteCliSiTefClient(base_url="https://127.0.0.1/agente/clisitef")
116
+
117
+ def responder_pergunta(pergunta):
118
+ print("CliSiTef pergunta:", pergunta)
119
+ return True # confirma
120
+
121
+ def coletar_dado(prompt, field_id, min_len, max_len):
122
+ return input(f"{prompt} ({min_len}-{max_len} caracteres): ")
123
+
124
+ tef = TefTransaction(
125
+ client,
126
+ on_message=lambda msg: print("Status:", msg),
127
+ on_confirm=responder_pergunta,
128
+ on_input=coletar_dado,
129
+ )
130
+
131
+ result = tef.run(
132
+ function_id=constants.FUNCTION_VENDA_CREDITO,
133
+ trn_amount=1000, # R$ 10,00 em centavos
134
+ tax_invoice_number="1234",
135
+ tax_invoice_date="20260714",
136
+ tax_invoice_time="153000",
137
+ cashier_operator="CAIXA",
138
+ sitef_ip="127.0.0.1",
139
+ store_id="00000000",
140
+ terminal_id="REST0001",
141
+ )
142
+
143
+ if result.success:
144
+ print("Aprovado! NSU/campos:", result.fields)
145
+ print("Cupom estabelecimento:\n", result.merchant_receipt)
146
+ print("Cupom cliente:\n", result.customer_receipt)
147
+ else:
148
+ print("Transação não aprovada, status CliSiTef:", result.clisitef_status)
149
+ ```
150
+
151
+ ### Usando sessão persistente
152
+
153
+ ```python
154
+ session = client.create_session(sitef_ip="127.0.0.1", store_id="00000000", terminal_id="REST0001")
155
+ session_id = session["sessionId"]
156
+
157
+ result = tef.run(function_id=constants.FUNCTION_VENDA_CREDITO, trn_amount=2500, session_id=session_id)
158
+
159
+ # ... outras transações reaproveitando session_id ...
160
+
161
+ client.delete_session()
162
+ ```
163
+
164
+ ### Cancelando fora do fluxo normal
165
+
166
+ Equivalente ao botão "Finaliza estornando" dos exemplos HTML — útil quando a
167
+ aplicação perdeu o estado em memória (ex.: crash) mas precisa desfazer uma
168
+ transação pendente na CliSiTef:
169
+
170
+ ```python
171
+ tef.finish_out_of_band(
172
+ confirm=0,
173
+ sitef_ip="127.0.0.1", store_id="00000000", terminal_id="REST0001",
174
+ tax_invoice_number="1234", tax_invoice_date="20260714", tax_invoice_time="153000",
175
+ )
176
+ ```
177
+
178
+ ### Funções de pinpad
179
+
180
+ ```python
181
+ client.pinpad_open(session_id)
182
+ client.pinpad_set_display_message(session_id, "Aguarde...", persistent=True)
183
+ presente = client.pinpad_is_present(session_id)["clisitefStatus"] == 1
184
+ resposta = client.pinpad_read_yes_no(session_id, "Aceita o valor?") # clisitefStatus: 0=Anula, 1=Entra
185
+ client.pinpad_close(session_id)
186
+ ```
187
+
188
+ ## Tratamento de erros
189
+
190
+ - `AgentServiceError`: o **agente** (não a CliSiTef) reportou um problema —
191
+ `serviceStatus != 0` (agente ocupado, sessão inválida, parâmetros
192
+ incorretos). Contém `.service_status` e `.service_message`.
193
+ - `ClisitefProtocolError`: resposta em formato inesperado (ex.: sem
194
+ `commandId` quando deveria ter).
195
+ - Falhas de rede (`requests.RequestException`) propagam normalmente — trate
196
+ como indisponibilidade do agente (ex.: serviço não iniciado na máquina do
197
+ caixa).
198
+
199
+ `result.success` (`clisitef_status == 0`) indica que a transação foi
200
+ aprovada; qualquer outro valor de `clisitef_status` (inclusive cancelamento
201
+ pelo operador/cliente) é considerado não sucesso e `run()` desfaz
202
+ automaticamente (`confirm=0`) quando `auto_finish=True`.
203
+
204
+ ## Códigos de função (`functionId`)
205
+
206
+ `FUNCTION_VENDA_CREDITO = 3` está confirmado pelos exemplos deste projeto
207
+ (comentário "Inicia uma venda crédito" nos HTMLs). `FUNCTION_VENDA_DEBITO = 2`
208
+ foi confirmado no manual/contrato SiTef do terminal em uso neste projeto. Os
209
+ demais códigos de função (voucher, cheque, funções administrativas etc.)
210
+ variam conforme a versão/contrato da CliSiTef instalada — confirme com o
211
+ manual do fornecedor (Software Express) ou com quem configurou o SiTef antes
212
+ de utilizá-los em produção.
213
+
214
+ ## Integração com Odoo
215
+
216
+ Veja [`examples/odoo_addon_example/`](examples/odoo_addon_example/) para um
217
+ esqueleto de addon do Odoo (Point of Sale) que:
218
+
219
+ 1. Adiciona campos de configuração (`clisitef_base_url`, `sitef_ip`,
220
+ `store_id`, `terminal_id`, `function_id`) em `pos.payment.method`.
221
+ 2. Expõe um método `clisitef_run_payment()` que roda `TefTransaction.run()`
222
+ inteiramente no servidor e devolve um dict serializável em JSON (sucesso,
223
+ status, cupons, campos capturados).
224
+ 3. Expõe esse método via um controller JSON (`/clisitef_tef/pay`) para ser
225
+ chamado pela UI do PDV.
226
+ 4. Traz um esqueleto de `PaymentInterface` em JS que apenas chama o backend
227
+ e trata o retorno — **a API exata do `PaymentInterface` muda entre
228
+ versões do Odoo** (14/16/17/18 têm assinaturas diferentes), então adapte
229
+ os nomes de método conforme a sua versão.
230
+
231
+ Pontos importantes para adaptar aos seus módulos:
232
+
233
+ - **Onde roda o agente**: o AgenteCliSiTef roda na máquina física do caixa
234
+ (pinpad conectado nela). Se o servidor Odoo for centralizado (não é a
235
+ mesma máquina do caixa), o controller do exemplo não vai alcançar
236
+ `127.0.0.1` do caixa — nesse caso, rode a lógica de pagamento em um
237
+ processo local no caixa (ex.: um pequeno serviço Python que expõe uma rota
238
+ para a UI do PDV) ao invés de no controller do servidor Odoo central.
239
+ - **Callbacks de negócio**: `on_confirm`/`on_input` no exemplo usam os
240
+ padrões conservadores da lib (recusam/cancelam). Decida como isso deve se
241
+ comportar no seu fluxo antes de ir para produção — por exemplo, se sua
242
+ CliSiTef está configurada para pedir CPF do cliente, você precisa buscar
243
+ esse dado do próprio pedido do Odoo e devolvê-lo via `on_input`.
244
+ - Este addon de exemplo **não foi testado contra uma instância real do
245
+ Odoo** (não há um Odoo disponível no ambiente onde esta lib foi escrita) —
246
+ trate-o como ponto de partida, não como módulo pronto para produção.
247
+
248
+ ## Estrutura do pacote
249
+
250
+ ```
251
+ clisitef_odoo/
252
+ client.py # AgenteCliSiTefClient - chamadas HTTP 1:1 com o agente
253
+ transaction.py # TefTransaction - loop interativo orientado a callbacks
254
+ constants.py # clisitefStatus, commandId, fieldId, functionId conhecidos
255
+ models.py # FieldCapture, TransactionResult
256
+ exceptions.py # AgentServiceError, ClisitefProtocolError, ...
257
+ examples/
258
+ odoo_addon_example/ # esqueleto de addon Odoo (POS payment)
259
+ tests/
260
+ test_client.py # testes do cliente HTTP com mocks
261
+ test_transaction.py # testes do loop interativo com mocks
262
+ ```
@@ -0,0 +1,246 @@
1
+ # clisitef_odoo
2
+
3
+ Cliente Python para o **AgenteCliSiTef** (a API REST local que expõe o TEF
4
+ CliSiTef/Software Express), construído a partir da lógica de
5
+ `agenteCliSiTef.js` deste projeto. O objetivo é permitir que um backend em
6
+ Python — em particular o Odoo — conduza transações de TEF (crédito, débito
7
+ etc.) sem depender de um navegador/DOM.
8
+
9
+ > O código em `agenteCliSiTef.js` e os HTMLs de exemplo (`index.html`,
10
+ > `venda_com_sessao.html`, `sessao.html`) foram usados como especificação do
11
+ > protocolo. Esta lib reimplementa o mesmo fluxo (`/state`, `/session`,
12
+ > `/startTransaction`, `/continueTransaction`, `/finishTransaction`,
13
+ > `/pinpad/*`) em Python puro, trocando os `onclick`/DOM por *callbacks*.
14
+
15
+ ## Instalação
16
+
17
+ ```bash
18
+ pip install -e /caminho/para/clisitef_odoo
19
+ # ou, publicando em um índice interno:
20
+ pip install clisitef-odoo
21
+ ```
22
+
23
+ Dependência única: `requests`.
24
+
25
+ Para rodar os testes:
26
+
27
+ ```bash
28
+ pip install -e ".[dev]"
29
+ pytest
30
+ ```
31
+
32
+ ## Conceitos e mapeamento com o AgenteCliSiTef
33
+
34
+ | Endpoint AgenteCliSiTef | Método em `AgenteCliSiTefClient` |
35
+ |---|---|
36
+ | `GET /state` | `get_state()` |
37
+ | `POST /getVersion` | `get_version()` |
38
+ | `POST /session` | `create_session()` |
39
+ | `GET /session` | `get_session()` |
40
+ | `DELETE /session` | `delete_session()` |
41
+ | `POST /startTransaction` | `start_transaction()` |
42
+ | `POST /continueTransaction` | `continue_transaction()` |
43
+ | `POST /finishTransaction` | `finish_transaction()` |
44
+ | `POST /pinpad/open` | `pinpad_open()` |
45
+ | `POST /pinpad/close` | `pinpad_close()` |
46
+ | `POST /pinpad/isPresent` | `pinpad_is_present()` |
47
+ | `POST /pinpad/setDisplayMessage` | `pinpad_set_display_message()` |
48
+ | `POST /pinpad/readYesNo` | `pinpad_read_yes_no()` |
49
+
50
+ `AgenteCliSiTefClient` é *stateless* em relação ao fluxo: cada chamada apenas
51
+ faz o HTTP e valida `serviceStatus`. Quem interpreta o fluxo interativo da
52
+ CliSiTef (`clisitefStatus`/`commandId`) é a classe `TefTransaction`.
53
+
54
+ ### Dois modos de sessão (igual ao JS original)
55
+
56
+ - **Sessão efêmera** (`inicio(1, funcao)` no JS): passe `sitef_ip`,
57
+ `store_id`, `terminal_id` diretamente para `start_transaction`/`run` — o
58
+ agente cria e destrói a sessão internamente a cada transação.
59
+ - **Sessão persistente** (`inicio(2, funcao)` no JS): crie a sessão uma vez
60
+ com `create_session(...)` e reaproveite `sessionId` em várias
61
+ transações (útil para caixas que ficam o turno todo conectados),
62
+ encerrando com `delete_session()` ao final.
63
+
64
+ ### O loop interativo (`TefTransaction`)
65
+
66
+ A CliSiTef conduz a transação em passos: a cada `continueTransaction`, ela
67
+ retorna um `commandId` que diz o que fazer a seguir. `TefTransaction.run()`
68
+ implementa esse loop (equivalente à função `continua()` do JS) e traduz cada
69
+ `commandId` em uma chamada de callback:
70
+
71
+ | commandId | Significado | Callback |
72
+ |---|---|---|
73
+ | `0` | Campo capturado (ex.: cupom, NSU, etc.) | acumulado em `result.fields`; `on_receipt` para os fieldId 121/122 (cupom estabelecimento/cliente) |
74
+ | `1,2,3,4,15` | Atualiza mensagem de status | `on_message(texto)` |
75
+ | `11,12,13,14,16` | Limpa mensagem de status | `on_message("")` |
76
+ | `22` | Alerta | `on_message(texto)` |
77
+ | `23` | Aguardando pinpad/cliente (pode cancelar) | `on_cancel_check()` a cada `poll_interval` segundos |
78
+ | `20` | Pergunta Sim/Não | `on_confirm(pergunta) -> bool` |
79
+ | `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) |
80
+
81
+ Todos os callbacks têm um padrão conservador (perguntas são respondidas
82
+ "Não", coletas de dado são canceladas) — **implemente `on_confirm`/`on_input`
83
+ de acordo com a regra de negócio real antes de usar em produção**, já que a
84
+ resposta correta depende do que está configurado na CliSiTef para aquele
85
+ terminal/loja.
86
+
87
+ Ao final do loop, `run()` chama automaticamente `finishTransaction`
88
+ (`confirm=1` se `clisitefStatus == 0`, `confirm=0` caso contrário). Isso é
89
+ mais seguro que o HTML de exemplo original, que só finaliza no caminho de
90
+ sucesso — use `auto_finish=False` e o método `finish()` se você quiser
91
+ controlar isso manualmente (ex.: pedir confirmação do operador antes de
92
+ efetivar).
93
+
94
+ ## Exemplo básico
95
+
96
+ ```python
97
+ from clisitef_odoo import AgenteCliSiTefClient, TefTransaction, constants
98
+
99
+ client = AgenteCliSiTefClient(base_url="https://127.0.0.1/agente/clisitef")
100
+
101
+ def responder_pergunta(pergunta):
102
+ print("CliSiTef pergunta:", pergunta)
103
+ return True # confirma
104
+
105
+ def coletar_dado(prompt, field_id, min_len, max_len):
106
+ return input(f"{prompt} ({min_len}-{max_len} caracteres): ")
107
+
108
+ tef = TefTransaction(
109
+ client,
110
+ on_message=lambda msg: print("Status:", msg),
111
+ on_confirm=responder_pergunta,
112
+ on_input=coletar_dado,
113
+ )
114
+
115
+ result = tef.run(
116
+ function_id=constants.FUNCTION_VENDA_CREDITO,
117
+ trn_amount=1000, # R$ 10,00 em centavos
118
+ tax_invoice_number="1234",
119
+ tax_invoice_date="20260714",
120
+ tax_invoice_time="153000",
121
+ cashier_operator="CAIXA",
122
+ sitef_ip="127.0.0.1",
123
+ store_id="00000000",
124
+ terminal_id="REST0001",
125
+ )
126
+
127
+ if result.success:
128
+ print("Aprovado! NSU/campos:", result.fields)
129
+ print("Cupom estabelecimento:\n", result.merchant_receipt)
130
+ print("Cupom cliente:\n", result.customer_receipt)
131
+ else:
132
+ print("Transação não aprovada, status CliSiTef:", result.clisitef_status)
133
+ ```
134
+
135
+ ### Usando sessão persistente
136
+
137
+ ```python
138
+ session = client.create_session(sitef_ip="127.0.0.1", store_id="00000000", terminal_id="REST0001")
139
+ session_id = session["sessionId"]
140
+
141
+ result = tef.run(function_id=constants.FUNCTION_VENDA_CREDITO, trn_amount=2500, session_id=session_id)
142
+
143
+ # ... outras transações reaproveitando session_id ...
144
+
145
+ client.delete_session()
146
+ ```
147
+
148
+ ### Cancelando fora do fluxo normal
149
+
150
+ Equivalente ao botão "Finaliza estornando" dos exemplos HTML — útil quando a
151
+ aplicação perdeu o estado em memória (ex.: crash) mas precisa desfazer uma
152
+ transação pendente na CliSiTef:
153
+
154
+ ```python
155
+ tef.finish_out_of_band(
156
+ confirm=0,
157
+ sitef_ip="127.0.0.1", store_id="00000000", terminal_id="REST0001",
158
+ tax_invoice_number="1234", tax_invoice_date="20260714", tax_invoice_time="153000",
159
+ )
160
+ ```
161
+
162
+ ### Funções de pinpad
163
+
164
+ ```python
165
+ client.pinpad_open(session_id)
166
+ client.pinpad_set_display_message(session_id, "Aguarde...", persistent=True)
167
+ presente = client.pinpad_is_present(session_id)["clisitefStatus"] == 1
168
+ resposta = client.pinpad_read_yes_no(session_id, "Aceita o valor?") # clisitefStatus: 0=Anula, 1=Entra
169
+ client.pinpad_close(session_id)
170
+ ```
171
+
172
+ ## Tratamento de erros
173
+
174
+ - `AgentServiceError`: o **agente** (não a CliSiTef) reportou um problema —
175
+ `serviceStatus != 0` (agente ocupado, sessão inválida, parâmetros
176
+ incorretos). Contém `.service_status` e `.service_message`.
177
+ - `ClisitefProtocolError`: resposta em formato inesperado (ex.: sem
178
+ `commandId` quando deveria ter).
179
+ - Falhas de rede (`requests.RequestException`) propagam normalmente — trate
180
+ como indisponibilidade do agente (ex.: serviço não iniciado na máquina do
181
+ caixa).
182
+
183
+ `result.success` (`clisitef_status == 0`) indica que a transação foi
184
+ aprovada; qualquer outro valor de `clisitef_status` (inclusive cancelamento
185
+ pelo operador/cliente) é considerado não sucesso e `run()` desfaz
186
+ automaticamente (`confirm=0`) quando `auto_finish=True`.
187
+
188
+ ## Códigos de função (`functionId`)
189
+
190
+ `FUNCTION_VENDA_CREDITO = 3` está confirmado pelos exemplos deste projeto
191
+ (comentário "Inicia uma venda crédito" nos HTMLs). `FUNCTION_VENDA_DEBITO = 2`
192
+ foi confirmado no manual/contrato SiTef do terminal em uso neste projeto. Os
193
+ demais códigos de função (voucher, cheque, funções administrativas etc.)
194
+ variam conforme a versão/contrato da CliSiTef instalada — confirme com o
195
+ manual do fornecedor (Software Express) ou com quem configurou o SiTef antes
196
+ de utilizá-los em produção.
197
+
198
+ ## Integração com Odoo
199
+
200
+ Veja [`examples/odoo_addon_example/`](examples/odoo_addon_example/) para um
201
+ esqueleto de addon do Odoo (Point of Sale) que:
202
+
203
+ 1. Adiciona campos de configuração (`clisitef_base_url`, `sitef_ip`,
204
+ `store_id`, `terminal_id`, `function_id`) em `pos.payment.method`.
205
+ 2. Expõe um método `clisitef_run_payment()` que roda `TefTransaction.run()`
206
+ inteiramente no servidor e devolve um dict serializável em JSON (sucesso,
207
+ status, cupons, campos capturados).
208
+ 3. Expõe esse método via um controller JSON (`/clisitef_tef/pay`) para ser
209
+ chamado pela UI do PDV.
210
+ 4. Traz um esqueleto de `PaymentInterface` em JS que apenas chama o backend
211
+ e trata o retorno — **a API exata do `PaymentInterface` muda entre
212
+ versões do Odoo** (14/16/17/18 têm assinaturas diferentes), então adapte
213
+ os nomes de método conforme a sua versão.
214
+
215
+ Pontos importantes para adaptar aos seus módulos:
216
+
217
+ - **Onde roda o agente**: o AgenteCliSiTef roda na máquina física do caixa
218
+ (pinpad conectado nela). Se o servidor Odoo for centralizado (não é a
219
+ mesma máquina do caixa), o controller do exemplo não vai alcançar
220
+ `127.0.0.1` do caixa — nesse caso, rode a lógica de pagamento em um
221
+ processo local no caixa (ex.: um pequeno serviço Python que expõe uma rota
222
+ para a UI do PDV) ao invés de no controller do servidor Odoo central.
223
+ - **Callbacks de negócio**: `on_confirm`/`on_input` no exemplo usam os
224
+ padrões conservadores da lib (recusam/cancelam). Decida como isso deve se
225
+ comportar no seu fluxo antes de ir para produção — por exemplo, se sua
226
+ CliSiTef está configurada para pedir CPF do cliente, você precisa buscar
227
+ esse dado do próprio pedido do Odoo e devolvê-lo via `on_input`.
228
+ - Este addon de exemplo **não foi testado contra uma instância real do
229
+ Odoo** (não há um Odoo disponível no ambiente onde esta lib foi escrita) —
230
+ trate-o como ponto de partida, não como módulo pronto para produção.
231
+
232
+ ## Estrutura do pacote
233
+
234
+ ```
235
+ clisitef_odoo/
236
+ client.py # AgenteCliSiTefClient - chamadas HTTP 1:1 com o agente
237
+ transaction.py # TefTransaction - loop interativo orientado a callbacks
238
+ constants.py # clisitefStatus, commandId, fieldId, functionId conhecidos
239
+ models.py # FieldCapture, TransactionResult
240
+ exceptions.py # AgentServiceError, ClisitefProtocolError, ...
241
+ examples/
242
+ odoo_addon_example/ # esqueleto de addon Odoo (POS payment)
243
+ tests/
244
+ test_client.py # testes do cliente HTTP com mocks
245
+ test_transaction.py # testes do loop interativo com mocks
246
+ ```
@@ -0,0 +1,41 @@
1
+ """clisitef_odoo - cliente Python para o AgenteCliSiTef, pensado para uso em
2
+ integracoes com Odoo (pos_payment, wizards de pagamento, etc.).
3
+
4
+ Uso tipico:
5
+
6
+ from clisitef_odoo import AgenteCliSiTefClient, TefTransaction, constants
7
+
8
+ client = AgenteCliSiTefClient(base_url="https://127.0.0.1/agente/clisitef")
9
+ tef = TefTransaction(client)
10
+ result = tef.run(
11
+ function_id=constants.FUNCTION_VENDA_CREDITO,
12
+ trn_amount=1000, # R$ 10,00, em centavos
13
+ sitef_ip="127.0.0.1", store_id="00000000", terminal_id="REST0001",
14
+ )
15
+
16
+ Veja o README.md do projeto para detalhes de configuracao e exemplos de
17
+ integracao com Odoo.
18
+ """
19
+
20
+ from .client import AgenteCliSiTefClient
21
+ from .exceptions import (
22
+ AgentServiceError,
23
+ ClisitefError,
24
+ ClisitefProtocolError,
25
+ TransactionCancelled,
26
+ )
27
+ from .models import FieldCapture, TransactionResult
28
+ from .transaction import TefTransaction
29
+
30
+ __all__ = [
31
+ "AgenteCliSiTefClient",
32
+ "TefTransaction",
33
+ "TransactionResult",
34
+ "FieldCapture",
35
+ "ClisitefError",
36
+ "AgentServiceError",
37
+ "ClisitefProtocolError",
38
+ "TransactionCancelled",
39
+ ]
40
+
41
+ __version__ = "0.1.0"