File size: 9,793 Bytes
33a29c9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e0d4be0
33a29c9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import pandas as pd
from datetime import datetime
import gspread

class DataSyncForecasting:
    """
    Synchronise automatiquement les données de Forecasting 
    à partir des déblocages dans Prets_Master
    """
    
    def __init__(self, client, sheet_name):
        self.client = client
        self.sheet_name = sheet_name
        self.sh = client.open(sheet_name)
    
    def extraire_deblocages_mensuels(self):
        """
        Extrait et agrège les déblocages par mois depuis Prets_Master
        
        Returns:
            DataFrame avec Date (YYYY-MM-01) et Montant_Total_Sortie
        """
        try:
            ws_prets = self.sh.worksheet("Prets_Master")
            df_prets = pd.DataFrame(ws_prets.get_all_records())
            
            if df_prets.empty:
                return pd.DataFrame(columns=['Date', 'Montant_Total_Sortie'])
            
            # Vérifier les colonnes nécessaires
            if 'Date_Deblocage' not in df_prets.columns or 'Montant_Capital' not in df_prets.columns:
                raise ValueError("Colonnes 'Date_Deblocage' ou 'Montant_Capital' manquantes dans Prets_Master")
            
            # Garder uniquement les lignes avec données valides
            df_prets = df_prets[
                (df_prets['Date_Deblocage'].notna()) & 
                (df_prets['Date_Deblocage'] != '') &
                (df_prets['Montant_Capital'].notna()) &
                (df_prets['Montant_Capital'] != 0)
            ].copy()
            
            if df_prets.empty:
                return pd.DataFrame(columns=['Date', 'Montant_Total_Sortie'])
            
            # Conversion des montants en numérique
            df_prets['Montant_Capital'] = pd.to_numeric(df_prets['Montant_Capital'], errors='coerce')
            
            # Parser les dates (format DD/MM/YYYY)
            df_prets['Date_Parsed'] = pd.to_datetime(
                df_prets['Date_Deblocage'], 
                format='%d/%m/%Y',
                errors='coerce'
            )
            
            # Supprimer les dates invalides
            df_prets = df_prets.dropna(subset=['Date_Parsed', 'Montant_Capital'])
            
            if df_prets.empty:
                return pd.DataFrame(columns=['Date', 'Montant_Total_Sortie'])
            
            # Créer une colonne année-mois pour le groupement
            df_prets['Annee_Mois'] = df_prets['Date_Parsed'].dt.to_period('M')
            
            # Grouper par mois et sommer
            df_mensuel = df_prets.groupby('Annee_Mois')['Montant_Capital'].sum().reset_index()
            
            # Convertir en premier jour du mois
            df_mensuel['Date'] = df_mensuel['Annee_Mois'].apply(lambda x: x.to_timestamp())
            df_mensuel = df_mensuel.rename(columns={'Montant_Capital': 'Montant_Total_Sortie'})
            
            # Garder uniquement Date et Montant
            df_mensuel = df_mensuel[['Date', 'Montant_Total_Sortie']].sort_values('Date')
            
            return df_mensuel
            
        except gspread.WorksheetNotFound:
            raise Exception("Feuille 'Prets_Master' introuvable")
        except Exception as e:
            raise Exception(f"Erreur lors de l'extraction : {str(e)}")
    
    def lire_forecasting_actuel(self):
        """
        Lit les données actuelles de Forecasting
        
        Returns:
            DataFrame avec les données existantes
        """
        try:
            ws_forecasting = self.sh.worksheet("Forecasting")
            df_forecasting = pd.DataFrame(ws_forecasting.get_all_records())
            
            if df_forecasting.empty or 'Date' not in df_forecasting.columns:
                return pd.DataFrame(columns=['Date', 'Montant_Total_Sortie'])
            
            # Parser les dates
            df_forecasting['Date'] = pd.to_datetime(df_forecasting['Date'], errors='coerce')
            df_forecasting = df_forecasting.dropna(subset=['Date'])
            
            # Conversion montants
            df_forecasting['Montant_Total_Sortie'] = pd.to_numeric(
                df_forecasting['Montant_Total_Sortie'], 
                errors='coerce'
            ).fillna(0)
            
            return df_forecasting[['Date', 'Montant_Total_Sortie']]
            
        except gspread.WorksheetNotFound:
            # Si la feuille n'existe pas, la créer
            return pd.DataFrame(columns=['Date', 'Montant_Total_Sortie'])
        except Exception as e:
            raise Exception(f"Erreur lors de la lecture de Forecasting : {str(e)}")
    
    def fusionner_donnees(self, df_existant, df_nouveau):
        """
        Fusionne intelligemment les données existantes et nouvelles
        
        Args:
            df_existant: DataFrame avec données actuelles de Forecasting
            df_nouveau: DataFrame avec données calculées depuis Prets_Master
            
        Returns:
            dict avec DataFrame fusionné + statistiques
        """
        # Convertir en période pour faciliter la comparaison
        df_existant['Periode'] = df_existant['Date'].dt.to_period('M')
        df_nouveau['Periode'] = df_nouveau['Date'].dt.to_period('M')
        
        # Identifier les mois
        mois_existants = set(df_existant['Periode'])
        mois_nouveaux = set(df_nouveau['Periode'])
        
        # Statistiques
        mois_a_ajouter = mois_nouveaux - mois_existants
        mois_a_mettre_a_jour = mois_existants & mois_nouveaux
        mois_conserves = mois_existants - mois_nouveaux
        
        # Créer le DataFrame fusionné
        # 1. Garder les mois qui ne sont pas dans Prets_Master (données manuelles historiques)
        df_conserves = df_existant[df_existant['Periode'].isin(mois_conserves)].copy()
        
        # 2. Mettre à jour les mois qui existent dans les deux
        df_mis_a_jour = df_nouveau[df_nouveau['Periode'].isin(mois_a_mettre_a_jour)].copy()
        
        # 3. Ajouter les nouveaux mois
        df_ajoutes = df_nouveau[df_nouveau['Periode'].isin(mois_a_ajouter)].copy()
        
        # Fusionner tout
        df_final = pd.concat([df_conserves, df_mis_a_jour, df_ajoutes], ignore_index=True)
        
        # Trier par date
        df_final = df_final.sort_values('Date').reset_index(drop=True)
        
        # Supprimer la colonne Periode
        df_final = df_final[['Date', 'Montant_Total_Sortie']]
        
        # Statistiques du changement
        stats = {
            'nb_conserves': len(mois_conserves),
            'nb_mis_a_jour': len(mois_a_mettre_a_jour),
            'nb_ajoutes': len(mois_a_ajouter),
            'total_lignes': len(df_final),
            'mois_conserves': sorted([str(p) for p in mois_conserves]),
            'mois_mis_a_jour': sorted([str(p) for p in mois_a_mettre_a_jour]),
            'mois_ajoutes': sorted([str(p) for p in mois_a_ajouter])  # ✅ Corrigé ici
        }
        
        return {
            'dataframe': df_final,
            'stats': stats
        }
    
    def ecrire_forecasting(self, df_final):
        """
        Écrit le DataFrame final dans la feuille Forecasting
        
        Args:
            df_final: DataFrame à écrire
        """
        try:
            ws_forecasting = self.sh.worksheet("Forecasting")
        except gspread.WorksheetNotFound:
            # Créer la feuille si elle n'existe pas
            ws_forecasting = self.sh.add_worksheet(title="Forecasting", rows="1000", cols="10")
        
        # Vider la feuille
        ws_forecasting.clear()
        
        # Préparer les données pour l'écriture
        df_write = df_final.copy()
        df_write['Date'] = df_write['Date'].dt.strftime('%Y-%m-%d')
        df_write['Montant_Total_Sortie'] = df_write['Montant_Total_Sortie'].astype(int)
        
        # Écrire l'en-tête
        ws_forecasting.update('A1:B1', [['Date', 'Montant_Total_Sortie']])
        
        # Écrire les données
        if not df_write.empty:
            data_to_write = df_write.values.tolist()
            ws_forecasting.update(f'A2:B{len(data_to_write) + 1}', data_to_write)
    
    def synchroniser(self):
        """
        Fonction principale de synchronisation
        
        Returns:
            dict avec résultat de la synchronisation
        """
        try:
            # 1. Extraire les déblocages mensuels depuis Prets_Master
            df_nouveau = self.extraire_deblocages_mensuels()
            
            # 2. Lire Forecasting actuel
            df_existant = self.lire_forecasting_actuel()
            
            # 3. Fusionner intelligemment
            result = self.fusionner_donnees(df_existant, df_nouveau)
            df_final = result['dataframe']
            stats = result['stats']
            
            # 4. Écrire dans Forecasting
            self.ecrire_forecasting(df_final)
            
            return {
                'success': True,
                'stats': stats,
                'dataframe': df_final,
                'message': f"Synchronisation réussie : {stats['nb_conserves']} mois conservés, {stats['nb_mis_a_jour']} mis à jour, {stats['nb_ajoutes']} ajoutés"
            }
            
        except Exception as e:
            return {
                'success': False,
                'error': str(e),
                'message': f"Erreur lors de la synchronisation : {str(e)}"
            }


def actualiser_forecasting_depuis_prets(client, sheet_name):
    """
    Fonction helper pour synchroniser facilement
    
    Args:
        client: Client gspread
        sheet_name: Nom du fichier Google Sheets
        
    Returns:
        dict avec résultat de la synchronisation
    """
    sync = DataSyncForecasting(client, sheet_name)
    return sync.synchroniser()