worker-automate-hub 0.5.749__py3-none-any.whl → 0.5.912__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.
- worker_automate_hub/api/client.py +186 -68
- worker_automate_hub/api/rpa_historico_service.py +1 -0
- worker_automate_hub/cli.py +91 -111
- worker_automate_hub/tasks/jobs/abertura_livros_fiscais.py +112 -229
- worker_automate_hub/tasks/jobs/descartes.py +91 -77
- worker_automate_hub/tasks/jobs/devolucao_produtos.py +1386 -0
- worker_automate_hub/tasks/jobs/entrada_de_notas_15.py +3 -46
- worker_automate_hub/tasks/jobs/entrada_de_notas_22.py +833 -0
- worker_automate_hub/tasks/jobs/entrada_de_notas_36.py +29 -9
- worker_automate_hub/tasks/jobs/entrada_de_notas_37.py +619 -0
- worker_automate_hub/tasks/jobs/entrada_de_notas_39.py +1 -1
- worker_automate_hub/tasks/jobs/entrada_de_notas_9.py +63 -16
- worker_automate_hub/tasks/jobs/extracao_dados_nielsen.py +504 -0
- worker_automate_hub/tasks/jobs/extracao_saldo_estoque.py +242 -108
- worker_automate_hub/tasks/jobs/extracao_saldo_estoque_fiscal.py +688 -0
- worker_automate_hub/tasks/jobs/fidc_gerar_nosso_numero.py +2 -2
- worker_automate_hub/tasks/jobs/fidc_remessa_cobranca_cnab240.py +25 -16
- worker_automate_hub/tasks/jobs/geracao_balancetes_filial.py +330 -0
- worker_automate_hub/tasks/jobs/importacao_extratos.py +538 -0
- worker_automate_hub/tasks/jobs/importacao_extratos_748.py +800 -0
- worker_automate_hub/tasks/jobs/inclusao_pedidos_ipiranga.py +222 -0
- worker_automate_hub/tasks/jobs/inclusao_pedidos_raizen.py +174 -0
- worker_automate_hub/tasks/jobs/inclusao_pedidos_vibra.py +327 -0
- worker_automate_hub/tasks/jobs/notas_faturamento_sap.py +438 -157
- worker_automate_hub/tasks/jobs/opex_capex.py +540 -326
- worker_automate_hub/tasks/jobs/sped_fiscal.py +8 -8
- worker_automate_hub/tasks/jobs/transferencias.py +52 -41
- worker_automate_hub/tasks/task_definitions.py +46 -1
- worker_automate_hub/tasks/task_executor.py +11 -0
- worker_automate_hub/utils/util.py +252 -215
- worker_automate_hub/utils/utils_nfe_entrada.py +1 -1
- worker_automate_hub/worker.py +1 -9
- {worker_automate_hub-0.5.749.dist-info → worker_automate_hub-0.5.912.dist-info}/METADATA +4 -2
- {worker_automate_hub-0.5.749.dist-info → worker_automate_hub-0.5.912.dist-info}/RECORD +36 -25
- {worker_automate_hub-0.5.749.dist-info → worker_automate_hub-0.5.912.dist-info}/WHEEL +1 -1
- {worker_automate_hub-0.5.749.dist-info → worker_automate_hub-0.5.912.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,688 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import os
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from pywinauto import Application, timings, findwindows, keyboard, Desktop
|
|
5
|
+
import sys
|
|
6
|
+
import io
|
|
7
|
+
import win32gui
|
|
8
|
+
import pyperclip
|
|
9
|
+
from unidecode import unidecode
|
|
10
|
+
|
|
11
|
+
# sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..')))
|
|
12
|
+
|
|
13
|
+
from worker_automate_hub.models.dto.rpa_historico_request_dto import (
|
|
14
|
+
RpaHistoricoStatusEnum,
|
|
15
|
+
RpaRetornoProcessoDTO,
|
|
16
|
+
RpaTagDTO,
|
|
17
|
+
RpaTagEnum,
|
|
18
|
+
)
|
|
19
|
+
from worker_automate_hub.models.dto.rpa_processo_entrada_dto import (
|
|
20
|
+
RpaProcessoEntradaDTO,
|
|
21
|
+
)
|
|
22
|
+
from rich.console import Console
|
|
23
|
+
import re
|
|
24
|
+
import time
|
|
25
|
+
from pywinauto.keyboard import send_keys
|
|
26
|
+
import warnings
|
|
27
|
+
from pywinauto.application import Application
|
|
28
|
+
from worker_automate_hub.api.client import get_config_by_name, send_file
|
|
29
|
+
from worker_automate_hub.utils.util import (
|
|
30
|
+
kill_all_emsys,
|
|
31
|
+
login_emsys_fiscal,
|
|
32
|
+
set_variable,
|
|
33
|
+
type_text_into_field,
|
|
34
|
+
worker_sleep,
|
|
35
|
+
)
|
|
36
|
+
from pywinauto_recorder.player import set_combobox
|
|
37
|
+
|
|
38
|
+
from datetime import timedelta
|
|
39
|
+
import pyautogui
|
|
40
|
+
from worker_automate_hub.utils.logger import logger
|
|
41
|
+
from worker_automate_hub.utils.utils_nfe_entrada import EMSys
|
|
42
|
+
|
|
43
|
+
emsys = EMSys()
|
|
44
|
+
|
|
45
|
+
console = Console()
|
|
46
|
+
pyautogui.PAUSE = 0.5
|
|
47
|
+
pyautogui.FAILSAFE = False
|
|
48
|
+
|
|
49
|
+
# -------- Configs de velocidade --------
|
|
50
|
+
SLEEP_BETWEEN_KEYS = 0.03 # 30ms entre DOWNs
|
|
51
|
+
SLEEP_AFTER_COPY = 0.06 # 60ms após Ctrl+C
|
|
52
|
+
|
|
53
|
+
# -------- Utilidades --------
|
|
54
|
+
def _get_main_window_by_class(timeout_s: int = 5):
|
|
55
|
+
app = Application(backend="win32").connect(class_name="TFrmMovtoLivroFiscal", timeout=timeout_s)
|
|
56
|
+
win = app.window(class_name="TFrmMovtoLivroFiscal")
|
|
57
|
+
try: win.set_focus()
|
|
58
|
+
except Exception: pass
|
|
59
|
+
return app, win
|
|
60
|
+
|
|
61
|
+
def _find_grid_descendant(win):
|
|
62
|
+
# pega o maior TcxGridSite/TcxGrid
|
|
63
|
+
best, best_area = None, -1
|
|
64
|
+
for d in win.descendants():
|
|
65
|
+
try:
|
|
66
|
+
cls = (d.class_name() or "").lower()
|
|
67
|
+
if "tcxgridsite" in cls or "tcxgrid" in cls:
|
|
68
|
+
r = d.rectangle()
|
|
69
|
+
area = max(0,(r.right-r.left)) * max(0,(r.bottom-r.top))
|
|
70
|
+
if area > best_area:
|
|
71
|
+
best, best_area = d, area
|
|
72
|
+
except:
|
|
73
|
+
pass
|
|
74
|
+
if not best:
|
|
75
|
+
raise RuntimeError("Grid não localizado (TcxGrid/TcxGridSite).")
|
|
76
|
+
return best
|
|
77
|
+
|
|
78
|
+
def _copy_active_row_text(retries: int = 1) -> str:
|
|
79
|
+
pyperclip.copy("")
|
|
80
|
+
send_keys("^c")
|
|
81
|
+
time.sleep(SLEEP_AFTER_COPY)
|
|
82
|
+
txt = pyperclip.paste().strip()
|
|
83
|
+
if txt or retries <= 0:
|
|
84
|
+
return txt
|
|
85
|
+
# 1 tentativa extra (às vezes a 1ª vem vazia)
|
|
86
|
+
return _copy_active_row_text(retries-1)
|
|
87
|
+
|
|
88
|
+
def _linha_bate_criterio(txt: str, mes: str, ano: str) -> bool:
|
|
89
|
+
if not txt:
|
|
90
|
+
return False
|
|
91
|
+
t = unidecode(re.sub(r"\s+"," ", txt)).lower()
|
|
92
|
+
# dd/MM/AAAA do mês/ano desejado
|
|
93
|
+
if not re.search(rf"\b(0[1-9]|[12]\d|3[01])/{mes}/{ano}\b", t):
|
|
94
|
+
return False
|
|
95
|
+
return "livro - inventario" in t
|
|
96
|
+
|
|
97
|
+
# -------- Varredura assumindo que JÁ ESTÁ na 1ª linha --------
|
|
98
|
+
def selecionar_inventario_por_competencia(competencia_mm_aaaa: str, max_linhas: int = 800) -> bool:
|
|
99
|
+
"""
|
|
100
|
+
Pré-condição: o foco já está na PRIMEIRA LINHA do grid (você clicou por coordenada).
|
|
101
|
+
A função só navega com SETA-PARA-BAIXO até encontrar a linha alvo e PARA.
|
|
102
|
+
"""
|
|
103
|
+
# Selecionar primeira linha inventario
|
|
104
|
+
pyautogui.click(928, 475)
|
|
105
|
+
time.sleep(1)
|
|
106
|
+
m = re.fullmatch(r"(\d{2})/(\d{4})", competencia_mm_aaaa.strip())
|
|
107
|
+
if not m:
|
|
108
|
+
raise ValueError("Competência deve ser MM/AAAA (ex.: '09/2025').")
|
|
109
|
+
mes, ano = m.groups()
|
|
110
|
+
|
|
111
|
+
# garantir foco na janela/grid (não move o cursor de linha)
|
|
112
|
+
_, win = _get_main_window_by_class()
|
|
113
|
+
grid = _find_grid_descendant(win)
|
|
114
|
+
try:
|
|
115
|
+
grid.set_focus()
|
|
116
|
+
except Exception:
|
|
117
|
+
pass
|
|
118
|
+
|
|
119
|
+
for i in range(max_linhas):
|
|
120
|
+
linha = _copy_active_row_text()
|
|
121
|
+
if _linha_bate_criterio(linha, mes, ano):
|
|
122
|
+
# ✅ Linha correta já está selecionada (sem cliques)
|
|
123
|
+
# print(f"[OK] Encontrado em {i+1} passos: {linha}")
|
|
124
|
+
return True
|
|
125
|
+
send_keys("{DOWN}")
|
|
126
|
+
time.sleep(SLEEP_BETWEEN_KEYS)
|
|
127
|
+
|
|
128
|
+
# print("[WARN] Não encontrado dentro do limite de linhas.")
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
async def extracao_saldo_estoque_fiscal(
|
|
133
|
+
task: RpaProcessoEntradaDTO,
|
|
134
|
+
) -> RpaRetornoProcessoDTO:
|
|
135
|
+
try:
|
|
136
|
+
config = await get_config_by_name("login_emsys_fiscal")
|
|
137
|
+
periodo = task.configEntrada["periodo"]
|
|
138
|
+
periodo_format = periodo.replace("/", "")
|
|
139
|
+
filial = task.configEntrada["filialEmpresaOrigem"]
|
|
140
|
+
historico_id = task.historico_id
|
|
141
|
+
await kill_all_emsys()
|
|
142
|
+
|
|
143
|
+
config = await get_config_by_name("login_emsys_fiscal")
|
|
144
|
+
|
|
145
|
+
# Fecha a instancia do emsys - caso esteja aberta
|
|
146
|
+
await kill_all_emsys()
|
|
147
|
+
|
|
148
|
+
app = Application(backend="win32").start("C:\\Rezende\\EMSys3\\EMSysFiscal.exe")
|
|
149
|
+
warnings.filterwarnings(
|
|
150
|
+
"ignore",
|
|
151
|
+
category=UserWarning,
|
|
152
|
+
message="32-bit application should be automated using 32-bit Python",
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
await worker_sleep(5)
|
|
156
|
+
|
|
157
|
+
try:
|
|
158
|
+
app = Application(backend="win32").connect(
|
|
159
|
+
class_name="TFrmLoginModulo", timeout=100
|
|
160
|
+
)
|
|
161
|
+
except:
|
|
162
|
+
return RpaRetornoProcessoDTO(
|
|
163
|
+
sucesso=False,
|
|
164
|
+
retorno="Erro ao abrir o EMSys Fiscal, tela de login não encontrada",
|
|
165
|
+
status=RpaHistoricoStatusEnum.Falha,
|
|
166
|
+
tags=[RpaTagDTO(descricao=RpaTagEnum.Tecnico)],
|
|
167
|
+
)
|
|
168
|
+
return_login = await login_emsys_fiscal(config.conConfiguracao, app, task)
|
|
169
|
+
if return_login.sucesso:
|
|
170
|
+
await worker_sleep(2)
|
|
171
|
+
type_text_into_field(
|
|
172
|
+
"Livros Fiscais", app["TFrmMenuPrincipal"]["Edit"], True, "50"
|
|
173
|
+
)
|
|
174
|
+
pyautogui.press("enter")
|
|
175
|
+
await worker_sleep(2)
|
|
176
|
+
pyautogui.press("down")
|
|
177
|
+
await worker_sleep(2)
|
|
178
|
+
pyautogui.press("enter")
|
|
179
|
+
console.print(
|
|
180
|
+
"\nPesquisa: 'Livros Fiscais' realizada com sucesso.",
|
|
181
|
+
style="bold green",
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
else:
|
|
185
|
+
logger.info(f"\nError Message: {return_login.retorno}")
|
|
186
|
+
console.print(f"\nError Message: {return_login.retorno}", style="bold red")
|
|
187
|
+
return return_login
|
|
188
|
+
|
|
189
|
+
await worker_sleep(7)
|
|
190
|
+
|
|
191
|
+
##### Janela Movimento Livros Fiscais #####
|
|
192
|
+
# Conecta na janela principal
|
|
193
|
+
app = Application().connect(class_name="TFrmMovtoLivroFiscal")
|
|
194
|
+
main_window = app.window(class_name="TFrmMovtoLivroFiscal")
|
|
195
|
+
main_window.wait("exists enabled visible ready", timeout=20)
|
|
196
|
+
|
|
197
|
+
# Pegar o wrapper do campo
|
|
198
|
+
campo_data = main_window.child_window(class_name="TDBIEditDate")
|
|
199
|
+
campo_data.wait("exists enabled visible ready", timeout=10)
|
|
200
|
+
campo_data = campo_data.wrapper_object() # agora é o controle de fato
|
|
201
|
+
|
|
202
|
+
# Foco e clique
|
|
203
|
+
campo_data.set_focus()
|
|
204
|
+
campo_data.click_input()
|
|
205
|
+
|
|
206
|
+
# Limpa e digita
|
|
207
|
+
keyboard.send_keys("^a{BACKSPACE}" + periodo)
|
|
208
|
+
|
|
209
|
+
# Seleciona inventário
|
|
210
|
+
chk_inventario = main_window.child_window(
|
|
211
|
+
class_name="TcxCheckBox", found_index=6
|
|
212
|
+
).click_input()
|
|
213
|
+
|
|
214
|
+
await worker_sleep(2)
|
|
215
|
+
|
|
216
|
+
# Caminho da imagem do botão
|
|
217
|
+
imagem_botao = r"assets\\extracao_relatorios\\btn_incluir_livro.png"
|
|
218
|
+
# imagem_botao = r"C:\Users\automatehub\Documents\GitHub\worker-automate-hub\assets\extracao_relatorios\btn_incluir_livro.png"
|
|
219
|
+
|
|
220
|
+
if os.path.exists(imagem_botao):
|
|
221
|
+
try:
|
|
222
|
+
# Localiza a imagem na tela
|
|
223
|
+
botao = pyautogui.locateCenterOnScreen(
|
|
224
|
+
imagem_botao, confidence=0.9
|
|
225
|
+
) # confidence precisa do opencv instalado
|
|
226
|
+
if botao:
|
|
227
|
+
pyautogui.click(botao)
|
|
228
|
+
print("Botão clicado com sucesso!")
|
|
229
|
+
else:
|
|
230
|
+
print("Não encontrou o botão na tela.")
|
|
231
|
+
except Exception as e:
|
|
232
|
+
print(f"Erro ao localizar/clicar na imagem: {e}")
|
|
233
|
+
else:
|
|
234
|
+
print("Caminho da imagem não existe.")
|
|
235
|
+
|
|
236
|
+
##### Janela Perguntas da Geração Livros Fiscais #####
|
|
237
|
+
app = Application().connect(class_name="TPerguntasLivrosFiscaisForm")
|
|
238
|
+
main_window = app.window(class_name="TPerguntasLivrosFiscaisForm")
|
|
239
|
+
main_window.wait("exists enabled visible ready", timeout=20)
|
|
240
|
+
|
|
241
|
+
respostas = ["Não", "Sim", "Não", "Não"]
|
|
242
|
+
|
|
243
|
+
for i, resposta in enumerate(respostas):
|
|
244
|
+
combo = main_window.child_window(
|
|
245
|
+
class_name="TDBIComboBoxValues", found_index=i
|
|
246
|
+
).wrapper_object()
|
|
247
|
+
combo.set_focus()
|
|
248
|
+
combo.click_input()
|
|
249
|
+
await worker_sleep(0.1)
|
|
250
|
+
keyboard.send_keys(resposta + "{ENTER}")
|
|
251
|
+
await worker_sleep(0.2)
|
|
252
|
+
# Clicar em confirmar
|
|
253
|
+
main_window.child_window(class_name="TButton", found_index=1).click_input()
|
|
254
|
+
|
|
255
|
+
await worker_sleep(2)
|
|
256
|
+
|
|
257
|
+
##### Janela Gerar Registros #####
|
|
258
|
+
app = Application(backend="win32").connect(title="Gerar Registros")
|
|
259
|
+
main_window = app.window(title="Gerar Registros")
|
|
260
|
+
|
|
261
|
+
# Clicar no botão "Sim"
|
|
262
|
+
main_window.child_window(title="&Sim", class_name="Button").click_input()
|
|
263
|
+
|
|
264
|
+
await worker_sleep(2)
|
|
265
|
+
|
|
266
|
+
##### Janela Informa Motivo do Inventario #####
|
|
267
|
+
app = Application().connect(class_name="TFrmMotvoMotivoInventario")
|
|
268
|
+
main_window = app.window(class_name="TFrmMotvoMotivoInventario")
|
|
269
|
+
main_window.wait("exists enabled visible ready", timeout=20)
|
|
270
|
+
slc_01 = main_window.child_window(
|
|
271
|
+
class_name="TDBIComboBoxValues", found_index=0
|
|
272
|
+
).click_input()
|
|
273
|
+
await worker_sleep(1)
|
|
274
|
+
keyboard.send_keys("01" + "{ENTER}")
|
|
275
|
+
await worker_sleep(2)
|
|
276
|
+
|
|
277
|
+
# Clicar em confirmar
|
|
278
|
+
main_window.child_window(class_name="TBitBtn", found_index=0).click_input()
|
|
279
|
+
|
|
280
|
+
await worker_sleep(5)
|
|
281
|
+
|
|
282
|
+
CLASS = "TFrmPreviewRelatorio"
|
|
283
|
+
|
|
284
|
+
# 1) Espera a janela aparecer (até 180s)
|
|
285
|
+
desk = Desktop(backend="win32")
|
|
286
|
+
deadline = time.time() + 180
|
|
287
|
+
win = None
|
|
288
|
+
while time.time() < deadline:
|
|
289
|
+
try:
|
|
290
|
+
w = desk.window(class_name=CLASS)
|
|
291
|
+
if w.exists(timeout=0.5):
|
|
292
|
+
w.wait("visible enabled ready", timeout=30)
|
|
293
|
+
win = w
|
|
294
|
+
break
|
|
295
|
+
except Exception:
|
|
296
|
+
pass
|
|
297
|
+
time.sleep(0.5)
|
|
298
|
+
|
|
299
|
+
if win is None:
|
|
300
|
+
raise TimeoutError(f"Janela '{CLASS}' não apareceu dentro do timeout.")
|
|
301
|
+
|
|
302
|
+
w.close()
|
|
303
|
+
|
|
304
|
+
await worker_sleep(2)
|
|
305
|
+
|
|
306
|
+
##### Janela Movimento Livro Fiscal #####
|
|
307
|
+
# Selecionar primeira linha inventario
|
|
308
|
+
selecionar_inventario_por_competencia(periodo)
|
|
309
|
+
|
|
310
|
+
await worker_sleep(2)
|
|
311
|
+
|
|
312
|
+
# Clicar em visualizar livro
|
|
313
|
+
caminho = r"assets\\extracao_relatorios\\btn_visu_livros.png"
|
|
314
|
+
# caminho =r"C:\Users\automatehub\Documents\GitHub\worker-automate-hub\assets\extracao_relatorios\btn_visu_livros.png"
|
|
315
|
+
# Verifica se o arquivo existe
|
|
316
|
+
if os.path.isfile(caminho):
|
|
317
|
+
print("A imagem existe:", caminho)
|
|
318
|
+
|
|
319
|
+
# Procura a imagem na tela
|
|
320
|
+
pos = pyautogui.locateCenterOnScreen(
|
|
321
|
+
caminho, confidence=0.9
|
|
322
|
+
) # ajuste o confidence se necessário
|
|
323
|
+
if pos:
|
|
324
|
+
pyautogui.click(pos) # clica no centro da imagem
|
|
325
|
+
print("Clique realizado na imagem.")
|
|
326
|
+
else:
|
|
327
|
+
print("Imagem encontrada no disco, mas não está visível na tela.")
|
|
328
|
+
else:
|
|
329
|
+
print("A imagem NÃO existe:", caminho)
|
|
330
|
+
|
|
331
|
+
await worker_sleep(5)
|
|
332
|
+
|
|
333
|
+
##### Janela Movimento Livro Fiscal - Livro - Inventario para Competencia #####
|
|
334
|
+
app = Application().connect(class_name="TFrmMovtoLivroFiscal")
|
|
335
|
+
main_window = app.window(class_name="TFrmMovtoLivroFiscal")
|
|
336
|
+
main_window.wait("exists enabled visible ready", timeout=20)
|
|
337
|
+
input_7 = main_window.child_window(
|
|
338
|
+
class_name="TDBIEditCode", found_index=0
|
|
339
|
+
).click_input()
|
|
340
|
+
await worker_sleep(0.1)
|
|
341
|
+
keyboard.send_keys("7" + "{TAB}")
|
|
342
|
+
await worker_sleep(0.2)
|
|
343
|
+
# Clicar em imprimir
|
|
344
|
+
btn_imprimir = main_window.child_window(
|
|
345
|
+
class_name="TBitBtn", found_index=0
|
|
346
|
+
).click_input()
|
|
347
|
+
|
|
348
|
+
await worker_sleep(2)
|
|
349
|
+
|
|
350
|
+
##### Janela Selecion o Template Desejado #####
|
|
351
|
+
app = Application().connect(class_name="TFrmFRVisualizaTemplateMenuNew")
|
|
352
|
+
main_window = app.window(class_name="TFrmFRVisualizaTemplateMenuNew")
|
|
353
|
+
main_window.wait("exists enabled visible ready", timeout=20)
|
|
354
|
+
btn_gerar_rel = main_window.child_window(
|
|
355
|
+
class_name="TBitBtn", found_index=1
|
|
356
|
+
).click_input()
|
|
357
|
+
|
|
358
|
+
await worker_sleep(2)
|
|
359
|
+
|
|
360
|
+
##### Janela Parametros #####
|
|
361
|
+
app = Application().connect(class_name="TFrmFRParametroRelatorio")
|
|
362
|
+
main_window = app.window(class_name="TFrmFRParametroRelatorio")
|
|
363
|
+
main_window.wait("exists enabled visible ready", timeout=20)
|
|
364
|
+
slc_nao = main_window.child_window(
|
|
365
|
+
class_name="TComboBox", found_index=0
|
|
366
|
+
).click_input()
|
|
367
|
+
await worker_sleep(0.1)
|
|
368
|
+
keyboard.send_keys("NAO" + "{ENTER}")
|
|
369
|
+
await worker_sleep(0.2)
|
|
370
|
+
|
|
371
|
+
# Clicar BOTAO OK
|
|
372
|
+
slc_nao = main_window.child_window(
|
|
373
|
+
class_name="TBitBtn", found_index=1
|
|
374
|
+
).click_input()
|
|
375
|
+
|
|
376
|
+
await worker_sleep(2)
|
|
377
|
+
|
|
378
|
+
max_tentativas = 5
|
|
379
|
+
tentativa = 1
|
|
380
|
+
sucesso = False
|
|
381
|
+
|
|
382
|
+
# defina caminho_arquivo ANTES para não ficar indefinido
|
|
383
|
+
caminho_arquivo = rf"C:\Users\automatehub\Downloads\saldo_estoque_fiscal_{periodo_format}_{filial}.xlsx"
|
|
384
|
+
|
|
385
|
+
while tentativa <= max_tentativas and not sucesso:
|
|
386
|
+
console.print(
|
|
387
|
+
f"Tentativa {tentativa} de {max_tentativas}", style="bold cyan"
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
# 1) Abrir o picker pelo botão (imagem)
|
|
391
|
+
console.print("Procurando botão de salvar (imagem)...", style="bold cyan")
|
|
392
|
+
caminho_img = r"assets\\extracao_relatorios\btn_salvar.png"
|
|
393
|
+
# caminho_img = r"C:\Users\automatehub\Documents\GitHub\worker-automate-hub\assets\extracao_relatorios\btn_salvar.png"
|
|
394
|
+
if os.path.isfile(caminho_img):
|
|
395
|
+
pos = pyautogui.locateCenterOnScreen(caminho_img, confidence=0.9)
|
|
396
|
+
if pos:
|
|
397
|
+
pyautogui.click(pos)
|
|
398
|
+
console.print(
|
|
399
|
+
"Clique realizado no botão salvar", style="bold green"
|
|
400
|
+
)
|
|
401
|
+
else:
|
|
402
|
+
console.print(
|
|
403
|
+
"Imagem encontrada mas não está visível na tela",
|
|
404
|
+
style="bold yellow",
|
|
405
|
+
)
|
|
406
|
+
else:
|
|
407
|
+
console.print("Imagem do botão salvar NÃO existe", style="bold red")
|
|
408
|
+
|
|
409
|
+
await worker_sleep(8)
|
|
410
|
+
|
|
411
|
+
# 2) Selecionar formato Excel (desambiguando múltiplas TFrmRelatorioFormato)
|
|
412
|
+
console.print("Selecionando formato Excel...", style="bold cyan")
|
|
413
|
+
try:
|
|
414
|
+
desktop = Desktop(backend="win32")
|
|
415
|
+
|
|
416
|
+
# Liste todas as visíveis
|
|
417
|
+
wins_visiveis = desktop.windows(
|
|
418
|
+
class_name="TFrmRelatorioFormato", visible_only=True
|
|
419
|
+
)
|
|
420
|
+
if not wins_visiveis:
|
|
421
|
+
raise RuntimeError("Janela de formato não apareceu.")
|
|
422
|
+
|
|
423
|
+
# 2.1) Tente a janela em foco (foreground)
|
|
424
|
+
h_fore = win32gui.GetForegroundWindow()
|
|
425
|
+
alvo = None
|
|
426
|
+
for w in wins_visiveis:
|
|
427
|
+
if w.handle == h_fore:
|
|
428
|
+
alvo = w
|
|
429
|
+
break
|
|
430
|
+
|
|
431
|
+
# 2.2) Se não estiver em foco, pegue a que contém um TComboBox (a 'Configuração para Salvar arq...')
|
|
432
|
+
if alvo is None:
|
|
433
|
+
candidatos = []
|
|
434
|
+
for w in wins_visiveis:
|
|
435
|
+
try:
|
|
436
|
+
if w.child_window(class_name="TComboBox").exists(
|
|
437
|
+
timeout=0.8
|
|
438
|
+
):
|
|
439
|
+
candidatos.append(w)
|
|
440
|
+
except Exception:
|
|
441
|
+
pass
|
|
442
|
+
if candidatos:
|
|
443
|
+
alvo = candidatos[-1] # a mais recente
|
|
444
|
+
else:
|
|
445
|
+
alvo = wins_visiveis[-1] # fallback
|
|
446
|
+
|
|
447
|
+
# Trabalhe via WindowSpecification
|
|
448
|
+
spec_fmt = desktop.window(handle=alvo.handle)
|
|
449
|
+
spec_fmt.wait("visible", timeout=10)
|
|
450
|
+
win_fmt = spec_fmt.wrapper_object()
|
|
451
|
+
win_fmt.set_focus()
|
|
452
|
+
|
|
453
|
+
# Acessar o ComboBox
|
|
454
|
+
try:
|
|
455
|
+
combo_spec = spec_fmt.child_window(class_name="TComboBox")
|
|
456
|
+
except Exception:
|
|
457
|
+
combo_spec = spec_fmt.child_window(control_type="ComboBox")
|
|
458
|
+
combo_spec.wait("exists enabled", timeout=10)
|
|
459
|
+
combo = combo_spec.wrapper_object()
|
|
460
|
+
|
|
461
|
+
textos = combo.texts()
|
|
462
|
+
console.print(f"Itens do ComboBox: {textos}", style="bold yellow")
|
|
463
|
+
|
|
464
|
+
# Seleção por índice conhecido; fallback por texto
|
|
465
|
+
try:
|
|
466
|
+
combo.select(8)
|
|
467
|
+
except Exception:
|
|
468
|
+
alvo_idx = None
|
|
469
|
+
for i, t in enumerate(textos):
|
|
470
|
+
if "EXCEL" in str(t).upper() or "XLSX" in str(t).upper():
|
|
471
|
+
alvo_idx = i
|
|
472
|
+
break
|
|
473
|
+
if alvo_idx is None:
|
|
474
|
+
console.print(
|
|
475
|
+
"Não foi possível localizar a opção de Excel no ComboBox.",
|
|
476
|
+
style="bold red",
|
|
477
|
+
)
|
|
478
|
+
tentativa += 1
|
|
479
|
+
await worker_sleep(2)
|
|
480
|
+
continue
|
|
481
|
+
combo.select(alvo_idx)
|
|
482
|
+
|
|
483
|
+
await worker_sleep(1)
|
|
484
|
+
|
|
485
|
+
# Clique em OK
|
|
486
|
+
btn_ok_spec = spec_fmt.child_window(class_name="TBitBtn", found_index=1)
|
|
487
|
+
btn_ok_spec.wait("enabled", timeout=5)
|
|
488
|
+
btn_ok_spec.click_input()
|
|
489
|
+
|
|
490
|
+
# Aguarde a janela de formato desaparecer
|
|
491
|
+
try:
|
|
492
|
+
spec_fmt.wait_not("visible", timeout=10)
|
|
493
|
+
except Exception:
|
|
494
|
+
pass
|
|
495
|
+
|
|
496
|
+
# Feche possíveis duplicatas remanescentes (defensivo)
|
|
497
|
+
for w in desktop.windows(
|
|
498
|
+
class_name="TFrmRelatorioFormato", visible_only=True
|
|
499
|
+
):
|
|
500
|
+
if w.handle != alvo.handle:
|
|
501
|
+
try:
|
|
502
|
+
w.close()
|
|
503
|
+
except Exception:
|
|
504
|
+
pass
|
|
505
|
+
|
|
506
|
+
except Exception as e:
|
|
507
|
+
console.print(f"Falha ao selecionar formato: {e}", style="bold red")
|
|
508
|
+
tentativa += 1
|
|
509
|
+
await worker_sleep(3)
|
|
510
|
+
continue
|
|
511
|
+
|
|
512
|
+
await worker_sleep(5)
|
|
513
|
+
|
|
514
|
+
# 3) Janela "Salvar para arquivo"
|
|
515
|
+
console.print("Abrindo janela de salvar arquivo...", style="bold cyan")
|
|
516
|
+
try:
|
|
517
|
+
app_save = Application(backend="win32").connect(
|
|
518
|
+
title_re="Salvar para arquivo|Salvar como|Save As", timeout=30
|
|
519
|
+
)
|
|
520
|
+
spec_save = app_save.window(
|
|
521
|
+
title_re="Salvar para arquivo|Salvar como|Save As"
|
|
522
|
+
)
|
|
523
|
+
spec_save.wait("visible", timeout=30)
|
|
524
|
+
win_save = spec_save.wrapper_object()
|
|
525
|
+
except Exception as e:
|
|
526
|
+
console.print(
|
|
527
|
+
f"Não achou a janela 'Salvar para arquivo': {e}", style="bold red"
|
|
528
|
+
)
|
|
529
|
+
tentativa += 1
|
|
530
|
+
await worker_sleep(3)
|
|
531
|
+
continue
|
|
532
|
+
|
|
533
|
+
# 3.1) Remover arquivo pré-existente
|
|
534
|
+
if os.path.exists(caminho_arquivo):
|
|
535
|
+
try:
|
|
536
|
+
os.remove(caminho_arquivo)
|
|
537
|
+
console.print(
|
|
538
|
+
"Arquivo existente removido para evitar prompt de sobrescrita.",
|
|
539
|
+
style="bold yellow",
|
|
540
|
+
)
|
|
541
|
+
except Exception as e:
|
|
542
|
+
console.print(
|
|
543
|
+
f"Não foi possível remover o arquivo existente: {e}",
|
|
544
|
+
style="bold red",
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
# 3.2) Preencher nome e salvar
|
|
548
|
+
try:
|
|
549
|
+
campo_spec = spec_save.child_window(class_name="Edit", control_id=1148)
|
|
550
|
+
campo_spec.wait("exists enabled visible", timeout=10)
|
|
551
|
+
campo_nome = campo_spec.wrapper_object()
|
|
552
|
+
campo_nome.set_focus()
|
|
553
|
+
try:
|
|
554
|
+
campo_nome.set_edit_text("")
|
|
555
|
+
except Exception:
|
|
556
|
+
campo_nome.type_keys("^a{DELETE}", pause=0.02)
|
|
557
|
+
|
|
558
|
+
campo_nome.type_keys(caminho_arquivo, with_spaces=True, pause=0.01)
|
|
559
|
+
console.print(
|
|
560
|
+
f"Arquivo configurado para: {caminho_arquivo}", style="bold green"
|
|
561
|
+
)
|
|
562
|
+
|
|
563
|
+
await worker_sleep(1)
|
|
564
|
+
|
|
565
|
+
btn_salvar_spec = spec_save.child_window(
|
|
566
|
+
class_name="Button", found_index=0
|
|
567
|
+
)
|
|
568
|
+
btn_salvar_spec.wait("enabled", timeout=10)
|
|
569
|
+
btn_salvar_spec.click_input()
|
|
570
|
+
|
|
571
|
+
# Esperar a janela sumir
|
|
572
|
+
try:
|
|
573
|
+
spec_save.wait_not("visible", timeout=15)
|
|
574
|
+
except Exception:
|
|
575
|
+
pass
|
|
576
|
+
|
|
577
|
+
except Exception as e:
|
|
578
|
+
console.print(f"Erro ao confirmar salvar: {e}", style="bold red")
|
|
579
|
+
tentativa += 1
|
|
580
|
+
await worker_sleep(3)
|
|
581
|
+
continue
|
|
582
|
+
|
|
583
|
+
await worker_sleep(2)
|
|
584
|
+
|
|
585
|
+
# 3.3) Confirmar sobrescrita (se houver)
|
|
586
|
+
try:
|
|
587
|
+
app_conf = Application(backend="win32").connect(
|
|
588
|
+
title_re="Confirm(ar)?( )?Salvar( )?Como|Confirm Save As", timeout=3
|
|
589
|
+
)
|
|
590
|
+
spec_conf = app_conf.window(
|
|
591
|
+
title_re="Confirm(ar)?( )?Salvar( )?Como|Confirm Save As"
|
|
592
|
+
)
|
|
593
|
+
spec_conf.wait("visible", timeout=3)
|
|
594
|
+
spec_conf.child_window(class_name="Button", found_index=0).click_input()
|
|
595
|
+
console.print(
|
|
596
|
+
"Confirmação de sobrescrita respondida.", style="bold yellow"
|
|
597
|
+
)
|
|
598
|
+
except Exception:
|
|
599
|
+
pass
|
|
600
|
+
|
|
601
|
+
await worker_sleep(2)
|
|
602
|
+
|
|
603
|
+
# 4) Aguardar 'Printing' (se existir)
|
|
604
|
+
console.print(
|
|
605
|
+
"Aguardando finalização do processo de impressão/salvamento...",
|
|
606
|
+
style="bold cyan",
|
|
607
|
+
)
|
|
608
|
+
try:
|
|
609
|
+
app_print = Application(backend="win32").connect(
|
|
610
|
+
title_re="Printing", timeout=5
|
|
611
|
+
)
|
|
612
|
+
spec_print = app_print.window(title_re="Printing")
|
|
613
|
+
try:
|
|
614
|
+
spec_print.wait_not("visible", timeout=60)
|
|
615
|
+
console.print("Janela 'Printing' fechada.", style="bold green")
|
|
616
|
+
except Exception:
|
|
617
|
+
console.print(
|
|
618
|
+
"Janela 'Printing' não fechou no tempo esperado. Seguindo.",
|
|
619
|
+
style="bold yellow",
|
|
620
|
+
)
|
|
621
|
+
except findwindows.ElementNotFoundError:
|
|
622
|
+
console.print("Janela 'Printing' não apareceu.", style="bold yellow")
|
|
623
|
+
except Exception as e:
|
|
624
|
+
console.print(f"Erro ao aguardar 'Printing': {e}", style="bold yellow")
|
|
625
|
+
|
|
626
|
+
# 5) Validar arquivo salvo
|
|
627
|
+
if os.path.exists(caminho_arquivo):
|
|
628
|
+
console.print(
|
|
629
|
+
f"Arquivo encontrado: {caminho_arquivo}", style="bold green"
|
|
630
|
+
)
|
|
631
|
+
with open(caminho_arquivo, "rb") as f:
|
|
632
|
+
file_bytes = io.BytesIO(f.read())
|
|
633
|
+
sucesso = True
|
|
634
|
+
else:
|
|
635
|
+
console.print(
|
|
636
|
+
"Arquivo não encontrado, tentando novamente...", style="bold red"
|
|
637
|
+
)
|
|
638
|
+
tentativa += 1
|
|
639
|
+
await worker_sleep(3)
|
|
640
|
+
|
|
641
|
+
if not sucesso:
|
|
642
|
+
console.print(
|
|
643
|
+
"Falha após 5 tentativas. Arquivo não foi gerado.", style="bold red"
|
|
644
|
+
)
|
|
645
|
+
|
|
646
|
+
nome_com_extensao = f"saldo_estoque_fiscal_{periodo_format}_{filial}.xlsx"
|
|
647
|
+
# lê o arquivo
|
|
648
|
+
print(caminho_arquivo)
|
|
649
|
+
with open(f"{caminho_arquivo}", "rb") as file:
|
|
650
|
+
file_bytes = io.BytesIO(file.read())
|
|
651
|
+
|
|
652
|
+
console.print("Enviar Excel para o BOF")
|
|
653
|
+
try:
|
|
654
|
+
await send_file(
|
|
655
|
+
historico_id,
|
|
656
|
+
nome_com_extensao,
|
|
657
|
+
"xlsx",
|
|
658
|
+
file_bytes,
|
|
659
|
+
file_extension="xlsx",
|
|
660
|
+
)
|
|
661
|
+
console.print("Removendo arquivo XLS da pasta downloads")
|
|
662
|
+
os.remove(f"{caminho_arquivo}")
|
|
663
|
+
return RpaRetornoProcessoDTO(
|
|
664
|
+
sucesso=True,
|
|
665
|
+
retorno="Relatório gerado com sucesso",
|
|
666
|
+
status=RpaHistoricoStatusEnum.Sucesso,
|
|
667
|
+
)
|
|
668
|
+
|
|
669
|
+
except Exception as e:
|
|
670
|
+
console.print(f"Erro ao enviar o arquivo: {e}", style="bold red")
|
|
671
|
+
return RpaRetornoProcessoDTO(
|
|
672
|
+
sucesso=False,
|
|
673
|
+
retorno=f"Erro ao enviar o arquivo: {e}",
|
|
674
|
+
status=RpaHistoricoStatusEnum.Falha,
|
|
675
|
+
tags=[RpaTagDTO(descricao=RpaTagEnum.Tecnico)],
|
|
676
|
+
)
|
|
677
|
+
|
|
678
|
+
except Exception as ex:
|
|
679
|
+
retorno = f"Erro Processo Saldo Estoque Fiscal: {str(ex)}"
|
|
680
|
+
logger.error(retorno)
|
|
681
|
+
console.print(retorno, style="bold red")
|
|
682
|
+
return RpaRetornoProcessoDTO(
|
|
683
|
+
sucesso=False,
|
|
684
|
+
retorno=retorno,
|
|
685
|
+
status=RpaHistoricoStatusEnum.Falha,
|
|
686
|
+
tags=[RpaTagDTO(descricao=RpaTagEnum.Tecnico)],
|
|
687
|
+
)
|
|
688
|
+
|