bfocus 0.2.0 → 0.2.1
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.
- checksums.yaml +4 -4
- data/README.md +121 -4
- data/lib/bfocus/errors.rb +8 -1
- data/lib/bfocus/resources/people.rb +32 -3
- data/lib/bfocus/transport.rb +6 -0
- data/lib/bfocus/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3daefe09e7aa1fed098c71ffdbc8f0b43c020519b72031355aac21a8b16011ed
|
|
4
|
+
data.tar.gz: a21b02db9aff17837cf93cc6063b16324e4593382543fa7c1c0eedd032d916a9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 111b5a2790952c3ba34c11d7d7f8d101b620b3e44b1869730d6663daf97a2f28c27880febb48aca95e3112f41e9159ba5c2174986d7242ad4b242f9ab8ff15c0
|
|
7
|
+
data.tar.gz: 8a8ee123b356b52e8c8fbfeded349d168bdad83c39a010e3328b7635a0424e13f5413295e2c991e4a9a82fe3444c3200cc0fbbe471c82132468a548ac02c0e02
|
data/README.md
CHANGED
|
@@ -154,6 +154,22 @@ client.people.identifiers.add("app-77", "crm-p5")
|
|
|
154
154
|
client.people.identifiers.remove("app-77", "crm-p5")
|
|
155
155
|
```
|
|
156
156
|
|
|
157
|
+
### Ler os identificadores da pessoa (para reconciliar)
|
|
158
|
+
|
|
159
|
+
`client.people.list(...)` mostra só o identificador **principal** de cada pessoa. Quando dois
|
|
160
|
+
cadastros seus eram a mesma pessoa, um dos ids virou **extra** — e some da listagem sem ter sumido
|
|
161
|
+
do cadastro. É isso que faz a sua conferência fechar "633 de 636" sem explicar os 3.
|
|
162
|
+
|
|
163
|
+
`people.identifiers.list` é a fonte de verdade dessa conferência, e é **leitura**: antes dela era
|
|
164
|
+
preciso ESCREVER (tentar um `add`) para descobrir o que tinha acontecido. Aceita no caminho o id
|
|
165
|
+
principal **ou qualquer um dos extras**.
|
|
166
|
+
|
|
167
|
+
```ruby
|
|
168
|
+
ids = client.people.identifiers.list("crm-p5") # o id extra que "sumiu" da listagem
|
|
169
|
+
ids["external_id"] # => "app-77" — o principal do cadastro
|
|
170
|
+
ids["identifiers"].each { |i| puts "#{i['external_id']} #{i['label']} #{i['source']}" }
|
|
171
|
+
```
|
|
172
|
+
|
|
157
173
|
## Pessoas
|
|
158
174
|
|
|
159
175
|
As pessoas (usuários do seu sistema) de cada cliente, em `client.people`. O `external_id` da pessoa
|
|
@@ -177,7 +193,105 @@ client.people.upsert("erp-1042", "app-77", access: true) # devolve o acesso
|
|
|
177
193
|
- A mesma pessoa enviada com **outro cliente** é **transferida** para ele.
|
|
178
194
|
- Como no resto da SDK, só o que você passa muda; `nil` limpa (`phone: nil`).
|
|
179
195
|
- Campos: `name`, `email`, `phone`, `role`, `access` (pode usar o atendimento), `is_primary`
|
|
180
|
-
(contato principal), `extra_emails`, `extra_phones`.
|
|
196
|
+
(contato principal), `extra_emails`, `extra_phones`, `custom_fields`, `clear`.
|
|
197
|
+
- Erros comuns (`code`): `CUSTOMER_NOT_FOUND`, `NAME_REQUIRED` (ao criar), `PERSON_EMAIL_TAKEN`,
|
|
198
|
+
`PERSON_PHONE_TAKEN`, `PERSON_CONTACT_OTHER_CUSTOMER`, `PERSON_EMAIL_STAFF`,
|
|
199
|
+
`PERSON_CLEAR_FIELD_INVALID`, `PERSON_CLEAR_NOT_OWN_RECORD`.
|
|
200
|
+
|
|
201
|
+
### Campos personalizados da pessoa
|
|
202
|
+
|
|
203
|
+
`custom_fields` leva o que só existe no seu sistema (matrícula, centro de custo, filial). É a
|
|
204
|
+
**exceção** ao "só o que vier muda": a lista enviada **substitui a lista inteira** — campo que
|
|
205
|
+
ficar de fora é **removido**. Mande sempre a lista que o seu sistema tem hoje; omitir o argumento não mexe
|
|
206
|
+
em nada, como em qualquer outro campo.
|
|
207
|
+
|
|
208
|
+
A `visibility` é decidida no bFocus e **preservada entre sincronizações** — por isso ela não vai
|
|
209
|
+
no envio, só volta na resposta: o seu ERP não rebaixa nem promove a exposição de um dado sem
|
|
210
|
+
querer.
|
|
211
|
+
|
|
212
|
+
Vale no upsert de pessoa, no lote de pessoas e na listagem de pessoas do cliente.
|
|
213
|
+
|
|
214
|
+
```ruby
|
|
215
|
+
pessoa = client.people.upsert(
|
|
216
|
+
"erp-1042", "app-77",
|
|
217
|
+
custom_fields: [ # a lista INTEIRA do seu sistema
|
|
218
|
+
{ key: "matricula", label: "Matrícula", value: "4471" },
|
|
219
|
+
{ key: "filial", label: "Filial", value: "Centro" }
|
|
220
|
+
]
|
|
221
|
+
)
|
|
222
|
+
pessoa["custom_fields"].each do |campo|
|
|
223
|
+
puts "#{campo['key']} #{campo['value']} #{campo['visibility']}" # visibility vem do bFocus
|
|
224
|
+
end
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Apagar o e-mail ou o telefone da pessoa
|
|
228
|
+
|
|
229
|
+
Um contato gravado errado ficava preso para sempre: enquanto a ficha errada segurasse o telefone,
|
|
230
|
+
nenhum reenvio o soltava. `clear` apaga.
|
|
231
|
+
|
|
232
|
+
```ruby
|
|
233
|
+
client.people.upsert("erp-1042", "app-77", clear: ["phone"]) # some o telefone
|
|
234
|
+
client.people.upsert("erp-1042", "app-77", clear: %w[email phone]) # somem os dois
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Três regras que parecem contraintuitivas e são de propósito:
|
|
238
|
+
|
|
239
|
+
- **Apagar é explícito.** `phone: nil`, `clear: []` e não passar o argumento continuam significando
|
|
240
|
+
**"não mexe"** — a SDK não traduz `nil` em `clear`. Fazer o `nil` apagar teria apagado, em
|
|
241
|
+
silêncio e na primeira carga seguinte, o dado de todo sistema que manda `nil` para "não tenho
|
|
242
|
+
esse valor".
|
|
243
|
+
- **Campo fora da lista é recusado, não ignorado**: hoje só `"email"` e `"phone"`; qualquer outro
|
|
244
|
+
devolve 422 `PERSON_CLEAR_FIELD_INVALID` (`Bfocus::ValidationError`).
|
|
245
|
+
- **Só se limpa a própria ficha.** Se você alcançou a pessoa por um identificador **extra**, a API
|
|
246
|
+
recusa com 409 `PERSON_CLEAR_NOT_OWN_RECORD` (`Bfocus::ConflictError`): apagar o contato de uma
|
|
247
|
+
ficha alcançada por apelido seria apagar dado de outro sistema. Para saber se o id que você tem em
|
|
248
|
+
mãos é o principal ou um extra, use `client.people.identifiers.list(...)`.
|
|
249
|
+
|
|
250
|
+
Vale no `people.upsert` e no `people.batch` (`"clear" => ["phone"]` no item).
|
|
251
|
+
|
|
252
|
+
### Contato já usado: um 409 que você consegue resolver
|
|
253
|
+
|
|
254
|
+
`PERSON_EMAIL_TAKEN` e `PERSON_PHONE_TAKEN` (409) não são "tente de novo": o e-mail (ou o
|
|
255
|
+
telefone) já é de outra pessoa da conta. O erro diz **de quem**, em `error.data` (a API repete o mesmo
|
|
256
|
+
detalhe em `error.validation`, por compatibilidade):
|
|
257
|
+
|
|
258
|
+
| campo | o que é |
|
|
259
|
+
| --- | --- |
|
|
260
|
+
| `field` | `email` ou `phone` — qual contato está tomado |
|
|
261
|
+
| `owner_external_id` | o identificador da pessoa que já usa esse contato |
|
|
262
|
+
| `owner_name` | o nome dela |
|
|
263
|
+
| `owner_customer_external_id` | o cliente a que ela pertence |
|
|
264
|
+
|
|
265
|
+
**É o `owner_customer_external_id` que decide a ação**, e os dois casos pedem coisas opostas:
|
|
266
|
+
|
|
267
|
+
- **mesmo cliente que você enviou** → é quase sempre a MESMA pessoa em dois sistemas. Uma pessoa
|
|
268
|
+
tem **N identificadores**: registre o seu como **extra** dela. A partir daí o seu id encontra
|
|
269
|
+
essa pessoa.
|
|
270
|
+
- **outro cliente** → ninguém decide sozinho a quem a pessoa pertence. Não force: registre o caso
|
|
271
|
+
e leve para quem conhece o cadastro. Unificar dois clientes é decisão de gente, não de um
|
|
272
|
+
casamento por e-mail.
|
|
273
|
+
|
|
274
|
+
```ruby
|
|
275
|
+
begin
|
|
276
|
+
client.people.upsert("erp-1042", "app-77", name: "Paula Reis", email: "paula@padaria.example")
|
|
277
|
+
rescue Bfocus::ConflictError => e
|
|
278
|
+
raise unless %w[PERSON_EMAIL_TAKEN PERSON_PHONE_TAKEN].include?(e.code)
|
|
279
|
+
|
|
280
|
+
dono = e.data
|
|
281
|
+
if dono["owner_customer_external_id"] == "erp-1042"
|
|
282
|
+
# A mesma pessoa, com dois ids: o seu vira mais um identificador dela.
|
|
283
|
+
client.people.identifiers.add(dono["owner_external_id"], "app-77", label: "ERP")
|
|
284
|
+
else
|
|
285
|
+
# Dono em OUTRO cliente: não decida sozinho — registre e leve para o cadastro.
|
|
286
|
+
avisar_cadastro(e.code, dono)
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
`PERSON_CONTACT_OTHER_CUSTOMER` (409) é o mesmo assunto pelo outro lado, e é **recusa
|
|
292
|
+
definitiva**: a API não move mais uma pessoa de um cliente para outro só porque o e-mail (ou o
|
|
293
|
+
telefone) casou. Repetir a chamada não resolve — trate como caso para o cadastro, nunca como
|
|
294
|
+
falha temporária.
|
|
181
295
|
|
|
182
296
|
## Lotes — `customers.batch` e `people.batch`
|
|
183
297
|
|
|
@@ -410,10 +524,10 @@ Todos são `Hash` com chaves string (campos novos podem aparecer a qualquer mome
|
|
|
410
524
|
| Contato (`customers.contacts.*`) | `id`, `external_id`, `name`, `role`, `email`, `phone`, `notes`, `is_primary`, `created_at`, `updated_at` |
|
|
411
525
|
| Produto vinculado (`customers.products.*`) | `id`, `slug`, `name`, `is_active` |
|
|
412
526
|
| Interação (`customers.interactions.*`) | `id`, `content`, `is_internal`, `author_kind`, `author_name`, `created_at` |
|
|
413
|
-
| Pessoa (`people.list`, `delete`) | `external_id` (pode ser `nil`), `name`, `email`, `phone`, `role`, `access`, `is_primary`, `customer_external_id` |
|
|
527
|
+
| Pessoa (`people.list`, `delete`) | `external_id` (pode ser `nil`), `name`, `email`, `phone`, `role`, `access`, `is_primary`, `customer_external_id`, `custom_fields` (lista de `{key, label, value, visibility}`) |
|
|
414
528
|
| Pessoa gravada (`people.upsert`) | a pessoa + `status` (`created`/`updated`/`unchanged`) |
|
|
415
529
|
| Cliente com identificadores (`customers.identifiers.add`, `remove`) | o cliente + `identifiers` (lista de `{external_id, label, source}`) |
|
|
416
|
-
| Identificadores da pessoa (`people.identifiers.add`, `remove`) | `external_id`, `identifiers` (lista de `{external_id, label, source}`) |
|
|
530
|
+
| Identificadores da pessoa (`people.identifiers.list`, `add`, `remove`) | `external_id`, `identifiers` (lista de `{external_id, label, source}`) |
|
|
417
531
|
| Lote (`customers.batch`, `people.batch`) | `results` (lista de `{index, status, external_id, merged_into, error, code}`; `status` ∈ `created`/`updated`/`unchanged`/`error`), `summary` (`{created, updated, unchanged, error}`) |
|
|
418
532
|
| Produto (`products.*`) | `id`, `slug`, `name`, `description`, `color`, `icon`, `is_active`, `sort_order`, `current_version`, `ai_level`, `created_at`, `updated_at` |
|
|
419
533
|
| Release note (`release_notes.*`) | `id`, `product`, `version`, `title`, `description_html`, `audience`, `is_published`, `require_ack_internal`, `require_ack_external`, `published_at`, `created_at`, `updated_at` |
|
|
@@ -440,7 +554,10 @@ Qualquer resposta fora de 2xx levanta `Bfocus::Error` (ou uma subclasse):
|
|
|
440
554
|
| `Bfocus::ServerError` | 5xx |
|
|
441
555
|
| `Bfocus::NetworkError` | conexão/timeout — `status == 0`, `code == "NETWORK_ERROR"` |
|
|
442
556
|
|
|
443
|
-
Todas têm `code`, `status`, `request_id`, `validation`, `retry_after`, `required_scope` e
|
|
557
|
+
Todas têm `code`, `status`, `request_id`, `validation`, `data`, `retry_after`, `required_scope` e
|
|
558
|
+
`body`. O `data` é o `data` do corpo: o detalhe estruturado que alguns erros trazem (`{}` quando
|
|
559
|
+
não há) — é por ele que um 409 de contato tomado diz de **quem** é o contato (veja
|
|
560
|
+
[Pessoas](#pessoas)).
|
|
444
561
|
**Decida pelo `code`** — ele é estável (`CUSTOMER_NOT_FOUND`, `INTEGRATION_SCOPE_MISSING`,
|
|
445
562
|
`MODULE_NOT_CONTRACTED`, `VALIDATION_ERROR`…). O `message` é texto para humanos e pode mudar. Ao
|
|
446
563
|
falar com o suporte, informe o `request_id`: ele vem do corpo da resposta, senão do header
|
data/lib/bfocus/errors.rb
CHANGED
|
@@ -21,6 +21,12 @@ module Bfocus
|
|
|
21
21
|
attr_reader :request_id
|
|
22
22
|
# @return [Hash{String=>String}] motivos por campo (erros de validação); `{}` quando não há.
|
|
23
23
|
attr_reader :validation
|
|
24
|
+
# @return [Hash] o `data` do corpo do erro: o detalhe estruturado que alguns erros trazem
|
|
25
|
+
# (`{}` quando não há). É onde vem, por exemplo, de quem é o contato já usado num 409
|
|
26
|
+
# `PERSON_EMAIL_TAKEN`/`PERSON_PHONE_TAKEN` (`field`, `owner_external_id`, `owner_name`,
|
|
27
|
+
# `owner_customer_external_id`) e o `owner` de um `IDENTIFIER_IN_USE`. A API repete esse
|
|
28
|
+
# detalhe em {#validation}, por compatibilidade com as SDKs que ainda não expunham `data`.
|
|
29
|
+
attr_reader :data
|
|
24
30
|
# @return [Integer, Float, nil] segundos do header `Retry-After` (só em 429).
|
|
25
31
|
attr_reader :retry_after
|
|
26
32
|
# @return [String, nil] escopo que faltou na chave (header `X-Required-Scope`, só em 403).
|
|
@@ -29,11 +35,12 @@ module Bfocus
|
|
|
29
35
|
attr_reader :body
|
|
30
36
|
|
|
31
37
|
def initialize(message = nil, code: nil, status: 0, request_id: nil, validation: nil,
|
|
32
|
-
retry_after: nil, required_scope: nil, body: nil)
|
|
38
|
+
retry_after: nil, required_scope: nil, body: nil, data: nil)
|
|
33
39
|
@code = code
|
|
34
40
|
@status = status
|
|
35
41
|
@request_id = request_id
|
|
36
42
|
@validation = validation.is_a?(Hash) ? validation.dup : {}
|
|
43
|
+
@data = data.is_a?(Hash) ? data.dup : {}
|
|
37
44
|
@retry_after = retry_after
|
|
38
45
|
@required_scope = required_scope
|
|
39
46
|
@body = body
|
|
@@ -8,6 +8,21 @@ module Bfocus
|
|
|
8
8
|
# Retorno: `{"external_id", "identifiers" => [{"external_id", "label", "source"}, …]}`.
|
|
9
9
|
# Id que já pertence a outro cadastro: `ConflictError` com `code == "IDENTIFIER_IN_USE"`.
|
|
10
10
|
class PersonIdentifiers < Base
|
|
11
|
+
# Todos os identificadores da pessoa: o principal (`"external_id"` do retorno) e os extras.
|
|
12
|
+
# `GET /people/{person_external_id}/identifiers` (escopo `customers:read`). Aceita no
|
|
13
|
+
# caminho o principal OU qualquer um dos extras.
|
|
14
|
+
#
|
|
15
|
+
# É a fonte de verdade para RECONCILIAR: {People#list} mostra só o identificador principal,
|
|
16
|
+
# então um id que virou extra some de lá sem ter sumido do cadastro — e, sem esta leitura,
|
|
17
|
+
# era preciso ESCREVER (tentar um {#add}) para descobrir o que tinha acontecido.
|
|
18
|
+
#
|
|
19
|
+
# @return [Hash]
|
|
20
|
+
# @raise [Bfocus::NotFoundError] `PERSON_NOT_FOUND`.
|
|
21
|
+
def list(person_external_id, timeout: nil)
|
|
22
|
+
call("GET", "/people/#{segment(person_external_id, 'person_external_id')}/identifiers",
|
|
23
|
+
timeout: timeout)
|
|
24
|
+
end
|
|
25
|
+
|
|
11
26
|
# Liga `extra_id` à pessoa (idempotente). `PUT /people/{person_external_id}/identifiers/{extra_id}`
|
|
12
27
|
#
|
|
13
28
|
# @param label [String, nil] rótulo livre. Não informado = sem corpo.
|
|
@@ -35,7 +50,8 @@ module Bfocus
|
|
|
35
50
|
#
|
|
36
51
|
# Pessoa: `"external_id"` (pode ser `nil` para quem chegou por e-mail/widget sem id),
|
|
37
52
|
# `"name"`, `"email"`, `"phone"`, `"role"`, `"access"`, `"is_primary"`,
|
|
38
|
-
# `"customer_external_id"
|
|
53
|
+
# `"customer_external_id"` e `"custom_fields"` (lista de `{"key", "label", "value",
|
|
54
|
+
# "visibility"}`). O `upsert` devolve também `"status"`
|
|
39
55
|
# (`"created"`/`"updated"`/`"unchanged"`).
|
|
40
56
|
#
|
|
41
57
|
# O `external_id` da pessoa é o mesmo `user_external_id` assinado no widget — por isso não
|
|
@@ -60,15 +76,28 @@ module Bfocus
|
|
|
60
76
|
# @param is_primary [Boolean] contato principal do cliente.
|
|
61
77
|
# @param extra_emails [Array<String>] e-mails adicionais.
|
|
62
78
|
# @param extra_phones [Array<String>] telefones adicionais.
|
|
79
|
+
# @param custom_fields [Array<Hash>] campos personalizados (`{"key", "label", "value"}`).
|
|
80
|
+
# Ao contrário de `extra_emails`/`extra_phones`, a lista SUBSTITUI a lista inteira: mande
|
|
81
|
+
# o que o seu sistema tem hoje, porque campo que ficar de fora é REMOVIDO. Não passar o
|
|
82
|
+
# argumento não mexe em nada. A visibilidade é decidida no bFocus e preservada entre
|
|
83
|
+
# sincronizações.
|
|
84
|
+
# @param clear [Array<String>] campos a APAGAR nesta pessoa: `["email"]`, `["phone"]` ou os
|
|
85
|
+
# dois. Apagar é EXPLÍCITO: `phone: nil`, `clear: []` e não passar o argumento continuam
|
|
86
|
+
# significando "não mexe" — a SDK não traduz `nil` em `clear`. Campo fora da lista aceita
|
|
87
|
+
# é RECUSADO pela API (422 `PERSON_CLEAR_FIELD_INVALID`), não ignorado; e só se limpa a
|
|
88
|
+
# PRÓPRIA ficha: alcançando a pessoa por um identificador EXTRA, a API recusa (409
|
|
89
|
+
# `PERSON_CLEAR_NOT_OWN_RECORD`) — apagar contato de ficha alcançada por apelido seria
|
|
90
|
+
# apagar dado de outro sistema.
|
|
63
91
|
# @return [Hash] a pessoa + `"status"`.
|
|
64
92
|
def upsert(customer_external_id, person_external_id, name: UNSET, email: UNSET, phone: UNSET,
|
|
65
93
|
role: UNSET, access: UNSET, is_primary: UNSET, extra_emails: UNSET, extra_phones: UNSET,
|
|
66
|
-
idempotency_key: nil, timeout: nil)
|
|
94
|
+
custom_fields: UNSET, clear: UNSET, idempotency_key: nil, timeout: nil)
|
|
67
95
|
cid = segment(customer_external_id, "customer_external_id")
|
|
68
96
|
pid = segment(person_external_id, "person_external_id")
|
|
69
97
|
person = compact(
|
|
70
98
|
"name" => name, "email" => email, "phone" => phone, "role" => role, "access" => access,
|
|
71
|
-
"is_primary" => is_primary, "extra_emails" => extra_emails, "extra_phones" => extra_phones
|
|
99
|
+
"is_primary" => is_primary, "extra_emails" => extra_emails, "extra_phones" => extra_phones,
|
|
100
|
+
"custom_fields" => custom_fields, "clear" => clear
|
|
72
101
|
)
|
|
73
102
|
call("PUT", "/customers/#{cid}/people/#{pid}",
|
|
74
103
|
body: { "person" => person }, idempotency_key: idempotency_key, timeout: timeout)
|
data/lib/bfocus/transport.rb
CHANGED
|
@@ -211,6 +211,7 @@ module Bfocus
|
|
|
211
211
|
human = nil
|
|
212
212
|
request_id = nil
|
|
213
213
|
validation = {}
|
|
214
|
+
data = {}
|
|
214
215
|
|
|
215
216
|
if payload.is_a?(Hash)
|
|
216
217
|
err = payload["error"]
|
|
@@ -222,6 +223,10 @@ module Bfocus
|
|
|
222
223
|
end
|
|
223
224
|
human = msg if msg.is_a?(String) && !msg.empty? && msg != code
|
|
224
225
|
validation = payload["validation"] if payload["validation"].is_a?(Hash)
|
|
226
|
+
# `data` é o detalhe estruturado do erro (de quem é o contato já usado, o dono de um
|
|
227
|
+
# identificador…). A API também o repete em `validation`, mas quem lê o erro precisa
|
|
228
|
+
# alcançá-lo sem depender dessa duplicação.
|
|
229
|
+
data = payload["data"] if payload["data"].is_a?(Hash)
|
|
225
230
|
rid = payload["request_id"]
|
|
226
231
|
request_id = rid if rid.is_a?(String) && !rid.empty?
|
|
227
232
|
elsif !text.strip.empty?
|
|
@@ -247,6 +252,7 @@ module Bfocus
|
|
|
247
252
|
status: status,
|
|
248
253
|
request_id: request_id,
|
|
249
254
|
validation: validation,
|
|
255
|
+
data: data,
|
|
250
256
|
retry_after: retry_after,
|
|
251
257
|
required_scope: required_scope,
|
|
252
258
|
body: payload.nil? ? present(text) : payload
|
data/lib/bfocus/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bfocus
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Berni Software
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: Clientes, produtos, release notes, base de conhecimento e agentes de
|
|
14
14
|
IA do bFocus. Zero dependências de runtime, novas tentativas e idempotência automáticas.
|