klydekushy commited on
Commit
2b7f829
·
verified ·
1 Parent(s): 800f139

Create kyc_form.py

Browse files
Files changed (1) hide show
  1. src/modules/kyc_form.py +203 -0
src/modules/kyc_form.py ADDED
@@ -0,0 +1,203 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import re
3
+ from datetime import date, timedelta
4
+
5
+ # --- FONCTIONS UTILITAIRES INTERNES ---
6
+ def inject_pulsing_css():
7
+ st.markdown("""
8
+ <style>
9
+ @keyframes pulse-red {
10
+ 0% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(255, 82, 82, 0.7); }
11
+ 70% { transform: scale(1); box-shadow: 0 0 0 10px rgba(255, 82, 82, 0); }
12
+ 100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(255, 82, 82, 0); }
13
+ }
14
+ .mandatory-dot {
15
+ display: inline-block;
16
+ width: 10px;
17
+ height: 10px;
18
+ background-color: #ff5252;
19
+ border-radius: 50%;
20
+ margin-left: 8px;
21
+ animation: pulse-red 2s infinite;
22
+ vertical-align: middle;
23
+ }
24
+ .field-label { font-weight: 600; color: #F5F8FA; margin-bottom: 5px; display: block; }
25
+ </style>
26
+ """, unsafe_allow_html=True)
27
+
28
+ def lbl(text, mandatory=False):
29
+ if mandatory:
30
+ st.markdown(f'<span class="field-label">{text} <span class="mandatory-dot" title="Obligatoire"></span></span>', unsafe_allow_html=True)
31
+ else:
32
+ st.markdown(f'<span class="field-label">{text}</span>', unsafe_allow_html=True)
33
+
34
+ # Listes de référence
35
+ LISTE_PROFESSIONS = ["Commerçant", "Fonctionnaire", "Agriculteur", "Etudiant", "Ouvrier", "Cadre Supérieur", "Ingénieur", "Médecin", "Autre"]
36
+ LISTE_QUARTIERS = ["Plateau", "Médina", "Yoff", "Ouakam", "Almadies", "Mermoz", "Pikine", "Guédiawaye", "Autre"]
37
+ LISTE_VILLES = ["Dakar", "Touba", "Thiès", "Saint-Louis", "Ziguinchor", "Autre"]
38
+ LISTE_PAYS = ["Sénégal", "France", "Côte d'Ivoire", "Mali", "États-Unis", "Autre"]
39
+
40
+ # --- FONCTION PRINCIPALE APPELÉE PAR L'APP ---
41
+ def show_kyc_form(client, sheet_name, generate_id_func):
42
+ """
43
+ Affiche le formulaire KYC.
44
+ Args:
45
+ client: La connexion gspread active (passée depuis main).
46
+ sheet_name: Le nom du fichier Google Sheet.
47
+ generate_id_func: La fonction pour générer l'ID (passée depuis main).
48
+ """
49
+ inject_pulsing_css()
50
+
51
+ st.header("ENTITÉ : NOUVEL OBJET CLIENT")
52
+
53
+ # Appel de la fonction parente pour l'ID
54
+ new_id = generate_id_func("CLI", "Clients_KYC")
55
+ st.caption(f"Système ID : {new_id}")
56
+
57
+ type_personne = st.radio("Nature", ["Personne Physique", "Personne Morale"], horizontal=True)
58
+ if type_personne == "Personne Morale":
59
+ st.warning("⚠️ Module Personne Morale en construction.")
60
+
61
+ with st.form("kyc_form_module", clear_on_submit=False):
62
+ # --- BLOC 1 : IDENTITÉ ---
63
+ st.markdown("### 👤 IDENTITÉ & CONTACT")
64
+ c1, c2, c3 = st.columns(3)
65
+ with c1:
66
+ lbl("Nom Complet", True)
67
+ nom = st.text_input("Nom", label_visibility="collapsed")
68
+ lbl("Date de Naissance", True)
69
+ date_naiss = st.date_input("Date Naiss", value=date.today()-timedelta(days=365*18), min_value=date.today()-timedelta(days=365*100), max_value=date.today()-timedelta(days=365*18), label_visibility="collapsed")
70
+ with c2:
71
+ lbl("Adresse", True)
72
+ adresse = st.text_input("Adresse", label_visibility="collapsed")
73
+ lbl("Téléphone", True)
74
+ telephone = st.text_input("Tel", label_visibility="collapsed")
75
+ with c3:
76
+ lbl("Email", True)
77
+ email = st.text_input("Email", label_visibility="collapsed").lower()
78
+ lbl("État Civil", True)
79
+ etat_civil = st.selectbox("Etat", ["Célibataire", "Marié(e)", "Divorcé(e)", "Veuf(ve)"], label_visibility="collapsed")
80
+
81
+ # --- BLOC 2 : DOCUMENTS ---
82
+ st.markdown("### 🆔 DOCUMENTS OFFICIELS")
83
+ c4, c5, c6 = st.columns(3)
84
+ with c4:
85
+ lbl("Type Pièce", True)
86
+ type_id = st.selectbox("Type ID", ["CNI", "Passeport", "Permis", "Carte_Electeur"], label_visibility="collapsed")
87
+ with c5:
88
+ lbl("Numéro ID", True)
89
+ id_officiel = st.text_input("Num ID", label_visibility="collapsed")
90
+ with c6:
91
+ lbl("Expiration", True)
92
+ date_exp_id = st.date_input("Exp ID", value=date.today()+timedelta(days=90), min_value=date.today(), label_visibility="collapsed")
93
+
94
+ # --- BLOC 3 : PRO ---
95
+ st.markdown("### 💼 PROFESSION")
96
+ c7, c8, c9 = st.columns(3)
97
+ with c7:
98
+ lbl("Statut Pro", True)
99
+ statut_pro = st.selectbox("Statut", ["Salarié", "Indépendant", "Fonctionnaire", "Etudiant", "Sans_Emploi", "Retraité"], label_visibility="collapsed")
100
+ lbl("Profession", True)
101
+ prof_val = st.selectbox("Choix Profession", LISTE_PROFESSIONS, label_visibility="collapsed")
102
+ if prof_val == "Autre": prof_val = st.text_input("Préciser Profession")
103
+ with c8:
104
+ lbl("Employeur", False)
105
+ employeur = st.text_input("Employeur", label_visibility="collapsed")
106
+ lbl("Secteur", True)
107
+ secteur = st.selectbox("Secteur", ["Commerce", "Services", "Agriculture", "Autre"], label_visibility="collapsed")
108
+ with c9:
109
+ lbl("Pers. Charge", True)
110
+ pers_charge = st.number_input("Charge", min_value=0, step=1, label_visibility="collapsed")
111
+ lbl("Ancienneté (mois)", True)
112
+ anciennete = st.number_input("Ancienneté", min_value=0, step=1, label_visibility="collapsed")
113
+
114
+ # --- BLOC 4 : FINANCES ---
115
+ st.markdown("### 💰 FINANCES (XOF)")
116
+ c10, c11, c12 = st.columns(3)
117
+ with c10:
118
+ lbl("Revenus Mensuels", True)
119
+ revenus = st.number_input("Rev. Mensuel", min_value=0.0, step=10000.0, format="%.2f", label_visibility="collapsed")
120
+ with c11:
121
+ lbl("Autres Revenus", False)
122
+ autres_rev = st.number_input("Autre Rev.", min_value=0.0, step=5000.0, format="%.2f", label_visibility="collapsed")
123
+ with c12:
124
+ lbl("Charges Estimées", False)
125
+ charges = st.number_input("Charges", min_value=0.0, step=5000.0, format="%.2f", label_visibility="collapsed")
126
+
127
+ # --- BLOC 5 : LOCALISATION ---
128
+ st.markdown("### 🏠 LOCALISATION")
129
+ c13, c14, c15 = st.columns(3)
130
+ with c13:
131
+ lbl("Statut Logement", True)
132
+ statut_log = st.selectbox("Logement", ["Propriétaire", "Locataire", "Hébergé"], label_visibility="collapsed")
133
+ lbl("Patrimoine", False)
134
+ patrimoine = st.number_input("Patrimoine", min_value=0.0, step=100000.0, label_visibility="collapsed")
135
+ with c14:
136
+ lbl("Ville", True)
137
+ ville_val = st.selectbox("Ville", LISTE_VILLES, label_visibility="collapsed")
138
+ if ville_val == "Autre": ville_val = st.text_input("Préciser Ville")
139
+ lbl("Quartier", True)
140
+ quartier_val = st.selectbox("Quartier", LISTE_QUARTIERS, label_visibility="collapsed")
141
+ if quartier_val == "Autre": quartier_val = st.text_input("Préciser Quartier")
142
+ with c15:
143
+ lbl("Pays", True)
144
+ pays_val = st.selectbox("Pays", LISTE_PAYS, label_visibility="collapsed")
145
+ if pays_val == "Autre": pays_val = st.text_input("Préciser Pays")
146
+ lbl("N° Fiscal", False)
147
+ n_fiscal = st.text_input("Fiscal", label_visibility="collapsed")
148
+
149
+ # --- BLOC 6 : CONFORMITÉ ---
150
+ st.markdown("### 🛡️ CONFORMITÉ")
151
+ c16, c17 = st.columns(2)
152
+ with c16:
153
+ lbl("Moyen Transfert", True)
154
+ transfert = st.selectbox("Transfert", ["Virement", "Mobile_Money", "Cash", "Chèque"], label_visibility="collapsed")
155
+ lbl("Banque", True)
156
+ banque = st.text_input("Banque", label_visibility="collapsed")
157
+ with c17:
158
+ lbl("Origine Fonds", True)
159
+ origine = st.selectbox("Origine", ["Salaire", "Commerce", "Epargne", "Héritage", "Autre"], label_visibility="collapsed")
160
+ lbl("AML Check", True)
161
+ aml = st.selectbox("AML", ["Non_Fait", "OK", "Match", "Review"], label_visibility="collapsed")
162
+
163
+ st.divider()
164
+ lbl("Notes", False)
165
+ notes = st.text_area("Notes", label_visibility="collapsed")
166
+
167
+ submit_btn = st.form_submit_button("🔍 VÉRIFIER ET ENREGISTRER")
168
+
169
+ # --- LOGIQUE DE VALIDATION ET ENVOI ---
170
+ if submit_btn:
171
+ errors = []
172
+ if not re.match(r"^[a-zA-Z\s\-\']{2,100}$", nom): errors.append("❌ Nom invalide.")
173
+ clean_phone = re.sub(r"[\s\-\.]", "", telephone)
174
+ if not re.match(r"^(\+|00)?221(7[0678]|33)[0-9]{7}$", clean_phone) and not re.match(r"^\+?[0-9]{9,15}$", clean_phone): errors.append("❌ Téléphone invalide.")
175
+ if not re.match(r"[^@]+@[^@]+\.[^@]+", email): errors.append("❌ Email invalide.")
176
+ if " " in id_officiel or len(id_officiel) < 5: errors.append("❌ ID Officiel invalide.")
177
+ if statut_pro in ["Salarié", "Fonctionnaire"] and len(employeur) < 2: errors.append("❌ Employeur obligatoire.")
178
+
179
+ if errors:
180
+ for e in errors: st.error(e)
181
+ else:
182
+ try:
183
+ # Préparation de la ligne
184
+ row_to_add = [
185
+ new_id, nom.upper(), str(date_naiss), adresse, telephone, email,
186
+ type_id, id_officiel.upper(), str(date_exp_id), etat_civil, pers_charge,
187
+ prof_val.upper(), statut_pro, employeur.upper(), secteur, anciennete,
188
+ revenus, autres_rev, charges, patrimoine,
189
+ statut_log, quartier_val.upper(), ville_val.upper(), pays_val.upper(),
190
+ transfert, banque.upper(), n_fiscal, aml, origine, notes
191
+ ]
192
+
193
+ # ÉCRITURE VIA LA CONNEXION PASSÉE EN ARGUMENT
194
+ sh = client.open(sheet_name)
195
+ worksheet = sh.worksheet("Clients_KYC")
196
+ worksheet.append_row(row_to_add)
197
+
198
+ st.success(f"✅ CLIENT {new_id} VALIDÉ ET ENREGISTRÉ !")
199
+ st.balloons()
200
+ except Exception as e:
201
+ st.error(f"Erreur technique module KYC : {e}")
202
+
203
+