Comitando flow_respose_without_one_store
This commit is contained in:
parent
53d57e4fa3
commit
897b3b11ac
Binary file not shown.
1
app/active_sessions.json
Normal file
1
app/active_sessions.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
||||
@ -9,7 +9,10 @@ VERIFY_TOKEN = os.getenv("WEBHOOK_VERIFICATION_TOKEN")
|
||||
WHATSAPP_ACCESS_TOKEN = os.getenv("WHATSAPP_ACCESS_TOKEN")
|
||||
WHATSAPP_PHONE_NUMBER_ID = os.getenv("WHATSAPP_PHONE_NUMBER_ID")
|
||||
FLOW_PRIVATE_KEY_PASSWORD = os.getenv("FLOW_PRIVATE_KEY_PASSWORD")
|
||||
DATABASE_ODBC_CONN_STR = os.getenv("DATABASE_ODBC_CONN_STR")
|
||||
|
||||
if not DATABASE_ODBC_CONN_STR:
|
||||
raise ValueError("DATABASE_ODBC_CONN_STR não está definida no arquivo .env")
|
||||
# Verificação para garantir que as variáveis críticas estão presentes
|
||||
if not VERIFY_TOKEN:
|
||||
raise ValueError("WEBHOOK_VERIFICATION_TOKEN não está definido no .env")
|
||||
|
||||
@ -13,28 +13,73 @@ DIALOG_GRAPH: Dict[str, Dict[str, Any]] = {
|
||||
|
||||
},
|
||||
|
||||
"MENU_PRINCIPAL": {
|
||||
"message": "*Olá você inicou o Consultme!:*",
|
||||
"action_to_perform": "send_main_menu", # Ação para enviar um menu interativo (botões)
|
||||
"expected_input_type": "button_click",
|
||||
"MENU_PRINCIPAL": {
|
||||
"message": "*Olá, você iniciou o Consultme!:*",
|
||||
"action_to_perform": "send_main_menu", # Ação para enviar o menu principal (lista)
|
||||
"expected_input_type": "list_reply", # Espera um clique em um item da lista
|
||||
"transitions": {
|
||||
"OPTION_AGENDAR": "MENU_PRINCIPAL_STORE",
|
||||
"OPTION_CADASTRO_FLOW": "MENU_PRINCIPAL_STORE",
|
||||
"OPTION_STATUS": "MENU_PRINCIPAL_STORE",
|
||||
"OPTION_FALAR_ATENDENTE": "MENU_PRINCIPAL_STORE",
|
||||
"default": "MENU_PRINCIPAL_STORE" # Volta para o menu se a opção não for reconhecida
|
||||
# Transições para os estados intermediários de tempo/período
|
||||
"OPTION_ANO": "MENU_PRINCIPAL_ANO",
|
||||
"OPTION_MES": "MENU_PRINCIPAL_MES",
|
||||
"OPTION_ONTEM": "MENU_PRINCIPAL_ONTEM",
|
||||
"OPTION_HOJE": "MENU_PRINCIPAL_HOJE",
|
||||
"OPTION_SAIR": "ENCERRAR_CONVERSA",
|
||||
"default": "RESPOSTA_NAO_ENTENDIDA" # Volta para o menu se a opção não for reconhecida
|
||||
}
|
||||
},
|
||||
|
||||
"MENU_PRINCIPAL_STORE": {
|
||||
"action_to_perform": "send_main_store", # Ação para enviar um menu interativo (botões)
|
||||
"ENCERRAR_CONVERSA": {
|
||||
"message": "Você encerrou o Chat, digite algo caso precise consultar novamente.",
|
||||
"action_to_perform": "saindo_da_sessao" # Ação para enviar o menu principal (lista)
|
||||
},
|
||||
|
||||
# --- ESTADOS INTERMEDIÁRIOS APÓS ESCOLHA DO PERÍODO (MANDAM O SEGUNDO MENU) ---
|
||||
"MENU_PRINCIPAL_ANO": {
|
||||
"message": "Você escolheu o *Ano*. Agora, qual indicador deseja visualizar?",
|
||||
"action_to_perform": "send_main_store",
|
||||
"expected_input_type": "button_click",
|
||||
"transitions": {
|
||||
"OPTION_AGENDAR": "AGENDAMENTO_INICIO",
|
||||
"OPTION_CADASTRO_FLOW": "INICIAR_FLOW_CADASTRO",
|
||||
"OPTION_STATUS": "PEDIR_NUMERO_PEDIDO",
|
||||
"OPTION_FALAR_ATENDENTE": "ENCAMINHAR_ATENDENTE",
|
||||
"default": "MENU_PRINCIPAL" # Volta para o menu se a opção não for reconhecida
|
||||
"OPTION_TOTAL_CP": "RESPOSTA_ANO_TOTAL_CP", # Combinação final
|
||||
"OPTION_TOTAL_LOJAS": "RESPOSTA_ANO_TOTAL_LOJAS",
|
||||
"OPTION_TOTAL_UMA_LOJA": "RESPOSTA_ANO_UMA_LOJA",
|
||||
"OPTION_SAIR": "ENCERRAR_CONVERSA",
|
||||
"default": "RESPOSTA_NAO_ENTENDIDA" # Se não reconhecer, não entendi
|
||||
}
|
||||
},
|
||||
"MENU_PRINCIPAL_MES": {
|
||||
"message": "Você escolheu o *Mês*. Agora, qual indicador deseja visualizar?",
|
||||
"action_to_perform": "send_main_store",
|
||||
"expected_input_type": "button_click",
|
||||
"transitions": {
|
||||
"OPTION_TOTAL_CP": "RESPOSTA_MES_TOTAL_CP",
|
||||
"OPTION_TOTAL_LOJAS": "RESPOSTA_MES_TOTAL_LOJAS",
|
||||
"OPTION_TOTAL_UMA_LOJA": "RESPOSTA_MES_UMA_LOJA",
|
||||
"OPTION_SAIR": "ENCERRAR_CONVERSA",
|
||||
"default": "RESPOSTA_NAO_ENTENDIDA"
|
||||
}
|
||||
},
|
||||
"MENU_PRINCIPAL_ONTEM": {
|
||||
"message": "Você escolheu *Ontem*. Agora, qual indicador deseja visualizar?",
|
||||
"action_to_perform": "send_main_store",
|
||||
"expected_input_type": "button_click",
|
||||
"transitions": {
|
||||
"OPTION_TOTAL_CP": "RESPOSTA_ONTEM_TOTAL_CP",
|
||||
"OPTION_TOTAL_LOJAS": "RESPOSTA_ONTEM_TOTAL_LOJAS",
|
||||
"OPTION_TOTAL_UMA_LOJA": "RESPOSTA_ONTEM_UMA_LOJA",
|
||||
"OPTION_SAIR": "ENCERRAR_CONVERSA",
|
||||
"default": "RESPOSTA_NAO_ENTENDIDA"
|
||||
}
|
||||
},
|
||||
"MENU_PRINCIPAL_HOJE": {
|
||||
"message": "Você escolheu *Hoje*. Agora, qual indicador deseja visualizar?",
|
||||
"action_to_perform": "send_main_store",
|
||||
"expected_input_type": "button_click",
|
||||
"transitions": {
|
||||
"OPTION_TOTAL_CP": "RESPOSTA_HOJE_TOTAL_CP",
|
||||
"OPTION_TOTAL_LOJAS": "RESPOSTA_HOJE_TOTAL_LOJAS",
|
||||
"OPTION_TOTAL_UMA_LOJA": "RESPOSTA_HOJE_UMA_LOJA",
|
||||
"OPTION_SAIR": "ENCERRAR_CONVERSA",
|
||||
"default": "RESPOSTA_NAO_ENTENDIDA"
|
||||
}
|
||||
},
|
||||
|
||||
@ -44,97 +89,149 @@ DIALOG_GRAPH: Dict[str, Dict[str, Any]] = {
|
||||
"transitions": {
|
||||
"menu": "MENU_PRINCIPAL",
|
||||
"ajuda": "MENU_PRINCIPAL",
|
||||
"default": "RESPOSTA_NAO_ENTENDIDA" # Continua não entendendo
|
||||
"default": "RESPOSTA_NAO_ENTENDIDA"
|
||||
}
|
||||
},
|
||||
# --- FLUXO DE CADASTRO (com Flow) ---
|
||||
"INICIAR_FLOW_CADASTRO": {
|
||||
"action_to_perform": "send_flow_cadastro", # Ação para enviar o Flow
|
||||
"flow_id": "COLOQUE_AQUI_O_FLOW_ID_DO_SEU_CADASTRO_PUBLICADO", # ID do seu Flow publicado
|
||||
"flow_cta": "Abrir Cadastro",
|
||||
"expected_input_type": "flow_nfm_reply", # Espera a resposta do Flow
|
||||
"transitions": {
|
||||
"success": "CADASTRO_CONCLUIDO", # Se o Flow for concluído com sucesso
|
||||
"failure": "CADASTRO_FALHA" # Se houver um problema no Flow (ou o usuário não preencher)
|
||||
}
|
||||
},
|
||||
"CADASTRO_CONCLUIDO": {
|
||||
"message": "Obrigado por se cadastrar, ${nome_completo}! Seu e-mail é: ${email}. Já pode explorar nossos serviços!",
|
||||
"action_to_perform": "process_cadastro_data", # Ação para salvar no BD/CRM
|
||||
"expected_input_type": "any", # Qualquer coisa leva ao menu
|
||||
"transitions": {
|
||||
"default": "MENU_PRINCIPAL"
|
||||
}
|
||||
},
|
||||
"CADASTRO_FALHA": {
|
||||
"message": "Não foi possível completar seu cadastro. Por favor, tente novamente ou digite 'ajuda'.",
|
||||
"expected_input_type": "text",
|
||||
"transitions": {
|
||||
"default": "MENU_PRINCIPAL"
|
||||
}
|
||||
},
|
||||
# --- FLUXO DE AGENDAMENTO (Exemplo Básico) ---
|
||||
"AGENDAMENTO_INICIO": {
|
||||
"message": "Certo! Para agendar, qual serviço você precisa?",
|
||||
"expected_input_type": "text",
|
||||
"transitions": {
|
||||
"default": "AGENDAMENTO_CONFIRMAR_SERVICO"
|
||||
}
|
||||
},
|
||||
"AGENDAMENTO_CONFIRMAR_SERVICO": {
|
||||
"message": "Você precisa de *${servico_agendado}*. Qual data e horário você prefere?",
|
||||
"expected_input_type": "text",
|
||||
"transitions": {
|
||||
"default": "AGENDAMENTO_FINALIZAR"
|
||||
},
|
||||
"action_to_perform": "save_temp_service" # Ação para guardar o serviço temporariamente
|
||||
},
|
||||
"AGENDAMENTO_FINALIZAR": {
|
||||
"message": "Seu agendamento para *${servico_agendado}* em *${data_horario_agendado}* foi confirmado! Te vejo lá!",
|
||||
"action_to_perform": "confirm_appointment", # Ação para agendar no sistema real
|
||||
|
||||
# --- ESTADOS FINAIS COMBINATÓRIOS (12 ESTADOS) ---
|
||||
# Período: ANO
|
||||
"RESPOSTA_ANO_TOTAL_CP": {
|
||||
"message": " Consulta realizada com sucesso! \n\n💰 O realizado da Receita em GMV é: *${receita}* e o Boleto Médio é: *${boleto}*",
|
||||
"action_to_perform": "get_combined_indicator_data_ano_cp", # Ação para buscar e formatar dados
|
||||
"expected_input_type": "any",
|
||||
"transitions": {
|
||||
"default": "MENU_PRINCIPAL"
|
||||
}
|
||||
"transitions": {"default": "MENU_PRINCIPAL"}
|
||||
},
|
||||
# --- FLUXO DE STATUS DO PEDIDO ---
|
||||
"PEDIR_NUMERO_PEDIDO": {
|
||||
"message": "Por favor, digite o número do seu pedido para consultar o status:",
|
||||
"expected_input_type": "text",
|
||||
"transitions": {
|
||||
"default": "CONSULTAR_STATUS_API"
|
||||
}
|
||||
},
|
||||
"CONSULTAR_STATUS_API": {
|
||||
"message": "Consultando o status do pedido *${numero_pedido}*...",
|
||||
"action_to_perform": "call_external_status_api", # Ação para chamar um sistema externo (API futura)
|
||||
"expected_input_type": "api_response", # A resposta vem de uma API, não do usuário
|
||||
"transitions": {
|
||||
"success": "STATUS_EXIBIR",
|
||||
"failure": "STATUS_NAO_ENCONTRADO"
|
||||
}
|
||||
},
|
||||
"STATUS_EXIBIR": {
|
||||
"message": "O status do pedido *${numero_pedido}* é: *${status_retornado}*.",
|
||||
"RESPOSTA_ANO_TOTAL_LOJAS": {
|
||||
"message": " Consulta realizada com sucesso! \n\n💰 O realizado da Receita em GMV é: *${receita}* e o Boleto Médio é: *${boleto}*",
|
||||
"action_to_perform": "get_combined_indicator_data_ano_lojas",
|
||||
"expected_input_type": "any",
|
||||
"transitions": {
|
||||
"default": "MENU_PRINCIPAL"
|
||||
}
|
||||
"transitions": {"default": "MENU_PRINCIPAL"}
|
||||
},
|
||||
"STATUS_NAO_ENCONTRADO": {
|
||||
"message": "Não consegui encontrar o pedido *${numero_pedido}*. Verifique e digite novamente, ou 'menu'.",
|
||||
"RESPOSTA_ANO_UMA_LOJA": {
|
||||
"message": "Você escolheu o Total de uma Loja do *Ano*. Por favor, digite o ID da loja.",
|
||||
"action_to_perform": "set_context_for_store_id_input_ano_loja", # Ação para guardar o contexto de "Ano" e esperar o ID da loja
|
||||
"expected_input_type": "text",
|
||||
"transitions": {
|
||||
"menu": "MENU_PRINCIPAL",
|
||||
"default": "PEDIR_NUMERO_PEDIDO"
|
||||
"default": "PROCESSAR_ID_LOJA_ANO" # Leva ao processamento do ID da loja
|
||||
}
|
||||
},
|
||||
# --- FLUXO DE ATENDENTE ---
|
||||
"ENCAMINHAR_ATENDENTE": {
|
||||
"message": "Encaminhando você para um de nossos atendentes. Por favor, aguarde.",
|
||||
"terminal": True # Indica que a conversa termina aqui (até o atendente assumir)
|
||||
|
||||
# Período: MÊS
|
||||
"RESPOSTA_MES_TOTAL_CP": {
|
||||
"message": " Consulta realizada com sucesso! \n\n💰 O realizado da Receita em GMV é: *${receita}* e o Boleto Médio é: *${boleto}*",
|
||||
"action_to_perform": "get_combined_indicator_data_mes_cp",
|
||||
"expected_input_type": "any",
|
||||
"transitions": {"default": "MENU_PRINCIPAL"}
|
||||
},
|
||||
# ... Adicione mais estados conforme a complexidade da sua conversa ...
|
||||
"RESPOSTA_MES_TOTAL_LOJAS": {
|
||||
"message": " Consulta realizada com sucesso! \n\n💰 O realizado da Receita em GMV é: *${receita}* e o Boleto Médio é: *${boleto}*",
|
||||
"action_to_perform": "get_combined_indicator_data_mes_lojas",
|
||||
"expected_input_type": "any",
|
||||
"transitions": {"default": "MENU_PRINCIPAL"}
|
||||
},
|
||||
"RESPOSTA_MES_UMA_LOJA": {
|
||||
"message": "Você escolheu o Total de uma Loja do *Mês*. Por favor, digite o ID da loja.",
|
||||
"action_to_perform": "set_context_for_store_id_input_mes_loja",
|
||||
"expected_input_type": "text",
|
||||
"transitions": {
|
||||
"default": "PROCESSAR_ID_LOJA_MES"
|
||||
}
|
||||
},
|
||||
|
||||
# Período: ONTEM
|
||||
"RESPOSTA_ONTEM_TOTAL_CP": {
|
||||
"message": " Consulta realizada com sucesso! \n\n💰 O realizado da Receita em GMV é: *${receita}* e o Boleto Médio é: *${boleto}*",
|
||||
"action_to_perform": "get_combined_indicator_data_ontem_cp",
|
||||
"expected_input_type": "any",
|
||||
"transitions": {"default": "MENU_PRINCIPAL"}
|
||||
},
|
||||
"RESPOSTA_ONTEM_TOTAL_LOJAS": {
|
||||
"message": " Consulta realizada com sucesso! \n\n💰 O realizado da Receita em GMV é: *${receita}* e o Boleto Médio é: *${boleto}*",
|
||||
"action_to_perform": "get_combined_indicator_data_ontem_lojas",
|
||||
"expected_input_type": "any",
|
||||
"transitions": {"default": "MENU_PRINCIPAL"}
|
||||
},
|
||||
"RESPOSTA_ONTEM_UMA_LOJA": {
|
||||
"message": "Você escolheu o Total de uma Loja de *Ontem*. Por favor, digite o ID da loja.",
|
||||
"action_to_perform": "set_context_for_store_id_input_ontem_loja",
|
||||
"expected_input_type": "text",
|
||||
"transitions": {
|
||||
"default": "PROCESSAR_ID_LOJA_ONTEM"
|
||||
}
|
||||
},
|
||||
|
||||
# Período: HOJE
|
||||
"RESPOSTA_HOJE_TOTAL_CP": {
|
||||
"message": " Consulta realizada com sucesso! \n\n💰 O realizado da Receita em GMV é: *${receita}* e o Boleto Médio é: *${boleto}*",
|
||||
"action_to_perform": "get_combined_indicator_data_hoje_cp",
|
||||
"expected_input_type": "any",
|
||||
"transitions": {"default": "MENU_PRINCIPAL"}
|
||||
},
|
||||
"RESPOSTA_HOJE_TOTAL_LOJAS": {
|
||||
"message": " Consulta realizada com sucesso! \n\n💰 O realizado da Receita em GMV é: *${receita}* e o Boleto Médio é: *${boleto}*",
|
||||
"action_to_perform": "get_combined_indicator_data_hoje_lojas",
|
||||
"expected_input_type": "any",
|
||||
"transitions": {"default": "MENU_PRINCIPAL"}
|
||||
},
|
||||
"RESPOSTA_HOJE_UMA_LOJA": {
|
||||
"message": "Você escolheu o Total de uma Loja de *Hoje*. Por favor, digite o ID da loja.",
|
||||
"action_to_perform": "set_context_for_store_id_input_hoje_loja",
|
||||
"expected_input_type": "text",
|
||||
"transitions": {
|
||||
"default": "PROCESSAR_ID_LOJA_HOJE"
|
||||
}
|
||||
},
|
||||
|
||||
# --- ESTADOS PARA PROCESSAR O ID DA LOJA (PARA 'UMA LOJA') ---
|
||||
"PROCESSAR_ID_LOJA_ANO": {
|
||||
"message": "Consultando indicador da loja ${id_loja} para o *Ano*...",
|
||||
"action_to_perform": "get_store_indicator_ano",
|
||||
"expected_input_type": "api_response", # Ação para pegar o ID digitado e consultar a API
|
||||
"transitions": {
|
||||
"success": "EXIBIR_INDICADOR_LOJA_ANO",
|
||||
"failure": "LOJA_NAO_ENCONTRADA_ANO"
|
||||
}
|
||||
},
|
||||
"PROCESSAR_ID_LOJA_MES": {
|
||||
"message": "Consultando indicador da loja ${id_loja} para o *Mês*...",
|
||||
"action_to_perform": "get_store_indicator_mes",
|
||||
"expected_input_type": "api_response",
|
||||
"transitions": {
|
||||
"success": "EXIBIR_INDICADOR_LOJA_MES",
|
||||
"failure": "LOJA_NAO_ENCONTRADA_MES"
|
||||
}
|
||||
},
|
||||
"PROCESSAR_ID_LOJA_ONTEM": {
|
||||
"message": "Consultando indicador da loja ${id_loja} para *Ontem*...",
|
||||
"action_to_perform": "get_store_indicator_ontem",
|
||||
"expected_input_type": "api_response",
|
||||
"transitions": {
|
||||
"success": "EXIBIR_INDICADOR_LOJA_ONTEM",
|
||||
"failure": "LOJA_NAO_ENCONTRADA_ONTEM"
|
||||
}
|
||||
},
|
||||
"PROCESSAR_ID_LOJA_HOJE": {
|
||||
"message": "Consultando indicador da loja ${id_loja} para *Hoje*...",
|
||||
"action_to_perform": "get_store_indicator_hoje",
|
||||
"expected_input_type": "api_response",
|
||||
"transitions": {
|
||||
"success": "EXIBIR_INDICADOR_LOJA_HOJE",
|
||||
"failure": "LOJA_NAO_ENCONTRADA_HOJE"
|
||||
}
|
||||
},
|
||||
|
||||
# --- ESTADOS PARA EXIBIR INDICADOR DE LOJA ESPECÍFICA (COMBINAÇÕES) ---
|
||||
"EXIBIR_INDICADOR_LOJA_ANO": {
|
||||
"message": "O indicador da loja *${id_loja}* do *Ano* é: *${indicador_loja}*.",
|
||||
"expected_input_type": "any",
|
||||
"transitions": {"default": "MENU_PRINCIPAL"}
|
||||
},
|
||||
"LOJA_NAO_ENCONTRADA_ANO": {
|
||||
"message": "Loja *${id_loja}* não encontrada para o *Ano*. Digite novamente ou 'menu'.",
|
||||
"expected_input_type": "text",
|
||||
"transitions": {"default": "RESPOSTA_ANO_UMA_LOJA", "menu": "MENU_PRINCIPAL"} # Volta para pedir a loja para o Ano
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
# --- ESTADO INICIAL ---
|
||||
|
||||
Binary file not shown.
@ -3,6 +3,9 @@ from typing import List, Dict, Any, Optional
|
||||
import json
|
||||
import base64
|
||||
import os
|
||||
import pyodbc
|
||||
import requests
|
||||
|
||||
|
||||
|
||||
|
||||
@ -20,11 +23,22 @@ scheduler = AsyncIOScheduler()
|
||||
|
||||
# --- Variáveis Globais para Gerenciamento de Sessão (APENAS PARA TESTE) ---
|
||||
# Em produção, isso seria um banco de dados
|
||||
ACTIVE_SESSIONS_FILE = "active_sessions.json" # Nome do arquivo de persistência
|
||||
ACTIVE_SESSIONS = {} # Dicionário para armazenar sessões ativas
|
||||
SESSION_TIMEOUT_SECONDS = 120 # 5 minutos (5 * 60 segundos)
|
||||
# --- FIM DAS VARIÁVEIS GLOBAIS DE SESSÃO ---
|
||||
|
||||
|
||||
# Em produção, esta lista viria de um banco de dados ou de um sistema de gerenciamento de usuários.
|
||||
AUTHORIZED_NUMBERS = {
|
||||
"558291655353", # João <-- SEU NÚMERO DE TELEFONE (com 55 DDD)
|
||||
"558282309484", # Fernanda
|
||||
"558298104313", # Efigenia
|
||||
"557981017347", # Taciana
|
||||
"558291202979", # Gabrielle
|
||||
"557196046142" # Laiane Exemplo de outro número autorizado
|
||||
# Adicione os números de telefone (apenas dígitos, no formato 55DDDxxxxxxxx) que você quer autorizar
|
||||
}
|
||||
UNAUTHORIZED_MESSAGE = "Desculpe, seu número não está cadastrado em nosso sistema. Por favor, entre em contato com nosso suporte para mais informações. Obrigado!"
|
||||
|
||||
# Importações para criptografia
|
||||
from cryptography.hazmat.primitives.asymmetric import padding # <-- Esta linha está correta!
|
||||
@ -34,7 +48,7 @@ from cryptography.hazmat.primitives.serialization import load_pem_private_key, l
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
|
||||
|
||||
from config import WHATSAPP_ACCESS_TOKEN, WHATSAPP_PHONE_NUMBER_ID, VERIFY_TOKEN, FLOW_PRIVATE_KEY_PASSWORD # <-- ADICIONADO
|
||||
from config import WHATSAPP_ACCESS_TOKEN, WHATSAPP_PHONE_NUMBER_ID, VERIFY_TOKEN, FLOW_PRIVATE_KEY_PASSWORD, DATABASE_ODBC_CONN_STR # <-- ADICIONADO
|
||||
# Importe Message para type hinting. Certifique-se que webhook_model.py está correto primeiro!
|
||||
# Certifique-se de importar o grafo e o estado inicial
|
||||
from dialog_flow.graph_definition import DIALOG_GRAPH, INITIAL_STATE_ID
|
||||
@ -204,59 +218,443 @@ def encrypt_flow_response_data(response_data: Dict[str, Any], aes_key: bytes, in
|
||||
|
||||
return base64_encoded_final_response
|
||||
|
||||
# Persistência de Sessão:
|
||||
# --- Funções de Persistência de Sessão ---
|
||||
def load_sessions():
|
||||
"""Carrega sessões ativas de um arquivo JSON. Chamada na inicialização do app."""
|
||||
global ACTIVE_SESSIONS
|
||||
if os.path.exists(ACTIVE_SESSIONS_FILE):
|
||||
try:
|
||||
with open(ACTIVE_SESSIONS_FILE, 'r') as f:
|
||||
data = json.load(f)
|
||||
loaded_count = 0
|
||||
for sender_id, session_info in data.items():
|
||||
# Converte last_activity_time de string ISO para datetime
|
||||
session_info['last_activity_time'] = datetime.fromisoformat(session_info['last_activity_time'])
|
||||
ACTIVE_SESSIONS[sender_id] = session_info
|
||||
loaded_count += 1
|
||||
print(f"DEBUG_SESSION_PERSIST: {loaded_count} sessões carregadas do arquivo {ACTIVE_SESSIONS_FILE}.")
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"❌ ERRO_SESSION_PERSIST: Falha ao carregar sessões (JSON inválido): {e}")
|
||||
except Exception as e:
|
||||
print(f"❌ ERRO_SESSION_PERSIST: Erro inesperado ao carregar sessões: {e}")
|
||||
else:
|
||||
print(f"DEBUG_SESSION_PERSIST: Nenhum arquivo de sessão '{ACTIVE_SESSIONS_FILE}' encontrado para carregar.")
|
||||
|
||||
def save_sessions():
|
||||
"""Salva sessões ativas em um arquivo JSON. Chamada após modificações e no desligamento do app."""
|
||||
data_to_save = {}
|
||||
for sender_id, session_info in ACTIVE_SESSIONS.items():
|
||||
# Converte datetime para string ISO para serialização JSON
|
||||
session_info_copy = session_info.copy()
|
||||
session_info_copy['last_activity_time'] = session_info_copy['last_activity_time'].isoformat()
|
||||
data_to_save[sender_id] = session_info_copy
|
||||
try:
|
||||
with open(ACTIVE_SESSIONS_FILE, 'w') as f:
|
||||
json.dump(data_to_save, f, indent=4)
|
||||
print(f"DEBUG_SESSION_PERSIST: {len(ACTIVE_SESSIONS)} sessões salvas no arquivo {ACTIVE_SESSIONS_FILE}.")
|
||||
except Exception as e:
|
||||
print(f"❌ ERRO_SESSION_PERSIST: Falha ao salvar sessões: {e}")
|
||||
|
||||
|
||||
# --- Funções de Gerenciamento de Sessão ---
|
||||
|
||||
def get_session_state(sender_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Retorna o estado atual da sessão para um usuário.
|
||||
"""
|
||||
return ACTIVE_SESSIONS.get(sender_id)
|
||||
|
||||
async def clean_session_and_notify(sender_id: str): # <-- FUNÇÃO ASSÍNCRONA PARA LIMPAR E NOTIFICAR
|
||||
def start_or_update_session(sender_id: str, new_state: Optional[str] = None, last_message_id: Optional[str] = None):
|
||||
"""
|
||||
Função assíncrona que limpa a sessão e envia a mensagem de timeout.
|
||||
Chamada pelo scheduler.
|
||||
"""
|
||||
if sender_id in ACTIVE_SESSIONS: # Verifica se a sessão ainda está ativa (não foi atualizada antes do timeout)
|
||||
timeout_message = "Sua sessão foi encerrada por inatividade. Por favor, envie uma nova mensagem para iniciar um novo atendimento. 😊"
|
||||
print(f"DEBUG_SESSION: Enviando mensagem de timeout para {sender_id}.")
|
||||
await send_text_message(sender_id, timeout_message) # <-- CHAMA A FUNÇÃO DE ENVIO
|
||||
|
||||
del ACTIVE_SESSIONS[sender_id]
|
||||
print(f"DEBUG_SESSION: Sessão para {sender_id} encerrada/limpa pelo agendador.")
|
||||
else:
|
||||
print(f"DEBUG_SESSION: Sessão para {sender_id} já limpa ou atualizada antes do agendamento.")
|
||||
|
||||
def start_or_update_session(sender_id: str):
|
||||
"""
|
||||
Inicia uma nova sessão para o usuário ou atualiza o timestamp da última atividade.
|
||||
Agenda ou reagenda a tarefa de limpeza.
|
||||
Inicia uma nova sessão para o usuário ou atualiza o timestamp, estado e último ID de mensagem.
|
||||
"""
|
||||
current_time = datetime.now()
|
||||
ACTIVE_SESSIONS[sender_id] = {
|
||||
"last_activity_time": current_time,
|
||||
"current_state": "INICIO" # Ou qualquer estado inicial padrão
|
||||
}
|
||||
print(f"DEBUG_SESSION: Sessão para {sender_id} iniciada/atualizada em {current_time.strftime('%Y-%m-%d %H:%M:%S')}.")
|
||||
session_info = ACTIVE_SESSIONS.get(sender_id, {})
|
||||
|
||||
# Define o estado da sessão:
|
||||
if new_state:
|
||||
session_info["current_state"] = new_state
|
||||
elif "current_state" not in session_info: # Apenas se for uma sessão nova (não tem estado ainda)
|
||||
session_info["current_state"] = INITIAL_STATE_ID # Usar INITIAL_STATE_ID do graph_definition
|
||||
|
||||
session_info["last_activity_time"] = current_time
|
||||
if last_message_id:
|
||||
session_info["last_processed_message_id"] = last_message_id
|
||||
|
||||
ACTIVE_SESSIONS[sender_id] = session_info # Salva as informações atualizadas na memória
|
||||
|
||||
save_sessions() # Persiste na memória para o arquivo
|
||||
|
||||
print(f"DEBUG_SESSION: Sessão para {sender_id} iniciada/atualizada em {current_time.strftime('%Y-%m-%d %H:%M:%S')}. Estado: {session_info['current_state']}.")
|
||||
if last_message_id:
|
||||
print(f"DEBUG_SESSION: Último ID de mensagem para {sender_id}: {session_info.get('last_processed_message_id')}.")
|
||||
|
||||
# Remover tarefa agendada anterior para esta sessão, se houver
|
||||
job_id = f"session_clean_{sender_id}"
|
||||
if scheduler.get_job(job_id):
|
||||
scheduler.remove_job(job_id)
|
||||
print(f"DEBUG_SESSION: Tarefa de limpeza anterior para {sender_id} cancelada.")
|
||||
|
||||
# Agendar nova tarefa de limpeza para esta sessão
|
||||
scheduler.add_job(
|
||||
clean_session_and_notify,
|
||||
'date', # Agendamento único para uma data/hora específica
|
||||
'date',
|
||||
run_date=current_time + timedelta(seconds=SESSION_TIMEOUT_SECONDS),
|
||||
args=[sender_id],
|
||||
id=job_id,
|
||||
replace_existing=True # Para garantir que não haja duplicatas, embora remove_job já ajude
|
||||
replace_existing=True
|
||||
)
|
||||
print(f"DEBUG_SESSION: Tarefa de limpeza para {sender_id} agendada para {current_time + timedelta(seconds=SESSION_TIMEOUT_SECONDS)}.")
|
||||
|
||||
|
||||
async def clean_session_and_notify(sender_id: str):
|
||||
"""
|
||||
Função assíncrona que limpa a sessão e envia a mensagem de timeout.
|
||||
Chamada pelo scheduler.
|
||||
"""
|
||||
# A função send_text_message deve estar disponível no webhook_service.py
|
||||
# (ou importada, mas ela já deveria estar definida no seu código)
|
||||
|
||||
if sender_id in ACTIVE_SESSIONS:
|
||||
timeout_message = "Sua sessão foi encerrada por inatividade. Caso precise, inicie uma nova conversa."
|
||||
print(f"DEBUG_SESSION: Enviando mensagem de timeout para {sender_id}.")
|
||||
# Você deve ter a função send_text_message definida e funcional neste arquivo
|
||||
# ou importada de outro módulo de serviço.
|
||||
await send_text_message(sender_id, timeout_message) # <-- Assume que send_text_message está disponível
|
||||
|
||||
del ACTIVE_SESSIONS[sender_id]
|
||||
print(f"DEBUG_SESSION: Sessão para {sender_id} encerrada/limpa pelo agendador.")
|
||||
save_sessions() # Persiste no arquivo após limpeza
|
||||
else:
|
||||
print(f"DEBUG_SESSION: Sessão para {sender_id} já limpa ou atualizada antes do agendamento.")
|
||||
|
||||
async def saindo_da_sessao(sender_id: str):
|
||||
"""
|
||||
Função assíncrona que limpa a sessão e envia a mensagem de timeout.
|
||||
Chamada pelo scheduler.
|
||||
"""
|
||||
# A função send_text_message deve estar disponível no webhook_service.py
|
||||
# (ou importada, mas ela já deveria estar definida no seu código)
|
||||
|
||||
if sender_id in ACTIVE_SESSIONS:
|
||||
timeout_message = "Sua sessão foi encerrada por inatividade. Caso precise, inicie uma nova conversa."
|
||||
print(f"DEBUG_SESSION: Enviando mensagem de timeout para {sender_id}.")
|
||||
# Você deve ter a função send_text_message definida e funcional neste arquivo
|
||||
# ou importada de outro módulo de serviço.
|
||||
del ACTIVE_SESSIONS[sender_id]
|
||||
print(f"DEBUG_SESSION: Sessão para {sender_id} encerrada/limpa pelo agendador.")
|
||||
save_sessions() # Persiste no arquivo após limpeza
|
||||
else:
|
||||
print(f"DEBUG_SESSION: Sessão para {sender_id} já limpa ou atualizada antes do agendamento.")
|
||||
|
||||
# Funções que fazem a consulta no Banco de Dados.
|
||||
async def get_combined_indicator_data_ano_cp() -> Dict[str, Any]:
|
||||
try:
|
||||
conn = pyodbc.connect(DATABASE_ODBC_CONN_STR)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Comando SQL para selecionar o token
|
||||
sql = "SELECT SUM([RECEITA (R$)]) AS receita_total, SUM([RECEITA (R$)]) / SUM([NUMERO DE BOLETOS]) AS boleto_medio FROM HUBSUPPLY.dbo.gmv_ano WHERE PDV != 'TOTAL';"
|
||||
cursor.execute(sql)
|
||||
|
||||
# Fetch o resultado
|
||||
row = cursor.fetchone()
|
||||
|
||||
if row:
|
||||
receita = str(row[0])
|
||||
boleto = str(row[1])
|
||||
|
||||
except pyodbc.Error as ex:
|
||||
sqlstate = ex.args[0] if ex.args else 'N/A'
|
||||
# NOVO: Imprima o erro completo, incluindo a mensagem detalhada do pyodbc
|
||||
print(f"❌ ERRO_DB: Erro de conexão ou execução no SQL Server.")
|
||||
print(f"❌ ERRO_DB: SQLSTATE={sqlstate}")
|
||||
print(f"❌ ERRO_DB: Mensagem detalhada: {ex.args[1] if len(ex.args) > 1 else 'N/A'}")
|
||||
print(f"❌ ERRO_DB: Objeto de exceção completo: {ex}") # Isso mostrará o erro completo
|
||||
return {}
|
||||
|
||||
print(f"DEBUG_ACTION: Valores obtidos: CP={receita}, Boleto Médio={boleto}")
|
||||
|
||||
return {
|
||||
"receita": receita,
|
||||
"boleto": boleto
|
||||
}
|
||||
|
||||
async def get_combined_indicator_data_ano_lojas(sender_id: str) -> Dict[str, Any]:
|
||||
|
||||
supervisores_pdvs = {
|
||||
"558298104313": "AND PDV in ('4560', '12522', '12823', '12826', '12828', '12829', '14617', '19103', '20969', '20991', '21647', '910173', '910291')",
|
||||
"558282309484": "AND PDV in ('20005', '20006', '20009', '20056', '21068', '21375', '21381')",
|
||||
"557981017347": "AND PDV in ('20441', '20968')",
|
||||
"557196046142": "AND PDV in ('20006', '20056', '21068')",
|
||||
"558291655353" : "AND PDV in ('20006', '20056', '21068')",
|
||||
"558291202979" :"AND PDV in ('20441', '20968')"
|
||||
}
|
||||
|
||||
print(supervisores_pdvs[sender_id])
|
||||
|
||||
try:
|
||||
conn = pyodbc.connect(DATABASE_ODBC_CONN_STR)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Comando SQL para selecionar o token
|
||||
sql = f"SELECT SUM([RECEITA (R$)]) AS receita_total, SUM([RECEITA (R$)]) / SUM([NUMERO DE BOLETOS]) AS boleto_medio FROM HUBSUPPLY.dbo.gmv_ano WHERE PDV != 'TOTAL' {supervisores_pdvs[sender_id]};"
|
||||
cursor.execute(sql)
|
||||
|
||||
# Fetch o resultado
|
||||
row = cursor.fetchone()
|
||||
|
||||
if row:
|
||||
receita = str(row[0])
|
||||
boleto = str(row[1])
|
||||
|
||||
except pyodbc.Error as ex:
|
||||
sqlstate = ex.args[0] if ex.args else 'N/A'
|
||||
# NOVO: Imprima o erro completo, incluindo a mensagem detalhada do pyodbc
|
||||
print(f"❌ ERRO_DB: Erro de conexão ou execução no SQL Server.")
|
||||
print(f"❌ ERRO_DB: SQLSTATE={sqlstate}")
|
||||
print(f"❌ ERRO_DB: Mensagem detalhada: {ex.args[1] if len(ex.args) > 1 else 'N/A'}")
|
||||
print(f"❌ ERRO_DB: Objeto de exceção completo: {ex}") # Isso mostrará o erro completo
|
||||
return {}
|
||||
|
||||
print(f"DEBUG_ACTION: Valores obtidos: CP={receita}, Boleto Médio={boleto}")
|
||||
|
||||
return {
|
||||
"receita": receita,
|
||||
"boleto": boleto
|
||||
}
|
||||
|
||||
async def get_combined_indicator_data_mes_cp() -> Dict[str, Any]:
|
||||
try:
|
||||
conn = pyodbc.connect(DATABASE_ODBC_CONN_STR)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Comando SQL para selecionar o token
|
||||
sql = "SELECT SUM([RECEITA (R$)]) AS receita_total, SUM([RECEITA (R$)]) / SUM([NUMERO DE BOLETOS]) AS boleto_medio FROM HUBSUPPLY.dbo.gmv_mês WHERE PDV != 'TOTAL';"
|
||||
cursor.execute(sql)
|
||||
|
||||
# Fetch o resultado
|
||||
row = cursor.fetchone()
|
||||
|
||||
if row:
|
||||
receita = str(row[0])
|
||||
boleto = str(row[1])
|
||||
|
||||
except pyodbc.Error as ex:
|
||||
sqlstate = ex.args[0] if ex.args else 'N/A'
|
||||
# NOVO: Imprima o erro completo, incluindo a mensagem detalhada do pyodbc
|
||||
print(f"❌ ERRO_DB: Erro de conexão ou execução no SQL Server.")
|
||||
print(f"❌ ERRO_DB: SQLSTATE={sqlstate}")
|
||||
print(f"❌ ERRO_DB: Mensagem detalhada: {ex.args[1] if len(ex.args) > 1 else 'N/A'}")
|
||||
print(f"❌ ERRO_DB: Objeto de exceção completo: {ex}") # Isso mostrará o erro completo
|
||||
return {}
|
||||
|
||||
print(f"DEBUG_ACTION: Valores obtidos: CP={receita}, Boleto Médio={boleto}")
|
||||
|
||||
return {
|
||||
"receita": receita,
|
||||
"boleto": boleto
|
||||
}
|
||||
|
||||
async def get_combined_indicator_data_mes_lojas(sender_id: str) -> Dict[str, Any]:
|
||||
|
||||
supervisores_pdvs = {
|
||||
"558298104313": "AND PDV in ('4560', '12522', '12823', '12826', '12828', '12829', '14617', '19103', '20969', '20991', '21647', '910173', '910291')",
|
||||
"558282309484": "AND PDV in ('20005', '20006', '20009', '20056', '21068', '21375', '21381')",
|
||||
"557981017347": "AND PDV in ('20441', '20968')",
|
||||
"557196046142": "AND PDV in ('20006', '20056', '21068')",
|
||||
"558291655353" : "AND PDV in ('20006', '20056', '21068')",
|
||||
"558291202979" :"AND PDV in ('20441', '20968')"
|
||||
}
|
||||
|
||||
print(supervisores_pdvs[sender_id])
|
||||
|
||||
|
||||
|
||||
try:
|
||||
conn = pyodbc.connect(DATABASE_ODBC_CONN_STR)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Comando SQL para selecionar o token
|
||||
sql = f"SELECT SUM([RECEITA (R$)]) AS receita_total, SUM([RECEITA (R$)]) / SUM([NUMERO DE BOLETOS]) AS boleto_medio FROM HUBSUPPLY.dbo.gmv_mês WHERE PDV != 'TOTAL' {supervisores_pdvs[sender_id]};"
|
||||
cursor.execute(sql)
|
||||
|
||||
# Fetch o resultado
|
||||
row = cursor.fetchone()
|
||||
|
||||
if row:
|
||||
receita = str(row[0])
|
||||
boleto = str(row[1])
|
||||
|
||||
except pyodbc.Error as ex:
|
||||
sqlstate = ex.args[0] if ex.args else 'N/A'
|
||||
# NOVO: Imprima o erro completo, incluindo a mensagem detalhada do pyodbc
|
||||
print(f"❌ ERRO_DB: Erro de conexão ou execução no SQL Server.")
|
||||
print(f"❌ ERRO_DB: SQLSTATE={sqlstate}")
|
||||
print(f"❌ ERRO_DB: Mensagem detalhada: {ex.args[1] if len(ex.args) > 1 else 'N/A'}")
|
||||
print(f"❌ ERRO_DB: Objeto de exceção completo: {ex}") # Isso mostrará o erro completo
|
||||
return {}
|
||||
|
||||
print(f"DEBUG_ACTION: Valores obtidos: CP={receita}, Boleto Médio={boleto}")
|
||||
|
||||
return {
|
||||
"receita": receita,
|
||||
"boleto": boleto
|
||||
}
|
||||
|
||||
|
||||
async def get_combined_indicator_data_ontem_cp() -> Dict[str, Any]:
|
||||
|
||||
|
||||
|
||||
try:
|
||||
conn = pyodbc.connect(DATABASE_ODBC_CONN_STR)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Comando SQL para selecionar o token
|
||||
sql = "SELECT SUM([RECEITA (R$)]) AS receita_total, SUM([RECEITA (R$)]) / SUM([NUMERO DE BOLETOS]) AS boleto_medio FROM HUBSUPPLY.dbo.gmv_ontem WHERE PDV != 'TOTAL';"
|
||||
cursor.execute(sql)
|
||||
|
||||
# Fetch o resultado
|
||||
row = cursor.fetchone()
|
||||
|
||||
if row:
|
||||
receita = str(row[0])
|
||||
boleto = str(row[1])
|
||||
|
||||
except pyodbc.Error as ex:
|
||||
sqlstate = ex.args[0] if ex.args else 'N/A'
|
||||
# NOVO: Imprima o erro completo, incluindo a mensagem detalhada do pyodbc
|
||||
print(f"❌ ERRO_DB: Erro de conexão ou execução no SQL Server.")
|
||||
print(f"❌ ERRO_DB: SQLSTATE={sqlstate}")
|
||||
print(f"❌ ERRO_DB: Mensagem detalhada: {ex.args[1] if len(ex.args) > 1 else 'N/A'}")
|
||||
print(f"❌ ERRO_DB: Objeto de exceção completo: {ex}") # Isso mostrará o erro completo
|
||||
return {}
|
||||
|
||||
print(f"DEBUG_ACTION: Valores obtidos: CP={receita}, Boleto Médio={boleto}")
|
||||
|
||||
return {
|
||||
"receita": receita,
|
||||
"boleto": boleto
|
||||
}
|
||||
|
||||
async def get_combined_indicator_data_ontem_lojas(sender_id: str) -> Dict[str, Any]:
|
||||
|
||||
supervisores_pdvs = {
|
||||
"558298104313": "AND PDV in ('4560', '12522', '12823', '12826', '12828', '12829', '14617', '19103', '20969', '20991', '21647', '910173', '910291')",
|
||||
"558282309484": "AND PDV in ('20005', '20006', '20009', '20056', '21068', '21375', '21381')",
|
||||
"557981017347": "AND PDV in ('20441', '20968')",
|
||||
"557196046142": "AND PDV in ('20006', '20056', '21068')",
|
||||
"558291655353" : "AND PDV in ('20006', '20056', '21068')",
|
||||
"558291202979" :"AND PDV in ('20441', '20968')"
|
||||
}
|
||||
|
||||
print(supervisores_pdvs[sender_id])
|
||||
|
||||
try:
|
||||
conn = pyodbc.connect(DATABASE_ODBC_CONN_STR)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Comando SQL para selecionar o token
|
||||
sql = f"SELECT SUM([RECEITA (R$)]) AS receita_total, SUM([RECEITA (R$)]) / SUM([NUMERO DE BOLETOS]) AS boleto_medio FROM HUBSUPPLY.dbo.gmv_ontem WHERE PDV != 'TOTAL' {supervisores_pdvs[sender_id]};"
|
||||
cursor.execute(sql)
|
||||
|
||||
# Fetch o resultado
|
||||
row = cursor.fetchone()
|
||||
|
||||
if row:
|
||||
receita = str(row[0])
|
||||
boleto = str(row[1])
|
||||
|
||||
except pyodbc.Error as ex:
|
||||
sqlstate = ex.args[0] if ex.args else 'N/A'
|
||||
# NOVO: Imprima o erro completo, incluindo a mensagem detalhada do pyodbc
|
||||
print(f"❌ ERRO_DB: Erro de conexão ou execução no SQL Server.")
|
||||
print(f"❌ ERRO_DB: SQLSTATE={sqlstate}")
|
||||
print(f"❌ ERRO_DB: Mensagem detalhada: {ex.args[1] if len(ex.args) > 1 else 'N/A'}")
|
||||
print(f"❌ ERRO_DB: Objeto de exceção completo: {ex}") # Isso mostrará o erro completo
|
||||
return {}
|
||||
|
||||
print(f"DEBUG_ACTION: Valores obtidos: CP={receita}, Boleto Médio={boleto}")
|
||||
|
||||
return {
|
||||
"receita": receita,
|
||||
"boleto": boleto
|
||||
}
|
||||
|
||||
async def get_combined_indicator_data_hoje_cp() -> Dict[str, Any]:
|
||||
try:
|
||||
conn = pyodbc.connect(DATABASE_ODBC_CONN_STR)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Comando SQL para selecionar o token
|
||||
sql = "SELECT SUM([RECEITA (R$)]) AS receita_total, SUM([RECEITA (R$)]) / SUM([NUMERO DE BOLETOS]) AS boleto_medio FROM HUBSUPPLY.dbo.gmv_hoje WHERE PDV != 'TOTAL';"
|
||||
cursor.execute(sql)
|
||||
|
||||
# Fetch o resultado
|
||||
row = cursor.fetchone()
|
||||
|
||||
if row:
|
||||
receita = str(row[0])
|
||||
boleto = str(row[1])
|
||||
|
||||
except pyodbc.Error as ex:
|
||||
sqlstate = ex.args[0] if ex.args else 'N/A'
|
||||
# NOVO: Imprima o erro completo, incluindo a mensagem detalhada do pyodbc
|
||||
print(f"❌ ERRO_DB: Erro de conexão ou execução no SQL Server.")
|
||||
print(f"❌ ERRO_DB: SQLSTATE={sqlstate}")
|
||||
print(f"❌ ERRO_DB: Mensagem detalhada: {ex.args[1] if len(ex.args) > 1 else 'N/A'}")
|
||||
print(f"❌ ERRO_DB: Objeto de exceção completo: {ex}") # Isso mostrará o erro completo
|
||||
return {}
|
||||
|
||||
print(f"DEBUG_ACTION: Valores obtidos: CP={receita}, Boleto Médio={boleto}")
|
||||
|
||||
return {
|
||||
"receita": receita,
|
||||
"boleto": boleto
|
||||
}
|
||||
|
||||
async def get_combined_indicator_data_hoje_lojas(sender_id: str) -> Dict[str, Any]:
|
||||
|
||||
supervisores_pdvs = {
|
||||
"558298104313": "AND PDV in ('4560', '12522', '12823', '12826', '12828', '12829', '14617', '19103', '20969', '20991', '21647', '910173', '910291')",
|
||||
"558282309484": "AND PDV in ('20005', '20006', '20009', '20056', '21068', '21375', '21381')",
|
||||
"557981017347": "AND PDV in ('20441', '20968')",
|
||||
"557196046142": "AND PDV in ('20006', '20056', '21068')",
|
||||
"558291655353" : "AND PDV in ('20006', '20056', '21068')",
|
||||
"558291202979" :"AND PDV in ('20441', '20968')"
|
||||
}
|
||||
|
||||
print(supervisores_pdvs[sender_id])
|
||||
|
||||
|
||||
try:
|
||||
conn = pyodbc.connect(DATABASE_ODBC_CONN_STR)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Comando SQL para selecionar o token
|
||||
sql = f"SELECT SUM([RECEITA (R$)]) AS receita_total, SUM([RECEITA (R$)]) / SUM([NUMERO DE BOLETOS]) AS boleto_medio FROM HUBSUPPLY.dbo.gmv_hoje WHERE PDV != 'TOTAL' {supervisores_pdvs[sender_id]};"
|
||||
cursor.execute(sql)
|
||||
|
||||
# Fetch o resultado
|
||||
row = cursor.fetchone()
|
||||
|
||||
if row:
|
||||
receita = str(row[0])
|
||||
boleto = str(row[1])
|
||||
|
||||
except pyodbc.Error as ex:
|
||||
sqlstate = ex.args[0] if ex.args else 'N/A'
|
||||
# NOVO: Imprima o erro completo, incluindo a mensagem detalhada do pyodbc
|
||||
print(f"❌ ERRO_DB: Erro de conexão ou execução no SQL Server.")
|
||||
print(f"❌ ERRO_DB: SQLSTATE={sqlstate}")
|
||||
print(f"❌ ERRO_DB: Mensagem detalhada: {ex.args[1] if len(ex.args) > 1 else 'N/A'}")
|
||||
print(f"❌ ERRO_DB: Objeto de exceção completo: {ex}") # Isso mostrará o erro completo
|
||||
return {}
|
||||
|
||||
print(f"DEBUG_ACTION: Valores obtidos: CP={receita}, Boleto Médio={boleto}")
|
||||
|
||||
return {
|
||||
"receita": receita,
|
||||
"boleto": boleto
|
||||
}
|
||||
|
||||
#Fluxo de mensagens:
|
||||
|
||||
@ -302,6 +700,10 @@ async def process_user_input_with_graph(sender_id: str, message_type: str, messa
|
||||
# O message_content já é o payload do botão
|
||||
next_state_id = current_state.get("transitions", {}).get(message_content, INITIAL_STATE_ID)
|
||||
|
||||
elif message_type == "list_reply":
|
||||
# O message_content já é o payload do botão
|
||||
next_state_id = current_state.get("transitions", {}).get(message_content, INITIAL_STATE_ID)
|
||||
|
||||
elif message_type == "flow_nfm_reply":
|
||||
# Quando uma resposta de Flow chega, o next_state depende do sucesso/falha do Flow
|
||||
# Assumimos que handle_flow_response já processou o Flow e determinou sucesso/falha
|
||||
@ -318,7 +720,34 @@ async def process_user_input_with_graph(sender_id: str, message_type: str, messa
|
||||
|
||||
print(f"DEBUG_GRAPH: Usuário {sender_id} transicionou para o estado: '{next_state_id}'")
|
||||
await execute_state_action_and_respond(sender_id, next_state_id, session_data)
|
||||
|
||||
def format_currency_brl(value: Any) -> str: # <-- Mude o tipo para Any para ser mais flexível na entrada
|
||||
"""
|
||||
Formata um valor para o formato de moeda BRL (R$ 0.000,00),
|
||||
garantindo a conversão para float primeiro.
|
||||
"""
|
||||
if value is None:
|
||||
return "N/A"
|
||||
|
||||
# Tenta converter o valor para float, capturando possíveis erros
|
||||
try:
|
||||
float_value = float(value)
|
||||
except (ValueError, TypeError):
|
||||
print(f"❌ ERRO_FORMAT: Valor '{value}' não pode ser convertido para float para formatação de moeda. Retornando como string.")
|
||||
return str(value) # Se não puder converter, retorna o valor original como string
|
||||
|
||||
# Formatação original (robusta para floats)
|
||||
formatted_value = f"{float_value:,.2f}"
|
||||
|
||||
if '.' in formatted_value and ',' in formatted_value:
|
||||
parts = formatted_value.split('.')
|
||||
integer_part_with_commas = parts[0]
|
||||
decimal_part = parts[1]
|
||||
formatted_value = integer_part_with_commas.replace(',', '.') + ',' + decimal_part
|
||||
elif '.' in formatted_value:
|
||||
formatted_value = formatted_value.replace('.', ',')
|
||||
|
||||
return f"R$ {formatted_value}"
|
||||
|
||||
async def execute_state_action_and_respond(sender_id: str, state_id: str, session_data: Dict[str, Any]):
|
||||
"""
|
||||
@ -341,6 +770,159 @@ async def execute_state_action_and_respond(sender_id: str, state_id: str, sessio
|
||||
await send_time_menu(sender_id)
|
||||
elif action == "send_main_store":
|
||||
await send_store_menu(sender_id)
|
||||
elif action == "get_combined_indicator_data_ano_cp": # <-- NOVA AÇÃO AGORA
|
||||
db_results = await get_combined_indicator_data_ano_cp()
|
||||
|
||||
if db_results:
|
||||
# Armazene cada valor retornado em chaves separadas da sessão
|
||||
session_data["receita"] = format_currency_brl(db_results.get('receita', 0.0))
|
||||
session_data["boleto"] = format_currency_brl(db_results.get('boleto', 0.0)) # Aplica a formatação também para boleto
|
||||
|
||||
print(f"DEBUG_GRAPH: Valores CP='{session_data["receita"]}' e Boleto_Medio='{session_data["boleto"]}' salvos na sessão.")
|
||||
start_or_update_session(sender_id, new_state=state_id, last_message_id=session_data.get("last_processed_message_id")) # Atualiza sessão com os dados
|
||||
else:
|
||||
session_data["receita"] = "indisponível"
|
||||
session_data["boleto"] = "indisponível"
|
||||
print("DEBUG_GRAPH: Falha ao obter Total do CP e Lojas Ativas.")
|
||||
await send_text_message(sender_id, "Não foi possível obter os indicadores no momento. Por favor, tente mais tarde.")
|
||||
# Opcional: Transicionar para um estado de erro ou para o menu principal
|
||||
session_data["current_state"] = DIALOG_GRAPH.get("MENU_PRINCIPAL", {}).get("transitions", {}).get("default", INITIAL_STATE_ID)
|
||||
start_or_update_session(sender_id, new_state=session_data["current_state"], last_message_id=session_data.get("last_processed_message_id"))
|
||||
return # Interrompe a execução para não enviar a mensagem do estado normal
|
||||
|
||||
elif action == "get_combined_indicator_data_ano_lojas": # <-- NOVA AÇÃO AGORA
|
||||
db_results = await get_combined_indicator_data_ano_lojas(sender_id)
|
||||
if db_results:
|
||||
# Armazene cada valor retornado em chaves separadas da sessão
|
||||
session_data["receita"] = format_currency_brl(db_results.get('receita', 0.0))
|
||||
session_data["boleto"] = format_currency_brl(db_results.get('boleto', 0.0)) # Aplica a formatação também para boleto
|
||||
|
||||
print(f"DEBUG_GRAPH: Valores CP='{session_data["receita"]}' e Boleto_Medio='{session_data["boleto"]}' salvos na sessão.")
|
||||
start_or_update_session(sender_id, new_state=state_id, last_message_id=session_data.get("last_processed_message_id")) # Atualiza sessão com os dados
|
||||
else:
|
||||
session_data["receita"] = "indisponível"
|
||||
session_data["boleto"] = "indisponível"
|
||||
print("DEBUG_GRAPH: Falha ao obter Total do CP e Lojas Ativas.")
|
||||
await send_text_message(sender_id, "Não foi possível obter os indicadores no momento. Por favor, tente mais tarde.")
|
||||
# Opcional: Transicionar para um estado de erro ou para o menu principal
|
||||
session_data["current_state"] = DIALOG_GRAPH.get("MENU_PRINCIPAL", {}).get("transitions", {}).get("default", INITIAL_STATE_ID)
|
||||
start_or_update_session(sender_id, new_state=session_data["current_state"], last_message_id=session_data.get("last_processed_message_id"))
|
||||
return # Interrompe a execução para não enviar a mensagem do estado normal
|
||||
|
||||
elif action == "get_combined_indicator_data_mes_cp": # <-- NOVA AÇÃO AGORA
|
||||
db_results = await get_combined_indicator_data_mes_cp()
|
||||
if db_results:
|
||||
# Armazene cada valor retornado em chaves separadas da sessão
|
||||
session_data["receita"] = format_currency_brl(db_results.get('receita', 0.0))
|
||||
session_data["boleto"] = format_currency_brl(db_results.get('boleto', 0.0)) # Aplica a formatação também para boleto
|
||||
|
||||
print(f"DEBUG_GRAPH: Valores CP='{session_data["receita"]}' e Boleto_Medio='{session_data["boleto"]}' salvos na sessão.")
|
||||
start_or_update_session(sender_id, new_state=state_id, last_message_id=session_data.get("last_processed_message_id")) # Atualiza sessão com os dados
|
||||
else:
|
||||
session_data["receita"] = "indisponível"
|
||||
session_data["boleto"] = "indisponível"
|
||||
print("DEBUG_GRAPH: Falha ao obter Total do CP e Lojas Ativas.")
|
||||
await send_text_message(sender_id, "Não foi possível obter os indicadores no momento. Por favor, tente mais tarde.")
|
||||
# Opcional: Transicionar para um estado de erro ou para o menu principal
|
||||
session_data["current_state"] = DIALOG_GRAPH.get("MENU_PRINCIPAL", {}).get("transitions", {}).get("default", INITIAL_STATE_ID)
|
||||
start_or_update_session(sender_id, new_state=session_data["current_state"], last_message_id=session_data.get("last_processed_message_id"))
|
||||
return # Interrompe a execução para não enviar a mensagem do estado normal
|
||||
|
||||
elif action == "get_combined_indicator_data_mes_lojas": # <-- NOVA AÇÃO AGORA
|
||||
db_results = await get_combined_indicator_data_mes_lojas(sender_id)
|
||||
if db_results:
|
||||
# Armazene cada valor retornado em chaves separadas da sessão
|
||||
session_data["receita"] = format_currency_brl(db_results.get('receita', 0.0))
|
||||
session_data["boleto"] = format_currency_brl(db_results.get('boleto', 0.0)) # Aplica a formatação também para boleto
|
||||
|
||||
print(f"DEBUG_GRAPH: Valores CP='{session_data["receita"]}' e Boleto_Medio='{session_data["boleto"]}' salvos na sessão.")
|
||||
start_or_update_session(sender_id, new_state=state_id, last_message_id=session_data.get("last_processed_message_id")) # Atualiza sessão com os dados
|
||||
else:
|
||||
session_data["receita"] = "indisponível"
|
||||
session_data["boleto"] = "indisponível"
|
||||
print("DEBUG_GRAPH: Falha ao obter Total do CP e Lojas Ativas.")
|
||||
await send_text_message(sender_id, "Não foi possível obter os indicadores no momento. Por favor, tente mais tarde.")
|
||||
# Opcional: Transicionar para um estado de erro ou para o menu principal
|
||||
session_data["current_state"] = DIALOG_GRAPH.get("MENU_PRINCIPAL", {}).get("transitions", {}).get("default", INITIAL_STATE_ID)
|
||||
start_or_update_session(sender_id, new_state=session_data["current_state"], last_message_id=session_data.get("last_processed_message_id"))
|
||||
return # Interrompe a execução para não enviar a mensagem do estado normal
|
||||
|
||||
elif action == "get_combined_indicator_data_ontem_cp": # <-- NOVA AÇÃO AGORA
|
||||
db_results = await get_combined_indicator_data_ontem_cp()
|
||||
if db_results:
|
||||
# Armazene cada valor retornado em chaves separadas da sessão
|
||||
session_data["receita"] = format_currency_brl(db_results.get('receita', 0.0))
|
||||
session_data["boleto"] = format_currency_brl(db_results.get('boleto', 0.0)) # Aplica a formatação também para boleto
|
||||
|
||||
print(f"DEBUG_GRAPH: Valores CP='{session_data["receita"]}' e Boleto_Medio='{session_data["boleto"]}' salvos na sessão.")
|
||||
start_or_update_session(sender_id, new_state=state_id, last_message_id=session_data.get("last_processed_message_id")) # Atualiza sessão com os dados
|
||||
else:
|
||||
session_data["receita"] = "indisponível"
|
||||
session_data["boleto"] = "indisponível"
|
||||
print("DEBUG_GRAPH: Falha ao obter Total do CP e Lojas Ativas.")
|
||||
await send_text_message(sender_id, "Não foi possível obter os indicadores no momento. Por favor, tente mais tarde.")
|
||||
# Opcional: Transicionar para um estado de erro ou para o menu principal
|
||||
session_data["current_state"] = DIALOG_GRAPH.get("MENU_PRINCIPAL", {}).get("transitions", {}).get("default", INITIAL_STATE_ID)
|
||||
start_or_update_session(sender_id, new_state=session_data["current_state"], last_message_id=session_data.get("last_processed_message_id"))
|
||||
return # Interrompe a execução para não enviar a mensagem do estado normal
|
||||
|
||||
elif action == "get_combined_indicator_data_ontem_lojas": # <-- NOVA AÇÃO AGORA
|
||||
db_results = await get_combined_indicator_data_ontem_lojas()
|
||||
if db_results:
|
||||
# Armazene cada valor retornado em chaves separadas da sessão
|
||||
session_data["receita"] = format_currency_brl(db_results.get('receita', 0.0))
|
||||
session_data["boleto"] = format_currency_brl(db_results.get('boleto', 0.0)) # Aplica a formatação também para boleto
|
||||
|
||||
print(f"DEBUG_GRAPH: Valores CP='{session_data["receita"]}' e Boleto_Medio='{session_data["boleto"]}' salvos na sessão.")
|
||||
start_or_update_session(sender_id, new_state=state_id, last_message_id=session_data.get("last_processed_message_id")) # Atualiza sessão com os dados
|
||||
else:
|
||||
session_data["receita"] = "indisponível"
|
||||
session_data["boleto"] = "indisponível"
|
||||
print("DEBUG_GRAPH: Falha ao obter Total do CP e Lojas Ativas.")
|
||||
await send_text_message(sender_id, "Não foi possível obter os indicadores no momento. Por favor, tente mais tarde.")
|
||||
# Opcional: Transicionar para um estado de erro ou para o menu principal
|
||||
session_data["current_state"] = DIALOG_GRAPH.get("MENU_PRINCIPAL", {}).get("transitions", {}).get("default", INITIAL_STATE_ID)
|
||||
start_or_update_session(sender_id, new_state=session_data["current_state"], last_message_id=session_data.get("last_processed_message_id"))
|
||||
return # Interrompe a execução para não enviar a mensagem do estado normal
|
||||
|
||||
elif action == "get_combined_indicator_data_hoje_cp": # <-- NOVA AÇÃO AGORA
|
||||
db_results = await get_combined_indicator_data_hoje_cp()
|
||||
if db_results:
|
||||
# Armazene cada valor retornado em chaves separadas da sessão
|
||||
session_data["receita"] = format_currency_brl(db_results.get('receita', 0.0))
|
||||
session_data["boleto"] = format_currency_brl(db_results.get('boleto', 0.0)) # Aplica a formatação também para boleto
|
||||
|
||||
print(f"DEBUG_GRAPH: Valores CP='{session_data["receita"]}' e Boleto_Medio='{session_data["boleto"]}' salvos na sessão.")
|
||||
start_or_update_session(sender_id, new_state=state_id, last_message_id=session_data.get("last_processed_message_id")) # Atualiza sessão com os dados
|
||||
else:
|
||||
session_data["receita"] = "indisponível"
|
||||
session_data["boleto"] = "indisponível"
|
||||
print("DEBUG_GRAPH: Falha ao obter Total do CP e Lojas Ativas.")
|
||||
await send_text_message(sender_id, "Não foi possível obter os indicadores no momento. Por favor, tente mais tarde.")
|
||||
# Opcional: Transicionar para um estado de erro ou para o menu principal
|
||||
session_data["current_state"] = DIALOG_GRAPH.get("MENU_PRINCIPAL", {}).get("transitions", {}).get("default", INITIAL_STATE_ID)
|
||||
start_or_update_session(sender_id, new_state=session_data["current_state"], last_message_id=session_data.get("last_processed_message_id"))
|
||||
return # Interrompe a execução para não enviar a mensagem do estado normal
|
||||
|
||||
elif action == "get_combined_indicator_data_hoje_lojas": # <-- NOVA AÇÃO AGORA
|
||||
db_results = await get_combined_indicator_data_hoje_lojas()
|
||||
if db_results:
|
||||
# Armazene cada valor retornado em chaves separadas da sessão
|
||||
session_data["receita"] = format_currency_brl(db_results.get('receita', 0.0))
|
||||
session_data["boleto"] = format_currency_brl(db_results.get('boleto', 0.0)) # Aplica a formatação também para boleto
|
||||
|
||||
print(f"DEBUG_GRAPH: Valores CP='{session_data["receita"]}' e Boleto_Medio='{session_data["boleto"]}' salvos na sessão.")
|
||||
start_or_update_session(sender_id, new_state=state_id, last_message_id=session_data.get("last_processed_message_id")) # Atualiza sessão com os dados
|
||||
else:
|
||||
session_data["receita"] = "indisponível"
|
||||
session_data["boleto"] = "indisponível"
|
||||
print("DEBUG_GRAPH: Falha ao obter Total do CP e Lojas Ativas.")
|
||||
await send_text_message(sender_id, "Não foi possível obter os indicadores no momento. Por favor, tente mais tarde.")
|
||||
# Opcional: Transicionar para um estado de erro ou para o menu principal
|
||||
session_data["current_state"] = DIALOG_GRAPH.get("MENU_PRINCIPAL", {}).get("transitions", {}).get("default", INITIAL_STATE_ID)
|
||||
start_or_update_session(sender_id, new_state=session_data["current_state"], last_message_id=session_data.get("last_processed_message_id"))
|
||||
return # Interrompe a execução para não enviar a mensagem do estado normal
|
||||
|
||||
elif action == "send_flow_cadastro":
|
||||
flow_id = state_definition.get("flow_id")
|
||||
flow_cta = state_definition.get("flow_cta")
|
||||
@ -371,34 +953,7 @@ async def execute_state_action_and_respond(sender_id: str, state_id: str, sessio
|
||||
start_or_update_session(sender_id)
|
||||
|
||||
|
||||
elif action == "save_temp_service":
|
||||
# Exemplo: guardar o serviço agendado
|
||||
# service = session_data.get("last_text_input") # Pega o último texto que o usuário enviou
|
||||
# session_data["servico_agendado"] = service
|
||||
# start_or_update_session(sender_id) # Atualiza a sessão
|
||||
pass # Ação interna, não envia mensagem aqui
|
||||
|
||||
elif action == "confirm_appointment":
|
||||
# Exemplo: confirmar agendamento no sistema externo
|
||||
# (Chamada de API para sistema de agendamento)
|
||||
pass # Ação interna, a mensagem já vem do state_definition
|
||||
|
||||
|
||||
elif action == "call_external_status_api":
|
||||
# AQUI é onde você faria a chamada para o seu sistema externo
|
||||
# Por enquanto, apenas um placeholder
|
||||
# numero_pedido = session_data.get("last_text_input")
|
||||
# print(f"DEBUG_GRAPH: Chamando API externa para pedido {numero_pedido}")
|
||||
# try:
|
||||
# status_api = await external_api_call(numero_pedido)
|
||||
# session_data["status_retornado"] = status_api # Salva na sessão
|
||||
# # Transicionar internamente para STATUS_EXIBIR ou STATUS_NAO_ENCONTRADO
|
||||
# await execute_state_action_and_respond(sender_id, "STATUS_EXIBIR", session_data) # Transição interna
|
||||
# return
|
||||
# except Exception:
|
||||
# await execute_state_action_and_respond(sender_id, "STATUS_NAO_ENCONTRADO", session_data) # Transição interna
|
||||
# return
|
||||
pass # Implementação futura, a mensagem vem do estado.
|
||||
|
||||
|
||||
# --- Enviar a mensagem do novo estado (se houver) ---
|
||||
if "message" in state_definition:
|
||||
@ -410,6 +965,11 @@ async def execute_state_action_and_respond(sender_id: str, state_id: str, sessio
|
||||
if "${email}" in final_message and session_data.get("flow_data"):
|
||||
flow_data = json.loads(session_data["flow_data"])
|
||||
final_message = final_message.replace("${email}", flow_data.get("email", "não informado"))
|
||||
if "${receita}" in final_message:
|
||||
final_message = final_message.replace("${receita}", str(session_data.get("receita", "N/A")))
|
||||
if "${boleto}" in final_message:
|
||||
final_message = final_message.replace("${boleto}", str(session_data.get("boleto", "N/A")))
|
||||
|
||||
|
||||
# Para outros placeholders como ${servico_agendado}, ${data_horario_agendado}, ${numero_pedido}, ${status_retornado}
|
||||
# você faria substituições semelhantes baseadas em session_data
|
||||
@ -425,18 +985,6 @@ async def execute_state_action_and_respond(sender_id: str, state_id: str, sessio
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# --- Lógicas de Tratamento de Mensagens Recebidas (Funções Auxiliares) ---
|
||||
# Estas funções contêm a lógica de como seu bot irá interagir.
|
||||
|
||||
@ -444,66 +992,85 @@ async def execute_state_action_and_respond(sender_id: str, state_id: str, sessio
|
||||
#--- FUNÇÃO handle_message_type: A VERSÃO CORRETA PARA O TIMEOUT E GRAFO ---
|
||||
async def handle_message_type(message: Message):
|
||||
sender_id = message.from_
|
||||
message_id = message.id # O ID da mensagem recebida
|
||||
|
||||
# 1. Limpar sessões inativas
|
||||
# Esta função limpa as sessões que expiraram e envia a mensagem de timeout via scheduler.
|
||||
start_or_update_session(sender_id) # Esta é a que precisa ser assíncrona
|
||||
# 1. ANTI-DUPLICAÇÃO DE MENSAGENS:
|
||||
# Obtém a sessão ATUAL (se existir) para verificar o último ID processado.
|
||||
session_data_for_check = get_session_state(sender_id)
|
||||
if session_data_for_check and session_data_for_check.get("last_processed_message_id") == message_id:
|
||||
print(f"⚠️ Mensagem duplicada recebida para {sender_id} (ID: {message_id}). Ignorando.")
|
||||
# Retorna imediatamente para evitar reprocessamento.
|
||||
# Nenhuma sessão é atualizada, nem agendamento refeito.
|
||||
return
|
||||
|
||||
# 2. INICIAR OU ATUALIZAR A SESSÃO PARA O REMETENTE ATUAL.
|
||||
# ISSO É ESSENCIAL PARA O TIMEOUT. Se o usuário mandar mensagem, a sessão dele é atualizada
|
||||
# e o agendamento de timeout é resetado.
|
||||
start_or_update_session(sender_id)
|
||||
# 2. VERIFICAR WHITELIST DE NÚMEROS (SEGUNDO)
|
||||
if sender_id not in AUTHORIZED_NUMBERS:
|
||||
print(f"INFO_BOT_CONTROL: Número {sender_id} NÃO autorizado. Enviando mensagem de não cadastrado.")
|
||||
await send_text_message(sender_id, UNAUTHORIZED_MESSAGE)
|
||||
return # Não processa mais nada se o número não estiver na whitelist
|
||||
# NENHUMA CHAMADA explícita para clean_inactive_sessions() AQUI.
|
||||
# A limpeza de sessões inativas já é tratada pelo scheduler em background.
|
||||
|
||||
# 3. Recuperar o estado da sessão (já atualizado)
|
||||
session_data = get_session_state(sender_id)
|
||||
if session_data:
|
||||
print(f"DEBUG_SESSION: Estado atual da sessão para {sender_id}: {session_data['current_state']}")
|
||||
else:
|
||||
# Isso não deveria acontecer se start_or_update_session funcionou
|
||||
print(f"DEBUG_SESSION: ERRO: Sessão para {sender_id} não encontrada após atualização. Isso é inesperado.")
|
||||
# Se por algum motivo não tiver sessão, podemos resetar para o estado inicial
|
||||
start_or_update_session(sender_id)
|
||||
session_data = get_session_state(sender_id) # Tenta novamente
|
||||
|
||||
# 3. INICIAR OU ATUALIZAR A SESSÃO, INCLUINDO O ID DA MENSAGEM ATUAL:
|
||||
# Isso atualizará o last_activity_time, agendará o timeout, e salvará o novo message_id.
|
||||
start_or_update_session(sender_id, last_message_id=message_id)
|
||||
|
||||
# Recarrega session_data para garantir que 'last_processed_message_id' esteja lá e para
|
||||
# obter o 'current_state' mais recente que start_or_update_session pode ter inicializado.
|
||||
session_data = get_session_state(sender_id)
|
||||
|
||||
# Esta verificação agora é mais um fallback, pois start_or_update_session deve garantir a existência.
|
||||
if not session_data:
|
||||
print(f"DEBUG_SESSION: ERRO GRAVE: Sessão para {sender_id} ainda não encontrada após start_or_update_session. Isso é crítico.")
|
||||
# Tenta uma recuperação de emergência, mas indica um problema
|
||||
start_or_update_session(sender_id, new_state=INITIAL_STATE_ID, last_message_id=message_id)
|
||||
session_data = get_session_state(sender_id)
|
||||
if not session_data: # Se ainda assim falhar, algo está muito errado
|
||||
print("❌ ERRO FATAL: Falha crítica na gestão de sessão. Não é possível processar a mensagem.")
|
||||
return # Não é seguro continuar
|
||||
|
||||
|
||||
print(f"DEBUG_SESSION: Estado atual da sessão para {sender_id}: {session_data['current_state']}")
|
||||
print(f"DEBUG_SESSION: ID da mensagem atual processada: {message_id}")
|
||||
|
||||
message_content = None
|
||||
message_type = None
|
||||
|
||||
# --- IDENTIFICAÇÃO DO TIPO DE MENSAGEM ---
|
||||
# Esta parte permanece como estava, identificando o tipo e conteúdo.
|
||||
# --- IDENTIFICAÇÃO DO TIPO DE MENSAGEM (permanece a mesma) ---
|
||||
if message.type == 'text' and message.text:
|
||||
message_type = "text"
|
||||
message_content = message.text.body
|
||||
elif message.type == 'button' and message.button: # Resposta de botão de resposta rápida
|
||||
elif message.type == 'button' and message.button:
|
||||
message_type = "button_click"
|
||||
message_content = message.button.payload
|
||||
elif message.type == 'interactive' and message.interactive:
|
||||
if message.interactive.type == 'list_reply' and message.interactive.list_reply: # Resposta de lista
|
||||
if message.interactive.type == 'list_reply' and message.interactive.list_reply:
|
||||
message_type = "list_reply"
|
||||
message_content = message.interactive.list_reply.id
|
||||
elif message.interactive.type == 'button_reply' and message.interactive.button_reply: # Resposta de botão interativo
|
||||
message_type = "button_click" # Tratar como clique de botão
|
||||
elif message.interactive.type == 'button_reply' and message.interactive.button_reply:
|
||||
message_type = "button_click"
|
||||
message_content = message.interactive.button_reply.id
|
||||
elif message.interactive.type == 'nfm_reply' and message.interactive.nfm_reply: # Resposta de Flow
|
||||
elif message.interactive.type == 'nfm_reply' and message.interactive.nfm_reply:
|
||||
message_type = "flow_nfm_reply"
|
||||
message_content = message.interactive.nfm_reply.response_json
|
||||
else:
|
||||
print(f" Tipo interativo desconhecido recebido: {message.interactive.type}")
|
||||
await send_text_message(sender_id, "Desculpe, não entendi essa interação interativa.")
|
||||
return # Sai, não há transição no grafo para isso
|
||||
elif message.type == 'image': # Mensagens que não são processadas pelo grafo
|
||||
return
|
||||
elif message.type == 'image':
|
||||
message_type = "image"
|
||||
message_content = "imagem_recebida" # Placeholder
|
||||
message_content = "imagem_recebida"
|
||||
await send_text_message(sender_id, "Recebi sua imagem. No momento, só consigo processar texto e interações.")
|
||||
return # Sai, não há transição no grafo para isso
|
||||
else: # Tipo de mensagem não suportado ou desconhecido
|
||||
return
|
||||
else:
|
||||
message_type = "unsupported"
|
||||
message_content = "tipo_desconhecido"
|
||||
await send_text_message(sender_id, "Desculpe, não entendi o tipo de mensagem que você enviou.")
|
||||
return # Sai
|
||||
return
|
||||
|
||||
# 4. O motor do grafo processa a entrada e gerencia o estado da sessão.
|
||||
# A `session_data` já foi atualizada no passo 2.
|
||||
# 3. O motor do grafo processa a entrada e gerencia o estado da sessão.
|
||||
# A `session_data` já está atualizada com o último ID da mensagem.
|
||||
await process_user_input_with_graph(sender_id, message_type, message_content)
|
||||
|
||||
|
||||
@ -627,7 +1194,7 @@ async def send_time_menu(to: str): # <-- FUNÇÃO QUE VOCÊ PEDIU
|
||||
"""
|
||||
Envia uma mensagem interativa de LISTA para o usuário com opções de lojas.
|
||||
"""
|
||||
header_text = "Escolha o Período que você quer visualizar o indicador"
|
||||
header_text = "Escolha o Período que você deseja visualizar:"
|
||||
body_text = "Veja as opções a baixo"
|
||||
button_title = "Clique aqui" # Texto do botão que o usuário clica para ABRIR a lista
|
||||
|
||||
@ -663,23 +1230,22 @@ async def send_store_menu(to: str): # <-- FUNÇÃO QUE VOCÊ PEDIU
|
||||
"""
|
||||
Envia uma mensagem interativa de LISTA para o usuário com opções de lojas.
|
||||
"""
|
||||
header_text = "Escolha o Período que você quer visualizar o indicador"
|
||||
header_text = "Escolha a Dimensão das Lojas que você deseja visualizar:"
|
||||
body_text = "Veja as opções a baixo"
|
||||
button_title = "Clique aqui" # Texto do botão que o usuário clica para ABRIR a lista
|
||||
|
||||
sections = [
|
||||
{
|
||||
"title": "Acumulados", # Título da primeira seção
|
||||
"title": "Acumulado", # Título da primeira seção
|
||||
"rows": [
|
||||
{"id": 'OPTION_ANO', "title": 'Total do CP', "description": "Visualize o resultado Total do CP"},
|
||||
{"id": 'OPTION_MES', "title": 'Total do Estado', "description": "Visualize o resultado Total das suas Lojas"},
|
||||
{"id": 'OPTION_TOTAL_CP', "title": 'Total do CP', "description": "Visualize o resultado Total do CP"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Por Loja", # Título da segunda seção
|
||||
"rows": [
|
||||
{"id": 'OPTION_ONTEM', "title": 'Total das suas Lojas', "description": "Visualize o total das suas Lojas"},
|
||||
{"id": 'OPTION_HOJE', "title": 'Total de uma Loja', "description": "Visualize o total de uma loja."}
|
||||
{"id": 'OPTION_TOTAL_LOJAS', "title": 'Total das suas Lojas', "description": "Visualize o total das suas Lojas"},
|
||||
{"id": 'OPTION_TOTAL_UMA_LOJA', "title": 'Total de uma Loja', "description": "Visualize o total de uma loja."}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user