n8n-nodes-digitalsac 0.2.9 → 0.4.0
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.
- package/README.md +103 -31
- package/dist/nodes/Digitalsac/Digitalsac.node.js +250 -14
- package/nodes/Digitalsac/Digitalsac.node.ts +268 -14
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -20,6 +20,12 @@ Este pacote adiciona um nó personalizado ao n8n para interagir com a API do Dig
|
|
|
20
20
|
- Vincular Kanban
|
|
21
21
|
- Listar Carteiras
|
|
22
22
|
- Vincular Carteira
|
|
23
|
+
- **Agendamento:**
|
|
24
|
+
- Listar Serviços
|
|
25
|
+
- Listar Usuários Disponíveis
|
|
26
|
+
- Listar Horários Disponíveis
|
|
27
|
+
- Criar Agendamento
|
|
28
|
+
- Cancelar Agendamento
|
|
23
29
|
|
|
24
30
|
## Instalação
|
|
25
31
|
|
|
@@ -110,30 +116,22 @@ Onde:
|
|
|
110
116
|
|
|
111
117
|
### Enviar Mensagem de Texto
|
|
112
118
|
1. Selecione a operação **Enviar Mensagem**
|
|
113
|
-
2. No campo **Parâmetro**, insira o UUID da conexão
|
|
114
|
-
3. No campo **
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
"body": "Sua mensagem aqui",
|
|
118
|
-
"number": "5511999999999",
|
|
119
|
-
"externalKey": "chave_opcional"
|
|
120
|
-
}
|
|
121
|
-
```
|
|
119
|
+
2. No campo **Parâmetro**, insira o UUID da conexão (ex: 999ab3a2-9f1f-4ffb-969a-bfb72234ece1)
|
|
120
|
+
3. No campo **Corpo da Mensagem**, insira o texto da mensagem
|
|
121
|
+
4. No campo **Número de Telefone**, insira o número no formato DDI+DDD+Número (ex: 5511999999999)
|
|
122
|
+
5. No campo **Chave Externa**, insira um identificador único opcional
|
|
122
123
|
|
|
123
124
|
### Enviar Arquivo
|
|
124
|
-
1. Conecte um nó que forneça dados binários (ex: HTTP Request
|
|
125
|
-
2.
|
|
126
|
-
3.
|
|
127
|
-
4. No campo **
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
```
|
|
136
|
-
Onde `data` é o nome da propriedade binária que contém o arquivo.
|
|
125
|
+
1. Conecte um nó que forneça dados binários (ex: **HTTP Request**, **Read Binary File**, **Google Drive**)
|
|
126
|
+
2. Conecte ao nó **Digitalsac**
|
|
127
|
+
3. Selecione a operação **Enviar Mensagem**
|
|
128
|
+
4. No campo **Parâmetro**, insira o UUID da conexão
|
|
129
|
+
5. Preencha os demais campos normalmente
|
|
130
|
+
6. O nó detectará automaticamente o arquivo binário e enviará via FormData
|
|
131
|
+
|
|
132
|
+
**Nota**: O nó detecta automaticamente se há dados binários conectados e escolhe o método correto:
|
|
133
|
+
- **Sem arquivo**: Envia como JSON (texto)
|
|
134
|
+
- **Com arquivo**: Envia como FormData (arquivo + texto)
|
|
137
135
|
|
|
138
136
|
### Listar Tags
|
|
139
137
|
1. Selecione a operação **Listar Tags**
|
|
@@ -263,15 +261,10 @@ Onde:
|
|
|
263
261
|
- Configure para ler um arquivo PDF
|
|
264
262
|
2. Conecte ao nó **Digitalsac**
|
|
265
263
|
- Operação: **Enviar Mensagem**
|
|
266
|
-
- Parâmetro: `
|
|
267
|
-
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
"body": "Segue o PDF solicitado",
|
|
271
|
-
"number": "5511999999999",
|
|
272
|
-
"binaryPropertyName": "data"
|
|
273
|
-
}
|
|
274
|
-
```
|
|
264
|
+
- Parâmetro: `999ab3a2-9f1f-4ffb-969a-bfb72234ece1` (seu UUID de conexão)
|
|
265
|
+
- Corpo da Mensagem: `Segue o PDF solicitado`
|
|
266
|
+
- Número de Telefone: `5511999999999`
|
|
267
|
+
- Chave Externa: `pdf_documento_123`
|
|
275
268
|
|
|
276
269
|
### Transferir ticket para uma fila específica
|
|
277
270
|
1. Adicione um nó **Digitalsac**
|
|
@@ -336,6 +329,85 @@ Onde:
|
|
|
336
329
|
}
|
|
337
330
|
```
|
|
338
331
|
|
|
332
|
+
## Funcionalidades de Agendamento
|
|
333
|
+
|
|
334
|
+
### Listar Serviços
|
|
335
|
+
Lista todos os serviços disponíveis para agendamento.
|
|
336
|
+
1. Selecione a operação **Listar Serviços**
|
|
337
|
+
2. (Opcional) No campo **ID do Usuário**, insira o ID do usuário para filtrar apenas os serviços que ele atende
|
|
338
|
+
|
|
339
|
+
**Retorno exemplo:**
|
|
340
|
+
```json
|
|
341
|
+
{
|
|
342
|
+
"servicos": "*1* - Consulta (30min - R$ 100,00)\n*2* - Retorno (15min - R$ 50,00)"
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Listar Usuários Disponíveis
|
|
347
|
+
Lista os usuários/atendentes disponíveis para um serviço em uma data específica.
|
|
348
|
+
1. Selecione a operação **Listar Usuários Disponíveis**
|
|
349
|
+
2. Preencha:
|
|
350
|
+
- **ID do Serviço**: ID do serviço desejado
|
|
351
|
+
- **Data**: Data no formato YYYY-MM-DD (ex: 2025-08-07)
|
|
352
|
+
- **Horário** (opcional): Horário específico no formato HH:mm (ex: 09:00)
|
|
353
|
+
|
|
354
|
+
**Retorno exemplo:**
|
|
355
|
+
```json
|
|
356
|
+
{
|
|
357
|
+
"usuarios": "*28* - João Silva\n*29* - Maria Santos"
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### Listar Horários Disponíveis
|
|
362
|
+
Lista os horários disponíveis para um serviço e usuário em uma data específica.
|
|
363
|
+
1. Selecione a operação **Listar Horários Disponíveis**
|
|
364
|
+
2. Preencha:
|
|
365
|
+
- **ID do Serviço**: ID do serviço desejado
|
|
366
|
+
- **ID do Usuário**: ID do atendente/usuário
|
|
367
|
+
- **Data**: Data no formato YYYY-MM-DD
|
|
368
|
+
|
|
369
|
+
**Retorno exemplo:**
|
|
370
|
+
```json
|
|
371
|
+
{
|
|
372
|
+
"horarios": "*1* - 09:00\n*2* - 09:30\n*3* - 10:00\n*4* - 10:30"
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### Criar Agendamento
|
|
377
|
+
Cria um novo agendamento no sistema.
|
|
378
|
+
1. Selecione a operação **Criar Agendamento**
|
|
379
|
+
2. Preencha:
|
|
380
|
+
- **ID do Serviço**: ID do serviço
|
|
381
|
+
- **ID do Usuário**: ID do atendente/usuário
|
|
382
|
+
- **Data**: Data do agendamento (YYYY-MM-DD)
|
|
383
|
+
- **Horário**: Horário do agendamento (HH:mm)
|
|
384
|
+
- **Nome do Contato**: Nome do cliente
|
|
385
|
+
- **Telefone do Contato**: Telefone do cliente (formato: 5511999999999)
|
|
386
|
+
- **Observações** (opcional): Notas sobre o agendamento
|
|
387
|
+
|
|
388
|
+
**Retorno exemplo:**
|
|
389
|
+
```json
|
|
390
|
+
{
|
|
391
|
+
"status": 0,
|
|
392
|
+
"mensagem": "Agendamento criado com sucesso",
|
|
393
|
+
"scheduleId": 123
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### Cancelar Agendamento
|
|
398
|
+
Cancela um agendamento existente.
|
|
399
|
+
1. Selecione a operação **Cancelar Agendamento**
|
|
400
|
+
2. Preencha:
|
|
401
|
+
- **ID do Agendamento**: ID do agendamento a ser cancelado
|
|
402
|
+
|
|
403
|
+
**Retorno exemplo:**
|
|
404
|
+
```json
|
|
405
|
+
{
|
|
406
|
+
"status": 0,
|
|
407
|
+
"mensagem": "Agendamento cancelado com sucesso"
|
|
408
|
+
}
|
|
409
|
+
```
|
|
410
|
+
|
|
339
411
|
## Suporte
|
|
340
412
|
|
|
341
413
|
Para suporte, entre em contato com [contato@digitalsac.io](mailto:contato@digitalsac.io).
|
|
@@ -43,6 +43,12 @@ class Digitalsac {
|
|
|
43
43
|
{ name: 'Vincular Kanban', value: 'linkKanban' },
|
|
44
44
|
{ name: 'Listar Carteiras', value: 'listCarteiras' },
|
|
45
45
|
{ name: 'Vincular Carteira', value: 'linkCarteira' },
|
|
46
|
+
// Agendamento
|
|
47
|
+
{ name: 'Listar Serviços', value: 'listServices' },
|
|
48
|
+
{ name: 'Listar Usuários Disponíveis', value: 'listAvailableUsers' },
|
|
49
|
+
{ name: 'Listar Horários Disponíveis', value: 'listAvailableSlots' },
|
|
50
|
+
{ name: 'Criar Agendamento', value: 'createSchedule' },
|
|
51
|
+
{ name: 'Cancelar Agendamento', value: 'cancelSchedule' },
|
|
46
52
|
],
|
|
47
53
|
default: 'validateWhatsapp',
|
|
48
54
|
},
|
|
@@ -214,6 +220,118 @@ class Digitalsac {
|
|
|
214
220
|
},
|
|
215
221
|
description: 'ID do ticket e ID do usuário da carteira',
|
|
216
222
|
},
|
|
223
|
+
// Campos para Agendamento
|
|
224
|
+
{
|
|
225
|
+
displayName: 'ID do Usuário',
|
|
226
|
+
name: 'scheduleUserId',
|
|
227
|
+
type: 'number',
|
|
228
|
+
default: 0,
|
|
229
|
+
displayOptions: {
|
|
230
|
+
show: {
|
|
231
|
+
operation: ['listServices'],
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
description: 'ID do usuário para filtrar serviços (opcional)',
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
displayName: 'ID do Serviço',
|
|
238
|
+
name: 'serviceId',
|
|
239
|
+
type: 'number',
|
|
240
|
+
default: 1,
|
|
241
|
+
displayOptions: {
|
|
242
|
+
show: {
|
|
243
|
+
operation: ['listAvailableUsers', 'listAvailableSlots', 'createSchedule'],
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
description: 'ID do serviço',
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
displayName: 'Data',
|
|
250
|
+
name: 'scheduleDate',
|
|
251
|
+
type: 'string',
|
|
252
|
+
default: '',
|
|
253
|
+
placeholder: '2025-08-07',
|
|
254
|
+
displayOptions: {
|
|
255
|
+
show: {
|
|
256
|
+
operation: ['listAvailableUsers', 'listAvailableSlots', 'createSchedule'],
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
description: 'Data no formato YYYY-MM-DD',
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
displayName: 'Horário',
|
|
263
|
+
name: 'scheduleTime',
|
|
264
|
+
type: 'string',
|
|
265
|
+
default: '',
|
|
266
|
+
placeholder: '09:00',
|
|
267
|
+
displayOptions: {
|
|
268
|
+
show: {
|
|
269
|
+
operation: ['listAvailableUsers', 'createSchedule'],
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
description: 'Horário no formato HH:mm (opcional para listar usuários)',
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
displayName: 'ID do Usuário',
|
|
276
|
+
name: 'scheduleAttendantId',
|
|
277
|
+
type: 'number',
|
|
278
|
+
default: 1,
|
|
279
|
+
displayOptions: {
|
|
280
|
+
show: {
|
|
281
|
+
operation: ['listAvailableSlots', 'createSchedule'],
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
description: 'ID do atendente/usuário',
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
displayName: 'Nome do Contato',
|
|
288
|
+
name: 'contactName',
|
|
289
|
+
type: 'string',
|
|
290
|
+
default: '',
|
|
291
|
+
displayOptions: {
|
|
292
|
+
show: {
|
|
293
|
+
operation: ['createSchedule'],
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
description: 'Nome do cliente/contato',
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
displayName: 'Telefone do Contato',
|
|
300
|
+
name: 'contactPhone',
|
|
301
|
+
type: 'string',
|
|
302
|
+
default: '',
|
|
303
|
+
placeholder: '5511999999999',
|
|
304
|
+
displayOptions: {
|
|
305
|
+
show: {
|
|
306
|
+
operation: ['createSchedule'],
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
description: 'Telefone do cliente/contato',
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
displayName: 'Observações',
|
|
313
|
+
name: 'scheduleNotes',
|
|
314
|
+
type: 'string',
|
|
315
|
+
default: '',
|
|
316
|
+
displayOptions: {
|
|
317
|
+
show: {
|
|
318
|
+
operation: ['createSchedule'],
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
description: 'Observações sobre o agendamento (opcional)',
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
displayName: 'ID do Agendamento',
|
|
325
|
+
name: 'scheduleId',
|
|
326
|
+
type: 'number',
|
|
327
|
+
default: 0,
|
|
328
|
+
displayOptions: {
|
|
329
|
+
show: {
|
|
330
|
+
operation: ['cancelSchedule'],
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
description: 'ID do agendamento a ser cancelado',
|
|
334
|
+
},
|
|
217
335
|
],
|
|
218
336
|
};
|
|
219
337
|
}
|
|
@@ -321,26 +439,75 @@ class Digitalsac {
|
|
|
321
439
|
};
|
|
322
440
|
break;
|
|
323
441
|
case 'sendMessage':
|
|
442
|
+
// Validar se o UUID foi fornecido
|
|
443
|
+
if (!param || param.trim() === '') {
|
|
444
|
+
throw new Error('UUID da conexão é obrigatório para enviar mensagem. Preencha o campo "Parâmetro" com o UUID da conexão.');
|
|
445
|
+
}
|
|
324
446
|
url = `/v1/api/external/${param}`;
|
|
325
447
|
method = 'POST';
|
|
326
448
|
// Usar campos separados em vez de JSON
|
|
327
449
|
const messageBody = this.getNodeParameter('messageBody', i);
|
|
328
450
|
const phoneNumber = this.getNodeParameter('phoneNumber', i);
|
|
329
451
|
const externalKey = this.getNodeParameter('externalKey', i);
|
|
330
|
-
//
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
452
|
+
// Verificar se há dados binários (para envio de arquivo)
|
|
453
|
+
let hasBinaryData = false;
|
|
454
|
+
let binaryData;
|
|
455
|
+
let binaryFileName;
|
|
456
|
+
let binaryContentType;
|
|
457
|
+
if (items[i].binary) {
|
|
458
|
+
const binary = items[i].binary;
|
|
459
|
+
// Procurar por qualquer propriedade binária disponível
|
|
460
|
+
const binaryKeys = Object.keys(binary);
|
|
461
|
+
if (binaryKeys.length > 0) {
|
|
462
|
+
const binaryPropertyName = binaryKeys[0]; // Usar a primeira propriedade binária encontrada
|
|
463
|
+
hasBinaryData = true;
|
|
464
|
+
const binaryProperty = binary[binaryPropertyName];
|
|
465
|
+
binaryData = await this.helpers.getBinaryDataBuffer(i, binaryPropertyName);
|
|
466
|
+
binaryFileName = binaryProperty.fileName || 'file';
|
|
467
|
+
binaryContentType = binaryProperty.mimeType;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
if (hasBinaryData && binaryData) {
|
|
471
|
+
// Enviar como FormData (para arquivos)
|
|
472
|
+
const formData = {
|
|
473
|
+
body: messageBody,
|
|
474
|
+
number: phoneNumber,
|
|
475
|
+
externalKey: externalKey,
|
|
476
|
+
media: {
|
|
477
|
+
value: binaryData,
|
|
478
|
+
options: {
|
|
479
|
+
filename: binaryFileName,
|
|
480
|
+
contentType: binaryContentType,
|
|
481
|
+
},
|
|
482
|
+
},
|
|
483
|
+
};
|
|
484
|
+
options = {
|
|
485
|
+
method,
|
|
486
|
+
headers: {
|
|
487
|
+
Authorization: `Bearer ${token}`,
|
|
488
|
+
Accept: 'application/json',
|
|
489
|
+
},
|
|
490
|
+
formData: formData,
|
|
491
|
+
uri: `${baseUrl}${url}`,
|
|
492
|
+
json: true,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
else {
|
|
496
|
+
// Enviar como JSON (para texto)
|
|
497
|
+
body = {
|
|
498
|
+
body: messageBody,
|
|
499
|
+
number: phoneNumber,
|
|
500
|
+
externalKey: externalKey
|
|
501
|
+
};
|
|
502
|
+
headers['Content-Type'] = 'application/json';
|
|
503
|
+
options = {
|
|
504
|
+
method,
|
|
505
|
+
headers,
|
|
506
|
+
body,
|
|
507
|
+
uri: `${baseUrl}${url}`,
|
|
508
|
+
json: true,
|
|
509
|
+
};
|
|
510
|
+
}
|
|
344
511
|
break;
|
|
345
512
|
case 'listTags':
|
|
346
513
|
url = '/typebot/listar_tags';
|
|
@@ -425,6 +592,75 @@ class Digitalsac {
|
|
|
425
592
|
json: true,
|
|
426
593
|
};
|
|
427
594
|
break;
|
|
595
|
+
// Casos para Agendamento
|
|
596
|
+
case 'listServices':
|
|
597
|
+
const scheduleUserId = this.getNodeParameter('scheduleUserId', i);
|
|
598
|
+
if (scheduleUserId > 0) {
|
|
599
|
+
url = `/typebot/listar_servicos?userId=${scheduleUserId}`;
|
|
600
|
+
}
|
|
601
|
+
else {
|
|
602
|
+
url = '/typebot/listar_servicos';
|
|
603
|
+
}
|
|
604
|
+
break;
|
|
605
|
+
case 'listAvailableUsers':
|
|
606
|
+
const serviceIdForUsers = this.getNodeParameter('serviceId', i);
|
|
607
|
+
const dateForUsers = this.getNodeParameter('scheduleDate', i);
|
|
608
|
+
const timeForUsers = this.getNodeParameter('scheduleTime', i);
|
|
609
|
+
url = `/typebot/listar_usuarios_disponiveis?serviceId=${serviceIdForUsers}&date=${dateForUsers}`;
|
|
610
|
+
if (timeForUsers) {
|
|
611
|
+
url += `&time=${timeForUsers}`;
|
|
612
|
+
}
|
|
613
|
+
break;
|
|
614
|
+
case 'listAvailableSlots':
|
|
615
|
+
const serviceIdForSlots = this.getNodeParameter('serviceId', i);
|
|
616
|
+
const userIdForSlots = this.getNodeParameter('scheduleAttendantId', i);
|
|
617
|
+
const dateForSlots = this.getNodeParameter('scheduleDate', i);
|
|
618
|
+
url = `/typebot/listar_horarios_disponiveis?serviceId=${serviceIdForSlots}&userId=${userIdForSlots}&date=${dateForSlots}`;
|
|
619
|
+
break;
|
|
620
|
+
case 'createSchedule':
|
|
621
|
+
url = '/typebot/criar_agendamento';
|
|
622
|
+
method = 'POST';
|
|
623
|
+
const serviceIdForCreate = this.getNodeParameter('serviceId', i);
|
|
624
|
+
const userIdForCreate = this.getNodeParameter('scheduleAttendantId', i);
|
|
625
|
+
const dateForCreate = this.getNodeParameter('scheduleDate', i);
|
|
626
|
+
const timeForCreate = this.getNodeParameter('scheduleTime', i);
|
|
627
|
+
const contactNameForCreate = this.getNodeParameter('contactName', i);
|
|
628
|
+
const contactPhoneForCreate = this.getNodeParameter('contactPhone', i);
|
|
629
|
+
const notesForCreate = this.getNodeParameter('scheduleNotes', i);
|
|
630
|
+
body = {
|
|
631
|
+
serviceId: serviceIdForCreate,
|
|
632
|
+
userId: userIdForCreate,
|
|
633
|
+
date: dateForCreate,
|
|
634
|
+
time: timeForCreate,
|
|
635
|
+
contactName: contactNameForCreate,
|
|
636
|
+
contactPhone: contactPhoneForCreate,
|
|
637
|
+
notes: notesForCreate
|
|
638
|
+
};
|
|
639
|
+
headers['Content-Type'] = 'application/json';
|
|
640
|
+
options = {
|
|
641
|
+
method,
|
|
642
|
+
headers,
|
|
643
|
+
body,
|
|
644
|
+
uri: `${baseUrl}${url}`,
|
|
645
|
+
json: true,
|
|
646
|
+
};
|
|
647
|
+
break;
|
|
648
|
+
case 'cancelSchedule':
|
|
649
|
+
url = '/typebot/cancelar_agendamento';
|
|
650
|
+
method = 'POST';
|
|
651
|
+
const scheduleIdForCancel = this.getNodeParameter('scheduleId', i);
|
|
652
|
+
body = {
|
|
653
|
+
scheduleId: scheduleIdForCancel
|
|
654
|
+
};
|
|
655
|
+
headers['Content-Type'] = 'application/json';
|
|
656
|
+
options = {
|
|
657
|
+
method,
|
|
658
|
+
headers,
|
|
659
|
+
body,
|
|
660
|
+
uri: `${baseUrl}${url}`,
|
|
661
|
+
json: true,
|
|
662
|
+
};
|
|
663
|
+
break;
|
|
428
664
|
}
|
|
429
665
|
// Se as opções não foram definidas no switch, defina-as aqui para operações GET
|
|
430
666
|
if (!options.method) {
|
|
@@ -4,6 +4,8 @@ import {
|
|
|
4
4
|
INodeType,
|
|
5
5
|
INodeTypeDescription,
|
|
6
6
|
NodeConnectionType,
|
|
7
|
+
IDataObject,
|
|
8
|
+
IBinaryKeyData,
|
|
7
9
|
} from 'n8n-workflow';
|
|
8
10
|
|
|
9
11
|
export class Digitalsac implements INodeType {
|
|
@@ -48,6 +50,12 @@ export class Digitalsac implements INodeType {
|
|
|
48
50
|
{ name: 'Vincular Kanban', value: 'linkKanban' },
|
|
49
51
|
{ name: 'Listar Carteiras', value: 'listCarteiras' },
|
|
50
52
|
{ name: 'Vincular Carteira', value: 'linkCarteira' },
|
|
53
|
+
// Agendamento
|
|
54
|
+
{ name: 'Listar Serviços', value: 'listServices' },
|
|
55
|
+
{ name: 'Listar Usuários Disponíveis', value: 'listAvailableUsers' },
|
|
56
|
+
{ name: 'Listar Horários Disponíveis', value: 'listAvailableSlots' },
|
|
57
|
+
{ name: 'Criar Agendamento', value: 'createSchedule' },
|
|
58
|
+
{ name: 'Cancelar Agendamento', value: 'cancelSchedule' },
|
|
51
59
|
],
|
|
52
60
|
default: 'validateWhatsapp',
|
|
53
61
|
},
|
|
@@ -219,6 +227,118 @@ export class Digitalsac implements INodeType {
|
|
|
219
227
|
},
|
|
220
228
|
description: 'ID do ticket e ID do usuário da carteira',
|
|
221
229
|
},
|
|
230
|
+
// Campos para Agendamento
|
|
231
|
+
{
|
|
232
|
+
displayName: 'ID do Usuário',
|
|
233
|
+
name: 'scheduleUserId',
|
|
234
|
+
type: 'number',
|
|
235
|
+
default: 0,
|
|
236
|
+
displayOptions: {
|
|
237
|
+
show: {
|
|
238
|
+
operation: ['listServices'],
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
description: 'ID do usuário para filtrar serviços (opcional)',
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
displayName: 'ID do Serviço',
|
|
245
|
+
name: 'serviceId',
|
|
246
|
+
type: 'number',
|
|
247
|
+
default: 1,
|
|
248
|
+
displayOptions: {
|
|
249
|
+
show: {
|
|
250
|
+
operation: ['listAvailableUsers', 'listAvailableSlots', 'createSchedule'],
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
description: 'ID do serviço',
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
displayName: 'Data',
|
|
257
|
+
name: 'scheduleDate',
|
|
258
|
+
type: 'string',
|
|
259
|
+
default: '',
|
|
260
|
+
placeholder: '2025-08-07',
|
|
261
|
+
displayOptions: {
|
|
262
|
+
show: {
|
|
263
|
+
operation: ['listAvailableUsers', 'listAvailableSlots', 'createSchedule'],
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
description: 'Data no formato YYYY-MM-DD',
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
displayName: 'Horário',
|
|
270
|
+
name: 'scheduleTime',
|
|
271
|
+
type: 'string',
|
|
272
|
+
default: '',
|
|
273
|
+
placeholder: '09:00',
|
|
274
|
+
displayOptions: {
|
|
275
|
+
show: {
|
|
276
|
+
operation: ['listAvailableUsers', 'createSchedule'],
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
description: 'Horário no formato HH:mm (opcional para listar usuários)',
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
displayName: 'ID do Usuário',
|
|
283
|
+
name: 'scheduleAttendantId',
|
|
284
|
+
type: 'number',
|
|
285
|
+
default: 1,
|
|
286
|
+
displayOptions: {
|
|
287
|
+
show: {
|
|
288
|
+
operation: ['listAvailableSlots', 'createSchedule'],
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
description: 'ID do atendente/usuário',
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
displayName: 'Nome do Contato',
|
|
295
|
+
name: 'contactName',
|
|
296
|
+
type: 'string',
|
|
297
|
+
default: '',
|
|
298
|
+
displayOptions: {
|
|
299
|
+
show: {
|
|
300
|
+
operation: ['createSchedule'],
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
description: 'Nome do cliente/contato',
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
displayName: 'Telefone do Contato',
|
|
307
|
+
name: 'contactPhone',
|
|
308
|
+
type: 'string',
|
|
309
|
+
default: '',
|
|
310
|
+
placeholder: '5511999999999',
|
|
311
|
+
displayOptions: {
|
|
312
|
+
show: {
|
|
313
|
+
operation: ['createSchedule'],
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
description: 'Telefone do cliente/contato',
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
displayName: 'Observações',
|
|
320
|
+
name: 'scheduleNotes',
|
|
321
|
+
type: 'string',
|
|
322
|
+
default: '',
|
|
323
|
+
displayOptions: {
|
|
324
|
+
show: {
|
|
325
|
+
operation: ['createSchedule'],
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
description: 'Observações sobre o agendamento (opcional)',
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
displayName: 'ID do Agendamento',
|
|
332
|
+
name: 'scheduleId',
|
|
333
|
+
type: 'number',
|
|
334
|
+
default: 0,
|
|
335
|
+
displayOptions: {
|
|
336
|
+
show: {
|
|
337
|
+
operation: ['cancelSchedule'],
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
description: 'ID do agendamento a ser cancelado',
|
|
341
|
+
},
|
|
222
342
|
],
|
|
223
343
|
};
|
|
224
344
|
|
|
@@ -327,6 +447,11 @@ export class Digitalsac implements INodeType {
|
|
|
327
447
|
};
|
|
328
448
|
break;
|
|
329
449
|
case 'sendMessage':
|
|
450
|
+
// Validar se o UUID foi fornecido
|
|
451
|
+
if (!param || param.trim() === '') {
|
|
452
|
+
throw new Error('UUID da conexão é obrigatório para enviar mensagem. Preencha o campo "Parâmetro" com o UUID da conexão.');
|
|
453
|
+
}
|
|
454
|
+
|
|
330
455
|
url = `/v1/api/external/${param}`;
|
|
331
456
|
method = 'POST';
|
|
332
457
|
|
|
@@ -335,21 +460,69 @@ export class Digitalsac implements INodeType {
|
|
|
335
460
|
const phoneNumber = this.getNodeParameter('phoneNumber', i) as string;
|
|
336
461
|
const externalKey = this.getNodeParameter('externalKey', i) as string;
|
|
337
462
|
|
|
338
|
-
//
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
};
|
|
463
|
+
// Verificar se há dados binários (para envio de arquivo)
|
|
464
|
+
let hasBinaryData = false;
|
|
465
|
+
let binaryData: Buffer | undefined;
|
|
466
|
+
let binaryFileName: string | undefined;
|
|
467
|
+
let binaryContentType: string | undefined;
|
|
344
468
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
469
|
+
if (items[i].binary) {
|
|
470
|
+
const binary = items[i].binary as IBinaryKeyData;
|
|
471
|
+
// Procurar por qualquer propriedade binária disponível
|
|
472
|
+
const binaryKeys = Object.keys(binary);
|
|
473
|
+
if (binaryKeys.length > 0) {
|
|
474
|
+
const binaryPropertyName = binaryKeys[0]; // Usar a primeira propriedade binária encontrada
|
|
475
|
+
hasBinaryData = true;
|
|
476
|
+
|
|
477
|
+
const binaryProperty = binary[binaryPropertyName];
|
|
478
|
+
binaryData = await this.helpers.getBinaryDataBuffer(i, binaryPropertyName);
|
|
479
|
+
binaryFileName = binaryProperty.fileName || 'file';
|
|
480
|
+
binaryContentType = binaryProperty.mimeType;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (hasBinaryData && binaryData) {
|
|
485
|
+
// Enviar como FormData (para arquivos)
|
|
486
|
+
const formData: IDataObject = {
|
|
487
|
+
body: messageBody,
|
|
488
|
+
number: phoneNumber,
|
|
489
|
+
externalKey: externalKey,
|
|
490
|
+
media: {
|
|
491
|
+
value: binaryData,
|
|
492
|
+
options: {
|
|
493
|
+
filename: binaryFileName,
|
|
494
|
+
contentType: binaryContentType,
|
|
495
|
+
},
|
|
496
|
+
},
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
options = {
|
|
500
|
+
method,
|
|
501
|
+
headers: {
|
|
502
|
+
Authorization: `Bearer ${token}`,
|
|
503
|
+
Accept: 'application/json',
|
|
504
|
+
},
|
|
505
|
+
formData: formData,
|
|
506
|
+
uri: `${baseUrl}${url}`,
|
|
507
|
+
json: true,
|
|
508
|
+
};
|
|
509
|
+
} else {
|
|
510
|
+
// Enviar como JSON (para texto)
|
|
511
|
+
body = {
|
|
512
|
+
body: messageBody,
|
|
513
|
+
number: phoneNumber,
|
|
514
|
+
externalKey: externalKey
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
headers['Content-Type'] = 'application/json';
|
|
518
|
+
options = {
|
|
519
|
+
method,
|
|
520
|
+
headers,
|
|
521
|
+
body,
|
|
522
|
+
uri: `${baseUrl}${url}`,
|
|
523
|
+
json: true,
|
|
524
|
+
};
|
|
525
|
+
}
|
|
353
526
|
break;
|
|
354
527
|
case 'listTags':
|
|
355
528
|
url = '/typebot/listar_tags';
|
|
@@ -434,6 +607,87 @@ export class Digitalsac implements INodeType {
|
|
|
434
607
|
json: true,
|
|
435
608
|
};
|
|
436
609
|
break;
|
|
610
|
+
|
|
611
|
+
// Casos para Agendamento
|
|
612
|
+
case 'listServices':
|
|
613
|
+
const scheduleUserId = this.getNodeParameter('scheduleUserId', i) as number;
|
|
614
|
+
if (scheduleUserId > 0) {
|
|
615
|
+
url = `/typebot/listar_servicos?userId=${scheduleUserId}`;
|
|
616
|
+
} else {
|
|
617
|
+
url = '/typebot/listar_servicos';
|
|
618
|
+
}
|
|
619
|
+
break;
|
|
620
|
+
|
|
621
|
+
case 'listAvailableUsers':
|
|
622
|
+
const serviceIdForUsers = this.getNodeParameter('serviceId', i) as number;
|
|
623
|
+
const dateForUsers = this.getNodeParameter('scheduleDate', i) as string;
|
|
624
|
+
const timeForUsers = this.getNodeParameter('scheduleTime', i) as string;
|
|
625
|
+
|
|
626
|
+
url = `/typebot/listar_usuarios_disponiveis?serviceId=${serviceIdForUsers}&date=${dateForUsers}`;
|
|
627
|
+
if (timeForUsers) {
|
|
628
|
+
url += `&time=${timeForUsers}`;
|
|
629
|
+
}
|
|
630
|
+
break;
|
|
631
|
+
|
|
632
|
+
case 'listAvailableSlots':
|
|
633
|
+
const serviceIdForSlots = this.getNodeParameter('serviceId', i) as number;
|
|
634
|
+
const userIdForSlots = this.getNodeParameter('scheduleAttendantId', i) as number;
|
|
635
|
+
const dateForSlots = this.getNodeParameter('scheduleDate', i) as string;
|
|
636
|
+
|
|
637
|
+
url = `/typebot/listar_horarios_disponiveis?serviceId=${serviceIdForSlots}&userId=${userIdForSlots}&date=${dateForSlots}`;
|
|
638
|
+
break;
|
|
639
|
+
|
|
640
|
+
case 'createSchedule':
|
|
641
|
+
url = '/typebot/criar_agendamento';
|
|
642
|
+
method = 'POST';
|
|
643
|
+
|
|
644
|
+
const serviceIdForCreate = this.getNodeParameter('serviceId', i) as number;
|
|
645
|
+
const userIdForCreate = this.getNodeParameter('scheduleAttendantId', i) as number;
|
|
646
|
+
const dateForCreate = this.getNodeParameter('scheduleDate', i) as string;
|
|
647
|
+
const timeForCreate = this.getNodeParameter('scheduleTime', i) as string;
|
|
648
|
+
const contactNameForCreate = this.getNodeParameter('contactName', i) as string;
|
|
649
|
+
const contactPhoneForCreate = this.getNodeParameter('contactPhone', i) as string;
|
|
650
|
+
const notesForCreate = this.getNodeParameter('scheduleNotes', i) as string;
|
|
651
|
+
|
|
652
|
+
body = {
|
|
653
|
+
serviceId: serviceIdForCreate,
|
|
654
|
+
userId: userIdForCreate,
|
|
655
|
+
date: dateForCreate,
|
|
656
|
+
time: timeForCreate,
|
|
657
|
+
contactName: contactNameForCreate,
|
|
658
|
+
contactPhone: contactPhoneForCreate,
|
|
659
|
+
notes: notesForCreate
|
|
660
|
+
};
|
|
661
|
+
|
|
662
|
+
headers['Content-Type'] = 'application/json';
|
|
663
|
+
options = {
|
|
664
|
+
method,
|
|
665
|
+
headers,
|
|
666
|
+
body,
|
|
667
|
+
uri: `${baseUrl}${url}`,
|
|
668
|
+
json: true,
|
|
669
|
+
};
|
|
670
|
+
break;
|
|
671
|
+
|
|
672
|
+
case 'cancelSchedule':
|
|
673
|
+
url = '/typebot/cancelar_agendamento';
|
|
674
|
+
method = 'POST';
|
|
675
|
+
|
|
676
|
+
const scheduleIdForCancel = this.getNodeParameter('scheduleId', i) as number;
|
|
677
|
+
|
|
678
|
+
body = {
|
|
679
|
+
scheduleId: scheduleIdForCancel
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
headers['Content-Type'] = 'application/json';
|
|
683
|
+
options = {
|
|
684
|
+
method,
|
|
685
|
+
headers,
|
|
686
|
+
body,
|
|
687
|
+
uri: `${baseUrl}${url}`,
|
|
688
|
+
json: true,
|
|
689
|
+
};
|
|
690
|
+
break;
|
|
437
691
|
}
|
|
438
692
|
|
|
439
693
|
// Se as opções não foram definidas no switch, defina-as aqui para operações GET
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "n8n-nodes-digitalsac",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Izing Pro Digitalsac",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"n8n",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"n8n-core": "^1.0.0",
|
|
50
50
|
"n8n-workflow": "^1.0.0",
|
|
51
51
|
"prettier": "^2.8.8",
|
|
52
|
-
"typescript": "^5.
|
|
52
|
+
"typescript": "^5.9.2"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"axios": "^1.9.0"
|