worker-automate-hub 0.5.828__py3-none-any.whl → 0.5.830__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.
@@ -2,12 +2,13 @@ from decimal import ROUND_HALF_UP, Decimal
2
2
  import threading
3
3
  from typing import Optional
4
4
  import aiohttp
5
-
5
+ import re
6
+ from collections import defaultdict
6
7
  import aiohttp
7
8
  import requests
8
9
  from aiohttp import ClientSession
9
10
  from rich.console import Console
10
-
11
+ from typing import List, Dict, Any
11
12
  from worker_automate_hub.api.helpers.api_helpers import handle_api_response
12
13
  from worker_automate_hub.config.settings import load_env_config
13
14
  from worker_automate_hub.models.dao.rpa_configuracao import RpaConfiguracao
@@ -498,6 +499,89 @@ async def get_valor_remessa_cobranca(date: str):
498
499
  logger.info(err_msg)
499
500
 
500
501
 
502
+ async def get_notas_produtos(codFornecedor: int, codEmpresa: int, itens: List[Dict[str, Any]]) -> Dict[str, Any]:
503
+ """
504
+ Função única:
505
+ - Converte itens (aceita chaves codigo/codigoProduto e quantidade/qtd)
506
+ - Chama /nf-supplier/checker com Authorization: Basic
507
+ - Normaliza tipos do retorno
508
+ - Entrega:
509
+ {
510
+ "lista": [ {codItem, qtdTotal, notas, valorUnitario}, ... ],
511
+ "por_codigo": { codItem: {...}, ... }
512
+ }
513
+
514
+ Retorno esperado da API (ex):
515
+ [
516
+ {"codItem": 19969, "qtdTotal": 15, "notas": ["1418727","1410744"], "valorUnitario": 5.29},
517
+ {"codItem": 29272, "qtdTotal": 10, "notas": ["1418727"], "valorUnitario": 7.12}
518
+ ]
519
+ """
520
+ # --- Carrega config
521
+ env_config, _ = load_env_config()
522
+ url_base = env_config["API_BASE_URL"].rstrip("/")
523
+ url = f"{url_base}/nf-supplier/checker"
524
+
525
+ # --- Header Basic (aceita token puro ou já "Basic ...")
526
+ token = (env_config.get("API_AUTHORIZATION") or "").strip()
527
+ auth_header = token if token.lower().startswith("basic ") else f"Basic {token}"
528
+
529
+ # --- Converte itens de entrada
530
+ itens_convertidos: List[Dict[str, int]] = []
531
+ for it in itens or []:
532
+ codigo = re.findall(r"\d+", it.get("descricaoProduto"))[0]
533
+ quantidade = it.get("quantidade", it.get("qtd"))
534
+ if codigo is None or quantidade is None:
535
+ logger.warning(f"Item incompleto: {it}")
536
+ console.print(f"⚠️ Item incompleto: {it}", style="yellow")
537
+ continue
538
+ try:
539
+ itens_convertidos.append({"codigo": int(codigo), "quantidade": int(quantidade)})
540
+ except Exception:
541
+ logger.warning(f"Item inválido (não numérico): {it}")
542
+ console.print(f"⚠️ Item inválido (não numérico): {it}", style="yellow")
543
+
544
+ body = {
545
+ "codFornecedor": int(codFornecedor),
546
+ "codEmpresa": int(codEmpresa),
547
+ "itens": itens_convertidos,
548
+ }
549
+ headers = {"Authorization": auth_header, "Content-Type": "application/json"}
550
+
551
+ # --- Chamada HTTP
552
+ async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(verify_ssl=False)) as session:
553
+ async with session.post(url, json=body, headers=headers) as resp:
554
+ text = await resp.text()
555
+ if resp.status != 200:
556
+ raise RuntimeError(f"HTTP {resp.status} ao chamar {url}: {text}")
557
+ try:
558
+ data = await resp.json()
559
+ except Exception:
560
+ raise RuntimeError(f"Resposta não-JSON do servidor: {text[:600]}")
561
+
562
+ console.print(f"✅ Resposta da API: {data}", style="bold green")
563
+ logger.info(f"nf-supplier/checker -> {data}")
564
+
565
+ if not isinstance(data, list):
566
+ raise ValueError(f"Formato inesperado da API: {type(data)} -> {data}")
567
+
568
+ # --- Normaliza tipos e monta índices
569
+ lista_norm: List[Dict[str, Any]] = []
570
+ por_codigo: Dict[int, Dict[str, Any]] = {}
571
+
572
+ for row in data:
573
+ cod = int(row.get("codItem"))
574
+ item_norm = {
575
+ "codItem": cod,
576
+ "qtdTotal": int(row.get("qtdTotal", 0)),
577
+ "notas": [str(n) for n in (row.get("notas") or [])],
578
+ "valorUnitario": float(row.get("valorUnitario", 0.0)),
579
+ }
580
+ lista_norm.append(item_norm)
581
+ por_codigo[cod] = item_norm
582
+
583
+ return {"lista": lista_norm, "por_codigo": por_codigo}
584
+
501
585
  async def get_status_nf_emsys(chave: int):
502
586
  """
503
587
  Procura o status de nota fiscal no EMSYS.
@@ -0,0 +1,1102 @@
1
+ import asyncio
2
+ import warnings
3
+ from datetime import datetime
4
+ import json
5
+ import io
6
+ import pyautogui
7
+ from pywinauto.application import Application
8
+ from pywinauto import keyboard
9
+ from pywinauto import Desktop
10
+ from collections import defaultdict
11
+ from rich.console import Console
12
+ import getpass
13
+ import time
14
+ import re
15
+ import sys
16
+ import os
17
+
18
+ sys.path.append(
19
+ os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))
20
+ )
21
+
22
+ from worker_automate_hub.api.client import (
23
+ get_config_by_name,
24
+ send_file,
25
+ get_notas_produtos,
26
+ )
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_processo_entrada_dto import (
34
+ RpaProcessoEntradaDTO,
35
+ )
36
+ from worker_automate_hub.utils.logger import logger
37
+ from pywinauto.keyboard import send_keys
38
+
39
+ # from worker_automate_hub.utils.toast import show_toast
40
+ from worker_automate_hub.utils.util import (
41
+ send_to_webhook,
42
+ extract_nf_number, # permanece importado, mesmo sem uso
43
+ faturar_pre_venda,
44
+ find_element_center,
45
+ find_target_position,
46
+ kill_all_emsys,
47
+ login_emsys,
48
+ set_variable,
49
+ take_screenshot,
50
+ take_target_position,
51
+ type_text_into_field,
52
+ wait_nf_ready,
53
+ wait_window_close,
54
+ worker_sleep,
55
+ )
56
+
57
+ console = Console()
58
+
59
+ ASSETS_BASE_PATH = "assets/descartes_transferencias_images/"
60
+ ALMOXARIFADO_DEFAULT = "50"
61
+
62
+
63
+ def _parse_int_tolerante(val):
64
+ if val is None:
65
+ return 0
66
+ if isinstance(val, int):
67
+ return val
68
+ s = str(val).replace("\u00a0", "").strip() # remove NBSP
69
+ s = s.replace(".", "").replace(",", "") # remove separadores comuns
70
+ return int(float(s or 0))
71
+
72
+
73
+ def _coletar_itens_achatados(d: dict):
74
+ descrs, qtds = {}, {}
75
+ for k, v in d.items():
76
+ m = re.match(r"^descricaoProduto(\d+)$", k, flags=re.I)
77
+ if m:
78
+ descrs[m.group(1)] = v
79
+ continue
80
+ m = re.match(r"^qtd(\d+)$", k, flags=re.I)
81
+ if m:
82
+ qtds[m.group(1)] = v
83
+ continue
84
+
85
+ itens = []
86
+ idxs = sorted(set(descrs.keys()) | set(qtds.keys()), key=lambda x: int(x))
87
+ for n in idxs:
88
+ desc = (descrs.get(n) or "").strip()
89
+ if not desc:
90
+ continue
91
+ qtd = _parse_int_tolerante(qtds.get(n, 0))
92
+ if qtd <= 0:
93
+ continue
94
+ itens.append({"descricaoProduto": desc, "qtd": qtd})
95
+ return itens
96
+
97
+
98
+ def normalize_config_entrada(cfg: dict) -> dict:
99
+ """
100
+ Se vier com 'itens': mantém e normaliza qtd -> int.
101
+ Se vier como descricaoProdutoN/qtdN: converte para 'itens'.
102
+ Preserva demais campos.
103
+ """
104
+ cfg = dict(cfg or {})
105
+ if isinstance(cfg.get("itens"), list):
106
+ itens_norm = []
107
+ for it in cfg["itens"]:
108
+ if not isinstance(it, dict):
109
+ continue
110
+ desc = str(it.get("descricaoProduto", "")).strip()
111
+ if not desc:
112
+ continue
113
+ qtd = _parse_int_tolerante(it.get("qtd", it.get("quantidade", 0)))
114
+ if qtd <= 0:
115
+ continue
116
+ itens_norm.append({"descricaoProduto": desc, "qtd": qtd})
117
+ cfg["itens"] = itens_norm
118
+ return cfg
119
+
120
+ # formato achatado
121
+ itens = _coletar_itens_achatados(cfg)
122
+ cfg["itens"] = itens
123
+ # remove chaves achatadas da saída (opcional)
124
+ chaves_remover = [
125
+ k for k in cfg.keys() if re.match(r"^(descricaoProduto|qtd)\d+$", k, flags=re.I)
126
+ ]
127
+ for k in chaves_remover:
128
+ cfg.pop(k, None)
129
+ return cfg
130
+
131
+
132
+ # --- fim do normalizador ---
133
+
134
+
135
+ async def devolucao_produtos(task: RpaProcessoEntradaDTO) -> RpaRetornoProcessoDTO:
136
+ try:
137
+ # Get config from BOF
138
+ config = await get_config_by_name("login_emsys")
139
+ console.print(task)
140
+
141
+ # Seta config entrada na var nota para melhor entendimento (normalizando formatos)
142
+ nota = normalize_config_entrada(task.configEntrada)
143
+ itens = nota.get("itens", [])
144
+
145
+ descricao_filial = task.configEntrada.get("descricaoFilial", "")
146
+ empresa = descricao_filial.split(" - ")[0]
147
+ estado = nota.get("estado", "")
148
+ descricao_fornecedor = nota.get("descricaoFornecedor", "")
149
+ historico_id = task.historico_id
150
+ cod_fornecedor = descricao_fornecedor.split(" - ")[0]
151
+ identificador = nota.get("identificador", "")
152
+ url_retorno = nota.get("urlRetorno", "")
153
+ multiplicador_timeout = int(float(task.sistemas[0].timeout))
154
+ set_variable("timeout_multiplicador", multiplicador_timeout)
155
+
156
+ # ========= CHAMADA AO ENDPOINT E MONTAGEM DO DATA =========
157
+ # res deve retornar {"lista": [...], "por_codigo": {cod: {...}}}
158
+ res = await get_notas_produtos(int(cod_fornecedor), int(empresa), itens)
159
+ by_code = res.get("por_codigo", {}) or {} # dict dinâmico por código
160
+
161
+ # Constrói os itens na estrutura usada no fluxo de UI
162
+ itens_ui = []
163
+ notas_encontradas = [] # acumula notas para deduplicar depois
164
+
165
+ for it in itens:
166
+ # aceita "codigoProduto"/"quantidade" e "codigo"/"qtd"
167
+ desc = it.get("descricaoProduto", "") or ""
168
+ nums = re.findall(r"\d+", desc)
169
+ if not nums:
170
+ # fallback: tenta campo "codigo" direto (se existir)
171
+ cod_raw = it.get("codigo") or it.get("codigoProduto")
172
+ if cod_raw is None:
173
+ continue
174
+ try:
175
+ cod = int(re.findall(r"\d+", str(cod_raw))[0])
176
+ except Exception:
177
+ continue
178
+ else:
179
+ cod = int(nums[0])
180
+
181
+ # quantidade (como int)
182
+ qtd_raw = it.get("quantidade", it.get("qtd"))
183
+ try:
184
+ qtd = int(qtd_raw)
185
+ if qtd <= 0:
186
+ continue
187
+ except (TypeError, ValueError):
188
+ continue
189
+
190
+ info = by_code.get(cod) or {}
191
+ valor_unit = float(info.get("valorUnitario", 0) or 0)
192
+
193
+ # Normaliza "notas" para lista de strings
194
+ notas_item = info.get("notas") or []
195
+ if isinstance(notas_item, (str, int)):
196
+ notas_item = [notas_item]
197
+ notas_item = [str(n) for n in notas_item]
198
+
199
+ # Acumula para a lista geral (será deduplicada depois)
200
+ notas_encontradas.extend(notas_item)
201
+
202
+ itens_ui.append(
203
+ {
204
+ "codigo": cod,
205
+ "quantidade": qtd,
206
+ "valor_unitario": valor_unit,
207
+ "valor_total_item": round(valor_unit * qtd, 2),
208
+ "notas": notas_item, # mantém vínculo item↔notas
209
+ }
210
+ )
211
+
212
+ # Deduplica notas preservando a ordem de aparição
213
+ nf_ref = list(dict.fromkeys(notas_encontradas))
214
+
215
+ # Índice opcional: itens por cada nota (facilita inclusão na UI)
216
+ itens_por_nota = defaultdict(list)
217
+ for item in itens_ui:
218
+ for n in item["notas"]:
219
+ itens_por_nota[n].append(item)
220
+
221
+ data = {
222
+ "nf_referencia": nf_ref, # ex.: ['1418727', '1410744']
223
+ "itens": itens_ui, # cada item com suas notas
224
+ "totais": {
225
+ "valor_final": round(sum(i["valor_total_item"] for i in itens_ui), 2)
226
+ },
227
+ "itens_por_nota": itens_por_nota, # para uso direto no fluxo de UI
228
+ }
229
+ # ========= FIM DA MONTAGEM DO DATA =========
230
+
231
+ # Fecha a instancia do emsys - caso esteja aberta
232
+ await kill_all_emsys()
233
+
234
+ app = Application(backend="win32").start("C:\\Rezende\\EMSys3\\EMSys3_10.exe")
235
+ warnings.filterwarnings(
236
+ "ignore",
237
+ category=UserWarning,
238
+ message="32-bit application should be automated using 32-bit Python",
239
+ )
240
+ console.print("\nEMSys iniciando...", style="bold green")
241
+ return_login = await login_emsys(config.conConfiguracao, app, task)
242
+
243
+ if return_login.sucesso == True:
244
+ console.print("Pesquisando por: Cadastro Pré Venda")
245
+ type_text_into_field(
246
+ "Cadastro Pre-Venda", app["TFrmMenuPrincipal"]["Edit"], True, "50"
247
+ )
248
+ pyautogui.press("enter")
249
+ await worker_sleep(1)
250
+ pyautogui.press("enter")
251
+ console.print(
252
+ f"\nPesquisa: 'Cadastro Pre Venda' realizada com sucesso",
253
+ style="bold green",
254
+ )
255
+ await worker_sleep(3)
256
+ try:
257
+ app = Application().connect(class_name="TFrmSelecionaTipoPreVenda")
258
+ select_prevenda_type = app["Selecione o Tipo de Pré-Venda"]
259
+
260
+ if select_prevenda_type.exists():
261
+ tipo = select_prevenda_type.child_window(
262
+ class_name="TComboBox", found_index=0
263
+ )
264
+ tipo.select("Orçamento")
265
+ confirm = select_prevenda_type.child_window(
266
+ class_name="TDBIBitBtn", found_index=1
267
+ )
268
+ confirm.click()
269
+ except:
270
+ console.print(
271
+ "Sem tela de selecionar modelo de pre venda", style="bold green"
272
+ )
273
+ else:
274
+ logger.info(f"\nError Message: {return_login.retorno}")
275
+ console.print(f"\nError Message: {return_login.retorno}", style="bold red")
276
+ return return_login
277
+
278
+ await worker_sleep(7)
279
+
280
+ # Condição da Pré-Venda
281
+ console.print("Selecionando a Condição da Pré-Venda\n")
282
+ app = Application().connect(class_name="TFrmPreVenda")
283
+ main_window = app["TFrmPreVenda"]
284
+
285
+ condicao_field = main_window.child_window(
286
+ class_name="TDBIComboBox", found_index=2
287
+ )
288
+ condicao_field.select("21 DIAS")
289
+
290
+ # Código do fornecedor
291
+ input_cod_fornecedor = main_window.child_window(
292
+ class_name="TDBIEditNumber", found_index=2
293
+ )
294
+ input_cod_fornecedor.click_input()
295
+ await worker_sleep(0.2)
296
+ keyboard.send_keys("{END}+{HOME}{DEL}")
297
+ await worker_sleep(0.2)
298
+ input_cod_fornecedor.type_keys(
299
+ str(cod_fornecedor), with_spaces=True, set_foreground=True
300
+ )
301
+ keyboard.send_keys("{TAB}")
302
+ await worker_sleep(5)
303
+
304
+ # Popups
305
+ try:
306
+ app = Application().connect(class_name="TFrmSelecionaEndereco")
307
+ app["TFrmSelecionaEndereco"].close()
308
+ await worker_sleep(3)
309
+ app = Application().connect(class_name="TMessageForm")
310
+ app["TMessageForm"].child_window(
311
+ class_name="TButton", found_index=0
312
+ ).click()
313
+ except:
314
+ pass
315
+
316
+ app = Application().connect(class_name="TFrmPreVenda")
317
+ main_window = app["TFrmPreVenda"]
318
+ console.print("Verificar estado...")
319
+ if estado != "RS":
320
+ modelo = "DEVOLUÇÃO DE COMPRA DE MERCADORIAS SC"
321
+ else:
322
+ modelo = "DEVOLUCAO DE COMPRA DE MERCADORIAS - TRIBUTADO"
323
+
324
+ # Inserir modelo
325
+ console.print("Inserir modelo...")
326
+ select_modelo = main_window.child_window(
327
+ class_name="TDBIComboBox", found_index=0
328
+ )
329
+ select_modelo.select(modelo)
330
+ await worker_sleep(1)
331
+
332
+ # Abrir guia de itens (por imagem)
333
+ imagem_item = r"C:\Users\automatehub\Desktop\img_leo\itens.png"
334
+ for _ in range(10):
335
+ pos = pyautogui.locateCenterOnScreen(imagem_item, confidence=0.9)
336
+ if pos:
337
+ pyautogui.click(pos)
338
+ break
339
+ await worker_sleep(1)
340
+ else:
341
+ print("Imagem do item não encontrada na tela.")
342
+ await worker_sleep(2)
343
+
344
+ # Incluir item
345
+ botao_incluir = main_window.child_window(
346
+ title="Incluir", class_name="TDBIBitBtn"
347
+ ).wrapper_object()
348
+ botao_incluir.click_input()
349
+ await worker_sleep(5)
350
+
351
+ # =============== PREPARO DE DADOS ===============
352
+ almoxarifado = f"{descricao_filial}50"
353
+
354
+ # =============== ALMOXARIFADO ===============
355
+ console.print("Inserir Almoxarifado...")
356
+ # Abre tela de inclusão de item
357
+ app_item = Application().connect(class_name="TFrmIncluiItemPreVenda")
358
+ wnd_item = app_item["TFrmIncluiItemPreVenda"]
359
+ input_almoxarifado = wnd_item.child_window(
360
+ class_name="TDBIEditNumber", found_index=16
361
+ )
362
+ input_almoxarifado.click_input()
363
+ await worker_sleep(1)
364
+ keyboard.send_keys("{END}+{HOME}{DEL}")
365
+ await worker_sleep(1)
366
+ input_almoxarifado.type_keys(
367
+ almoxarifado, with_spaces=True, set_foreground=True
368
+ )
369
+ keyboard.send_keys("{TAB}")
370
+ await worker_sleep(1)
371
+
372
+ # Dicionário para guardar os itens sem saldo / com saldo
373
+ itens_sem_saldo = {}
374
+ itens_com_saldo = {}
375
+
376
+ # >>> NOVO: set para acumular SOMENTE notas dos itens com saldo
377
+ notas_validas_set = set()
378
+ # (opcional) controle de notas ligadas a itens sem saldo, útil para log
379
+ notas_descartadas_set = set()
380
+
381
+ # =============== LOOP POR ITEM (USANDO DADOS DA API) ===============
382
+ for item in data["itens"]:
383
+ await worker_sleep(2)
384
+ # Abre tela de inclusão de item
385
+ app_item = Application().connect(class_name="TFrmIncluiItemPreVenda")
386
+ wnd_item = app_item["TFrmIncluiItemPreVenda"]
387
+ codigo = str(item["codigo"])
388
+ quantidade = str(item["quantidade"])
389
+ val_unitario = f"{float(item['valor_unitario']):.2f}".replace(".", ",")
390
+ item_notas = item.get("notas", [])
391
+
392
+ # --- Código ---
393
+ console.print("Inserir código...")
394
+ input_codigo = wnd_item.child_window(
395
+ class_name="TDBIEditNumber", found_index=15
396
+ )
397
+ input_codigo.click_input()
398
+ await worker_sleep(1)
399
+ keyboard.send_keys("{END}+{HOME}{DEL}")
400
+ await worker_sleep(1)
401
+ input_codigo.type_keys(codigo, with_spaces=True, set_foreground=True)
402
+ keyboard.send_keys("{TAB}")
403
+ await worker_sleep(1)
404
+
405
+ # --- Unidade (UNI) ---
406
+ console.print("Selecionar Unidade...")
407
+ select_uni = wnd_item.child_window(class_name="TDBIComboBox", found_index=1)
408
+ try:
409
+ select_uni.select("UNI")
410
+ except Exception:
411
+ select_uni.set_focus()
412
+ select_uni.expand()
413
+ await worker_sleep(1)
414
+ select_uni.type_keys("UNI", with_spaces=True, set_foreground=True)
415
+ keyboard.send_keys("{ENTER}")
416
+ keyboard.send_keys("{TAB}")
417
+ await worker_sleep(1)
418
+
419
+ # --- Quantidade ---
420
+ console.print("Inserir quantidade...")
421
+ wnd_item.child_window(
422
+ class_name="TDBIEditNumber", found_index=8
423
+ ).click_input()
424
+ await worker_sleep(1)
425
+ keyboard.send_keys("{END}+{HOME}{DEL}")
426
+ await worker_sleep(1)
427
+ keyboard.send_keys(quantidade)
428
+ keyboard.send_keys("{TAB}")
429
+ await worker_sleep(1)
430
+
431
+ # --- Valor Unitário via popup ---
432
+ console.print("Inserir valor unitário...")
433
+ wnd_item.child_window(
434
+ class_name="TDBIEditNumber", found_index=6
435
+ ).click_input()
436
+ await worker_sleep(1)
437
+ keyboard.send_keys("{TAB}")
438
+ await worker_sleep(1)
439
+ keyboard.send_keys("{ENTER}")
440
+ await worker_sleep(1)
441
+
442
+ app_preco = Application().connect(class_name="TFrmInputBoxNumero")
443
+ wnd_preco = app_preco["TFrmInputBoxNumero"]
444
+
445
+ campo_preco = wnd_preco.child_window(
446
+ class_name="TDBIEditNumber", found_index=0
447
+ )
448
+ campo_preco.click_input()
449
+ await worker_sleep(1)
450
+ keyboard.send_keys("{END}+{HOME}{DEL}")
451
+ await worker_sleep(1)
452
+ campo_preco.type_keys(val_unitario, with_spaces=True, set_foreground=True)
453
+ await worker_sleep(1)
454
+ wnd_preco.child_window(class_name="TBitBtn", found_index=1).click_input()
455
+ await worker_sleep(2)
456
+
457
+ # --- Confirmar Incluir ---
458
+ console.print("Confirmar inclusão do item...")
459
+ app_prevenda = Application().connect(class_name="TFrmIncluiItemPreVenda")
460
+ wnd_prevenda = app_prevenda["TFrmIncluiItemPreVenda"]
461
+ botao_incluir = wnd_prevenda.child_window(
462
+ title="&Incluir", class_name="TDBIBitBtn"
463
+ ).wrapper_object()
464
+ botao_incluir.click_input()
465
+
466
+ await worker_sleep(4)
467
+
468
+ # ================== VERIFICAÇÃO DE SALDO ==================
469
+ # >>> REESCRITO para deixar claro had_saldo e alimentar notas_validas_set
470
+ had_saldo = True
471
+ try:
472
+ console.print("Verificar mensagem de saldo menor....")
473
+ img_saldo = r"C:\Users\automatehub\Documents\GitHub\worker-assets\emsys\saldo_menor.png"
474
+ img_saldo_bool = False
475
+
476
+ for _ in range(10):
477
+ pos = pyautogui.locateCenterOnScreen(img_saldo, confidence=0.9)
478
+ if pos:
479
+ console.print(
480
+ f"Saldo disponível menor para o item {codigo}: {quantidade} x {val_unitario}"
481
+ )
482
+
483
+ # adiciona no dicionário de erros
484
+ itens_sem_saldo[codigo] = {
485
+ "quantidade": quantidade,
486
+ "valor_unitario": val_unitario,
487
+ }
488
+
489
+ # fecha a mensagem
490
+ app = Application().connect(class_name="TMessageForm")
491
+ main_window_msg = app["TMessageForm"]
492
+ btn_no = main_window_msg.child_window(
493
+ title="&No", class_name="TButton"
494
+ )
495
+ btn_no.click_input()
496
+
497
+ # clica em limpar
498
+ app = Application().connect(class_name="TFrmIncluiItemPreVenda")
499
+ main_window_limpa = app["TFrmIncluiItemPreVenda"]
500
+ btn_limpa = main_window_limpa.child_window(
501
+ title="&Limpa", class_name="TDBIBitBtn"
502
+ )
503
+ btn_limpa.click_input()
504
+
505
+ img_saldo_bool = True
506
+ had_saldo = False
507
+ break
508
+ await worker_sleep(1)
509
+
510
+ await worker_sleep(3)
511
+
512
+ if img_saldo_bool:
513
+ # saldo menor que quantidade
514
+ # (já inserido em itens_sem_saldo; também marcaremos as notas deste item como descartadas)
515
+ for n in item_notas:
516
+ notas_descartadas_set.add(str(n))
517
+ continue
518
+
519
+ except Exception:
520
+ # Se der algum erro na verificação da imagem, assumimos sucesso (com saldo)
521
+ had_saldo = True
522
+
523
+ # Se teve saldo, registra e marca notas válidas
524
+ if had_saldo:
525
+ console.print(f"Item {codigo} incluído com sucesso.")
526
+ itens_com_saldo[codigo] = {
527
+ "quantidade": quantidade,
528
+ "valor_unitario": val_unitario,
529
+ }
530
+ for n in item_notas:
531
+ notas_validas_set.add(str(n))
532
+ continue
533
+
534
+ # Depois de processar todos os itens:
535
+ if itens_sem_saldo and not itens_com_saldo:
536
+ # Todos os itens ficaram sem saldo → para aqui
537
+ log_msg = "Todos os itens estão com saldo menor que a quantidade:\n" + "\n".join(
538
+ f"- Código: {cod} | Quantidade: {dados['quantidade']} | Valor Unitário: {dados['valor_unitario']}"
539
+ for cod, dados in itens_sem_saldo.items()
540
+ )
541
+ return RpaRetornoProcessoDTO(
542
+ sucesso=False,
543
+ retorno=log_msg.strip(),
544
+ status=RpaHistoricoStatusEnum.Falha,
545
+ tags=[RpaTagDTO(descricao=RpaTagEnum.Negocio)],
546
+ )
547
+
548
+ # Caso contrário, existe pelo menos 1 com saldo → segue o fluxo
549
+ console.print("Há itens com saldo. Continuando o fluxo até o final...")
550
+
551
+ try:
552
+ # Liberação de preço (se aparecer)
553
+ app = Application().connect(class_name="TFrmUsuariosLiberacaoPreco")
554
+ login = app.window(class_name="TFrmUsuariosLiberacaoPreco")
555
+ login.child_window(class_name="TEdit", found_index=0).click_input()
556
+ login.type_keys("rpa.marvin", with_spaces=True, set_foreground=True)
557
+ login.child_window(class_name="TEdit", found_index=1).click_input()
558
+ login.type_keys("cba321", with_spaces=True, set_foreground=True)
559
+ login.child_window(class_name="TBitBtn", found_index=0).click_input()
560
+ except:
561
+ pass
562
+
563
+ # Clicar em fechar
564
+ wnd_prevenda.close()
565
+
566
+ await worker_sleep(3)
567
+
568
+ # Clicar em recebimentos
569
+ console.print("Clicar em recebimentos...")
570
+ imagem_item = r"C:\Users\automatehub\Desktop\img_leo\btn_recebimentos.png"
571
+ for _ in range(10):
572
+ pos = pyautogui.locateCenterOnScreen(imagem_item, confidence=0.9)
573
+ if pos:
574
+ pyautogui.doubleClick(pos)
575
+ break
576
+ await worker_sleep(1)
577
+ else:
578
+ print("Imagem do item não encontrada na tela.")
579
+
580
+ await worker_sleep(3)
581
+
582
+ # Clicar em Parcelamento
583
+ console.print("Clicar em parcelamento...")
584
+ imagem_parc = r"C:\Users\automatehub\Desktop\img_leo\btn_parcelamento.png"
585
+ for _ in range(10):
586
+ pos = pyautogui.locateCenterOnScreen(imagem_parc, confidence=0.8)
587
+ if pos:
588
+ pyautogui.doubleClick(pos)
589
+ break
590
+ await worker_sleep(1)
591
+ else:
592
+ print("Imagem do item não encontrada na tela.")
593
+
594
+ await worker_sleep(3)
595
+
596
+ # Volta pra janela de pre venda
597
+ app = Application().connect(class_name="TFrmPreVenda")
598
+ main_window = app["TFrmPreVenda"]
599
+
600
+ # Condição de recebimento (boleto)
601
+ console.print("Selecionar boleto...")
602
+ condicao_field = main_window.child_window(
603
+ class_name="TDBIComboBox", found_index=0
604
+ )
605
+ try:
606
+ condicao_field.select("BANCO DO BRASIL BOLETO")
607
+ print("Selecionado: BANCO DO BRASIL BOLETO")
608
+ except Exception as e:
609
+ print(
610
+ f"Não foi possível selecionar 'BANCO DO BRASIL BOLETO' ({e}). Tentando 'BOLETO'..."
611
+ )
612
+ try:
613
+ condicao_field.select("BOLETO")
614
+ print("Selecionado: BOLETO")
615
+ except Exception as e2:
616
+ print(f"❌ Falha também ao selecionar 'BOLETO': {e2}")
617
+
618
+ # Clicar em Incluir
619
+ console.print("Incluir registro...")
620
+ imagem_incluir = r"C:\Users\automatehub\Documents\GitHub\worker-automate-hub\assets\entrada_notas\IncluirRegistro.png"
621
+ for _ in range(10):
622
+ pos = pyautogui.locateCenterOnScreen(imagem_incluir, confidence=0.8)
623
+ if pos:
624
+ pyautogui.click(pos)
625
+ break
626
+ await worker_sleep(1)
627
+ else:
628
+ print("Imagem do item não encontrada na tela.")
629
+
630
+ await worker_sleep(3)
631
+
632
+ # Capturar número da pré-venda
633
+ console.print("Capturar número da pré-venda...")
634
+ numero_pre_venda = None
635
+ timeout = 10
636
+ t0 = time.time()
637
+ while time.time() - t0 < timeout:
638
+ try:
639
+ win = Desktop(backend="win32").window(title_re=".*Informa.*")
640
+ if not win.exists(timeout=0.2):
641
+ time.sleep(0.3)
642
+ continue
643
+ win.set_focus()
644
+
645
+ textos = []
646
+ try:
647
+ textos.append(win.window_text())
648
+ except:
649
+ pass
650
+ try:
651
+ textos += [t for t in win.wrapper_object().texts() if t]
652
+ except:
653
+ pass
654
+ try:
655
+ for st in win.children(class_name="Static"):
656
+ textos += [t for t in st.texts() if t]
657
+ except:
658
+ pass
659
+
660
+ texto = "\n".join([t for t in textos if t])
661
+ if ("Venda inclu" in texto) or ("Pré" in texto) or ("Pr" in texto):
662
+ m = re.search(r"\b(\d{3,}-\d{1,})\b", texto)
663
+ if m:
664
+ numero_pre_venda = m.group(1)
665
+
666
+ clicked = False
667
+ for title in ("OK", "&OK"):
668
+ try:
669
+ win.child_window(
670
+ title=title, class_name="TButton"
671
+ ).click_input()
672
+ clicked = True
673
+ break
674
+ except:
675
+ pass
676
+ if not clicked:
677
+ try:
678
+ win.type_keys("{ENTER}")
679
+ except:
680
+ pass
681
+ break
682
+ except:
683
+ time.sleep(0.3)
684
+
685
+ print("Número da pré-venda:", numero_pre_venda)
686
+ await worker_sleep(5)
687
+
688
+ # Confirmar pré-venda (Yes)
689
+ console.print("Confirmar pré-venda...")
690
+ app = Application().connect(class_name="TMessageForm")
691
+ main_window = app["TMessageForm"]
692
+ btn_ok = main_window.child_window(title="&Yes", class_name="TButton")
693
+ btn_ok.click_input()
694
+ await worker_sleep(4)
695
+
696
+ # Botão confirma
697
+ app = Application().connect(class_name="TFrmPreVenda")
698
+ main_window = app["TFrmPreVenda"]
699
+ btn_confirmar = main_window.child_window(
700
+ title="&Confirma", class_name="TBitBtn"
701
+ )
702
+ btn_confirmar.click_input()
703
+ await worker_sleep(4)
704
+
705
+ # Confirmar (Yes)
706
+ app = Application().connect(class_name="TMessageForm")
707
+ main_window = app["TMessageForm"]
708
+ btn_confirmar = main_window.child_window(title="&Yes", class_name="TButton")
709
+ btn_confirmar.click_input()
710
+ await worker_sleep(5)
711
+
712
+ # Fechar "Informação"
713
+ for _ in range(10):
714
+ try:
715
+ dlg = Desktop(backend="win32").window(
716
+ title_re="Informação", class_name="#32770"
717
+ )
718
+ if dlg.exists(timeout=1):
719
+ dlg.child_window(title="OK").click_input()
720
+ print("✅ Fechou janela 'Informação'.")
721
+ break
722
+ except:
723
+ pass
724
+ time.sleep(1)
725
+
726
+ await worker_sleep(3)
727
+
728
+ # Faturar
729
+ console.print("Clicar em faturar...")
730
+ app = Application().connect(class_name="TFrmPreVenda")
731
+ main_window = app["TFrmPreVenda"]
732
+ main_window.set_focus()
733
+ btn_faturar = main_window.child_window(title="&Faturar", class_name="TBitBtn")
734
+ btn_faturar.click_input()
735
+ await worker_sleep(5)
736
+ print("Botão 'Faturar' clicado com sucesso!")
737
+
738
+ # Recalcular Parcelas? (Yes)
739
+ console.print("Clicar em recalcular parcelas...")
740
+ app = Application().connect(class_name="TMessageForm")
741
+ main_window = app["TMessageForm"]
742
+ main_window.set_focus()
743
+ btn_confirmar = main_window.child_window(title="&Yes", class_name="TButton")
744
+ btn_confirmar.click_input()
745
+
746
+ for _ in range(10):
747
+ try:
748
+ dlg = Desktop(backend="win32").window(
749
+ title_re="Parcelas - Nota Fiscal Sa", class_name="#32770"
750
+ )
751
+ if dlg.exists(timeout=1):
752
+ dlg.child_window(title="&Não").click_input()
753
+ print("Clicou em Não")
754
+ break
755
+ except:
756
+ pass
757
+
758
+ await worker_sleep(10)
759
+
760
+ # Notas referenciadas
761
+ console.print("CLicar em notas referenciadas...")
762
+ imagem_notas_ref = (
763
+ r"C:\Users\automatehub\Desktop\img_leo\notas_referenciadas.png"
764
+ )
765
+ for _ in range(20):
766
+ pos = pyautogui.locateCenterOnScreen(imagem_notas_ref, confidence=0.8)
767
+ if pos:
768
+ pyautogui.click(pos)
769
+ break
770
+ await worker_sleep(1)
771
+ else:
772
+ print("Imagem do item não encontrada na tela.")
773
+
774
+ await worker_sleep(2)
775
+
776
+ # Faturamento Pré-venda
777
+ app = Application().connect(class_name="TFrmDadosFaturamentoPreVenda")
778
+ main_window = app["TFrmDadosFaturamentoPreVenda"]
779
+ main_window.set_focus()
780
+
781
+ # Radio Entrada
782
+ main_window.child_window(
783
+ title="Entrada", class_name="TDBIRadioButton"
784
+ ).click_input()
785
+ console.print("Clicado em 'Entrada'")
786
+ await worker_sleep(4)
787
+
788
+ # ====== FILTRAR NOTAS VÁLIDAS ======
789
+ todas_as_notas = [str(n) for n in data.get("nf_referencia", [])]
790
+ notas_validas_ordenadas = [n for n in todas_as_notas if n in notas_validas_set]
791
+ notas_descartadas = [n for n in todas_as_notas if n not in notas_validas_set]
792
+
793
+ console.print(
794
+ f"[green]Notas a referenciar (itens com saldo): {notas_validas_ordenadas}[/]"
795
+ )
796
+ if notas_descartadas:
797
+ console.print(
798
+ f"[yellow]Notas descartadas (apenas itens sem saldo): {notas_descartadas}[/]"
799
+ )
800
+
801
+ # >>> NOVO: nota_arquivo (primeira válida; fallback: pré-venda ou timestamp) - SEM extract_nf_number
802
+ if notas_validas_ordenadas:
803
+ nota_arquivo = re.sub(r"\D+", "", str(notas_validas_ordenadas[0])) or str(
804
+ notas_validas_ordenadas[0]
805
+ )
806
+ else:
807
+ if numero_pre_venda:
808
+ nota_arquivo = re.sub(r"\D+", "", str(numero_pre_venda)) or str(
809
+ numero_pre_venda
810
+ )
811
+ else:
812
+ nota_arquivo = datetime.now().strftime("%Y%m%d%H%M%S")
813
+
814
+ # === LOOP REFERENCIANDO APENAS NOTAS VÁLIDAS ===
815
+ for nf_ref_atual in notas_validas_ordenadas:
816
+ itens_da_nota = data.get("itens_por_nota", {}).get(nf_ref_atual, [])
817
+ if not itens_da_nota:
818
+ console.print(
819
+ f"[amarelo]Nenhum item associado à nota {nf_ref_atual}. Pulando...[/]"
820
+ )
821
+ continue
822
+
823
+ console.print(f"[cyan]Processando nota {nf_ref_atual}...[/]")
824
+
825
+ # 1) Focar e limpar o campo da nota
826
+ input_num_nota = main_window.child_window(class_name="TDBIEditDescription")
827
+ input_num_nota.set_focus()
828
+ try:
829
+ input_num_nota.select() # alguns campos suportam select()
830
+ except Exception:
831
+ pass
832
+ keyboard.send_keys("^a{DEL}") # Ctrl+A + Delete (fallback)
833
+ await worker_sleep(0.4)
834
+
835
+ # 2) Digitar a nota e confirmar
836
+ input_num_nota.type_keys(
837
+ str(nf_ref_atual), with_spaces=True, set_foreground=True
838
+ )
839
+ await worker_sleep(0.6)
840
+ keyboard.send_keys("{ENTER}")
841
+ await worker_sleep(0.6)
842
+
843
+ # Clicar em incluir itens (folha de papel com +)
844
+ app = Application().connect(class_name="TFrmDadosFaturamentoPreVenda")
845
+ main_window = app["TFrmDadosFaturamentoPreVenda"]
846
+
847
+ # Clica no botão identificado como TDBIBitBtn2 (control_id 918912)
848
+ main_window.child_window(
849
+ class_name="TDBIBitBtn", found_index=1
850
+ ).click_input()
851
+
852
+ print("Botão clicado com sucesso!")
853
+ console.print(f"Incluindo itens vinculados à nota {nf_ref_atual}...")
854
+
855
+ # Aba mensagens
856
+ console.print("Clicar em mensagens...")
857
+ imagem_notas_ref = r"C:\Users\automatehub\Desktop\img_leo\aba_mensagem.png"
858
+ for _ in range(10):
859
+ pos = pyautogui.locateCenterOnScreen(imagem_notas_ref, confidence=0.9)
860
+ if pos:
861
+ pyautogui.click(pos)
862
+ break
863
+ await worker_sleep(1)
864
+ else:
865
+ print("Imagem do item não encontrada na tela.")
866
+ await worker_sleep(5)
867
+
868
+ # Mensagem interna
869
+ imagem_notas_ref = r"C:\Users\automatehub\Desktop\img_leo\mensagem_interna.png"
870
+ for _ in range(10):
871
+ pos = pyautogui.locateCenterOnScreen(imagem_notas_ref, confidence=0.8)
872
+ if pos:
873
+ pyautogui.click(pos)
874
+ break
875
+ await worker_sleep(1)
876
+ else:
877
+ print("Imagem do item não encontrada na tela.")
878
+ await worker_sleep(4)
879
+
880
+ # Inserir mensagem padrão
881
+ console.print("Inserir mensagem...")
882
+ fornecedor = "Disbal"
883
+ lista_fornecedores = ["Disbal", "Pepsico", "Punta Balena"]
884
+ mensagem = (
885
+ "PRODUTOS VENCIDOS"
886
+ if fornecedor in lista_fornecedores
887
+ else "ACORDO COMERCIAL"
888
+ )
889
+ input_mensagem = main_window.child_window(class_name="TDBIMemo", found_index=0)
890
+ input_mensagem.type_keys(mensagem, with_spaces=True, set_foreground=True)
891
+
892
+ # Aba itens
893
+ imagem_itens = r"C:\Users\automatehub\Desktop\img_leo\aba_itens.png"
894
+ for _ in range(10):
895
+ pos = pyautogui.locateCenterOnScreen(imagem_itens, confidence=0.9)
896
+ if pos:
897
+ pyautogui.click(pos)
898
+ break
899
+ await worker_sleep(1)
900
+ else:
901
+ print("Imagem do item não encontrada na tela.")
902
+
903
+ await worker_sleep(3)
904
+
905
+ # Corrige tributação
906
+ console.print("Corrigir tributação...")
907
+ imagem_itens = r"C:\Users\automatehub\Desktop\img_leo\corrige_tributacao.png"
908
+ for _ in range(10):
909
+ pos = pyautogui.locateCenterOnScreen(imagem_itens, confidence=0.9)
910
+ if pos:
911
+ pyautogui.click(pos)
912
+ break
913
+ await worker_sleep(1)
914
+ else:
915
+ print("Imagem do tributacao não encontrada na tela.")
916
+
917
+ await worker_sleep(3)
918
+
919
+ # Selecionar tributação
920
+ console.print("Selecionar tributação...")
921
+ app = Application().connect(class_name="TFrmDadosTributacaoProdutoPreVenda")
922
+ trib = app["TFrmDadosTributacaoProdutoPreVenda"]
923
+
924
+ if estado == "RS":
925
+ select_trib = trib.child_window(class_name="TDBIComboBox", found_index=4)
926
+ select_trib.select("051 - 051 - ICMS 17% RED BC 29,4118% - TRIBUT.CORRETA")
927
+ print("Selecionado: 051 - 051 - ICMS 17% RED BC 29,4118% - TRIBUT.CORRETA")
928
+ elif estado == "SC":
929
+ select_trib = trib.child_window(class_name="TDBIComboBox", found_index=4)
930
+ select_trib.select("000 - 000 - ICMS - 12%")
931
+ print("Selecionado: 000 - 000 - ICMS - 12%")
932
+ else:
933
+ print("Estado diferente dos mapeados")
934
+
935
+ if fornecedor == "Disbal":
936
+ select_trib.select("020 - 020 - ICMS 12% RED. BASE 41,667")
937
+ elif fornecedor == "Punta Balena":
938
+ select_trib.select("000 - 000 - ICMS - 12%")
939
+ elif fornecedor == "Vitrola":
940
+ select_trib.select("041 - 041 - ICMS - NAO INCIDENTE ")
941
+
942
+ await worker_sleep(2)
943
+
944
+ trib.child_window(title="&OK", class_name="TBitBtn").click_input()
945
+
946
+ await worker_sleep(1)
947
+
948
+ # Aba principal
949
+ imagem_principal = r"C:\Users\automatehub\Desktop\img_leo\aba_principal.png"
950
+ for _ in range(10):
951
+ pos = pyautogui.locateCenterOnScreen(imagem_principal, confidence=0.9)
952
+ if pos:
953
+ pyautogui.click(pos)
954
+ break
955
+ await worker_sleep(1)
956
+ else:
957
+ print("Imagem do item não encontrada na tela.")
958
+
959
+ await worker_sleep(5)
960
+
961
+ # DANFE 077
962
+ console.print(
963
+ "Selecionar NFe - NOTA FISCAL ELETRONICA PROPRIA - DANFE SERIE 077..."
964
+ )
965
+ app = Application().connect(class_name="TFrmDadosFaturamentoPreVenda")
966
+ main_window = app["TFrmDadosFaturamentoPreVenda"]
967
+ select_danfe = main_window.child_window(
968
+ class_name="TDBIComboBox", found_index=1
969
+ )
970
+ select_danfe.select("NFe - NOTA FISCAL ELETRONICA PROPRIA - DANFE SERIE 077")
971
+
972
+ await worker_sleep(2)
973
+
974
+ # OK
975
+ main_window.child_window(title="&OK", class_name="TBitBtn").click_input()
976
+
977
+ await worker_sleep(3)
978
+
979
+ # Faturar pré-venda (Yes)
980
+ app = Application().connect(class_name="TMessageForm")
981
+ main_window = app["TMessageForm"]
982
+ main_window.child_window(class_name="TButton", found_index=1).click()
983
+
984
+ await worker_sleep(5)
985
+
986
+ # Faturar pré-venda (Yes)
987
+ app = Application().connect(class_name="TMessageForm")
988
+ main_window = app["TMessageForm"]
989
+ main_window.child_window(
990
+ title="Transmitir e &Imprimir", class_name="TButton"
991
+ ).click_input()
992
+
993
+ await worker_sleep(10)
994
+
995
+ # Diálogo impressão
996
+ console.print("Confirmar impressão...")
997
+ app = Application().connect(class_name="TppPrintDialog")
998
+ main_window = app["TppPrintDialog"]
999
+ main_window.child_window(title="OK", class_name="TButton").click()
1000
+
1001
+ await worker_sleep(5)
1002
+
1003
+ console.print(f"NAVEGANDO NA TELA DE SALVAR RELATORIO\n")
1004
+ # INSERINDO O DIRETORIO E SALVANDO O ARQUIVO
1005
+ try:
1006
+ app = Application().connect(title="Salvar Saída de Impressão como")
1007
+ main_window = app["Dialog"]
1008
+ console.print("Tela 'Salvar' encontrada!")
1009
+
1010
+ console.print("Interagindo com a tela 'Salvar'...\n")
1011
+ username = getpass.getuser()
1012
+
1013
+ # Preenche o nome do arquivo - SOMENTE número da nota
1014
+ path_to_txt = f"C:\\Users\\{username}\\Downloads\\devolucao_nf_{estado}_{nota_arquivo}"
1015
+
1016
+ main_window.type_keys("%n")
1017
+ pyautogui.write(path_to_txt)
1018
+ await worker_sleep(1)
1019
+ main_window.type_keys("%l")
1020
+ console.print("Arquivo salvo com sucesso...\n")
1021
+ await worker_sleep(8)
1022
+ except Exception as e:
1023
+ retorno = f"Não foi salvar o arquivo: {e}"
1024
+ return RpaRetornoProcessoDTO(
1025
+ sucesso=False,
1026
+ retorno=retorno,
1027
+ status=RpaHistoricoStatusEnum.Falha,
1028
+ tags=[RpaTagDTO(descricao=RpaTagEnum.Tecnico)],
1029
+ )
1030
+
1031
+ with open(f"{path_to_txt}.pdf", "rb") as file:
1032
+ file_bytes = io.BytesIO(file.read())
1033
+
1034
+ desArquivo = f"devolucao_nf_{estado}_{nota_arquivo}.pdf"
1035
+ try:
1036
+ await send_file(
1037
+ historico_id, desArquivo, "pdf", file_bytes, file_extension="pdf"
1038
+ )
1039
+ os.remove(f"{path_to_txt}.pdf")
1040
+ except Exception as e:
1041
+ result = (
1042
+ f"Arquivo gerado com sucesso, porém erro ao enviar para o backoffice: {e} "
1043
+ f"- Arquivo salvo em {path_to_txt}.pdf"
1044
+ )
1045
+ console.print(result, style="bold red")
1046
+ return RpaRetornoProcessoDTO(
1047
+ sucesso=False,
1048
+ retorno=result,
1049
+ status=RpaHistoricoStatusEnum.Falha,
1050
+ tags=[RpaTagDTO(descricao=RpaTagEnum.Tecnico)],
1051
+ )
1052
+
1053
+ except Exception as ex:
1054
+ log_msg = f"Error: {ex}"
1055
+ print(ex)
1056
+ return RpaRetornoProcessoDTO(
1057
+ sucesso=False,
1058
+ retorno=log_msg,
1059
+ status=RpaHistoricoStatusEnum.Falha,
1060
+ tags=[RpaTagDTO(descricao=RpaTagEnum.Negocio)],
1061
+ )
1062
+
1063
+ # ============== RESUMO FINAL ==============
1064
+ def _fmt_linha(cod, dados):
1065
+ return f"- Código: {cod} | Quantidade: {dados.get('quantidade')} | Valor Unitário: {dados.get('valor_unitario')}"
1066
+
1067
+ resumo_partes = []
1068
+
1069
+ if itens_com_saldo:
1070
+ lista_ok = "\n".join(_fmt_linha(c, d) for c, d in list(itens_com_saldo.items()))
1071
+ resumo_partes.append(
1072
+ "✅ Itens incluídos:\n" + (lista_ok if lista_ok else "(vazio)")
1073
+ )
1074
+
1075
+ if itens_sem_saldo:
1076
+ lista_sem = "\n".join(
1077
+ _fmt_linha(c, d) for c, d in list(itens_sem_saldo.items())
1078
+ )
1079
+ resumo_partes.append(
1080
+ "⚠️ Itens sem saldo:\n" + (lista_sem if lista_sem else "(vazio)")
1081
+ )
1082
+
1083
+ # (Opcional) resumo sobre notas válidas/descartadas
1084
+ try:
1085
+ resumo_partes.append(
1086
+ "🧾 Notas referenciadas: " + ", ".join(sorted(list(notas_validas_set)))
1087
+ if notas_validas_set
1088
+ else "🧾 Notas referenciadas: (nenhuma)"
1089
+ )
1090
+ except:
1091
+ pass
1092
+
1093
+ resumo_txt = (
1094
+ "\n\n".join(resumo_partes) if resumo_partes else "Nenhum item processado."
1095
+ )
1096
+
1097
+ return RpaRetornoProcessoDTO(
1098
+ sucesso=True,
1099
+ retorno=f"Processo concluído.\n\n{resumo_txt}",
1100
+ status=RpaHistoricoStatusEnum.Sucesso,
1101
+ tags=[RpaTagDTO(descricao=RpaTagEnum.Negocio)],
1102
+ )
@@ -33,7 +33,9 @@ from worker_automate_hub.tasks.jobs.entrada_de_notas_36 import entrada_de_notas_
33
33
  from worker_automate_hub.tasks.jobs.entrada_de_notas_37 import entrada_de_notas_37
34
34
  from worker_automate_hub.tasks.jobs.entrada_de_notas_503 import entrada_de_notas_503
35
35
  from worker_automate_hub.tasks.jobs.extracao_saldo_estoque import extracao_saldo_estoque
36
- from worker_automate_hub.tasks.jobs.extracao_saldo_estoque_fiscal import extracao_saldo_estoque_fiscal
36
+ from worker_automate_hub.tasks.jobs.extracao_saldo_estoque_fiscal import (
37
+ extracao_saldo_estoque_fiscal,
38
+ )
37
39
  from worker_automate_hub.tasks.jobs.fidc_remessa_cobranca_cnab240 import (
38
40
  remessa_cobranca_cnab240,
39
41
  )
@@ -109,6 +111,9 @@ from worker_automate_hub.tasks.jobs.geracao_balancetes_filial import (
109
111
  geracao_balancetes_filial,
110
112
  )
111
113
 
114
+ from worker_automate_hub.tasks.jobs.devolucao_produtos import (
115
+ devolucao_produtos,
116
+ )
112
117
 
113
118
  task_definitions = {
114
119
  "5b295021-8df7-40a1-a45e-fe7109ae3902": exemplo_processo,
@@ -216,7 +221,8 @@ task_definitions = {
216
221
  "9cbc6016-7c0e-4a3a-8ee9-fb9dc4b35e33": extracao_saldo_estoque_fiscal,
217
222
  "07072711-c9d0-49e4-b180-530cecbe0728": opex_capex,
218
223
  "98bc6679-2e6b-4757-9fdc-b27eebd98f54": entrada_de_notas_22,
219
- "d7794924-0330-453c-b79b-74f3c8991562": geracao_balancetes_filial
224
+ "2ebcc2e5-2fa1-4130-a92a-3af349a1920c": devolucao_produtos,
225
+ "d7794924-0330-453c-b79b-74f3c8991562": geracao_balancetes_filial,
220
226
  }
221
227
 
222
228
 
@@ -722,10 +722,23 @@ async def login_emsys_old(
722
722
  )
723
723
 
724
724
  #Login novo
725
- async def login_emsys(config: dict, app, task: RpaProcessoEntradaDTO, **kwargs):
726
- # Para processos onde a config_entrada é enviada vazia, obtemos
727
- # o número da filial através do **kwargs
728
- filial_origem = kwargs.get("filial_origem", None)
725
+ async def login_emsys(config: dict, app, task: RpaProcessoEntradaDTO, filial_origem=None, **kwargs):
726
+ # Fonte de verdade: param explícito > kwargs > task.configEntrada
727
+ filial_origem = (
728
+ filial_origem
729
+ or kwargs.get("filial_origem")
730
+ or kwargs.get("descricaoFilial")
731
+ or (getattr(task, "configEntrada", {}) or {}).get("descricaoFilial")
732
+ or (getattr(task, "configEntrada", {}) or {}).get("codigoEmpresa")
733
+ or (getattr(task, "configEntrada", {}) or {}).get("filialEmpresaOrigem")
734
+ )
735
+
736
+ # Extrai só o número (ex.: "69" de "69 - Gravataí Free Way")
737
+ if filial_origem:
738
+ m = re.search(r"\d+", str(filial_origem))
739
+ if m:
740
+ filial_origem = m.group(0)
741
+
729
742
  warnings.filterwarnings(
730
743
  "ignore",
731
744
  category=UserWarning,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: worker-automate-hub
3
- Version: 0.5.828
3
+ Version: 0.5.830
4
4
  Summary: Worker Automate HUB é uma aplicação para automatizar rotinas de RPA nos ambientes Argenta.
5
5
  Author: Joel Paim
6
6
  Requires-Python: >=3.12,<4.0
@@ -1,7 +1,7 @@
1
1
  worker_automate_hub/__init__.py,sha256=LV28uQvBfpPIqudGIMJmVB8E941MjXHcu8DMoX5n8AM,25
2
2
  worker_automate_hub/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  worker_automate_hub/api/ahead_service.py,sha256=QbNrZf9q7fS0s-S5fZVytqC7dINi9u2f6aB6SDrGVVA,2231
4
- worker_automate_hub/api/client.py,sha256=FAjwte64gV8EGY9uaZAw1CiZQUcV2pZ9plcDyiv2tqU,32075
4
+ worker_automate_hub/api/client.py,sha256=efug1qAm_5i3eEYwu6bnJjREJP0UpUTjmaYIDcItV04,35614
5
5
  worker_automate_hub/api/datalake_service.py,sha256=qw_N_OOgDKDuPbI-fdYkWWTlT4CUtFTl0VVlZ0fLM-M,3001
6
6
  worker_automate_hub/api/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  worker_automate_hub/api/helpers/api_helpers.py,sha256=SkheO2fXexeh-a4shr8Qzsz_kZhuSG0DJ7kbODctRbM,3696
@@ -45,6 +45,7 @@ worker_automate_hub/tasks/jobs/descartes.py,sha256=wIi8n4vZrNE-03C5_lr4FmxuKoWSY
45
45
  worker_automate_hub/tasks/jobs/devolucao_ctf.py,sha256=7tdUihaDqjIf7POjM79EqKG0w-qqXbhC6jq6hteavkw,250822
46
46
  worker_automate_hub/tasks/jobs/devolucao_ctf_35.py,sha256=e9t5k2mtZcUcEGKPWysbWzsH_gqrK-6aBXjWe2jWfTg,253948
47
47
  worker_automate_hub/tasks/jobs/devolucao_prazo_a_faturar.py,sha256=kzPJazDRbz2CLn8tKja2Lg1N4UzTRF1V4Nc1elIqTGY,272145
48
+ worker_automate_hub/tasks/jobs/devolucao_produtos.py,sha256=ofVtWCAkkQgVZ_kxay4JgTUce6WZ_pHMWyeZ7mXuKHA,42527
48
49
  worker_automate_hub/tasks/jobs/ecac_estadual_go.py,sha256=dKkf22nH5gp3RErq5u0UzRsKyJ81fc6ZZ4vLtUuMwHA,21002
49
50
  worker_automate_hub/tasks/jobs/ecac_estadual_main.py,sha256=8WmKe4-MRtzHobXz2S4YBDNN8alfawkC-BBlRY-mn1g,1726
50
51
  worker_automate_hub/tasks/jobs/ecac_estadual_mt.py,sha256=C26zmpGQGUq6sP9lU9nanM3Fje-rkyx5tjwmRy4lyL8,25300
@@ -93,7 +94,7 @@ worker_automate_hub/tasks/jobs/opex_capex.py,sha256=RbifCpmacvWAbV3tAabee_I0dxb9
93
94
  worker_automate_hub/tasks/jobs/playground.py,sha256=7vWDg9DwToHwGJ6_XOa8TQ6LmfRV5Qz2TaOV3q3P9sA,1918
94
95
  worker_automate_hub/tasks/jobs/sped_fiscal.py,sha256=rinyHCF7QYHUc6oWACJgnQiAv0S-_Fel9Z0bImiHUoM,31064
95
96
  worker_automate_hub/tasks/jobs/transferencias.py,sha256=5TIktufkvUPnVTR2gf7GFQJ5KQP6PWnmoWiE08WiVDQ,46191
96
- worker_automate_hub/tasks/task_definitions.py,sha256=VD1xq76A_ai_3DHrnCy6aYG6HY_vqfs0ntS_QvloVEI,12994
97
+ worker_automate_hub/tasks/task_definitions.py,sha256=L4f5ZJ8lSyBwASFwRh0S3RlhvH--gXn6d4RBHVORX3k,13164
97
98
  worker_automate_hub/tasks/task_executor.py,sha256=F5ngJLGz7vbUrX-batUlt3FFwiCE8denFmtTTLaE77I,6044
98
99
  worker_automate_hub/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
99
100
  worker_automate_hub/utils/env.py,sha256=TacQjGRO7PUNpttrhTAc5Gnegaiysl2Knsv1P8qfkfs,57
@@ -101,10 +102,10 @@ worker_automate_hub/utils/get_creds_gworkspace.py,sha256=ZJ0IIEjM4IXIV9rwfbOZ1V1
101
102
  worker_automate_hub/utils/logger.py,sha256=FYV9fg0_RAYJF_ZOCJEbqQAiCXlXk2gMpvUU1rzT_xs,671
102
103
  worker_automate_hub/utils/toast.py,sha256=xPHc5r5uOxB_cZlCzm13Kt2qSKLLFZALncU6Qg3Ft68,1162
103
104
  worker_automate_hub/utils/updater.py,sha256=en2FCGhI8aZ-JNP3LQm64NJDc4awCNW7UhbVlwDl49Y,7972
104
- worker_automate_hub/utils/util.py,sha256=p15z2pqyV-fPD8lL6ulurecsWQw9XpKPFf_xEr4U5lM,210781
105
+ worker_automate_hub/utils/util.py,sha256=rDp5h36I_kMfaCDD9xgcxibRXe5AYaImlXKhGbQ7DHE,211273
105
106
  worker_automate_hub/utils/utils_nfe_entrada.py,sha256=F7jk95LpDwl5WfaQXahCA5yDdnySnWdctDqczHXwGqE,38195
106
107
  worker_automate_hub/worker.py,sha256=zEnYUrm5kY2cHbbee15QJkwkx4euD2SB2zRvUIbjS90,6850
107
- worker_automate_hub-0.5.828.dist-info/entry_points.txt,sha256=sddyhjx57I08RY8X7UxcTpdoOsWULAWNKN9Xr6pp_Kw,54
108
- worker_automate_hub-0.5.828.dist-info/METADATA,sha256=-wW3Y_qRH0X8e67XqLOyKOLS3YavPF2QYpMt-teVubQ,3142
109
- worker_automate_hub-0.5.828.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
110
- worker_automate_hub-0.5.828.dist-info/RECORD,,
108
+ worker_automate_hub-0.5.830.dist-info/entry_points.txt,sha256=sddyhjx57I08RY8X7UxcTpdoOsWULAWNKN9Xr6pp_Kw,54
109
+ worker_automate_hub-0.5.830.dist-info/METADATA,sha256=rEXR81qIUb7EE-vhWUEjqxwGy56fnILKpc4wimF5gAU,3142
110
+ worker_automate_hub-0.5.830.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
111
+ worker_automate_hub-0.5.830.dist-info/RECORD,,