File size: 12,970 Bytes
17b6d33
 
 
 
0505151
17b6d33
0505151
 
17b6d33
 
0505151
 
17b6d33
 
 
 
 
0505151
 
 
 
 
17b6d33
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1addc15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17b6d33
 
0505151
 
 
17b6d33
 
 
0505151
 
 
 
 
 
 
 
17b6d33
 
0505151
 
9b5afe8
0505151
 
 
 
17b6d33
0505151
 
 
 
 
 
17b6d33
9b5afe8
6009492
0505151
6009492
0505151
 
 
 
 
6009492
17b6d33
0505151
6009492
17b6d33
 
 
0505151
6009492
17b6d33
 
9b5afe8
0505151
1addc15
 
0505151
9b5afe8
1addc15
 
 
 
 
9b5afe8
1addc15
0505151
1addc15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9b5afe8
 
0505151
 
 
 
 
6009492
0505151
 
 
 
d1d9779
17b6d33
0505151
d1d9779
0505151
17b6d33
 
d1d9779
17b6d33
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
#Modulos
import Google_Sheets

#Bibliotecas externas ou internas
import os
import requests
from qlik_sdk import AuthType, Config, Apps
import pandas as pd
import time
import traceback  # Nova biblioteca para rastrear erros linha por linha!

# --- 1. CONFIGURAÇÕES ---
TENANT_URL = os.environ.get("TENANT_URL")
APP_ID = os.environ.get("APP_ID")
CLIENT_ID = os.environ.get("CLIENT_ID")
CLIENT_SECRET = os.environ.get("CLIENT_SECRET")


# =========================================================

def extrair_dados_engine():
    try:
        print("==========================================")
        print("Iniciando extração... Buscando um NOVO token M2M no Qlik...")

        # Fazemos o pedido oficial do Token DENTRO da função
        resposta = requests.post(
            f"{TENANT_URL}/oauth/token",
            json={
                "client_id": CLIENT_ID,
                "client_secret": CLIENT_SECRET,
                "grant_type": "client_credentials"
            },
            headers={"Accept": "application/json", "Content-Type": "application/json"}
        )

        if resposta.status_code != 200:
            print(f"❌ Erro na autenticação com o Qlik: {resposta.text}")
            return None, None

        token_seguro = resposta.json().get("access_token")
        print("✔️ Token gerado com sucesso!")

        # --- CORREÇÃO 1: O TRUQUE DO RELÓGIO ---
        print("⏳ Aguardando 3 segundos para sincronia de relógio entre os servidores...")

        time.sleep(3)
        # ---------------------------------------

        # --- CORREÇÃO 2: A LIMPEZA DA URL ---
        # O .strip("/") garante que, mesmo que você tenha colado a URL no
        # Hugging Face com uma barra no final, o Python tira ela.
        url_limpa = TENANT_URL.strip("/")

        config = Config(
            host=url_limpa,
            auth_type=AuthType.APIKey,
            api_key=token_seguro
        )

        apps = Apps(config)

        print("Conectando ao App do Qlik...")

        # --- NOVO: SISTEMA DE TENTATIVAS PARA ACORDAR O QLIK ---
        max_tentativas = 5
        app = None

        #Cria um loop que irá verificar se esta retornado memso depois de um tempo
        for tentativa in range(1, max_tentativas + 1):
            try:
                app = apps.get(APP_ID)
                break  # Se deu certo de primeira, quebra o loop e segue o jogo!
            except Exception as erro_conexao:
                if "Timeout" in str(erro_conexao) and tentativa < max_tentativas:
                    print(
                        f"⚠️ O Qlik está 'acordando' o App (Tentativa {tentativa}/{max_tentativas}). Aguardando 10 segundos...")
                    time.sleep(10)
                else:
                    raise erro_conexao



        with app.open():
            print(f"App '{app.attributes.name}' aberto! Montando a tabela virtual...")

            campos_desejados = ["Chave_CC_PRO", "SAFRA", "UF", "CÓDIGO DO PRODUTO", "DERIVAÇÃO", "DESCRIÇÃO PRODUTO",
                                "MARCA", "CUSTO ATUAL", "DATA VENC ATUAL", "DATA DE VENC NOVA", "NOVO CUSTO",
                                "% BARTER", "QUANTIDADE"]
            limite_linhas = int(10000 / len(campos_desejados))

            qDimensions = [{"qDef": {"qFieldDefs": [campo]}} for campo in campos_desejados]

            properties = {
                "qInfo": {"qType": "StraightTable"},
                "qHyperCubeDef": {
                    "qDimensions": qDimensions,
                    "qInitialDataFetch": [
                        {"qTop": 0, "qLeft": 0, "qHeight": limite_linhas, "qWidth": len(campos_desejados)}]
                }
            }

            obj = app.create_session_object(properties)
            layout = obj.get_layout()

            total_linhas_qlik = layout.qHyperCube.qSize.qcy
            print(f"O Qlik encontrou um total de {total_linhas_qlik} linhas. Iniciando extração...")

            dados_limpos = []

            for offset in range(0, total_linhas_qlik, limite_linhas):
                paginas = obj.get_hyper_cube_data(
                    qPath="/qHyperCubeDef",
                    qPages=[{"qTop": offset, "qLeft": 0, "qHeight": limite_linhas, "qWidth": len(campos_desejados)}]
                )

                matriz_pagina = paginas[0].qMatrix

                for linha in matriz_pagina:
                    registro = {}
                    for i, campo in enumerate(campos_desejados):
                        registro[campo] = linha[i].qText if linha[i].qText is not None else ""
                    dados_limpos.append(registro)

            print(f"Extração do Qlik concluída! {len(dados_limpos)} registros baixados.")
            df = pd.DataFrame(dados_limpos)

            df["Valor de reposição?"] = ""
            df["Atualizar no Senior?"] = ""
            df["DUPLICAR"] = ""
            df["Salvar alteração"] = ""

            print("Puxando dados do Google Sheets...")
            hist = Google_Sheets.ler_historico()

            if not hist.empty:
                print("Lógica Passo a Passo: Verificando produtos novos e variações...")

                hist['DATA REGISTRADA'] = pd.to_datetime(hist['DATA REGISTRADA'], format="%d/%m/%Y %H:%M:%S")

                falsos_vazios = ["nan", "NaN", "None", "none", "null", "-"]

                for col in ['Chave_CC_PRO', 'SAFRA', 'UF']:
                    df[col] = df[col].astype(str).str.strip().replace(falsos_vazios, "")
                    hist[col] = hist[col].astype(str).str.strip().replace(falsos_vazios, "")

                # 2. DEDUPLICAÇÃO: Pega só o registro mais recente de cada Chave+Safra+UF
                hist_recente = hist.sort_values(by='DATA REGISTRADA', ascending=False).drop_duplicates(
                    subset=['Chave_CC_PRO', 'SAFRA', 'UF'], keep='first')

                # Arruma os nomes do Sheets
                hist_recente = hist_recente.rename(
                    columns={"BARTER %": "% BARTER", "DATA VENC NOVA": "DATA DE VENC NOVA"})

                # O Qlik cria as colunas estritamente como texto. Se o Sheets trouxer um número (ex: 0), o Pandas dá erro.
                # Aqui nós forçamos tudo que vem do Sheets a virar texto puro antes do loop!
                colunas_valores = ['DATA DE VENC NOVA', 'NOVO CUSTO', '% BARTER',"DERIVAÇÃO",'QUANTIDADE']
                for col_val in colunas_valores:
                    if col_val in hist_recente.columns:
                        # Converte para string e limpa os "nan" que o Pandas cria ao converter células vazias
                        hist_recente[col_val] = hist_recente[col_val].astype(str).replace("nan", "")
                        hist_recente[col_val] = hist_recente[col_val].replace("nan.0","")  # Limpa casos de decimais vazios


                # Lista para guardar as variações que precisarem ser adicionadas no final
                linhas_para_adicionar = []

                # ==============================================================
                # 3. A SUA LÓGICA: VERIFICAÇÃO LINHA A LINHA (VERSÃO BLINDADA)
                # ==============================================================

                # O arsenal anti-lixo do Qlik
                vazios = ["", " ", "-", "nan", "NaN", "None", "none", "null"]

                for index, linha_sht in hist_recente.iterrows():
                    # Garantimos que a chave de busca não tenha espaços invisíveis
                    chave = str(linha_sht['Chave_CC_PRO']).strip()
                    safra_sht = str(linha_sht['SAFRA']).strip()
                    uf_sht = str(linha_sht['UF']).strip()

                    # Procura essa chave no df do Qlik (também limpando os espaços)
                    mascara_chave = df['Chave_CC_PRO'].astype(str).str.strip() == chave

                    if mascara_chave.any():
                        # REGRA 1: Já existe essa EXATA variação?
                        mascara_exata = mascara_chave & (df['SAFRA'].astype(str).str.strip() == safra_sht) & (
                                    df['UF'].astype(str).str.strip() == uf_sht)

                        if mascara_exata.any():
                            df.loc[mascara_exata, 'DATA DE VENC NOVA'] = linha_sht['DATA DE VENC NOVA']
                            df.loc[mascara_exata, 'NOVO CUSTO'] = linha_sht['NOVO CUSTO']
                            df.loc[mascara_exata, '% BARTER'] = linha_sht['% BARTER']
                            df.loc[mascara_exata, 'QUANTIDADE'] = linha_sht['QUANTIDADE']

                        else:
                            # REGRA 2 BLINDADA: Verifica se a linha do Qlik contém qualquer tipo de "lixo" vazio
                            # O .isin() olha pra nossa lista de vazios e dá o veredito final
                            safra_qlik_limpa = df['SAFRA'].astype(str).str.strip()
                            uf_qlik_limpa = df['UF'].astype(str).str.strip()

                            mascara_vazia = mascara_chave & safra_qlik_limpa.isin(vazios) & uf_qlik_limpa.isin(vazios)

                            print(f'Essa é a mascara as cahes que vai cair no if das vazias {mascara_vazia}')
                            if mascara_vazia.any():
                                # SOBRESCREVE A LINHA VAZIA!
                                idx_vazia = df[mascara_vazia].index[0]
                                print(f'Index para ser rescrita:{idx_vazia}')
                                df.loc[idx_vazia, 'SAFRA'] = safra_sht
                                df.loc[idx_vazia, 'UF'] = uf_sht
                                df.loc[idx_vazia, 'DERIVAÇÃO'] = f"00{linha_sht['DERIVAÇÃO']}"
                                df.loc[idx_vazia, 'NOVO CUSTO'] = linha_sht['NOVO CUSTO']
                                df.loc[idx_vazia, '% BARTER'] = linha_sht['% BARTER']
                                df.loc[idx_vazia, 'QUANTIDADE'] = linha_sht['QUANTIDADE']
                                print(f'Index para ser rescrita:{linha_sht}')
                            else:
                                # REGRA 3: O PRODUTO DUPLICADO (Nova Variação)
                                linha_nova = df[mascara_chave].iloc[0].copy()
                                linha_nova['SAFRA'] = safra_sht
                                linha_nova['UF'] = uf_sht
                                linha_nova['DERIVAÇÃO'] = f"00{linha_sht['DERIVAÇÃO']}"
                                linha_nova['QUANTIDADE'] = linha_sht['QUANTIDADE']
                                linha_nova['DATA DE VENC NOVA'] = linha_sht['DATA DE VENC NOVA']
                                linha_nova['NOVO CUSTO'] = linha_sht['NOVO CUSTO']
                                linha_nova['% BARTER'] = linha_sht['% BARTER']
                                linha_nova['QUANTIDADE'] = linha_sht['QUANTIDADE']

                                linha_nova['Valor de reposição?'] = ""
                                linha_nova['Atualizar no Senior?'] = ""
                                linha_nova['DUPLICAR'] = ""
                                linha_nova['Salvar alteração'] = ""

                                linhas_para_adicionar.append(linha_nova)
                    else:
                        # Se a chave do Google Sheets não existir de forma alguma no Qlik,
                        # podemos rastrear aqui no terminal se houve erro de digitação
                        print(f"⚠️ Atenção: A chave {chave} está no Sheets mas não foi encontrada no Qlik!")

                # 4. ADICIONA AS DUPLICADAS NO FINAL DA TABELA
                if linhas_para_adicionar:
                    df_novas = pd.DataFrame(linhas_para_adicionar)
                    df = pd.concat([df, df_novas], ignore_index=True)

                # 5. MAQUIAGEM PARA O FLET (Evita o erro de NaN na tela)
                df = df.fillna("")

            else:
                print("Planilha vazia. Criando colunas em branco.")
                df["DATA DE VENC NOVA"] = ""
                df["NOVO CUSTO"] = ""
                df["% BARTER"] = ""
                df["QUANTIDADE"] = ""

            ordem_colunas = df.columns.tolist()
            ordem_colunas.remove("DUPLICAR")
            ordem_colunas.insert(0, "DUPLICAR")
            df = df[ordem_colunas]

            print("Tudo pronto! Retornando os dados.")
            return df, hist

    except Exception as e:
        print("❌ ERRO FATAL DENTRO DA FUNÇÃO DE EXTRAÇÃO:")
        print(traceback.format_exc())  # Isso vai nos mostrar EXATAMENTE onde o código está quebrando!

        # Temos que retornar DOIS Nones para o main.py não dar o erro de unpack
        return None, None