File size: 14,856 Bytes
2b7f829
 
 
 
 
 
 
 
 
 
 
 
 
 
 
013456d
 
2b7f829
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f07edd7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2b7f829
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c9af6c4
2b7f829
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c9af6c4
2b7f829
 
 
f07edd7
2b7f829
 
 
 
 
 
 
 
c9af6c4
2b7f829
 
 
c741327
2b7f829
c741327
 
2b7f829
 
 
600e9f6
ea5f870
2b7f829
600e9f6
ef42244
600e9f6
ef42244
2b7f829
 
cc3b981
c9af6c4
2b7f829
 
 
cc3b981
2b7f829
cc3b981
600e9f6
 
cc3b981
600e9f6
 
2b7f829
 
cc3b981
600e9f6
cc3b981
2b7f829
c9af6c4
2b7f829
 
 
 
600e9f6
c741327
 
2b7f829
 
c741327
 
600e9f6
c741327
 
2b7f829
600e9f6
c741327
 
2b7f829
 
 
 
600e9f6
2b7f829
 
 
 
600e9f6
 
2b7f829
 
 
600e9f6
2b7f829
 
 
600e9f6
2b7f829
 
c9af6c4
2b7f829
c741327
2b7f829
 
 
 
 
 
 
 
 
c741327
2b7f829
 
 
 
c741327
 
 
 
 
 
 
600e9f6
2b7f829
600e9f6
 
07f0a79
600e9f6
 
 
 
 
07f0a79
600e9f6
 
c741327
600e9f6
 
 
 
 
 
c741327
 
600e9f6
 
 
c741327
 
 
600e9f6
 
 
c741327
600e9f6
 
 
2b7f829
 
 
 
 
 
 
013456d
c741327
2b7f829
f07edd7
c741327
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
import streamlit as st
import re
from datetime import date, timedelta

# --- FONCTIONS UTILITAIRES INTERNES ---
def inject_pulsing_css():
    st.markdown("""
    <style>
    @keyframes pulse-red {
        0% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(255, 82, 82, 0.7); }
        70% { transform: scale(1); box-shadow: 0 0 0 10px rgba(255, 82, 82, 0); }
        100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(255, 82, 82, 0); }
    }
    .mandatory-dot {
        display: inline-block;
        width: 8px;
        height: 8px;
        background-color: #ff5252;
        border-radius: 50%;
        margin-left: 8px;
        animation: pulse-red 2s infinite;
        vertical-align: middle;
    }
    .field-label { font-weight: 600; color: #F5F8FA; margin-bottom: 5px; display: block; }
    </style>
    """, unsafe_allow_html=True)

def lbl(text, mandatory=False):
    if mandatory:
        st.markdown(f'<span class="field-label">{text} <span class="mandatory-dot" title="Obligatoire"></span></span>', unsafe_allow_html=True)
    else:
        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 = [
    # Dakar Plateau
    "Plateau", "Médina", "Fann", "Point E", "Gueule Tapée", "Colobane",

    # Dakar Est
    "Hann Maristes 1", "Hann Maristes 2", "Hann Bel-Air",

    # Dakar Ouest
    "Yoff", "Ouakam", "Ngor", "Almadies", "Mamelles",

    # Dakar Centre
    "Mermoz", "Sacré-Cœur", "Liberté 1", "Liberté 2", "Liberté 3",
    "Liberté 4", "Liberté 5", "Liberté 6",
    "HLM", "Grand Dakar", "Dieuppeul", "Derklé",

    # Pikine
    "Pikine Est", "Pikine Ouest", "Pikine Nord", "Pikine Sud",
    "Guinaw Rails", "Thiaroye", "Dalifort",

    # Guédiawaye
    "Guédiawaye", "Golf Sud", "Medina Gounass", "Wakhinane Nimzatt",

    # Rufisque
    "Rufisque", "Bargny", "Diamniadio", "Sébikotane",

    # Autres
    "Autre"
]

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

LISTE_PAYS = ["Sénégal", "France", "Côte d'Ivoire", "Mali", "États-Unis", "Autre"]

# --- FONCTION PRINCIPALE APPELÉE PAR L'APP ---
def show_kyc_form(client, sheet_name, generate_id_func):
    """
    Affiche le formulaire KYC.
    Args:
        client: La connexion gspread active (passée depuis main).
        sheet_name: Le nom du fichier Google Sheet.
        generate_id_func: La fonction pour générer l'ID (passée depuis main).
    """
    inject_pulsing_css()
    
    st.header("ENTITÉ : NOUVEL OBJET CLIENT")
    
    # Appel de la fonction parente pour l'ID
    new_id = generate_id_func("CLI", "Clients_KYC")
    st.caption(f"Système ID : {new_id}")

    type_personne = st.radio("Nature", ["Personne Physique", "Personne Morale"], horizontal=True)
    if type_personne == "Personne Morale":
        st.warning("⚠️ Module Personne Morale en construction.")

    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("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 = st.selectbox("Statut", ["Salarié", "Indépendant", "Fonctionnaire", "Etudiant", "Sans_Emploi", "Retraité"], label_visibility="collapsed")
            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 = st.selectbox("Secteur", ["Commerce", "Services", "Agriculture", "Etudiant", "Autre"], label_visibility="collapsed")
        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é Emploi (mois)", True)
            anciennete_emploi = st.number_input("Ancienneté Emploi", min_value=3, step=1, label_visibility="collapsed")

        # --- BLOC 4 : FINANCES ---
# --- 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", "Commerce", "Epargne", "Héritage", "Autre"], label_visibility="collapsed")
            lbl("Vérification AML", True)
            aml = st.selectbox("AML", ["Non_Fait", "OK", "Match", "Review"], label_visibility="collapsed")

        st.divider()
        lbl("Commentaires / Notes", False)
        notes = st.text_area("Notes", label_visibility="collapsed")

        submit_btn = st.form_submit_button("VÉRIFIER ET ENREGISTRER")

   
    # --- 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 in ["Salarié", "Fonctionnaire"] and len(employeur) < 2: errors.append("❌ Employeur obligatoire.")
        
        if errors:
            for e in errors: st.error(e)
        else:
            try:
                # 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()
                
                # Préparation de la ligne DANS L'ORDRE EXACT DES COLONNES GOOGLE SHEET
                row_to_add = [
                    new_id,                      # ID_Client
                    nom.upper(),                 # Nom_Complet
                    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 (calculé ci-dessus)
                    statut_pro,                  # Statut_Pro
                    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 (calculé ci-dessus)
                    ville_val,                   # Ville (calculé ci-dessus)
                    pays_residence_val,          # Pays_Residence (calculé ci-dessus)
                    transfert,                   # Moyen_Transfert
                    entite_financiere.upper(),   # Entite_Financiere
                    n_fiscal,                    # Numero_Fiscal
                    pays_naissance_val,          # Pays_Naissance (calculé ci-dessus)
                    aml,                         # Verification_AML
                    origine,                     # Origine_Fonds
                    notes                        # Commentaires_Notes
                ]
                
                # ÉCRITURE VIA LA CONNEXION PASSÉE EN ARGUMENT
                sh = client.open(sheet_name)
                worksheet = sh.worksheet("Clients_KYC")
                worksheet.append_row(row_to_add)
                
                st.success(f"🟢 CLIENT {new_id} VALIDÉ ET ENREGISTRÉ !")
                st.balloons()
            except Exception as e:
                st.error(f"🔴 Erreur technique module KYC : {e}")