netgescon-master/scripts/Script fatti per prova e per ora sospesi/estrazione_e_controllo.py

315 lines
19 KiB
Python

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"] = ""
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.")