worker-automate-hub 0.5.912__py3-none-any.whl → 0.5.921__py3-none-any.whl

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,631 @@
1
+ import asyncio
2
+ import io
3
+ import os
4
+ import json
5
+ import shutil
6
+ from datetime import datetime
7
+ import requests
8
+ from pydantic import BaseModel
9
+ from typing import Optional
10
+ from rich.console import Console
11
+ from selenium import webdriver
12
+ from selenium.webdriver.chrome.service import Service
13
+ from webdriver_manager.chrome import ChromeDriverManager
14
+ from selenium.webdriver.common.by import By
15
+ from selenium.webdriver.common.action_chains import ActionChains
16
+ from selenium.webdriver.common.keys import Keys
17
+ import sys
18
+ import traceback # para logs de exceção detalhados
19
+
20
+ # Ajuste de path para rodar local ou no worker
21
+ sys.path.append(
22
+ os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))
23
+ )
24
+
25
+ from worker_automate_hub.api.client import get_config_by_name, send_file
26
+ from worker_automate_hub.api.datalake_service import send_file_to_datalake
27
+ from worker_automate_hub.models.dto.rpa_historico_request_dto import (
28
+ RpaHistoricoStatusEnum,
29
+ RpaRetornoProcessoDTO,
30
+ RpaTagDTO,
31
+ RpaTagEnum,
32
+ )
33
+ from worker_automate_hub.models.dto.rpa_sap_dto import RpaProcessoSapDTO
34
+ from worker_automate_hub.utils.util import (
35
+ kill_all_emsys,
36
+ worker_sleep,
37
+ )
38
+ from selenium.webdriver.support.ui import WebDriverWait
39
+ from selenium.webdriver.support import expected_conditions as EC
40
+
41
+ console = Console()
42
+
43
+ DOWNLOADS_PATH = os.path.join(os.path.expanduser("~"), "Downloads")
44
+ console.print(f"[INIT] Downloads dir: {DOWNLOADS_PATH}")
45
+
46
+ # ==========================
47
+ # CONFIGURAÇÕES / CONSTANTES
48
+ # ==========================
49
+
50
+
51
+ class ConfigEntradaSAP(BaseModel):
52
+ """
53
+ Modelo de referência para a configuração de entrada SAP.
54
+ Agora empresa/relatorio são opcionais e têm default.
55
+ """
56
+ user: str
57
+ password: str
58
+ empresa: Optional[str] = "DIS"
59
+ unique_id: Optional[str] = "default"
60
+ relatorio: Optional[str] = "FAT"
61
+
62
+
63
+ def _get_from_config(config, key, default=None):
64
+ """
65
+ Helper para funcionar tanto com dict quanto com BaseModel (Pydantic ou similar).
66
+ """
67
+ if isinstance(config, dict):
68
+ return config.get(key, default)
69
+ return getattr(config, key, default)
70
+
71
+
72
+ # (Mantidos para possível uso futuro)
73
+ SAP_VIEWS = {}
74
+ COMPANY_FILTER = {}
75
+
76
+ def clear_downloads_folder():
77
+ """
78
+ Remove todos os ARQUIVOS da pasta Downloads do usuário.
79
+ Não remove pastas internas.
80
+ """
81
+ try:
82
+ console.print("[CLEAN] Limpando pasta Downloads...")
83
+
84
+ for item in os.listdir(DOWNLOADS_PATH):
85
+ full_path = os.path.join(DOWNLOADS_PATH, item)
86
+
87
+ # apenas arquivos (evita excluir pastas do usuário)
88
+ if os.path.isfile(full_path):
89
+ try:
90
+ os.remove(full_path)
91
+ console.print(f"[CLEAN] Arquivo removido: {item}")
92
+ except Exception as e:
93
+ console.print(f"[CLEAN][WARN] Não foi possível remover {item}: {e}")
94
+
95
+ console.print("[CLEAN] Downloads limpo com sucesso!")
96
+
97
+ except Exception as e:
98
+ console.print(f"[CLEAN][ERRO] Falha ao limpar Downloads: {e}")
99
+ console.print(traceback.format_exc())
100
+
101
+
102
+
103
+ class ListaClientesSAP:
104
+ def __init__(
105
+ self,
106
+ task: RpaProcessoSapDTO,
107
+ sap_url: str,
108
+ sap_key: str,
109
+ sap_token: str,
110
+ base_url: str,
111
+ directory: str,
112
+ relatorio_override: Optional[str] = None,
113
+ ):
114
+ console.print("[STEP 0.1] Inicializando classe ListaClientesSAP.")
115
+ self.task = task
116
+ self.sap_url = sap_url
117
+ self.sap_key = sap_key
118
+ self.sap_token = sap_token
119
+ self.base_url = base_url
120
+ self.directory = directory
121
+
122
+ config = task.configEntrada # pode ser dict ou BaseModel
123
+
124
+ self.user = _get_from_config(config, "user")
125
+ self.password = _get_from_config(config, "password")
126
+
127
+ # empresa padrão DIS se não vier na config (não usamos no fluxo atual, mas deixei)
128
+ self.empresa = _get_from_config(config, "empresa", "DIS") or "DIS"
129
+
130
+ # relatorio default FAT (não usamos no fluxo atual, mas deixei)
131
+ self.relatorio = (
132
+ (relatorio_override or "").upper()
133
+ or _get_from_config(config, "relatorio", "").upper()
134
+ or getattr(task, "relatorio", "").upper()
135
+ or "FAT"
136
+ )
137
+
138
+ self.unique_id = "default"
139
+ self.driver = None
140
+
141
+ console.print(
142
+ f"[STEP 0.2] user={self.user} empresa={self.empresa} "
143
+ f"relatorio={self.relatorio}"
144
+ )
145
+
146
+ async def start_sap_process(self) -> RpaRetornoProcessoDTO:
147
+ """
148
+ Fluxo principal do SAP com logs de etapa:
149
+ - KILL_ALL_EMSYS
150
+ - INIT_CHROMEDRIVER
151
+ - INIT_WEBDRIVER
152
+ - VALIDATE_CONFIG
153
+ - SAVE_PID
154
+ - GET_BASE_URL
155
+ - LOGIN
156
+ - CLICK_EXIBIR_LISTA_CLIENTES
157
+ - EXPORTAR_LISTA_CLIENTES
158
+ """
159
+ step = "INIT"
160
+ console.print("[STEP] Iniciando o processo SAP.")
161
+
162
+ try:
163
+ # 0) Validar config básica
164
+ step = "VALIDATE_CONFIG"
165
+ if not self.user or not self.password:
166
+ msg = f"[{step}] Credenciais SAP inválidas: user={self.user} password={'***' if self.password else None}"
167
+ console.print("[ERRO] " + msg)
168
+ return RpaRetornoProcessoDTO(
169
+ sucesso=False,
170
+ retorno=msg,
171
+ status=RpaHistoricoStatusEnum.Falha,
172
+ tags=[RpaTagDTO(descricao=RpaTagEnum.Tecnico)],
173
+ )
174
+
175
+ if not self.base_url:
176
+ msg = f"[{step}] base_url não configurada."
177
+ console.print("[ERRO] " + msg)
178
+ return RpaRetornoProcessoDTO(
179
+ sucesso=False,
180
+ retorno=msg,
181
+ status=RpaHistoricoStatusEnum.Falha,
182
+ tags=[RpaTagDTO(descricao=RpaTagEnum.Tecnico)],
183
+ )
184
+
185
+ # 1) Matar instâncias antigas
186
+ step = "KILL_ALL_EMSYS"
187
+ console.print(f"[STEP={step}] Matando instâncias EMsys/Chrome antigas...")
188
+ await kill_all_emsys()
189
+ console.print(f"[STEP={step}] kill_all_emsys concluído.")
190
+
191
+ clear_downloads_folder()
192
+ console.print("[CLEAN] Downloads limpo antes da exportação.")
193
+
194
+ # 2) Instalar ChromeDriver
195
+ step = "INIT_CHROMEDRIVER"
196
+ console.print(f"[STEP={step}] Instalando/obtendo ChromeDriver...")
197
+ sim_service = Service(ChromeDriverManager().install())
198
+ console.print(f"[STEP={step}] ChromeDriverManager instalado com sucesso.")
199
+
200
+ # 3) Inicializar webdriver
201
+ step = "INIT_WEBDRIVER"
202
+ console.print(f"[STEP={step}] Inicializando webdriver.Chrome...")
203
+ self.driver = webdriver.Chrome(service=sim_service)
204
+ self.driver.maximize_window()
205
+ console.print(f"[STEP={step}] Driver inicializado e janela maximizada.")
206
+
207
+ # 4) Salvar PID
208
+ step = "SAVE_PID"
209
+ console.print(f"[STEP={step}] Salvando PID do Chrome...")
210
+ ret_pid = await self.save_process_pid()
211
+ if isinstance(ret_pid, RpaRetornoProcessoDTO) and not ret_pid.sucesso:
212
+ console.print(f"[STEP={step}] Falha ao salvar PID, retornando erro.")
213
+ return ret_pid
214
+ console.print(f"[STEP={step}] PID salvo com sucesso.")
215
+
216
+ # 5) Acessar URL base
217
+ step = "GET_BASE_URL"
218
+ console.print(f"[STEP={step}] Acessando URL base do SAP...")
219
+ console.print(f"[STEP={step}] base_url: {self.base_url}")
220
+ self.driver.get(self.base_url)
221
+ console.print(f"[STEP={step}] driver.get(base_url) concluído.")
222
+ await worker_sleep(3)
223
+
224
+ # 6) Login
225
+ step = "LOGIN"
226
+ console.print(f"[STEP={step}] Realizando login no SAP...")
227
+ login_ok = await self.login()
228
+ if not login_ok:
229
+ msg = f"Falha ao realizar login no SAP (etapa {step})"
230
+ console.print(f"[STEP={step}] {msg}")
231
+ return RpaRetornoProcessoDTO(
232
+ sucesso=False,
233
+ retorno=msg,
234
+ status=RpaHistoricoStatusEnum.Falha,
235
+ tags=[RpaTagDTO(descricao=RpaTagEnum.Tecnico)],
236
+ )
237
+ console.print(f"[STEP={step}] Login realizado com sucesso.")
238
+
239
+ # 7) Clicar na tela inicial: "Exibir lista de clientes"
240
+ step = "CLICK_EXIBIR_LISTA_CLIENTES"
241
+ try:
242
+ console.print(f"[STEP={step}] Procurando botão 'Exibir lista de clientes' na home...")
243
+ await worker_sleep(5) # tempo para a tela inicial carregar
244
+
245
+ try:
246
+ # Tenta com aproximação que ignora soft-hyphen
247
+ botao_exibir = WebDriverWait(self.driver, 20).until(
248
+ EC.element_to_be_clickable(
249
+ (
250
+ By.XPATH,
251
+ "//span[contains(normalize-space(.), 'Exi') and contains(normalize-space(.), 'lista de cli')]",
252
+ )
253
+ )
254
+ )
255
+ except Exception:
256
+ console.print(f"[STEP={step}] Tentando fallback com contains simples...")
257
+ botao_exibir = WebDriverWait(self.driver, 20).until(
258
+ EC.element_to_be_clickable(
259
+ (
260
+ By.XPATH,
261
+ "//span[contains(., 'Exibir lista de clientes')]",
262
+ )
263
+ )
264
+ )
265
+
266
+ botao_exibir.click()
267
+ console.print(f"[STEP={step}] Botão 'Exibir lista de clientes' clicado com sucesso!")
268
+ await worker_sleep(5)
269
+
270
+ except Exception as e:
271
+ console.print(f"[STEP={step}][ERRO] Não foi possível clicar em 'Exibir lista de clientes': {e}")
272
+ console.print("[STEP={step}] Abortando processo.")
273
+ return RpaRetornoProcessoDTO(
274
+ sucesso=False,
275
+ retorno=f"Erro ao clicar em 'Exibir lista de clientes': {e}",
276
+ status=RpaHistoricoStatusEnum.Falha,
277
+ tags=[RpaTagDTO(descricao=RpaTagEnum.Tecnico)],
278
+ )
279
+
280
+ # 8) Fluxo simples de exportação (Início + Exportar)
281
+ step = "EXPORTAR_LISTA_CLIENTES"
282
+ console.print(f"[STEP={step}] Iniciando fluxo de exportação da lista de clientes...")
283
+ await self.exportar_lista_clientes()
284
+ console.print(f"[STEP={step}] Exportação concluída com sucesso.")
285
+
286
+ return RpaRetornoProcessoDTO(
287
+ sucesso=True,
288
+ retorno="Processo SAP executado com sucesso.",
289
+ status=RpaHistoricoStatusEnum.Sucesso,
290
+ )
291
+
292
+ except Exception as e:
293
+ tb = traceback.format_exc()
294
+ msg = (
295
+ f"Erro em start_sap_process na etapa '{step}': "
296
+ f"{type(e).__name__}: {e}"
297
+ )
298
+ console.print("[ERRO] " + msg)
299
+ console.print("[ERRO] Traceback completo:")
300
+ console.print(tb)
301
+
302
+ return RpaRetornoProcessoDTO(
303
+ sucesso=False,
304
+ retorno=msg + "\n" + tb,
305
+ status=RpaHistoricoStatusEnum.Falha,
306
+ tags=[RpaTagDTO(descricao=RpaTagEnum.Tecnico)],
307
+ )
308
+
309
+ async def save_process_pid(self):
310
+ try:
311
+ console.print("[STEP PID] Salvando o PID do processo do Chrome.")
312
+ pid = str(self.driver.service.process.pid)
313
+ file_path = f"c:\\tmp\\chrome_pid_{self.unique_id}.txt"
314
+ os.makedirs(os.path.dirname(file_path), exist_ok=True)
315
+ with open(file_path, "w") as f:
316
+ f.write(pid)
317
+ console.print(f"[STEP PID] PID salvo em: {file_path}")
318
+ except Exception as e:
319
+ msg = f"Erro ao salvar PID: {e}"
320
+ console.print(f"[ERRO PID] {msg}")
321
+ console.print("[ERRO PID] Traceback:")
322
+ console.print(traceback.format_exc())
323
+ return RpaRetornoProcessoDTO(
324
+ sucesso=False,
325
+ retorno=msg,
326
+ status=RpaHistoricoStatusEnum.Falha,
327
+ tags=[RpaTagDTO(descricao=RpaTagEnum.Tecnico)],
328
+ )
329
+
330
+ async def login(self) -> bool:
331
+ try:
332
+ console.print("[LOGIN] Iniciando login no SAP.")
333
+ for i in range(30):
334
+ inputs = self.driver.find_elements(By.CLASS_NAME, "loginInputField")
335
+ console.print(
336
+ f"[LOGIN] Tentativa {i+1}/30 - qtd campos encontrados: {len(inputs)}"
337
+ )
338
+ if len(inputs) >= 2:
339
+ console.print("[LOGIN] Campos de entrada de login encontrados.")
340
+ break
341
+ await worker_sleep(1)
342
+
343
+ inputs = self.driver.find_elements(By.CLASS_NAME, "loginInputField")
344
+ if len(inputs) < 2:
345
+ console.print("[LOGIN][ERRO] Não encontrou campos de login.")
346
+ return False
347
+
348
+ inputs[0].send_keys(self.user)
349
+ console.print("[LOGIN] Usuário inserido no campo de login.")
350
+ await worker_sleep(2)
351
+ inputs[1].send_keys(self.password)
352
+ console.print("[LOGIN] Senha inserida no campo de login.")
353
+ await worker_sleep(1)
354
+
355
+ for i in range(10):
356
+ login_btn = self.driver.find_elements(By.ID, "LOGIN_SUBMIT_BLOCK")
357
+ console.print(
358
+ f"[LOGIN] Tentativa {i+1}/10 - btn login encontrado? {bool(login_btn)}"
359
+ )
360
+ if login_btn:
361
+ login_btn[0].click()
362
+ console.print("[LOGIN] Botão de login clicado.")
363
+ break
364
+ await worker_sleep(1)
365
+
366
+ await worker_sleep(3)
367
+ console.print("[LOGIN] Login finalizado (sem exceção).")
368
+ return True
369
+ except Exception as e:
370
+ console.print("[LOGIN][ERRO] Exceção ao realizar login.")
371
+ console.print(f"[LOGIN][ERRO] Tipo: {type(e).__name__}")
372
+ console.print(f"[LOGIN][ERRO] str(e): {str(e)}")
373
+ console.print("[LOGIN][ERRO] Traceback:")
374
+ console.print(traceback.format_exc())
375
+ return False
376
+
377
+ async def wait_for_xlsx_download(self, timeout_seconds=600):
378
+ """
379
+ Espera até 10 minutos por algum arquivo .xlsx aparecer nos Downloads.
380
+ """
381
+ console.print("[DW] Aguardando arquivo XLSX aparecer na pasta Downloads...")
382
+ start = datetime.now()
383
+
384
+ while (datetime.now() - start).total_seconds() < timeout_seconds:
385
+ for file in os.listdir(DOWNLOADS_PATH):
386
+ if file.lower().endswith(".xlsx"):
387
+ full_path = os.path.join(DOWNLOADS_PATH, file)
388
+ console.print(f"[DW] Arquivo encontrado: {full_path}")
389
+ return full_path
390
+
391
+ await worker_sleep(2)
392
+
393
+ raise TimeoutError("Nenhum arquivo XLSX encontrado após 10 minutos.")
394
+
395
+ async def exportar_lista_clientes(self):
396
+ console.print("[EXP] Iniciando passos de 'Início' e 'Exportar'...")
397
+ try:
398
+ # Clicar em "Início"
399
+ console.print("[EXP] Procurando botão 'Início'...")
400
+ botao_inicio = WebDriverWait(self.driver, 30).until(
401
+ EC.element_to_be_clickable(
402
+ (By.XPATH, "//bdi[contains(normalize-space(.), 'Iniciar')]")
403
+ )
404
+ )
405
+ botao_inicio.click()
406
+ console.print("[EXP] Botão 'Iniciar' clicado.")
407
+ await worker_sleep(5)
408
+
409
+ # Clicar em "Exportar" (botão)
410
+ console.print("[EXP] Localizando botão de Exportar...")
411
+ botao_exportar = WebDriverWait(self.driver, 30).until(
412
+ EC.element_to_be_clickable((By.XPATH, "//button[@aria-label='Abrir menu']"))
413
+ )
414
+ botao_exportar.click()
415
+ console.print("[EXP] Botão 'Exportar' clicado.")
416
+
417
+ await worker_sleep(3)
418
+
419
+ # Clicar na opção "Exportar" do menu
420
+ actions = ActionChains(self.driver)
421
+ actions.send_keys(Keys.ENTER)
422
+ actions.perform()
423
+ console.print("[EXP] Opção 'Exportar' clicada.")
424
+
425
+ # Clicar em "Exportar" para baixar
426
+ console.print("[EXP] Clicarndo no botao 'Exportar para baixar...")
427
+ botao_exportar = WebDriverWait(self.driver, 30).until(
428
+ EC.element_to_be_clickable((By.XPATH, "//bdi[text()='Exportar']"))
429
+ )
430
+ botao_exportar.click()
431
+ console.print("[EXP] Botão 'Exportar' clicado.")
432
+
433
+ # AQUI — aguarda até 10 MIN pelo arquivo
434
+ arquivo_baixado = await self.wait_for_xlsx_download()
435
+ console.print(f"[EXP] Download concluído! Arquivo: {arquivo_baixado}")
436
+
437
+ console.print("[EXP] Chamando rename_file...")
438
+ await self.rename_file()
439
+ console.print("[EXP] rename_file concluído.")
440
+
441
+ except Exception as e:
442
+ console.print("[EXP][ERRO] Exceção em exportar_lista_clientes.")
443
+ console.print(f"[EXP][ERRO] Tipo: {type(e).__name__}")
444
+ console.print(f"[EXP][ERRO] str(e): {str(e)}")
445
+ console.print("[EXP][ERRO] Traceback:")
446
+ console.print(traceback.format_exc())
447
+ raise
448
+
449
+ async def rename_file(self):
450
+ console.print("[RF] Iniciando renomeação e movimentação do arquivo.")
451
+ try:
452
+ # === 1) Localizar qualquer XLSX nos downloads ===
453
+ xlsx_files = [
454
+ os.path.join(DOWNLOADS_PATH, f)
455
+ for f in os.listdir(DOWNLOADS_PATH)
456
+ if f.lower().endswith(".xlsx")
457
+ ]
458
+
459
+ if not xlsx_files:
460
+ msg = "[RF][ERRO] Nenhum arquivo XLSX encontrado na pasta Downloads."
461
+ console.print(msg)
462
+ return RpaRetornoProcessoDTO(
463
+ sucesso=False,
464
+ retorno=msg,
465
+ status=RpaHistoricoStatusEnum.Falha,
466
+ tags=[RpaTagDTO(descricao=RpaTagEnum.Tecnico)],
467
+ )
468
+
469
+ # pega o arquivo mais recente
470
+ current_path = max(xlsx_files, key=os.path.getmtime)
471
+
472
+ console.print(f"[RF] Arquivo encontrado: {current_path}")
473
+
474
+ # === 2) Criar nome SIM_CLI + timestamp ===
475
+ date_now = datetime.now().strftime("%Y%m%d%H%M%S")
476
+ filename = f"SIM_CLI_{date_now}.xlsx"
477
+ final_path = os.path.join(DOWNLOADS_PATH, filename)
478
+
479
+ console.print(f"[RF] Novo filename: {filename}")
480
+
481
+ # === 3) Renomear ===
482
+ os.rename(current_path, final_path)
483
+ console.print(f"[RF] Arquivo renomeado para {final_path}.")
484
+
485
+ # === 4) Ler conteúdo para envio ===
486
+ with open(final_path, "rb") as file:
487
+ file_bytes = io.BytesIO(file.read())
488
+
489
+ await worker_sleep(2)
490
+
491
+ # === 5) Enviar para o datalake ===
492
+ try:
493
+ console.print(f"[RF] directory: {self.directory}")
494
+ console.print(f"[RF] file: {final_path}")
495
+ send_file_request = await send_file_to_datalake(
496
+ self.directory, file_bytes, filename, "xlsx"
497
+ )
498
+ console.print(f"[RF] Resposta send_file_to_datalake: {send_file_request}")
499
+ except Exception as e:
500
+ console.print(f"[RF][ERRO] Erro ao enviar o arquivo: {e}", style="bold red")
501
+ console.print("[RF][ERRO] Traceback:")
502
+ console.print(traceback.format_exc())
503
+ return RpaRetornoProcessoDTO(
504
+ sucesso=False,
505
+ retorno=f"Erro ao enviar o arquivo: {e}",
506
+ status=RpaHistoricoStatusEnum.Falha,
507
+ tags=[RpaTagDTO(descricao=RpaTagEnum.Tecnico)],
508
+ )
509
+
510
+ await worker_sleep(1)
511
+
512
+ # === 6) Deletar arquivo final após envio ===
513
+ if os.path.exists(final_path):
514
+ try:
515
+ os.remove(final_path)
516
+ console.print(f"[RF] Arquivo deletado: {final_path}")
517
+ except Exception as e:
518
+ msg = f"Erro ao deletar o arquivo: {e}"
519
+ console.print(f"[RF][ERRO] {msg}")
520
+ console.print("[RF][ERRO] Traceback:")
521
+ console.print(traceback.format_exc())
522
+ return RpaRetornoProcessoDTO(
523
+ sucesso=False,
524
+ retorno=msg,
525
+ status=RpaHistoricoStatusEnum.Falha,
526
+ tags=[RpaTagDTO(descricao=RpaTagEnum.Tecnico)],
527
+ )
528
+
529
+ except Exception as e:
530
+ msg = f"Erro ao manipular arquivo XLSX: {e}"
531
+ console.print(f"[RF][ERRO] {msg}")
532
+ console.print("[RF][ERRO] Traceback:")
533
+ console.print(traceback.format_exc())
534
+ return RpaRetornoProcessoDTO(
535
+ sucesso=False,
536
+ retorno=msg,
537
+ status=RpaHistoricoStatusEnum.Falha,
538
+ tags=[RpaTagDTO(descricao=RpaTagEnum.Tecnico)],
539
+ )
540
+
541
+
542
+ async def send_message_to_webhook(self, message: str):
543
+ console.print("[WEBHOOK] Enviando mensagem ao webhook.")
544
+ await worker_sleep(2)
545
+ try:
546
+ payload = {"text": "📢 " + message}
547
+ webhook_url = f"{self.sap_url}/key={self.sap_key}&token={self.sap_token}"
548
+ requests.post(
549
+ webhook_url,
550
+ data=json.dumps(payload),
551
+ headers={"Content-Type": "application/json"},
552
+ timeout=10,
553
+ )
554
+ console.print("[WEBHOOK] Mensagem enviada ao webhook com sucesso.")
555
+ await worker_sleep(2)
556
+ except Exception as e:
557
+ msg = f"Erro ao enviar mensagem ao webhook: {e}"
558
+ console.print(f"[WEBHOOK][ERRO] {msg}")
559
+ console.print("[WEBHOOK][ERRO] Traceback:")
560
+ console.print(traceback.format_exc())
561
+ return RpaRetornoProcessoDTO(
562
+ sucesso=False,
563
+ retorno=msg,
564
+ status=RpaHistoricoStatusEnum.Falha,
565
+ tags=[RpaTagDTO(descricao=RpaTagEnum.Tecnico)],
566
+ )
567
+
568
+
569
+ async def lista_clientes_sap(task: RpaProcessoSapDTO) -> RpaRetornoProcessoDTO:
570
+ console.print("[MAIN] Iniciando processo de lista de clientes SAP.")
571
+ notas_sap = None
572
+ try:
573
+ console.print("[MAIN] Buscando configuração 'SAP_Faturamento'...")
574
+ config = await get_config_by_name("SAP_Faturamento")
575
+ console.print(f"[MAIN] config recebido: {config}")
576
+ sap_url = config.conConfiguracao.get("sapUrl")
577
+ sap_key = config.conConfiguracao.get("sapKey")
578
+ sap_token = config.conConfiguracao.get("sapToken")
579
+ base_url = config.conConfiguracao.get("baseUrl")
580
+ directory = config.conConfiguracao.get("directoryBucket")
581
+ console.print(
582
+ f"[MAIN] sap_url={sap_url} base_url={base_url} directory={directory}"
583
+ )
584
+
585
+ # PEGAR RELATORIO DE FORMA SEGURA (default FAT – não interfere no fluxo atual)
586
+ relatorio = None
587
+ config_entrada = task.configEntrada
588
+
589
+ if isinstance(config_entrada, dict):
590
+ relatorio = config_entrada.get("relatorio")
591
+ else:
592
+ relatorio = getattr(config_entrada, "relatorio", None)
593
+
594
+ if not relatorio:
595
+ relatorio = "FAT"
596
+
597
+ console.print(f"[MAIN] Relatório usado: {relatorio}")
598
+
599
+ notas_sap = ListaClientesSAP(
600
+ task,
601
+ sap_url,
602
+ sap_key,
603
+ sap_token,
604
+ base_url,
605
+ directory,
606
+ relatorio_override=relatorio,
607
+ )
608
+
609
+ console.print("[MAIN] Chamando start_sap_process...")
610
+ resultado = await notas_sap.start_sap_process()
611
+ console.print(f"[MAIN] Resultado do start_sap_process: sucesso={resultado.sucesso}")
612
+
613
+ return resultado
614
+
615
+ except Exception as ex:
616
+ console.print("[MAIN][ERRO] Exceção em lista_clientes_sap.")
617
+ console.print(f"[MAIN][ERRO] Tipo: {type(ex).__name__}")
618
+ console.print(f"[MAIN][ERRO] str(ex): {str(ex)}")
619
+ console.print("[MAIN][ERRO] Traceback completo:")
620
+ console.print(traceback.format_exc())
621
+ return RpaRetornoProcessoDTO(
622
+ sucesso=False,
623
+ retorno=f"Erro na automação SAP: {ex}",
624
+ status=RpaHistoricoStatusEnum.Falha,
625
+ tags=[RpaTagDTO(descricao=RpaTagEnum.Tecnico)],
626
+ )
627
+ finally:
628
+ if notas_sap and notas_sap.driver:
629
+ console.print("[MAIN] Fechando driver no final do processo.")
630
+ notas_sap.driver.quit()
631
+ console.print("[MAIN] Fim do processo (finally).")