File size: 16,878 Bytes
2b7f829
 
 
 
 
 
 
 
d0cfc78
 
 
 
 
2b7f829
 
 
 
 
6b02e82
 
2b7f829
 
f07edd7
 
 
 
 
 
 
 
 
 
fc170a6
f07edd7
 
 
 
 
 
 
 
 
 
 
 
 
fc170a6
2b7f829
 
 
 
281774d
2b7f829
281774d
2b7f829
281774d
 
 
 
 
 
 
 
 
 
 
 
 
 
2b7f829
281774d
 
2b7f829
 
 
 
c9af6c4
2b7f829
 
 
 
fb90262
 
2b7f829
 
 
 
 
 
 
 
 
 
 
 
 
 
c9af6c4
2b7f829
 
 
f07edd7
2b7f829
 
 
 
 
 
 
 
c9af6c4
2b7f829
 
 
fb90262
 
2b7f829
c741327
 
2b7f829
 
 
600e9f6
fc170a6
2eb709b
2b7f829
600e9f6
ef42244
fc170a6
ef42244
2b7f829
 
c9af6c4
2b7f829
 
 
cc3b981
2b7f829
cc3b981
600e9f6
 
cc3b981
600e9f6
 
2b7f829
 
cc3b981
600e9f6
cc3b981
46b7ccb
 
2b7f829
c9af6c4
2b7f829
 
 
 
600e9f6
c741327
 
2b7f829
 
c741327
 
600e9f6
c741327
 
2b7f829
600e9f6
c741327
 
2b7f829
 
 
 
600e9f6
2b7f829
 
 
 
600e9f6
 
2b7f829
 
fc170a6
600e9f6
2b7f829
a5ce1ce
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fc170a6
a5ce1ce
 
 
 
 
281774d
 
 
2b7f829
281774d
2b7f829
 
 
 
 
 
 
 
fb90262
c741327
a5ce1ce
2b7f829
 
 
 
fb90262
 
c741327
 
 
 
 
 
2eb709b
 
 
 
b7cb0e5
 
 
600e9f6
2b7f829
281774d
 
fb90262
281774d
 
 
 
 
 
07f0a79
281774d
 
 
fb90262
281774d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fb90262
 
2b7f829
 
 
 
281774d
 
2b7f829
 
a27f013
9900689
2b7f829
a27f013
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
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
import streamlit as st
import re
from datetime import date, timedelta

# --- FONCTIONS UTILITAIRES INTERNES ---
def inject_pulsing_css():
    st.markdown("""
    <style>
    .field-label { 
        font-weight: 600; 
        color: #F5F8FA; 
        margin-bottom: 5px; 
        display: block; 
    }
    </style>
    """, unsafe_allow_html=True)

def lbl(text, mandatory=False):
    st.markdown(f'<span class="field-label">{text}</span>', unsafe_allow_html=True)
    
# Listes de référence
LISTE_PROFESSIONS = ["Commerçant", "Fonctionnaire", "Agriculteur", "Etudiant", "Ouvrier", "Cadre Supérieur", "Ingénieur", "Médecin", "Autre"]
LISTE_QUARTIERS = [
    "Plateau", "Médina", "Fann", "Point E", "Gueule Tapée", "Colobane",
    "Hann Maristes 1", "Hann Maristes 2", "Hann Bel-Air",
    "Yoff", "Ouakam", "Ngor", "Almadies", "Mamelles",
    "Mermoz", "Sacré-Cœur", "Liberté 1", "Liberté 2", "Liberté 3",
    "Liberté 4", "Liberté 5", "Liberté 6",
    "HLM", "Grand Dakar", "Dieuppeul", "Derklé",
    "Pikine Est", "Pikine Ouest", "Pikine Nord", "Pikine Sud",
    "Guinaw Rails", "Thiaroye", "Dalifort",
    "Guédiawaye", "Golf Sud", "Medina Gounass", "Wakhinane Nimzatt",
    "Rufisque", "Bargny", "Diamniadio", "Sébikotane", "Zac Mbao"
    "Autre"
]

LISTE_VILLES = [
    "Dakar", "Pikine", "Guédiawaye", "Rufisque",
    "Thiès", "Mbour", "Tivaouane", "Joal-Fadiouth", "Popenguine",
    "Touba", "Diourbel", "Kaolack", "Fatick", "Foundiougne",
    "Saint-Louis", "Dagana", "Podor", "Richard-Toll",
    "Ziguinchor", "Oussouye", "Bignona", "Kolda", "Sédhiou",
    "Tambacounda", "Kédougou", "Bakel", "Koumpentoum",
    "Autre"
]

LISTE_PAYS = ["Sénégal", "Gabon", "Congo", "Guinée", "Côte d'Ivoire", "Mali", "Tchad", "France", "Autre"]

# --- FONCTION PRINCIPALE APPELÉE PAR L'APP ---
def show_kyc_form(client, sheet_name, generate_id_func):
    """
    Affiche le formulaire KYC (Client ou Garant).
    """
    # inject_pulsing_css() # Décommenter si le CSS n'est pas chargé ailleurs
    
    # Sélecteur de Type (Client ou Garant)
    type_entite = st.radio("Type d'enregistrement", ["Client Physique", "Garant Physique"], horizontal=True)

    # Configuration dynamique selon le choix
    if type_entite == "Client Physique":
        prefix_id = "CLI"
        target_sheet = "Clients_KYC"
        header_title = "ENTITÉ : NOUVEAU CLIENT"
    else:
        prefix_id = "GAR"
        target_sheet = "Garants_KYC"
        header_title = "ENTITÉ : NOUVEAU GARANT"

    st.header(header_title)
    
    # Appel de la fonction parente pour l'ID avec les bons paramètres
    new_id = generate_id_func(prefix_id, target_sheet)
    st.caption(f"Système ID : {new_id}")

    with st.form("kyc_form_module", clear_on_submit=False):
        # --- BLOC 1 : IDENTITÉ ---
        st.markdown("### IDENTITÉ & CONTACT")
        c1, c2, c3 = st.columns(3)
        with c1:
            lbl("Nom Complet", True)
            nom = st.text_input("Nom", label_visibility="collapsed")
            lbl("Genre", True)  # ← NOUVEAU
            genre = st.selectbox("Genre", ["Homme", "Femme"], label_visibility="collapsed")  # ← NOUVEAU
            lbl("Date de Naissance", True)
            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")
        with c2:
            lbl("Adresse", True)
            adresse = st.text_input("Adresse", label_visibility="collapsed")
            lbl("Téléphone", True)
            telephone = st.text_input("Tel", label_visibility="collapsed")
        with c3:
            lbl("Email", True)
            email = st.text_input("Email", label_visibility="collapsed").lower()
            lbl("État Civil", True)
            etat_civil = st.selectbox("Etat", ["Célibataire", "Marié(e)", "Divorcé(e)", "Veuf(ve)"], label_visibility="collapsed")

        # --- BLOC 2 : DOCUMENTS ---
        st.markdown("### DOCUMENTS OFFICIELS")
        c4, c5, c6 = st.columns(3)
        with c4:
            lbl("Type Pièce", True)
            type_id = st.selectbox("Type ID", ["CNI", "Passeport", "Carte d'étranger", "Carte Consulaire", "Permis de conduire", "Carte Electeur"], label_visibility="collapsed")
        with c5:
            lbl("Numéro ID", True)
            id_officiel = st.text_input("Num ID", label_visibility="collapsed")
        with c6:
            lbl("Expiration", True)
            date_exp_id = st.date_input("Exp ID", value=date.today()+timedelta(days=90), min_value=date.today(), label_visibility="collapsed")

        # --- BLOC 3 : PRO ---
        st.markdown("### PROFESSION")
        c7, c8, c9 = st.columns(3)
        with c7:
            lbl("Statut Pro", True)
            statut_pro_select = st.selectbox("Statut", ["Salarié", "Indépendant", "Entrepreneur", "Fonctionnaire", "Etudiant", "Sans_Emploi", "Retraité", "Autre"], label_visibility="collapsed")  # ← MODIFIÉ
            statut_pro_autre = st.text_input("Si Autre, préciser", key="statut_pro_autre", placeholder="Statut personnalisé...")  # ← NOUVEAU
            lbl("Profession", True)
            prof_select = st.selectbox("Choix Profession", LISTE_PROFESSIONS, label_visibility="collapsed")
            prof_autre = st.text_input("Si Autre, préciser", key="prof_autre", placeholder="Profession personnalisée...")
        with c8:
            lbl("Employeur", False)
            employeur = st.text_input("Employeur", label_visibility="collapsed")
            lbl("Secteur d'Activité", True)
            secteur_select = st.selectbox("Secteur", ["Commerce", "Reseaux et Telecommunication", "Services", "Agriculture", "Etudiant", "Industrie", "Sante", "Education", "Transport", "Autre"], label_visibility="collapsed")
            secteur_autre = st.text_input("Si Autre, préciser", key="secteur_autre", placeholder="Secteur personnalisé...")
        with c9:
            lbl("Pers. à Charge", True)
            pers_charge = st.number_input("Charge", min_value=0, step=1, max_value=3, label_visibility="collapsed")
            lbl("Ancienneté Statut pro (mois)", True)
            anciennete_emploi = st.number_input("Ancienneté Emploi", min_value=3, step=1, label_visibility="collapsed")

        # --- BLOC 4 : FINANCES ---
        st.markdown("### FINANCES (XOF)")
        c10, c11, c12 = st.columns(3)
        with c10:
            lbl("Revenus Mensuels", True)
            revenus = st.number_input("Rev. Mensuel", min_value=0.0, step=10000.0, format="%.2f", value=0.0, label_visibility="collapsed")
            lbl("Autres Revenus", False)
            autres_rev = st.number_input("Autre Rev.", min_value=0.0, step=5000.0, format="%.2f", value=0.0, label_visibility="collapsed")
        with c11:
            lbl("Ancienneté Revenus (mois)", False)
            anciennete_revenu = st.number_input("Ancienneté Rev.", min_value=0, step=1, value=0, label_visibility="collapsed")
            lbl("Autres Sources Revenus", False)
            autres_sources = st.text_input("Préciser Sources", label_visibility="collapsed")
        with c12:
            lbl("Charges Estimées", False)
            charges = st.number_input("Charges", min_value=0.0, step=5000.0, format="%.2f", value=0.0, label_visibility="collapsed")
            lbl("Patrimoine Déclaré", False)
            patrimoine = st.number_input("Patrimoine", min_value=0.0, step=100000.0, format="%.2f", value=0.0, label_visibility="collapsed")

            
        # --- BLOC 5 : LOCALISATION ---
        st.markdown("### LOCALISATION")
        c13, c14, c15 = st.columns(3)
        with c13:
            lbl("Statut Logement", True)
            statut_log = st.selectbox("Logement", ["Propriétaire", "Locataire", "Hébergé"], label_visibility="collapsed")
            lbl("Quartier", True)
            quartier_select = st.selectbox("Quartier", LISTE_QUARTIERS, label_visibility="collapsed")
            quartier_autre = st.text_input("Si Autre, préciser", key="quartier_autre", placeholder="Quartier personnalisé...")
        with c14:
            lbl("Ville", True)
            ville_select = st.selectbox("Ville", LISTE_VILLES, label_visibility="collapsed")
            ville_autre = st.text_input("Si Autre, préciser", key="ville_autre", placeholder="Ville personnalisée...")
            lbl("Pays de Résidence", True)
            pays_residence_select = st.selectbox("Pays Résidence", LISTE_PAYS, label_visibility="collapsed")
            pays_residence_autre = st.text_input("Si Autre, préciser", key="pays_res_autre", placeholder="Pays personnalisé...")
        with c15:
            lbl("Pays de Naissance", True)
            pays_naissance_select = st.selectbox("Pays Naissance", LISTE_PAYS, key="pays_naiss", label_visibility="collapsed")
            pays_naissance_autre = st.text_input("Si Autre, préciser", key="pays_naiss_autre", placeholder="Pays personnalisé...")
            lbl("N° Fiscal", False)
            n_fiscal = st.text_input("Fiscal", label_visibility="collapsed")

        # --- BLOC 6 : CONFORMITÉ ---
        st.markdown("### CONFORMITÉ & BANQUE")
        c16, c17 = st.columns(2)
        with c16:
            lbl("Moyen Transfert", True)
            transfert = st.selectbox("Transfert", ["Virement", "Mobile_Money", "Cash", "Chèque"], label_visibility="collapsed")
            lbl("Entité Financière (Banque)", True)
            entite_financiere = st.text_input("Banque", label_visibility="collapsed")
        with c17:
            lbl("Origine Fonds", True)
            origine = st.selectbox("Origine", ["Salaire", "Entreprise", "Contribution Familiale", "Epargne", "Héritage", "Autre"], label_visibility="collapsed")
            lbl("Vérification AML", True)
            aml = st.selectbox("AML", ["Non_Fait", "OK", "Match", "Review"], label_visibility="collapsed")
            
        # --- BLOC 7 : RÉSEAUX SOCIAUX ---
        st.markdown("### RÉSEAUX SOCIAUX")
        c18, c19, c20 = st.columns(3)
        with c18:
            lbl("Facebook", False)
            facebook = st.text_input("Lien Facebook", placeholder="https://facebook.com/...", label_visibility="collapsed")
            lbl("Instagram", False)
            instagram = st.text_input("Lien Instagram", placeholder="https://instagram.com/...", label_visibility="collapsed")
        with c19:
            lbl("LinkedIn", False)
            linkedin = st.text_input("Lien LinkedIn", placeholder="https://linkedin.com/in/...", label_visibility="collapsed")
            lbl("Twitter / X", False)
            twitter = st.text_input("Lien Twitter/X", placeholder="https://twitter.com/...", label_visibility="collapsed")
        with c20:
            lbl("TikTok", False)
            tiktok = st.text_input("Lien TikTok", placeholder="https://tiktok.com/@...", label_visibility="collapsed")
            lbl("WhatsApp", False)
            whatsapp = st.text_input("WhatsApp", placeholder="wa.me/+221XXXXXXXXX", label_visibility="collapsed")
        
        st.divider()
        lbl("Commentaires / Notes", False)
        notes = st.text_area("Notes", label_visibility="collapsed")

        # Bouton dynamique selon le type
        label_btn = f"VÉRIFIER ET ENREGISTRER LE {prefix_id}"
        submit_btn = st.form_submit_button(label_btn)

    
    # --- LOGIQUE DE VALIDATION ET ENVOI ---
    if submit_btn:
        errors = []
        if not re.match(r"^[a-zA-Z\s\-\']{2,100}$", nom): errors.append("❌ Nom invalide.")
        clean_phone = re.sub(r"[\s\-\.]", "", telephone)
        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.")
        if not re.match(r"[^@]+@[^@]+\.[^@]+", email): errors.append("❌ Email invalide.")
        if " " in id_officiel or len(id_officiel) < 5: errors.append("❌ ID Officiel invalide.")
        if statut_pro_select in ["Salarié", "Fonctionnaire"] and len(employeur) < 2: errors.append("❌ Employeur obligatoire.")
        
    # --- LOGIQUE DE RANGEMENT ---
        if errors:
            for e in errors: st.error(e)
        else:
            try:
                # Calcul statut_pro avec gestion "Autre"
                statut_pro_val = statut_pro_autre.strip().upper() if statut_pro_select == "Autre" and statut_pro_autre.strip() else statut_pro_select.upper()
                # Calcul des valeurs "Autre" avant préparation de la ligne
                prof_val = prof_autre.strip().upper() if prof_select == "Autre" and prof_autre.strip() else prof_select.upper()
                quartier_val = quartier_autre.strip().upper() if quartier_select == "Autre" and quartier_autre.strip() else quartier_select.upper()
                ville_val = ville_autre.strip().upper() if ville_select == "Autre" and ville_autre.strip() else ville_select.upper()
                pays_residence_val = pays_residence_autre.strip().upper() if pays_residence_select == "Autre" and pays_residence_autre.strip() else pays_residence_select.upper()
                pays_naissance_val = pays_naissance_autre.strip().upper() if pays_naissance_select == "Autre" and pays_naissance_autre.strip() else pays_naissance_select.upper()

                # Calcul secteur avec gestion "Autre"
                secteur = secteur_autre.strip().upper() if secteur_select == "Autre" and secteur_autre.strip() else secteur_select.upper()
    
                # Concaténation des réseaux sociaux
                reseaux_sociaux = f"FB: {facebook} | IG: {instagram} | LI: {linkedin} | TW: {twitter} | TT: {tiktok} | WA: {whatsapp}".strip()
                
                # Préparation de la ligne DANS L'ORDRE EXACT DES COLONNES GOOGLE SHEET
                row_to_add = [
                    new_id,                          # ID_Client ou ID_Garant
                    nom.upper(),                     # Nom_Complet
                    genre,                           # Genre ← NOUVEAU
                    date_naiss.strftime("%d-%m-%Y"), # Date_Naissance
                    adresse,                         # Adresse
                    telephone,                       # Telephone
                    email,                           # Email
                    type_id,                         # Type_Piece_Identite
                    id_officiel.upper(),             # ID_Officiel
                    date_exp_id.strftime("%d-%m-%Y"),# Date_Expiration_ID
                    etat_civil,                      # Etat_Civil
                    pers_charge,                     # Pers_Charge
                    prof_val,                        # Profession
                    statut_pro_val,                  # Statut_Pro ← MODIFIÉ
                    employeur.upper(),               # Employeur
                    secteur,                         # Secteur_Activite
                    anciennete_emploi,               # Anciennete_Emploi
                    revenus,                         # Revenus_Mensuels
                    autres_rev,                      # Autres_Revenus
                    anciennete_revenu,               # Anciennete_Revenu
                    autres_sources,                  # Autres_Sources_Revenu
                    charges,                         # Charges_Estimees
                    patrimoine,                      # Patrimoine_Declare
                    statut_log,                      # Statut_Logement
                    quartier_val,                    # Quartier
                    ville_val,                       # Ville
                    pays_residence_val,              # Pays_Residence
                    transfert,                       # Moyen_Transfert
                    entite_financiere.upper(),       # Entite_Financiere
                    n_fiscal,                        # Numero_Fiscal
                    pays_naissance_val,              # Pays_Naissance
                    aml,                             # Verification_AML
                    origine,                         # Origine_Fonds
                    reseaux_sociaux,                 # Reseau_sociaux
                    notes,                           # Commentaires_Notes
                    date.today().strftime("%d-%m-%Y") # Date_Creation ← NOUVEAU
                ]
                
                # ÉCRITURE VIA LA CONNEXION PASSÉE EN ARGUMENT
                sh = client.open(sheet_name)
                # On cible la feuille dynamiquement selon le type
                worksheet = sh.worksheet(target_sheet)
                worksheet.append_row(row_to_add)
                
                st.success(f"♦️ {new_id} VALIDÉ ET ENREGISTRÉ DANS {target_sheet} !")
                
            except Exception as e:
                st.error(f"🔻 Erreur technique module KYC : {e}")