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