Spaces:
Running
Running
Update src/modules/notifications.py
Browse files- src/modules/notifications.py +138 -43
src/modules/notifications.py
CHANGED
|
@@ -1,73 +1,168 @@
|
|
|
|
|
|
|
|
| 1 |
import smtplib
|
| 2 |
from email.mime.text import MIMEText
|
| 3 |
from email.mime.multipart import MIMEMultipart
|
| 4 |
from datetime import datetime, timedelta
|
| 5 |
-
import pandas as pd
|
| 6 |
-
import streamlit as st
|
| 7 |
|
| 8 |
-
#
|
| 9 |
-
#
|
| 10 |
-
#
|
| 11 |
-
#
|
| 12 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
-
def
|
|
|
|
|
|
|
|
|
|
| 15 |
try:
|
| 16 |
-
# Récupération des secrets (si configurés)
|
| 17 |
smtp_server = st.secrets["smtp"]["server"]
|
| 18 |
smtp_port = st.secrets["smtp"]["port"]
|
| 19 |
sender_email = st.secrets["smtp"]["email"]
|
| 20 |
sender_password = st.secrets["smtp"]["password"]
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
server = smtplib.SMTP(smtp_server, smtp_port)
|
| 29 |
server.starttls()
|
| 30 |
server.login(sender_email, sender_password)
|
| 31 |
-
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
server.quit()
|
| 34 |
return True
|
| 35 |
except Exception as e:
|
| 36 |
-
st.error(f"Erreur
|
| 37 |
return False
|
| 38 |
|
| 39 |
def verifier_et_notifier_echeances(client, sheet_name):
|
| 40 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
|
| 42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
try:
|
| 44 |
sh = client.open(sheet_name)
|
| 45 |
-
|
| 46 |
-
#
|
| 47 |
ws_prets = sh.worksheet("Prets_Master")
|
| 48 |
df_prets = pd.DataFrame(ws_prets.get_all_records())
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
|
| 50 |
-
#
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
|
| 72 |
except Exception as e:
|
| 73 |
-
st.error(f"Erreur : {e}")
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import pandas as pd
|
| 3 |
import smtplib
|
| 4 |
from email.mime.text import MIMEText
|
| 5 |
from email.mime.multipart import MIMEMultipart
|
| 6 |
from datetime import datetime, timedelta
|
|
|
|
|
|
|
| 7 |
|
| 8 |
+
# ==============================================================================
|
| 9 |
+
# CONFIGURATION SMTP (À sécuriser via st.secrets sur Hugging Face)
|
| 10 |
+
# ==============================================================================
|
| 11 |
+
# Dans st.secrets, ajoutez une section [smtp] avec :
|
| 12 |
+
# server = "smtp.gmail.com"
|
| 13 |
+
# port = 587
|
| 14 |
+
# email = "votre_email@gmail.com"
|
| 15 |
+
# password = "votre_mot_de_passe_application"
|
| 16 |
+
# ==============================================================================
|
| 17 |
|
| 18 |
+
def envoyer_email_rappel(destinataire_client, destinataire_admin, details_pret):
|
| 19 |
+
"""
|
| 20 |
+
Envoie un email au Client (Rappel) et à l'Admin (Notification).
|
| 21 |
+
"""
|
| 22 |
try:
|
|
|
|
| 23 |
smtp_server = st.secrets["smtp"]["server"]
|
| 24 |
smtp_port = st.secrets["smtp"]["port"]
|
| 25 |
sender_email = st.secrets["smtp"]["email"]
|
| 26 |
sender_password = st.secrets["smtp"]["password"]
|
| 27 |
+
except Exception:
|
| 28 |
+
st.error("⚠️ Secrets SMTP non configurés. Impossible d'envoyer l'email.")
|
| 29 |
+
return False
|
| 30 |
|
| 31 |
+
sujet = f"🔔 Rappel Échéance - Prêt {details_pret['ID_Pret']}"
|
| 32 |
+
|
| 33 |
+
# Corps du message
|
| 34 |
+
corps = f"""
|
| 35 |
+
Bonjour {details_pret['Nom_Complet']},
|
| 36 |
+
|
| 37 |
+
Ceci est un rappel automatique concernant votre prêt chez Vortex-Flux.
|
| 38 |
+
|
| 39 |
+
Une échéance de paiement est prévue pour demain.
|
| 40 |
+
|
| 41 |
+
📋 Détails de l'échéance :
|
| 42 |
+
--------------------------------
|
| 43 |
+
ID Prêt : {details_pret['ID_Pret']}
|
| 44 |
+
Date prévue : {details_pret['Date_Echeance']}
|
| 45 |
+
Montant : {details_pret['Montant_Versement']} XOF
|
| 46 |
+
--------------------------------
|
| 47 |
|
| 48 |
+
Merci de prévoir les fonds nécessaires.
|
| 49 |
+
|
| 50 |
+
Cordialement,
|
| 51 |
+
L'équipe Vortex-Flux.
|
| 52 |
+
"""
|
| 53 |
+
|
| 54 |
+
try:
|
| 55 |
+
# Connexion SMTP
|
| 56 |
server = smtplib.SMTP(smtp_server, smtp_port)
|
| 57 |
server.starttls()
|
| 58 |
server.login(sender_email, sender_password)
|
| 59 |
+
|
| 60 |
+
# 1. Envoi au CLIENT
|
| 61 |
+
if destinataire_client and "@" in destinataire_client:
|
| 62 |
+
msg_client = MIMEMultipart()
|
| 63 |
+
msg_client['From'] = sender_email
|
| 64 |
+
msg_client['To'] = destinataire_client
|
| 65 |
+
msg_client['Subject'] = sujet
|
| 66 |
+
msg_client.attach(MIMEText(corps, 'plain'))
|
| 67 |
+
server.sendmail(sender_email, destinataire_client, msg_client.as_string())
|
| 68 |
+
|
| 69 |
+
# 2. Envoi à l'ADMIN (Copie)
|
| 70 |
+
msg_admin = MIMEMultipart()
|
| 71 |
+
msg_admin['From'] = sender_email
|
| 72 |
+
msg_admin['To'] = destinataire_admin
|
| 73 |
+
msg_admin['Subject'] = f"[ADMIN] Copie Rappel - {details_pret['Nom_Complet']}"
|
| 74 |
+
msg_admin.attach(MIMEText(f"Notification envoyée au client.\n\n{corps}", 'plain'))
|
| 75 |
+
server.sendmail(sender_email, destinataire_admin, msg_admin.as_string())
|
| 76 |
+
|
| 77 |
server.quit()
|
| 78 |
return True
|
| 79 |
except Exception as e:
|
| 80 |
+
st.error(f"Erreur technique SMTP : {e}")
|
| 81 |
return False
|
| 82 |
|
| 83 |
def verifier_et_notifier_echeances(client, sheet_name):
|
| 84 |
+
st.markdown("### 📧 Centre de Notifications")
|
| 85 |
+
|
| 86 |
+
# Utilisation de session_state pour éviter de spammer à chaque rechargement de page
|
| 87 |
+
if 'check_done' not in st.session_state:
|
| 88 |
+
st.session_state.check_done = False
|
| 89 |
+
|
| 90 |
+
col_btn, col_info = st.columns([1, 3])
|
| 91 |
|
| 92 |
+
with col_btn:
|
| 93 |
+
lancer_verif = st.button("🔄 Vérifier Échéances (1.5j)")
|
| 94 |
+
|
| 95 |
+
if lancer_verif:
|
| 96 |
+
st.session_state.check_done = True
|
| 97 |
try:
|
| 98 |
sh = client.open(sheet_name)
|
| 99 |
+
|
| 100 |
+
# 1. Récupération des Prêts
|
| 101 |
ws_prets = sh.worksheet("Prets_Master")
|
| 102 |
df_prets = pd.DataFrame(ws_prets.get_all_records())
|
| 103 |
+
|
| 104 |
+
# 2. Récupération des Clients (pour avoir les emails)
|
| 105 |
+
ws_clients = sh.worksheet("Clients_KYC")
|
| 106 |
+
df_clients = pd.DataFrame(ws_clients.get_all_records())
|
| 107 |
|
| 108 |
+
# Création d'un dictionnaire ID -> Email pour recherche rapide
|
| 109 |
+
map_emails = dict(zip(df_clients['ID_Client'], df_clients['Email']))
|
| 110 |
+
|
| 111 |
+
now = datetime.now()
|
| 112 |
+
delta_limit = timedelta(days=1.5) # 36 heures
|
| 113 |
+
alertes_count = 0
|
| 114 |
+
|
| 115 |
+
st.write("Analyse des échéanciers en cours...")
|
| 116 |
+
|
| 117 |
+
for index, row in df_prets.iterrows():
|
| 118 |
+
# On ne traite que les prêts ACTIFS
|
| 119 |
+
if str(row.get('Statut', '')).upper() != "ACTIF":
|
| 120 |
+
continue
|
| 121 |
+
|
| 122 |
+
# Récupération de la chaîne de dates (ex: "10/05/2025;17/05/2025")
|
| 123 |
+
dates_str = str(row.get('Dates_Versements', ''))
|
| 124 |
+
if not dates_str:
|
| 125 |
+
continue
|
| 126 |
+
|
| 127 |
+
dates_list = dates_str.split(';')
|
| 128 |
+
|
| 129 |
+
for d_str in dates_list:
|
| 130 |
+
try:
|
| 131 |
+
d_obj = datetime.strptime(d_str.strip(), "%d/%m/%Y")
|
| 132 |
+
|
| 133 |
+
# LOGIQUE 1.5 JOURS
|
| 134 |
+
# L'échéance est dans le futur ET la différence est < 1.5 jours
|
| 135 |
+
diff = d_obj - now
|
| 136 |
+
|
| 137 |
+
if timedelta(seconds=0) < diff <= delta_limit:
|
| 138 |
+
client_id = row['ID_Client']
|
| 139 |
+
email_client = map_emails.get(client_id, "")
|
| 140 |
+
# Email Admin (Vous) - À changer ou mettre dans secrets
|
| 141 |
+
email_admin = st.secrets["smtp"]["email"] if "smtp" in st.secrets else "admin@vortex.com"
|
| 142 |
+
|
| 143 |
+
details = {
|
| 144 |
+
'ID_Pret': row['ID_Pret'],
|
| 145 |
+
'Nom_Complet': row['Nom_Complet'],
|
| 146 |
+
'Date_Echeance': d_str,
|
| 147 |
+
'Montant_Versement': row['Montant_Versement']
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
# Envoi
|
| 151 |
+
succes = envoyer_email_rappel(email_client, email_admin, details)
|
| 152 |
+
|
| 153 |
+
if succes:
|
| 154 |
+
st.toast(f"📧 Rappel envoyé pour {row['Nom_Complet']} (Echéance : {d_str})", icon="✅")
|
| 155 |
+
alertes_count += 1
|
| 156 |
+
else:
|
| 157 |
+
st.toast(f"❌ Échec envoi pour {row['Nom_Complet']}", icon="⚠️")
|
| 158 |
+
|
| 159 |
+
except ValueError:
|
| 160 |
+
continue # Format date invalide, on ignore
|
| 161 |
+
|
| 162 |
+
if alertes_count == 0:
|
| 163 |
+
st.info("Aucune échéance proche (< 1.5 jours) détectée.")
|
| 164 |
+
else:
|
| 165 |
+
st.success(f"✅ Terminé : {alertes_count} notifications envoyées.")
|
| 166 |
|
| 167 |
except Exception as e:
|
| 168 |
+
st.error(f"Erreur lors de la vérification : {e}")
|