import csv from datetime import datetime, timedelta from collections import defaultdict # ... (includere le funzioni formatta_importo_per_tipo1, formatta_importo_per_tipo3, # formatta_data_aammgg, crea_record_tipo1, crea_record_tipo3 definite sopra) ... def carica_dati_bollettini_strutturati(percorso_file_bollettini_csv): """ Carica i dati dei bollettini da un file CSV strutturato. Colonne attese nel CSV: NumeroRataPDF,NomeCondominoPDF,ScalaPDF,InternoPDF, TipoSpesaPDF,MeseRataPDF,AnnoRataPDF,ImportoRataPDF """ bollettini = {} # Dizionario per accesso rapido: chiave NumeroRataPDF try: with open(percorso_file_bollettini_csv, 'r', encoding='utf-8-sig') as file_csv: # utf-8-sig per BOM reader = csv.DictReader(file_csv, delimiter=';') for riga in reader: try: # Converti l'importo in float, gestendo la virgola europea importo_float = float(riga['ImportoRataPDF'].replace('.', '').replace(',', '.')) bollettini[riga['NumeroRataPDF']] = { 'condomino_pdf': riga['NomeCondominoPDF'].strip().upper(), 'unita_pdf': f"{riga.get('ScalaPDF','').strip()}/{riga.get('InternoPDF','').strip()}", 'tipo_spesa_pdf': riga['TipoSpesaPDF'].strip().upper(), 'mese_rata_pdf': riga['MeseRataPDF'].strip(), # Mantiene il nome del mese es. "Gennaio" 'anno_rata_pdf': riga['AnnoRataPDF'].strip(), 'importo_pdf': importo_float, 'numero_rata_pdf': riga['NumeroRataPDF'].strip() } except ValueError: print(f"WARN: Riga bollettino saltata per errore conversione importo: {riga}") except KeyError as ke: print(f"WARN: Colonna mancante nel CSV bollettini: {ke} in riga {riga}") except FileNotFoundError: print(f"ERRORE: File bollettini non trovato: {percorso_file_bollettini_csv}") return bollettini def estrai_nome_pagatore(descrizione_banca): """ Estrae il nome del pagatore dalla descrizione del bonifico (euristica). """ try: if "DA " in descrizione_banca: start_index = descrizione_banca.find("DA ") + 3 end_index = descrizione_banca.find(" PER ") if end_index == -1: # Prova a trovare altri delimitatori comuni se "PER" non c'è possible_ends = [" TRN ", " COMM ", " AVVISO ", " RIC."] for pe in possible_ends: if pe in descrizione_banca[start_index:]: end_index = descrizione_banca[start_index:].find(pe) + start_index break if end_index == -1 : # Se ancora non trovato, prendi una porzione end_index = start_index + 40 # Limite arbitrario return descrizione_banca[start_index:end_index].strip().upper() except Exception: pass # Ignora errori di parsing qui, il nome potrebbe non essere estraibile return "SCONOSCIUTO" def abbina_transazione_a_bollettini(transazione_banca, dati_bollettini_pdf, terzo_file_conferma=None): """ Tenta di abbinare una transazione bancaria a uno o più bollettini. Ritorna una lista di dizionari bollettino abbinati (o None se nessun abbinamento). Questa è la funzione "cuore" che andrà molto raffinata. """ abbinamenti = [] desc_banca_upper = transazione_banca['DescrizioneOriginale'].upper() nome_pagatore_banca = transazione_banca['OrdinanteEstratto'] # Già estratto e normalizzato # --- Logica Specifica per Multi-Rata (esempi) --- # Caso CECCHINI - RATE DA 1 A 6 LAVORI FOGNE 2022 if "CECCHINI ALESSANDRO" in nome_pagatore_banca and "RATE DA 1 A 6" in desc_banca_upper and "LAVORI FOGNE 2022" in desc_banca_upper: mesi_rate_cecchini = ["Gennaio", "Febbraio", "Marzo", "Aprile", "Maggio", "Giugno"] for i, mese_cercato in enumerate(mesi_rate_cecchini): for num_rata_pdf, b_info in dati_bollettini_pdf.items(): if "CECCHINI ALESSANDRO" in b_info['condomino_pdf'] and \ b_info['mese_rata_pdf'].upper() == mese_cercato.upper() and \ b_info['anno_rata_pdf'] == '2022' and "FOGNE" in b_info['tipo_spesa_pdf']: abbinamenti.append(b_info) break if len(abbinamenti) == 6: return abbinamenti else: return None # Qualcosa non quadra # Caso QUINTI PAOLO - RATE 1 E 2 LAVORI FOGNE 2022 if "QUINTI PAOLO" in nome_pagatore_banca and "RATE 1 E 2 LAVORI FOGNE 2022" in desc_banca_upper: mesi_rate_quinti = ["Gennaio", "Febbraio"] for mese_cercato in mesi_rate_quinti: for num_rata_pdf, b_info in dati_bollettini_pdf.items(): if "QUINTI PAOLO" in b_info['condomino_pdf'] and \ b_info['mese_rata_pdf'].upper() == mese_cercato.upper() and \ b_info['anno_rata_pdf'] == '2022' and "FOGNE" in b_info['tipo_spesa_pdf']: abbinamenti.append(b_info) break if len(abbinamenti) == 2: return abbinamenti else: return None # --- Logica di Abbinamento Generale per Rata Singola (da migliorare molto) --- # Questa è una logica molto basilare e andrebbe espansa con il "terzo file", # controlli sull'importo, riferimenti a scala/interno, ecc. if "FOGNE" in desc_banca_upper: # Parola chiave generica for num_rata_pdf, b_info in dati_bollettini_pdf.items(): if b_info['tipo_spesa_pdf'] == "LAV. FOGNE 22": # Filtra per il tipo di spesa dei bollettini che abbiamo # Tentativo di match su nome e riferimento alla rata/mese (molto semplificato) condomino_match = nome_pagatore_banca in b_info['condomino_pdf'] or \ b_info['condomino_pdf'] in nome_pagatore_banca rata_gen_match = ("1 RATA" in desc_banca_upper or "RATA GENNAIO" in desc_banca_upper or "1 LAVORI FOGNE 2022" in desc_banca_upper) and b_info['mese_rata_pdf'] == "Gennaio" rata_feb_match = ("2 RATA" in desc_banca_upper or "RATA FEBBRAIO" in desc_banca_upper) and b_info['mese_rata_pdf'] == "Febbraio" # ... Aggiungere logica per altre rate ... # Controllo importo (opzionale, potrebbe essere troppo restrittivo all'inizio) # importo_match = abs(transazione_banca['ImportoFloat'] - b_info['importo_pdf']) < 0.10 # Tolleranza 10 cent if condomino_match and (rata_gen_match or rata_feb_match): # Aggiungere OR per altre rate abbinamenti.append(b_info) return abbinamenti # Trovato un abbinamento, per ora ci fermiamo al primo return None # Nessun abbinamento trovato def processa_e_genera_file_incasso(percorso_estratto_csv, dati_bollettini_strutturati, file_output_incassi, file_output_confronto): """ Funzione principale per processare l'estratto conto, abbinare ai bollettini e generare i file di output. """ transazioni_bancarie_lette = [] righe_confronto = [] records_tipo1_per_mese = defaultdict(list) colonne_confronto = [ "DataValuta", "OrdinanteBonifico", "DescrizioneBonifico", "ImportoBonificoEUR", "MeseAnnoValuta", "AbbinatoAlPDF_LavoriFogne22", "NumeroRataUtilizzato", "ImportoRataBollettinoEUR", "NoteAbbinamento" ] try: with open(percorso_estratto_csv, 'r', encoding='utf-8-sig') as file_csv: reader = csv.DictReader(file_csv, delimiter=';') for i, riga_banca in enumerate(reader): if i == 0 and "Situazione al" in riga_banca.get(next(iter(riga_banca)),""): # Salta eventuale riga di intestazione aggiuntiva continue if not riga_banca.get('Valuta') or not riga_banca.get('Descrizione') or not riga_banca.get('Euro'): # Salta righe vuote o malformate continue try: data_valuta_str = riga_banca['Valuta'] data_valuta_obj = datetime.strptime(data_valuta_str, "%d/%m/%Y") descrizione_originale = riga_banca['Descrizione'] importo_str = riga_banca['Euro'].replace('.', '').replace(',', '.') importo_float = float(importo_str) nome_ordinante = estrai_nome_pagatore(descrizione_originale) mese_anno_valuta = data_valuta_obj.strftime("%Y-%m") riga_base_confronto = { "DataValuta": data_valuta_str, "OrdinanteBonifico": nome_ordinante, "DescrizioneBonifico": descrizione_originale, "ImportoBonificoEUR": importo_float, "MeseAnnoValuta": mese_anno_valuta, "AbbinatoAlPDF_LavoriFogne22": "No", "NumeroRataUtilizzato": "", "ImportoRataBollettinoEUR": "", "NoteAbbinamento": "" } if "BONIFICO A VOSTRO FAVORE" in descrizione_originale.upper() and importo_float > 0: if "FOGNE" in descrizione_originale.upper(): # Filtro principale bollettini_abbinati = abbina_transazione_a_bollettini( {'DescrizioneOriginale': descrizione_originale, 'ImportoFloat': importo_float, 'OrdinanteEstratto': nome_ordinante}, dati_bollettini_strutturati ) if bollettini_abbinati: riga_base_confronto["AbbinatoAlPDF_LavoriFogne22"] = "Sì" if len(bollettini_abbinati) > 1: riga_base_confronto["NoteAbbinamento"] = f"Multi-rata ({len(bollettini_abbinati)})" for bollettino_scelto in bollettini_abbinati: record_t1 = crea_record_tipo1( data_valuta_obj, bollettino_scelto['importo_pdf'], # Usiamo l'importo del bollettino bollettino_scelto['numero_rata_pdf'] ) records_tipo1_per_mese[mese_anno_valuta].append(record_t1) # Per il file di confronto, se multi-rata, potremmo listare tutti i num rata if len(bollettini_abbinati) == 1: riga_base_confronto["NumeroRataUtilizzato"] = bollettino_scelto['numero_rata_pdf'] riga_base_confronto["ImportoRataBollettinoEUR"] = bollettino_scelto['importo_pdf'] else: # Per multi-rata, è più complesso da rappresentare in una singola riga di confronto riga_base_confronto["NumeroRataUtilizzato"] += f"{bollettino_scelto['numero_rata_pdf']}({bollettino_scelto['importo_pdf']});" else: # Non abbinato ma contiene "FOGNE" riga_base_confronto["NoteAbbinamento"] = "Transazione 'FOGNE' non abbinata a bollettini LAVORI FOGNE 22." else: # Bonifico a favore, ma non contiene "FOGNE" riga_base_confronto["NoteAbbinamento"] = "Bonifico non riguardante 'FOGNE'." # Potresti decidere di non includerlo affatto nel file di confronto se non contiene 'FOGNE' # Per ora, se è un bonifico a favore, lo includo per completezza ma senza abbinamento. # Modifichiamo: includiamo solo se "FOGNE" era presente. if not "FOGNE" in descrizione_originale.upper(): continue # Salta questa transazione per il file di confronto se non ha "FOGNE" else: # Non è un "BONIFICO A VOSTRO FAVORE" o importo non positivo continue # Salta per entrambi i file righe_confronto.append(riga_base_confronto) except Exception as e: print(f"ERRORE durante l'elaborazione della riga: {riga_banca} - Dettaglio: {e}") righe_confronto.append({ "DataValuta": riga_banca.get('Valuta',"N/A"), "OrdinanteBonifico": "ERRORE PARSING", "DescrizioneBonifico": riga_banca.get('Descrizione',"N/A"), "ImportoBonificoEUR": riga_banca.get('Euro',"N/A"), "MeseAnnoValuta": "N/A", "AbbinatoAlPDF_LavoriFogne22": "Errore", "NumeroRataUtilizzato": "", "ImportoRataBollettinoEUR": "", "NoteAbbinamento": str(e) }) except FileNotFoundError: print(f"ERRORE: File estratto conto non trovato: {percorso_estratto_csv}") return # Scrittura File 1 (Incassi Formattato) with open(file_output_incassi, 'w', encoding='utf-8') as f_out_incassi: for mese_key in sorted(records_tipo1_per_mese.keys()): records_t1_mese = records_tipo1_per_mese[mese_key] if not records_t1_mese: # Salta mesi senza transazioni abbinate # Ma potremmo voler scrivere un riepilogo a zero se l'utente lo desidera per ogni mese del periodo # Per ora, lo salto se non ci sono record T1 continue for record_t1 in records_t1_mese: f_out_incassi.write(record_t1 + "\n") anno, mese = map(int, mese_key.split('-')) if mese == 12: data_fine_mese_obj = datetime(anno, mese, 31) else: data_fine_mese_obj = datetime(anno, mese + 1, 1) - timedelta(days=1) record_t3 = crea_record_tipo3(data_fine_mese_obj, records_t1_mese) f_out_incassi.write(record_t3 + "\n") print(f"File incassi generato: {file_output_incassi}") # Scrittura File 2 (Confronto) try: with open(file_output_confronto, 'w', newline='', encoding='utf-8') as f_out_confr: writer = csv.DictWriter(f_out_confr, fieldnames=colonne_confronto, delimiter=';') writer.writeheader() writer.writerows(righe_confronto) print(f"File di confronto generato: {file_output_confronto}") except IOError: print(f"Errore durante la scrittura del file di confronto: {file_output_confronto}") # --- Esempio di come potresti chiamare la funzione principale --- if __name__ == "__main__": # 1. Prepara il file CSV dei bollettini (dati_bollettini.csv) con le colonne: # NumeroRataPDF;NomeCondominoPDF;ScalaPDF;InternoPDF;TipoSpesaPDF;MeseRataPDF;AnnoRataPDF;ImportoRataPDF # Esempio di riga: 100014800010176971;SCILLIA MICHELE;A;2;LAV. FOGNE 22;Gennaio;2022;200,18 # Popolalo con più dati possibile dal tuo PDF. # 2. Assicurati che il file dell'estratto conto sia disponibile # (es. "estratto_conto_completo.txt" nel formato CSV delimitato da ';') # Percorsi dei file (DA MODIFICARE SECONDO LE TUE ESIGENZE) path_estratto_conto = "estratto_conto_completo.txt" # Sostituisci con il percorso al tuo file path_bollettini_csv = "dati_bollettini.csv" # Sostituisci con il percorso al tuo file # Creazione di un file dati_bollettini.csv di esempio basato sulla nostra discussione # In un caso reale, questo file sarebbe molto più completo. dati_bollettini_esempio_csv = [ "NumeroRataPDF;NomeCondominoPDF;ScalaPDF;InternoPDF;TipoSpesaPDF;MeseRataPDF;AnnoRataPDF;ImportoRataPDF", "100014800010176971;SCILLIA MICHELE;A;2;LAV. FOGNE 22;Gennaio;2022;200,18", "100014800010187988;SERRATORE CLARA;D;10;LAV. FOGNE 22;Gennaio;2022;188,71", "100014800010183847;BARRA DANIELA;B;27;LAV. FOGNE 22;Gennaio;2022;238,62", "100014800010179403;CECCHINI ALESSANDRO;A;26;LAV. FOGNE 22;Gennaio;2022;238,62", "100014800010191127;CECCHINI ALESSANDRO;A;26;LAV. FOGNE 22;Febbraio;2022;235,00", "100014800010202851;CECCHINI ALESSANDRO;A;26;LAV. FOGNE 22;Marzo;2022;235,00", "100014800010214575;CECCHINI ALESSANDRO;A;26;LAV. FOGNE 22;Aprile;2022;235,00", "100014800010226206;CECCHINI ALESSANDRO;A;26;LAV. FOGNE 22;Maggio;2022;235,00", "100014800010237930;CECCHINI ALESSANDRO;A;26;LAV. FOGNE 22;Giugno;2022;235,00", "100014800010181423;QUINTI PAOLO;B;3;LAV. FOGNE 22;Gennaio;2022;221,69", "100014800010193147;QUINTI PAOLO;B;3;LAV. FOGNE 22;Febbraio;2022;216,00", "100014800010181928;FANTATO EMANUELE;C;8;LAV. FOGNE 22;Gennaio;2022;230,47", # Associato a Bondi C/8 per bonifico "100014800010193652;FANTATO EMANUELE;C;8;LAV. FOGNE 22;Febbraio;2022;228,00",# Associato a Bondi C/8 per bonifico # Aggiungi qui gli altri bollettini per i condomini menzionati (GENTILI, MAZZOCCHI, QUADRIFOGLIO, GIACOBELLO, FAZI, ecc.) # per le 6 rate di LAVORI FOGNE 22 ] with open(path_bollettini_csv, "w", encoding="utf-8") as f_bol: for line in dati_bollettini_esempio_csv: f_bol.write(line + "\n") print(f"File bollettini di esempio creato: {path_bollettini_csv}") # Carica i dati dei bollettini strutturati bollettini_db = carica_dati_bollettini_strutturati(path_bollettini_csv) if bollettini_db: # Procedi solo se i bollettini sono stati caricati # Definisci i nomi dei file di output output_file_incassi_finale = "NetGesCon_Incassi_TOTALE.txt" output_file_confronto_finale = "NetGesCon_Confronto_FOGNE_TOTALE.csv" # Chiama la funzione principale per processare l'intero estratto (o una porzione) # Per processare tutto, non specificare mesi_obiettivo (o metti tutti i mesi di interesse) # Qui, per esempio, processiamo solo il 2022 per "LAVORI FOGNE 22" processa_e_genera_file_incasso( path_estratto_conto, bollettini_db, output_file_incassi_finale, output_file_confronto_finale ) else: print("Nessun dato bollettino caricato, elaborazione interrotta.")