worker-automate-hub 0.5.820__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.
- worker_automate_hub/api/client.py +121 -10
- worker_automate_hub/api/rpa_historico_service.py +1 -0
- worker_automate_hub/tasks/jobs/abertura_livros_fiscais.py +11 -14
- worker_automate_hub/tasks/jobs/descartes.py +8 -8
- worker_automate_hub/tasks/jobs/devolucao_produtos.py +1386 -0
- worker_automate_hub/tasks/jobs/extracao_dados_nielsen.py +504 -0
- worker_automate_hub/tasks/jobs/extracao_saldo_estoque_fiscal.py +90 -11
- worker_automate_hub/tasks/jobs/fidc_gerar_nosso_numero.py +2 -2
- worker_automate_hub/tasks/jobs/fidc_remessa_cobranca_cnab240.py +24 -15
- 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 +223 -0
- worker_automate_hub/tasks/jobs/inclusao_pedidos_raizen.py +187 -0
- worker_automate_hub/tasks/jobs/inclusao_pedidos_vibra.py +345 -0
- worker_automate_hub/tasks/jobs/lista_clientes_sap.py +631 -0
- worker_automate_hub/tasks/jobs/lista_devolucoes_sap.py +626 -0
- worker_automate_hub/tasks/jobs/notas_faturamento_sap.py +438 -157
- worker_automate_hub/tasks/jobs/opex_capex.py +523 -384
- worker_automate_hub/tasks/task_definitions.py +38 -2
- worker_automate_hub/utils/util.py +20 -10
- {worker_automate_hub-0.5.820.dist-info → worker_automate_hub-0.5.921.dist-info}/METADATA +2 -1
- {worker_automate_hub-0.5.820.dist-info → worker_automate_hub-0.5.921.dist-info}/RECORD +24 -15
- {worker_automate_hub-0.5.820.dist-info → worker_automate_hub-0.5.921.dist-info}/WHEEL +0 -0
- {worker_automate_hub-0.5.820.dist-info → worker_automate_hub-0.5.921.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,800 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import asyncio
|
|
3
|
+
import warnings
|
|
4
|
+
from datetime import datetime, date
|
|
5
|
+
import json
|
|
6
|
+
import io
|
|
7
|
+
import pyautogui
|
|
8
|
+
from pywinauto.application import Application, timings
|
|
9
|
+
from pywinauto import keyboard
|
|
10
|
+
from pywinauto import Desktop
|
|
11
|
+
from collections import defaultdict
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
import getpass
|
|
14
|
+
import time
|
|
15
|
+
import re
|
|
16
|
+
import sys
|
|
17
|
+
import os
|
|
18
|
+
import shutil
|
|
19
|
+
import win32wnet # pywin32
|
|
20
|
+
|
|
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 (
|
|
26
|
+
get_config_by_name,
|
|
27
|
+
send_file,
|
|
28
|
+
get_notas_produtos,
|
|
29
|
+
)
|
|
30
|
+
from worker_automate_hub.models.dto.rpa_historico_request_dto import (
|
|
31
|
+
RpaHistoricoStatusEnum,
|
|
32
|
+
RpaRetornoProcessoDTO,
|
|
33
|
+
RpaTagDTO,
|
|
34
|
+
RpaTagEnum,
|
|
35
|
+
)
|
|
36
|
+
from worker_automate_hub.models.dto.rpa_processo_entrada_dto import (
|
|
37
|
+
RpaProcessoEntradaDTO,
|
|
38
|
+
)
|
|
39
|
+
from worker_automate_hub.utils.logger import logger
|
|
40
|
+
from pywinauto.keyboard import send_keys
|
|
41
|
+
|
|
42
|
+
from worker_automate_hub.utils.util import (
|
|
43
|
+
kill_all_emsys,
|
|
44
|
+
login_emsys,
|
|
45
|
+
type_text_into_field,
|
|
46
|
+
worker_sleep,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
console = Console()
|
|
50
|
+
log = console.log
|
|
51
|
+
|
|
52
|
+
ASSETS_BASE_PATH = fr"assets\importacao_extratos"
|
|
53
|
+
# ASSETS_BASE_PATH = r"C:\Users\automatehub\Desktop\img_leo"
|
|
54
|
+
ano_atual = date.today().year
|
|
55
|
+
|
|
56
|
+
# === DESTINOS ===
|
|
57
|
+
DESTINO_BASE = fr"Z:\Nexera\Extrato\{ano_atual}" # tentativa principal (Z:)
|
|
58
|
+
DESTINO_IP_ROOT = r"\\fcaswfs01.ditrento.com.br\compartilhadas$" # root do share
|
|
59
|
+
DESTINO_BASE_IP = fr"{DESTINO_IP_ROOT}\Nexera\\{ano_atual}" # pasta final no UNC
|
|
60
|
+
|
|
61
|
+
EMPRESA = "1" # empresa fixa
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
async def importacao_extratos_748(task: RpaProcessoEntradaDTO) -> RpaRetornoProcessoDTO:
|
|
65
|
+
try:
|
|
66
|
+
# ======== PARÂMETROS ========
|
|
67
|
+
PASTA = r"Z:\Nexera\Extrato"
|
|
68
|
+
CONTEM = ["EXT_748_"] # padrões obrigatórios no nome do arquivo
|
|
69
|
+
EXT_PERMITIDAS = [".ret", ".txt"] # extensões permitidas (case-insensitive)
|
|
70
|
+
TEXTO_ALVO = "SIM REDE DE POSTOS" # texto a procurar no conteúdo
|
|
71
|
+
BTN_IMPORTAR_IMG = fr"{ASSETS_BASE_PATH}\btn_imp_arq.png"
|
|
72
|
+
BTN_CONCILIAR = fr"{ASSETS_BASE_PATH}\btn_conciliar.png"
|
|
73
|
+
DLG_TIT_RE = ".*Browse.*" # regex de título do diálogo de arquivo
|
|
74
|
+
|
|
75
|
+
IMAGEM_SUCESSO = fr"{ASSETS_BASE_PATH}\conciliados_sucesso.png"
|
|
76
|
+
|
|
77
|
+
EXT_STR = "/".join(EXT_PERMITIDAS).upper()
|
|
78
|
+
|
|
79
|
+
# Credenciais para fallback de rede
|
|
80
|
+
user_folder_login = await get_config_by_name("user_credentials")
|
|
81
|
+
user_folder_cfg = user_folder_login.conConfiguracao or {}
|
|
82
|
+
|
|
83
|
+
log("[cyan]Iniciando importacao_extratos[/] | empresa fixa = %s", EMPRESA)
|
|
84
|
+
log(
|
|
85
|
+
"Parâmetros -> pasta: %s | contem (obrigatório): %s | extensões: %s | texto alvo: %s",
|
|
86
|
+
PASTA,
|
|
87
|
+
CONTEM,
|
|
88
|
+
EXT_STR,
|
|
89
|
+
TEXTO_ALVO,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# ======== ACUMULADORES ========
|
|
93
|
+
selecionados = []
|
|
94
|
+
avaliados = 0
|
|
95
|
+
ignorados_nome = 0
|
|
96
|
+
nao_permitido = 0
|
|
97
|
+
lidos = 0
|
|
98
|
+
erros = 0
|
|
99
|
+
|
|
100
|
+
if not os.path.isdir(PASTA):
|
|
101
|
+
msg = f"Pasta não encontrada: {PASTA}"
|
|
102
|
+
log(f"[red]{msg}[/]")
|
|
103
|
+
raise SystemExit(msg)
|
|
104
|
+
|
|
105
|
+
log("Varredura em: %s", PASTA)
|
|
106
|
+
|
|
107
|
+
# ======== SCAN DE ARQUIVOS ========
|
|
108
|
+
for entry in os.scandir(PASTA):
|
|
109
|
+
if not entry.is_file():
|
|
110
|
+
continue
|
|
111
|
+
|
|
112
|
+
nome = entry.name
|
|
113
|
+
caminho = entry.path
|
|
114
|
+
upper_name = nome.upper()
|
|
115
|
+
lower_name = nome.lower()
|
|
116
|
+
|
|
117
|
+
# Seleciona somente arquivos que contenham os padrões em CONTEM
|
|
118
|
+
if not any(p.upper() in upper_name for p in CONTEM):
|
|
119
|
+
ignorados_nome += 1
|
|
120
|
+
log("Ignorado (não contém padrões obrigatórios): %s", nome)
|
|
121
|
+
continue
|
|
122
|
+
|
|
123
|
+
# Considera apenas arquivos com extensões permitidas
|
|
124
|
+
if not any(lower_name.endswith(ext.lower()) for ext in EXT_PERMITIDAS):
|
|
125
|
+
nao_permitido += 1
|
|
126
|
+
continue
|
|
127
|
+
|
|
128
|
+
avaliados += 1
|
|
129
|
+
|
|
130
|
+
# Procura o texto alvo dentro do arquivo (case-insensitive)
|
|
131
|
+
alvo_norm = TEXTO_ALVO.casefold()
|
|
132
|
+
encontrado = False
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
with open(caminho, "r", encoding="latin1", errors="ignore") as f:
|
|
136
|
+
for linha in f:
|
|
137
|
+
if alvo_norm in linha.casefold():
|
|
138
|
+
encontrado = True
|
|
139
|
+
break
|
|
140
|
+
lidos += 1
|
|
141
|
+
except Exception as e:
|
|
142
|
+
erros += 1
|
|
143
|
+
log("[red]Erro lendo '%s': %s[/red]", nome, e)
|
|
144
|
+
continue
|
|
145
|
+
|
|
146
|
+
# Se encontrou o texto alvo, adiciona à lista de selecionados
|
|
147
|
+
if encontrado:
|
|
148
|
+
selecionados.append(
|
|
149
|
+
{
|
|
150
|
+
"arquivo": nome,
|
|
151
|
+
"caminho": caminho,
|
|
152
|
+
"tamanho_bytes": os.path.getsize(caminho),
|
|
153
|
+
}
|
|
154
|
+
)
|
|
155
|
+
log("[green]Selecionado[/green]: %s", caminho)
|
|
156
|
+
|
|
157
|
+
# ======== SAÍDA NO CONSOLE ========
|
|
158
|
+
log("======== RESULTADO DA VARREDURA ========")
|
|
159
|
+
log(
|
|
160
|
+
"Avaliados (ext permitidas): %s | Ignorados por nome: %s | Ignorados por extensão: %s | Lidos: %s | Erros leitura: %s",
|
|
161
|
+
avaliados,
|
|
162
|
+
ignorados_nome,
|
|
163
|
+
nao_permitido,
|
|
164
|
+
lidos,
|
|
165
|
+
erros,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# ======== VERIFICA RESULTADO ========
|
|
169
|
+
if not selecionados:
|
|
170
|
+
if ignorados_nome > 0:
|
|
171
|
+
msg = (
|
|
172
|
+
"Arquivos foram ignorados por nome conforme os padrões definidos: "
|
|
173
|
+
+ ", ".join(CONTEM)
|
|
174
|
+
)
|
|
175
|
+
else:
|
|
176
|
+
msg = (
|
|
177
|
+
f"Nenhum arquivo {EXT_STR} contendo '{TEXTO_ALVO}' encontrado."
|
|
178
|
+
)
|
|
179
|
+
log(f"[yellow]{msg}[/yellow]")
|
|
180
|
+
return RpaRetornoProcessoDTO(
|
|
181
|
+
sucesso=False,
|
|
182
|
+
retorno=msg,
|
|
183
|
+
status=RpaHistoricoStatusEnum.Sucesso,
|
|
184
|
+
tags=[RpaTagDTO(descricao=RpaTagEnum.Negocio)],
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
# ======== EMSys: LOGIN ========
|
|
188
|
+
log("Carregando credenciais com get_config_by_name('login_emsys')...")
|
|
189
|
+
config = await get_config_by_name("login_emsys")
|
|
190
|
+
log(
|
|
191
|
+
"[green]Credenciais carregadas[/green]. Tarefa recebida | empresa fixa = %s",
|
|
192
|
+
EMPRESA,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
log("Verificando instâncias abertas do EMSys (kill_all_emsys)...")
|
|
196
|
+
await kill_all_emsys()
|
|
197
|
+
log("Iniciando EMSys...")
|
|
198
|
+
app = Application(backend="win32").start("C:\\Rezende\\EMSys3\\EMSys3_22.exe")
|
|
199
|
+
warnings.filterwarnings(
|
|
200
|
+
"ignore",
|
|
201
|
+
category=UserWarning,
|
|
202
|
+
message="32-bit application should be automated using 32-bit Python",
|
|
203
|
+
)
|
|
204
|
+
log("[green]EMSys iniciando[/green]...")
|
|
205
|
+
|
|
206
|
+
log("Realizando login no EMSys...")
|
|
207
|
+
return_login = await login_emsys(
|
|
208
|
+
config.conConfiguracao, app, task, filial_origem=EMPRESA
|
|
209
|
+
)
|
|
210
|
+
if not return_login.sucesso:
|
|
211
|
+
logger.info(f"\nError Message: {return_login.retorno}")
|
|
212
|
+
log(f"[red]Erro no login[/red]: {return_login.retorno}")
|
|
213
|
+
return return_login
|
|
214
|
+
log("[green]Login realizado com sucesso[/green].")
|
|
215
|
+
|
|
216
|
+
# ======== ABRE MÓDULO CONCILIADOR ========
|
|
217
|
+
log("Abrindo módulo: 'Conciliador Bancario 2.0'")
|
|
218
|
+
try:
|
|
219
|
+
type_text_into_field(
|
|
220
|
+
"Conciliador Bancario 2.0",
|
|
221
|
+
app["TFrmMenuPrincipal"]["Edit"],
|
|
222
|
+
True,
|
|
223
|
+
"50",
|
|
224
|
+
)
|
|
225
|
+
pyautogui.press("enter")
|
|
226
|
+
await worker_sleep(1)
|
|
227
|
+
pyautogui.press("down", presses=2, interval=0.2)
|
|
228
|
+
await worker_sleep(0.5)
|
|
229
|
+
pyautogui.press("enter")
|
|
230
|
+
log("[green]Módulo acionado[/green]. Aguardando carregamento...")
|
|
231
|
+
await worker_sleep(3)
|
|
232
|
+
except Exception as e:
|
|
233
|
+
log("[red]Falha ao abrir o módulo[/red]: %s", e)
|
|
234
|
+
return RpaRetornoProcessoDTO(
|
|
235
|
+
sucesso=False,
|
|
236
|
+
retorno=f"Falha ao abrir módulo Conciliador Bancario 2.0: {e}",
|
|
237
|
+
status=RpaHistoricoStatusEnum.Falha,
|
|
238
|
+
tags=[RpaTagDTO(descricao=RpaTagEnum.Tecnico)],
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
# ==== IMPORTAÇÃO: para cada arquivo selecionado ====
|
|
242
|
+
importados = []
|
|
243
|
+
movidos = []
|
|
244
|
+
falhas = []
|
|
245
|
+
|
|
246
|
+
for idx, sel in enumerate(selecionados, 1):
|
|
247
|
+
log(
|
|
248
|
+
"[bold cyan]Iniciando importação (%s/%s)[/bold cyan]: %s",
|
|
249
|
+
idx,
|
|
250
|
+
len(selecionados),
|
|
251
|
+
sel["arquivo"],
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
# Flag para controlar se chegamos na parte do conciliar com sucesso
|
|
255
|
+
conciliacao_ok = False
|
|
256
|
+
|
|
257
|
+
# 1) Clicar no botão "Importar Arquivo"
|
|
258
|
+
log("Procurando botão 'Importar Arquivo' pela imagem: %s", BTN_IMPORTAR_IMG)
|
|
259
|
+
for t in range(20):
|
|
260
|
+
pos = pyautogui.locateCenterOnScreen(
|
|
261
|
+
BTN_IMPORTAR_IMG, confidence=0.9
|
|
262
|
+
)
|
|
263
|
+
if pos:
|
|
264
|
+
pyautogui.click(pos)
|
|
265
|
+
log("[green]Botão encontrado e clicado[/green].")
|
|
266
|
+
break
|
|
267
|
+
await worker_sleep(0.5)
|
|
268
|
+
else:
|
|
269
|
+
msg = "Imagem do botão 'Importar Arquivo' não encontrada na tela."
|
|
270
|
+
log(f"[red]{msg}[/red]")
|
|
271
|
+
falhas.append({"arquivo": sel["arquivo"], "motivo": msg})
|
|
272
|
+
continue
|
|
273
|
+
|
|
274
|
+
await worker_sleep(2)
|
|
275
|
+
|
|
276
|
+
# 2) Conectar na janela de importação
|
|
277
|
+
try:
|
|
278
|
+
log(
|
|
279
|
+
"Conectando na janela 'TFrmImportarArquivoConciliadorBancario2' ..."
|
|
280
|
+
)
|
|
281
|
+
app_imp = Application(backend="win32").connect(
|
|
282
|
+
class_name="TFrmImportarArquivoConciliadorBancario2", timeout=1200
|
|
283
|
+
)
|
|
284
|
+
main_window = app_imp["TFrmImportarArquivoConciliadorBancario2"]
|
|
285
|
+
main_window.set_focus()
|
|
286
|
+
main_window.child_window(
|
|
287
|
+
class_name="TWinControl", found_index=0
|
|
288
|
+
).click_input()
|
|
289
|
+
log("[green]Janela de importação focada[/green].")
|
|
290
|
+
await worker_sleep(3)
|
|
291
|
+
except Exception:
|
|
292
|
+
log(
|
|
293
|
+
"[yellow]Janela 'TFrmImportarArquivoConciliadorBancario2' não encontrada. Tentando seguir...[/yellow]"
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
# 3) Selecionar tipo de arquivo
|
|
297
|
+
log("Selecionando tipo de arquivo: digitando 'A' e [TAB]...")
|
|
298
|
+
try:
|
|
299
|
+
pyautogui.click(982, 632)
|
|
300
|
+
await worker_sleep(3)
|
|
301
|
+
pyautogui.write("A")
|
|
302
|
+
pyautogui.press("tab")
|
|
303
|
+
await worker_sleep(0.3)
|
|
304
|
+
log("[green]Tipo selecionado[/green].")
|
|
305
|
+
except Exception as e:
|
|
306
|
+
log(
|
|
307
|
+
"[yellow]Não foi possível interagir com o tipo de arquivo: %s[/yellow]",
|
|
308
|
+
e,
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
# 4) Capturar janela de diálogo de arquivo
|
|
312
|
+
log("Aguardando diálogo de arquivo (%s)...", DLG_TIT_RE)
|
|
313
|
+
dlg = None
|
|
314
|
+
for _ in range(30):
|
|
315
|
+
try:
|
|
316
|
+
app_dlg = Application().connect(title_re=DLG_TIT_RE)
|
|
317
|
+
dlg = app_dlg.window(title_re=DLG_TIT_RE)
|
|
318
|
+
if dlg.exists() and dlg.is_enabled():
|
|
319
|
+
break
|
|
320
|
+
except Exception:
|
|
321
|
+
pass
|
|
322
|
+
await worker_sleep(0.5)
|
|
323
|
+
|
|
324
|
+
if dlg is None or not dlg.exists():
|
|
325
|
+
msg = "Diálogo de arquivo (#32770) não apareceu."
|
|
326
|
+
log(f"[red]{msg}[/red]")
|
|
327
|
+
falhas.append({"arquivo": sel["arquivo"], "motivo": msg})
|
|
328
|
+
continue
|
|
329
|
+
|
|
330
|
+
log("[green]Diálogo de arquivo detectado[/green]. Preenchendo caminho...")
|
|
331
|
+
dlg.set_focus()
|
|
332
|
+
await worker_sleep(0.2)
|
|
333
|
+
|
|
334
|
+
# Campo Nome:Edit
|
|
335
|
+
try:
|
|
336
|
+
nome_edit = dlg.child_window(best_match="&Nome:Edit")
|
|
337
|
+
except Exception:
|
|
338
|
+
try:
|
|
339
|
+
nome_edit = dlg.child_window(class_name="Edit", found_index=0)
|
|
340
|
+
except Exception:
|
|
341
|
+
msg = "Campo de nome (Edit) não localizado no diálogo."
|
|
342
|
+
log(f"[red]{msg}[/red]")
|
|
343
|
+
falhas.append({"arquivo": sel["arquivo"], "motivo": msg})
|
|
344
|
+
continue
|
|
345
|
+
|
|
346
|
+
try:
|
|
347
|
+
nome_edit.click_input()
|
|
348
|
+
await worker_sleep(0.2)
|
|
349
|
+
send_keys("^a{BACKSPACE}")
|
|
350
|
+
send_keys(sel["caminho"])
|
|
351
|
+
await worker_sleep(0.2)
|
|
352
|
+
send_keys("{ENTER}")
|
|
353
|
+
log("Arquivo confirmado no diálogo: %s", sel["caminho"])
|
|
354
|
+
|
|
355
|
+
await worker_sleep(2)
|
|
356
|
+
pyautogui.click(1203, 509)
|
|
357
|
+
|
|
358
|
+
await worker_sleep(10)
|
|
359
|
+
|
|
360
|
+
# Aumenta a tolerância global
|
|
361
|
+
timings.after_clickinput_wait = 1
|
|
362
|
+
|
|
363
|
+
# 1) Aguarda a janela de mensagem (TMessageForm) após importação
|
|
364
|
+
try:
|
|
365
|
+
console.print(
|
|
366
|
+
"[yellow]Aguardando a janela de mensagem (TMessageForm) da importação...[/yellow]"
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
msg_form = Desktop(backend="win32").window(class_name="TMessageForm")
|
|
370
|
+
msg_form.wait("exists visible ready", timeout=300)
|
|
371
|
+
|
|
372
|
+
console.print(
|
|
373
|
+
"[green]✅ Janela TMessageForm de importação apareceu.[/green]"
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
try:
|
|
377
|
+
btn_ok = msg_form.child_window(title_re="OK|Ok|ok")
|
|
378
|
+
if btn_ok.exists():
|
|
379
|
+
btn_ok.click()
|
|
380
|
+
else:
|
|
381
|
+
msg_form.type_keys("{ENTER}")
|
|
382
|
+
except Exception:
|
|
383
|
+
pass
|
|
384
|
+
|
|
385
|
+
except Exception as e:
|
|
386
|
+
console.print(
|
|
387
|
+
f"[red]❌ Erro ao aguardar TMessageForm da importação: {e}[/red]"
|
|
388
|
+
)
|
|
389
|
+
falhas.append(
|
|
390
|
+
{
|
|
391
|
+
"arquivo": sel["arquivo"],
|
|
392
|
+
"motivo": f"Erro ao aguardar TMessageForm da importação: {e}",
|
|
393
|
+
}
|
|
394
|
+
)
|
|
395
|
+
continue
|
|
396
|
+
|
|
397
|
+
# 2) Agora conecta na janela do Conciliador
|
|
398
|
+
try:
|
|
399
|
+
console.print(
|
|
400
|
+
"[yellow]Conectando na janela do Conciliador Bancário...[/yellow]"
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
app_conc = Application(backend="win32").connect(
|
|
404
|
+
class_name="TFrmConciliadorBancario2",
|
|
405
|
+
timeout=60,
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
wnd = app_conc.window(class_name="TFrmConciliadorBancario2")
|
|
409
|
+
wnd.wait("visible enabled ready", timeout=60)
|
|
410
|
+
|
|
411
|
+
console.print(
|
|
412
|
+
"[green]✅ Janela do Conciliador carregada com sucesso![/green]"
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
except Exception as e:
|
|
416
|
+
console.print(
|
|
417
|
+
f"[red]❌ Erro ao conectar na janela do Conciliador: {e}[/red]"
|
|
418
|
+
)
|
|
419
|
+
falhas.append(
|
|
420
|
+
{
|
|
421
|
+
"arquivo": sel["arquivo"],
|
|
422
|
+
"motivo": f"Erro ao conectar na janela do Conciliador: {e}",
|
|
423
|
+
}
|
|
424
|
+
)
|
|
425
|
+
continue
|
|
426
|
+
|
|
427
|
+
# Agora sim, busca os campos numéricos
|
|
428
|
+
campos = wnd.descendants(class_name="TDBIEditNumber")
|
|
429
|
+
|
|
430
|
+
# 3) Valida quantidade de campos
|
|
431
|
+
if len(campos) < 5:
|
|
432
|
+
console.print(
|
|
433
|
+
"[red]Não existem campos suficientes (precisa de pelo menos 5).[/red]"
|
|
434
|
+
)
|
|
435
|
+
falhas.append(
|
|
436
|
+
{
|
|
437
|
+
"arquivo": sel["arquivo"],
|
|
438
|
+
"motivo": "Campos numéricos insuficientes no Conciliador.",
|
|
439
|
+
}
|
|
440
|
+
)
|
|
441
|
+
continue
|
|
442
|
+
else:
|
|
443
|
+
# ===== ESPERA CAMPOS ≠ 0,00 =====
|
|
444
|
+
TIMEOUT_CAMPOS = 30 * 60 # 30 minutos
|
|
445
|
+
inicio_espera = time.time()
|
|
446
|
+
valores_preenchidos = False
|
|
447
|
+
|
|
448
|
+
console.print(
|
|
449
|
+
"[cyan]Aguardando até que os campos 0 e 4 deixem de ser '0,00' ou vazios (timeout 30 min)...[/cyan]"
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
while True:
|
|
453
|
+
valor_0 = campos[0].window_text().strip()
|
|
454
|
+
valor_4 = campos[4].window_text().strip()
|
|
455
|
+
|
|
456
|
+
console.print(
|
|
457
|
+
f"[cyan]Leitura atual -> índice 0: '{valor_0}' | índice 4: '{valor_4}'[/cyan]"
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
cond_0 = valor_0 not in ("", "0,00", "0.00")
|
|
461
|
+
cond_4 = valor_4 not in ("", "0,00", "0.00")
|
|
462
|
+
|
|
463
|
+
if cond_0 and cond_4:
|
|
464
|
+
valores_preenchidos = True
|
|
465
|
+
console.print(
|
|
466
|
+
"[green]Campos preenchidos com valores diferentes de 0,00. Prosseguindo para comparação...[/green]"
|
|
467
|
+
)
|
|
468
|
+
break
|
|
469
|
+
|
|
470
|
+
if time.time() - inicio_espera > TIMEOUT_CAMPOS:
|
|
471
|
+
console.print(
|
|
472
|
+
"[red]Timeout de 30 minutos aguardando os campos saírem de 0,00.[/red]"
|
|
473
|
+
)
|
|
474
|
+
falhas.append(
|
|
475
|
+
{
|
|
476
|
+
"arquivo": sel["arquivo"],
|
|
477
|
+
"motivo": "Timeout aguardando campos numéricos saírem de 0,00.",
|
|
478
|
+
}
|
|
479
|
+
)
|
|
480
|
+
valores_preenchidos = False
|
|
481
|
+
break
|
|
482
|
+
|
|
483
|
+
await worker_sleep(5)
|
|
484
|
+
|
|
485
|
+
if not valores_preenchidos:
|
|
486
|
+
continue
|
|
487
|
+
|
|
488
|
+
console.print(f"[cyan]Valor índice 0 final:[/] {valor_0}")
|
|
489
|
+
console.print(f"[cyan]Valor índice 4 final:[/] {valor_4}")
|
|
490
|
+
|
|
491
|
+
if not (valor_0 == valor_4 and valor_0 not in ("", "0,00", "0.00")):
|
|
492
|
+
console.print(
|
|
493
|
+
"[yellow]Valores diferentes. Não será conciliado.[/yellow]"
|
|
494
|
+
)
|
|
495
|
+
falhas.append(
|
|
496
|
+
{
|
|
497
|
+
"arquivo": sel["arquivo"],
|
|
498
|
+
"motivo": f"Valores diferentes no Conciliador (0={valor_0}, 4={valor_4}).",
|
|
499
|
+
}
|
|
500
|
+
)
|
|
501
|
+
continue
|
|
502
|
+
|
|
503
|
+
console.print(
|
|
504
|
+
"[green]Valores iguais e válidos! Tentando clicar no botão 'Conciliar'...[/green]"
|
|
505
|
+
)
|
|
506
|
+
pos_conc = pyautogui.locateCenterOnScreen(
|
|
507
|
+
BTN_CONCILIAR, confidence=0.9
|
|
508
|
+
)
|
|
509
|
+
if not pos_conc:
|
|
510
|
+
console.print(
|
|
511
|
+
"[red]Botão 'Conciliar' não encontrado na tela.[/red]"
|
|
512
|
+
)
|
|
513
|
+
falhas.append(
|
|
514
|
+
{
|
|
515
|
+
"arquivo": sel["arquivo"],
|
|
516
|
+
"motivo": "Botão 'Conciliar' não localizado na tela.",
|
|
517
|
+
}
|
|
518
|
+
)
|
|
519
|
+
continue
|
|
520
|
+
|
|
521
|
+
pyautogui.click(pos_conc)
|
|
522
|
+
console.print(
|
|
523
|
+
"[green]Botão 'Conciliar' clicado com sucesso![/green]"
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
# ===== Aguardar imagem de sucesso + janela de confirmação =====
|
|
527
|
+
timeout = 1800 # 30 min
|
|
528
|
+
inicio = time.time()
|
|
529
|
+
|
|
530
|
+
console.print(
|
|
531
|
+
"[cyan]Aguardando imagem de sucesso aparecer e janela de confirmação da conciliação...[/cyan]"
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
pos_sucesso = None
|
|
535
|
+
janela_ok_encontrada = False
|
|
536
|
+
|
|
537
|
+
while time.time() - inicio < timeout:
|
|
538
|
+
# 1) Verifica imagem
|
|
539
|
+
pos_sucesso = pyautogui.locateCenterOnScreen(
|
|
540
|
+
IMAGEM_SUCESSO, confidence=0.85
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
if pos_sucesso:
|
|
544
|
+
console.print(
|
|
545
|
+
"[green]Imagem 'conciliados_sucesso' encontrada na tela.[/green]"
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
# 2) Agora aguarda a TMessageForm / Information
|
|
549
|
+
try:
|
|
550
|
+
console.print(
|
|
551
|
+
"[yellow]Aguardando janela 'Movimentos selecionados conciliados com sucesso!'...[/yellow]"
|
|
552
|
+
)
|
|
553
|
+
msg_ok = Desktop(backend="win32").window(
|
|
554
|
+
class_name="TMessageForm"
|
|
555
|
+
)
|
|
556
|
+
msg_ok.wait("exists visible ready", timeout=60)
|
|
557
|
+
|
|
558
|
+
console.print(
|
|
559
|
+
"[green]✅ Janela de confirmação da conciliação encontrada.[/green]"
|
|
560
|
+
)
|
|
561
|
+
|
|
562
|
+
try:
|
|
563
|
+
btn_ok = msg_ok.child_window(title_re="OK|Ok|ok")
|
|
564
|
+
if btn_ok.exists():
|
|
565
|
+
btn_ok.click()
|
|
566
|
+
else:
|
|
567
|
+
msg_ok.type_keys("{ENTER}")
|
|
568
|
+
except Exception:
|
|
569
|
+
# se der algo errado no botão, pelo menos a janela existe
|
|
570
|
+
msg_ok.type_keys("{ENTER}")
|
|
571
|
+
|
|
572
|
+
janela_ok_encontrada = True
|
|
573
|
+
except Exception as e:
|
|
574
|
+
console.print(
|
|
575
|
+
f"[red]❌ Não foi possível encontrar/confirmar a janela de conciliação: {e}[/red]"
|
|
576
|
+
)
|
|
577
|
+
janela_ok_encontrada = False
|
|
578
|
+
|
|
579
|
+
# Sai do while (já encontrou imagem, independente da janela ter dado certo ou não)
|
|
580
|
+
break
|
|
581
|
+
|
|
582
|
+
await worker_sleep(1)
|
|
583
|
+
|
|
584
|
+
# Se a imagem nunca apareceu, falha
|
|
585
|
+
if not pos_sucesso:
|
|
586
|
+
console.print(
|
|
587
|
+
"[red]Imagem 'conciliados_sucesso.png' NÃO apareceu dentro do tempo limite.[/red]"
|
|
588
|
+
)
|
|
589
|
+
falhas.append(
|
|
590
|
+
{
|
|
591
|
+
"arquivo": sel["arquivo"],
|
|
592
|
+
"motivo": "Imagem de sucesso da conciliação não apareceu.",
|
|
593
|
+
}
|
|
594
|
+
)
|
|
595
|
+
continue
|
|
596
|
+
|
|
597
|
+
# Se a janela não foi confirmada, considerar falha também
|
|
598
|
+
if not janela_ok_encontrada:
|
|
599
|
+
msg = "Janela de confirmação da conciliação não foi encontrada/confirmada."
|
|
600
|
+
console.print(f"[red]{msg}[/red]")
|
|
601
|
+
falhas.append({"arquivo": sel["arquivo"], "motivo": msg})
|
|
602
|
+
continue
|
|
603
|
+
|
|
604
|
+
# Só aqui marca como conciliado com sucesso
|
|
605
|
+
conciliacao_ok = True
|
|
606
|
+
|
|
607
|
+
# tenta fechar janela de erro, se existir
|
|
608
|
+
try:
|
|
609
|
+
app_err = Application(backend="win32").connect(
|
|
610
|
+
title="Erro", found_index=0
|
|
611
|
+
)
|
|
612
|
+
main_err = app_err["Erro"]
|
|
613
|
+
log("[yellow]Janela 'Erro' detectada. Fechando...[/yellow]")
|
|
614
|
+
main_err.close()
|
|
615
|
+
await worker_sleep(1)
|
|
616
|
+
except Exception:
|
|
617
|
+
pass
|
|
618
|
+
|
|
619
|
+
# ============ CHECAGEM: SÓ MOVE SE CONCILIAR OK ============
|
|
620
|
+
if not conciliacao_ok:
|
|
621
|
+
msg = (
|
|
622
|
+
"Conciliador não foi executado/confirmado com sucesso para o arquivo."
|
|
623
|
+
)
|
|
624
|
+
log(f"[red]{msg}[/red]")
|
|
625
|
+
falhas.append({"arquivo": sel["arquivo"], "motivo": msg})
|
|
626
|
+
continue
|
|
627
|
+
|
|
628
|
+
# ============ MOVER ARQUIVO ============
|
|
629
|
+
origem = sel["caminho"]
|
|
630
|
+
arquivo = sel["arquivo"]
|
|
631
|
+
|
|
632
|
+
try:
|
|
633
|
+
# 1) Tenta mover para o destino padrão (DESTINO_BASE - Z:)
|
|
634
|
+
os.makedirs(DESTINO_BASE, exist_ok=True)
|
|
635
|
+
destino_arquivo = os.path.join(DESTINO_BASE, arquivo)
|
|
636
|
+
|
|
637
|
+
# Sobrescrita segura se já existir
|
|
638
|
+
if os.path.exists(destino_arquivo):
|
|
639
|
+
try:
|
|
640
|
+
os.remove(destino_arquivo)
|
|
641
|
+
except Exception as e_rm:
|
|
642
|
+
log(
|
|
643
|
+
"[yellow]Aviso[/yellow]: não foi possível remover no destino: %s (%s)",
|
|
644
|
+
destino_arquivo,
|
|
645
|
+
e_rm,
|
|
646
|
+
)
|
|
647
|
+
|
|
648
|
+
shutil.move(origem, destino_arquivo)
|
|
649
|
+
|
|
650
|
+
# >>> registrar também quando move pelo Z: <<<
|
|
651
|
+
movidos.append(destino_arquivo)
|
|
652
|
+
importados.append(arquivo)
|
|
653
|
+
|
|
654
|
+
# LOG DE VALIDAÇÃO
|
|
655
|
+
exist_dest = os.path.exists(destino_arquivo)
|
|
656
|
+
try:
|
|
657
|
+
lista = os.listdir(os.path.dirname(destino_arquivo))
|
|
658
|
+
except Exception as e:
|
|
659
|
+
lista = [f"<<erro ao listar pasta: {e}>>"]
|
|
660
|
+
|
|
661
|
+
log(
|
|
662
|
+
"[green]Arquivo movido[/green]: %s -> %s (existe_destino=%s)",
|
|
663
|
+
origem,
|
|
664
|
+
destino_arquivo,
|
|
665
|
+
exist_dest,
|
|
666
|
+
)
|
|
667
|
+
log("Conteúdo da pasta destino após o move: %s", lista)
|
|
668
|
+
|
|
669
|
+
except Exception as e1:
|
|
670
|
+
# 2) Fallback via UNC (compartilhadas$ -> Nexera\{ano})
|
|
671
|
+
try:
|
|
672
|
+
usuario = user_folder_cfg.get("usuario")
|
|
673
|
+
senha = user_folder_cfg.get("senha")
|
|
674
|
+
|
|
675
|
+
if win32wnet is None:
|
|
676
|
+
raise RuntimeError(
|
|
677
|
+
"pywin32 não disponível para mapear caminho de rede (win32wnet)."
|
|
678
|
+
)
|
|
679
|
+
|
|
680
|
+
log(
|
|
681
|
+
"Falha ao mover para Z:. Tentando fallback via UNC em %s ...",
|
|
682
|
+
DESTINO_IP_ROOT,
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
try:
|
|
686
|
+
win32wnet.WNetAddConnection2(
|
|
687
|
+
0,
|
|
688
|
+
None,
|
|
689
|
+
DESTINO_IP_ROOT,
|
|
690
|
+
None,
|
|
691
|
+
usuario,
|
|
692
|
+
senha,
|
|
693
|
+
)
|
|
694
|
+
except Exception as e_conn:
|
|
695
|
+
# Se der 1219 (conexão já existente), ignoramos
|
|
696
|
+
if getattr(e_conn, "winerror", None) != 1219:
|
|
697
|
+
raise
|
|
698
|
+
|
|
699
|
+
caminho_ip = DESTINO_BASE_IP
|
|
700
|
+
os.makedirs(caminho_ip, exist_ok=True)
|
|
701
|
+
|
|
702
|
+
if os.path.exists(origem):
|
|
703
|
+
destino_arquivo_ip = os.path.join(caminho_ip, arquivo)
|
|
704
|
+
|
|
705
|
+
if os.path.exists(destino_arquivo_ip):
|
|
706
|
+
try:
|
|
707
|
+
os.remove(destino_arquivo_ip)
|
|
708
|
+
except Exception as e_rm_ip:
|
|
709
|
+
log(
|
|
710
|
+
"[yellow]Aviso[/yellow]: não foi possível remover no destino IP: %s (%s)",
|
|
711
|
+
destino_arquivo_ip,
|
|
712
|
+
e_rm_ip,
|
|
713
|
+
)
|
|
714
|
+
|
|
715
|
+
shutil.move(origem, destino_arquivo_ip)
|
|
716
|
+
movidos.append(destino_arquivo_ip)
|
|
717
|
+
importados.append(arquivo)
|
|
718
|
+
log(
|
|
719
|
+
"[green]Arquivo movido (via IP)[/green]: %s -> %s",
|
|
720
|
+
origem,
|
|
721
|
+
destino_arquivo_ip,
|
|
722
|
+
)
|
|
723
|
+
else:
|
|
724
|
+
msg = (
|
|
725
|
+
f"Erro ao mover (via IP): origem não encontrada: {origem} | "
|
|
726
|
+
f"destino: {caminho_ip}"
|
|
727
|
+
)
|
|
728
|
+
log(f"[red]{msg}[/red]")
|
|
729
|
+
falhas.append({"arquivo": arquivo, "motivo": msg})
|
|
730
|
+
continue
|
|
731
|
+
|
|
732
|
+
except Exception as e2:
|
|
733
|
+
msg = f"Falha ao mover '{arquivo}' (fallback IP): {e2}"
|
|
734
|
+
log(f"[red]{msg}[/red]")
|
|
735
|
+
falhas.append({"arquivo": arquivo, "motivo": msg})
|
|
736
|
+
continue
|
|
737
|
+
|
|
738
|
+
except Exception as e:
|
|
739
|
+
msg = f"Falha geral no processamento do arquivo: {e}"
|
|
740
|
+
log(f"[red]{msg}[/red]")
|
|
741
|
+
falhas.append({"arquivo": sel["arquivo"], "motivo": msg})
|
|
742
|
+
continue
|
|
743
|
+
|
|
744
|
+
await worker_sleep(2)
|
|
745
|
+
|
|
746
|
+
# ======== RESUMO FINAL ========
|
|
747
|
+
log("======== RESUMO FINAL ========")
|
|
748
|
+
log("Importados (OK): %s", len(importados))
|
|
749
|
+
for n in importados:
|
|
750
|
+
log(" • %s", n)
|
|
751
|
+
if falhas:
|
|
752
|
+
log("[yellow]Falhas (%s):[/yellow]", len(falhas))
|
|
753
|
+
for f in falhas:
|
|
754
|
+
log(" • %s -> %s", f.get("arquivo"), f.get("motivo"))
|
|
755
|
+
|
|
756
|
+
# Se algum arquivo chegou até a conciliação/movimentação, sucesso
|
|
757
|
+
if importados:
|
|
758
|
+
retorno_payload = {
|
|
759
|
+
"empresa": EMPRESA,
|
|
760
|
+
"importados_count": len(importados),
|
|
761
|
+
"importados": importados,
|
|
762
|
+
"movidos_destino": movidos,
|
|
763
|
+
"falhas": falhas,
|
|
764
|
+
}
|
|
765
|
+
retorno_str = json.dumps(retorno_payload, ensure_ascii=False, indent=2)
|
|
766
|
+
return RpaRetornoProcessoDTO(
|
|
767
|
+
sucesso=True,
|
|
768
|
+
retorno=retorno_str,
|
|
769
|
+
status=RpaHistoricoStatusEnum.Sucesso,
|
|
770
|
+
tags=[RpaTagDTO(descricao=RpaTagEnum.Tecnico)],
|
|
771
|
+
)
|
|
772
|
+
else:
|
|
773
|
+
retorno_payload = {
|
|
774
|
+
"empresa": EMPRESA,
|
|
775
|
+
"importados_count": 0,
|
|
776
|
+
"falhas": falhas,
|
|
777
|
+
"mensagem": (
|
|
778
|
+
f"Nenhum arquivo foi conciliado/movido com sucesso. "
|
|
779
|
+
f"Verificar falhas detalhadas. (extensões permitidas: {EXT_STR})."
|
|
780
|
+
),
|
|
781
|
+
}
|
|
782
|
+
retorno_str = json.dumps(retorno_payload, ensure_ascii=False, indent=2)
|
|
783
|
+
return RpaRetornoProcessoDTO(
|
|
784
|
+
sucesso=False,
|
|
785
|
+
retorno=retorno_str,
|
|
786
|
+
status=RpaHistoricoStatusEnum.Falha,
|
|
787
|
+
tags=[RpaTagDTO(descricao=RpaTagEnum.Negocio)],
|
|
788
|
+
)
|
|
789
|
+
|
|
790
|
+
except Exception as ex:
|
|
791
|
+
log("[red]Exceção geral[/red]: %s", ex)
|
|
792
|
+
log(ex)
|
|
793
|
+
log("Traceback pode ser consultado no logger caso configurado.")
|
|
794
|
+
return RpaRetornoProcessoDTO(
|
|
795
|
+
sucesso=False,
|
|
796
|
+
retorno=f"Error: {ex}",
|
|
797
|
+
status=RpaHistoricoStatusEnum.Falha,
|
|
798
|
+
tags=[RpaTagDTO(descricao=RpaTagEnum.Negocio)],
|
|
799
|
+
)
|
|
800
|
+
|