diff --git a/csv_dbf_converter_v2.py b/csv_dbf_converter_v2.py new file mode 100644 index 0000000..7fca9a2 --- /dev/null +++ b/csv_dbf_converter_v2.py @@ -0,0 +1,677 @@ +import json +import csv +import os +import struct +from datetime import datetime, date + +# ===== CONFIG DATEIEN ERSTELLEN/LADEN ===== + +def create_default_configs(): + """Erstellt Standard-Config-Dateien falls nicht vorhanden""" + + # Mandanten Config + if not os.path.exists('mandanten_config.json'): + mandanten_config = { + "mandanten": [ + { + "name": "Mustermann GmbH", + "nummer": "001", + "konto": "1200", + "standard_gegenkonto": "8400", + "beleg_index": 1 + }, + { + "name": "Beispiel AG", + "nummer": "002", + "konto": "1800", + "standard_gegenkonto": "8500", + "beleg_index": 1 + } + ] + } + with open('mandanten_config.json', 'w', encoding='utf-8') as f: + json.dump(mandanten_config, f, indent=4, ensure_ascii=False) + print("✓ mandanten_config.json erstellt") + + # Prüfe ob Identitäten-Dateien für jeden Mandanten existieren + mandanten = load_mandanten() + for mandant in mandanten: + # Mieter-Identitäten + mieter_datei = f"identitaeten_mieter_{mandant['nummer']}.json" + if not os.path.exists(mieter_datei): + mieter_config = { + "mandant": mandant['name'], + "mandantennummer": mandant['nummer'], + "mieter": [ + { + "suchbegriffe": ["Thomas Müller", "Müller", "T. Müller"], + "konto": "1210", + "beschreibung": "Miete Thomas Müller" + }, + { + "suchbegriffe": ["Schmidt", "Anna Schmidt", "A. Schmidt"], + "konto": "1220", + "beschreibung": "Miete Anna Schmidt" + }, + { + "suchbegriffe": ["Weber GmbH", "Weber"], + "konto": "1230", + "beschreibung": "Miete Weber GmbH" + } + ] + } + with open(mieter_datei, 'w', encoding='utf-8') as f: + json.dump(mieter_config, f, indent=4, ensure_ascii=False) + print(f"✓ {mieter_datei} erstellt") + + # Kostenkonten-Identitäten + kosten_datei = f"identitaeten_kosten_{mandant['nummer']}.json" + if not os.path.exists(kosten_datei): + kosten_config = { + "mandant": mandant['name'], + "mandantennummer": mandant['nummer'], + "kostenkonten": [ + { + "suchbegriffe": ["Wasser", "Wasserbetriebe", "Berliner Wasserbetriebe"], + "gegenkonto": "6300", + "beschreibung": "Wasserkosten" + }, + { + "suchbegriffe": ["Strom", "Vattenfall", "Stromversorgung"], + "gegenkonto": "6310", + "beschreibung": "Stromkosten" + }, + { + "suchbegriffe": ["Gas", "Gasag", "Gasversorgung"], + "gegenkonto": "6320", + "beschreibung": "Gaskosten" + }, + { + "suchbegriffe": ["Versicherung", "Allianz", "Gebäudeversicherung"], + "gegenkonto": "6500", + "beschreibung": "Versicherung" + }, + { + "suchbegriffe": ["Hausverwaltung", "Verwaltung", "Verwaltungsgebühr"], + "gegenkonto": "6700", + "beschreibung": "Hausverwaltungskosten" + } + ] + } + with open(kosten_datei, 'w', encoding='utf-8') as f: + json.dump(kosten_config, f, indent=4, ensure_ascii=False) + print(f"✓ {kosten_datei} erstellt") + + # Projekte-Datei + projekte_datei = f"projekte_{mandant['nummer']}.json" + if not os.path.exists(projekte_datei): + projekte_config = { + "mandant": mandant['name'], + "mandantennummer": mandant['nummer'], + "default_projekt": "ALLG001", + "projekte": [ + { + "suchbegriffe": ["Projekt Alpha", "Alpha"], + "kst": "PRJ001", + "beschreibung": "Projekt Alpha" + }, + { + "suchbegriffe": ["Hausverwaltung", "HV2024"], + "kst": "HV2024", + "beschreibung": "Hausverwaltung 2024" + }, + { + "suchbegriffe": ["Musterstraße 10", "Musterstr. 10"], + "kst": "HAUS01", + "beschreibung": "Musterstraße 10" + } + ] + } + with open(projekte_datei, 'w', encoding='utf-8') as f: + json.dump(projekte_config, f, indent=4, ensure_ascii=False) + print(f"✓ {projekte_datei} erstellt") + +def load_mandanten(): + """Lädt Mandanten aus Config""" + with open('mandanten_config.json', 'r', encoding='utf-8') as f: + return json.load(f)['mandanten'] + +def save_mandanten(mandanten): + """Speichert Mandanten in Config""" + with open('mandanten_config.json', 'w', encoding='utf-8') as f: + json.dump({"mandanten": mandanten}, f, indent=4, ensure_ascii=False) + +def load_mieter(mandantennummer): + """Lädt Mieter für einen bestimmten Mandanten""" + mieter_datei = f"identitaeten_mieter_{mandantennummer}.json" + with open(mieter_datei, 'r', encoding='utf-8') as f: + return json.load(f)['mieter'] + +def load_kostenkonten(mandantennummer): + """Lädt Kostenkonten für einen bestimmten Mandanten""" + kosten_datei = f"identitaeten_kosten_{mandantennummer}.json" + with open(kosten_datei, 'r', encoding='utf-8') as f: + return json.load(f)['kostenkonten'] + +def load_projekte(mandantennummer): + """Lädt Projekte/Kostenstellen für einen bestimmten Mandanten""" + projekte_datei = f"projekte_{mandantennummer}.json" + with open(projekte_datei, 'r', encoding='utf-8') as f: + config = json.load(f) + return config['projekte'], config.get('default_projekt', '') + +def normalize_text(text): + """ + Normalisiert Text für Vergleiche: + - Kleinbuchstaben + - ü → ue, ö → oe, ä → ae, ß → ss + """ + text = text.lower() + replacements = { + 'ü': 'ue', 'ö': 'oe', 'ä': 'ae', 'ß': 'ss', + 'é': 'e', 'è': 'e', 'ê': 'e', 'à': 'a', 'â': 'a' + } + for old, new in replacements.items(): + text = text.replace(old, new) + return text + +# ===== PARSING LOGIK ===== + +def parse_mieter(buchungstext, mieter): + """ + Sucht nach Mietern im Buchungstext (OHNE Projektnummer) + Returns: (konto, beschreibung) oder (None, None) + """ + buchungstext_norm = normalize_text(buchungstext) + + for mieter_entry in mieter: + for suchbegriff in mieter_entry['suchbegriffe']: + suchbegriff_norm = normalize_text(suchbegriff) + if suchbegriff_norm in buchungstext_norm: + print(f" → Mieter gefunden: '{suchbegriff}' → Konto {mieter_entry['konto']}") + return mieter_entry['konto'], mieter_entry.get('beschreibung', '') + + return None, None + +def parse_kostenkonten(buchungstext, kostenkonten): + """ + Sucht nach Kostenkonten im Buchungstext + Returns: (gegenkonto, beschreibung) oder (None, None) + """ + buchungstext_norm = normalize_text(buchungstext) + + for kosten_entry in kostenkonten: + for suchbegriff in kosten_entry['suchbegriffe']: + suchbegriff_norm = normalize_text(suchbegriff) + if suchbegriff_norm in buchungstext_norm: + print(f" → Kostenkonto gefunden: '{suchbegriff}' → Gegenkonto {kosten_entry['gegenkonto']}") + return kosten_entry['gegenkonto'], kosten_entry.get('beschreibung', '') + + return None, None + +def parse_projekt(buchungstext, projekte, default_projekt): + """ + Sucht nach Projekten im Buchungstext + Wird NUR aufgerufen wenn Kostenkonto gefunden wurde! + Returns: kostenstelle + """ + buchungstext_norm = normalize_text(buchungstext) + + for projekt in projekte: + for suchbegriff in projekt['suchbegriffe']: + suchbegriff_norm = normalize_text(suchbegriff) + if suchbegriff_norm in buchungstext_norm: + print(f" → Projekt gefunden: '{suchbegriff}' → KST {projekt['kst']}") + return projekt['kst'] + + # Kein Projekt gefunden → Default verwenden + if default_projekt: + print(f" → Kein Projekt gefunden → Default-KST {default_projekt}") + return default_projekt + + return '' + +def bestimme_konten_und_kst(buchungstext, mandant, mieter, kostenkonten, projekte, default_projekt): + """ + Bestimmt Konten, Kostenstelle und Beschreibung basierend auf dem Buchungstext. + Mit Mieternummer-Parsing für Konten beginnend mit "1". + """ + norm_buchungstext = normalize_text(buchungstext) + + # --- TEIL 0: ZUERST NACH MIETERNUMMER SUCHEN (nur für Konten mit "1") --- + import re + buchungstyp = 'unbekannt' + gegenkonto = None + beschreibung = None + + # Suche nach 5-stelligen Zahlen im Buchungstext (potenzielle Mieternummern) + mieternummer_pattern = r'\b(1\d{4})\b' # 5-stellige Zahl beginnend mit 1 + gefundene_nummern = re.findall(mieternummer_pattern, buchungstext) + + if gefundene_nummern: + print(f" 🔍 Gefundene potenzielle Mieternummern: {gefundene_nummern}") + + # Prüfe ob eine der gefundenen Nummern ein Mieterkonto ist + for nummer in gefundene_nummern: + for mieter_entry in mieter: + if mieter_entry.get('konto') == nummer: + gegenkonto = mieter_entry.get('konto') + beschreibung = mieter_entry.get('beschreibung') + buchungstyp = 'mieter' + print(f" ✓ Mieter über Kontonummer gefunden: {nummer} → {beschreibung}") + break + if buchungstyp == 'mieter': + break + + # --- TEIL 1: NUR WENN KEINE MIETERNUMMER GEFUNDEN, NORMALES PARSING --- + if buchungstyp == 'unbekannt': + # Standard-Gegenkonto aus Mandanten-Config verwenden + gegenkonto = mandant.get('standard_gegenkonto', '1300') # KORRIGIERT! + beschreibung = "Unbekannte Buchung" + + # Suche nach Mietern über Suchbegriffe + for mieter_entry in mieter: + for suchbegriff in mieter_entry.get('suchbegriffe', []): + if normalize_text(suchbegriff) in norm_buchungstext: + gegenkonto = mieter_entry.get('konto') + beschreibung = mieter_entry.get('beschreibung') + buchungstyp = 'mieter' + print(f" ✓ Buchungstyp: Mieter (gefunden über '{suchbegriff}')") + break + if buchungstyp == 'mieter': + break + + # --- TEIL 1b: KOSTENKONTEN PRÜFEN --- + if buchungstyp == 'unbekannt': + for kosten_entry in kostenkonten: + for suchbegriff in kosten_entry.get('suchbegriffe', []): + if normalize_text(suchbegriff) in norm_buchungstext: + gegenkonto = kosten_entry.get('gegenkonto') + beschreibung = kosten_entry.get('bezeichnung', '') + buchungstyp = 'kosten' + print(f" ✓ Buchungstyp: Kosten (gefunden über '{suchbegriff}')") + break + if buchungstyp == 'kosten': + break + + # --- TEIL 2: KOSTENSTELLE BESTIMMEN (nur bei Kostenkonten) --- + gefundene_kst = '' + if buchungstyp == 'kosten': + for projekt in projekte: + for suchbegriff in projekt.get('suchbegriffe', []): + if normalize_text(suchbegriff) in norm_buchungstext: + gefundene_kst = projekt.get('kst', '') + print(f" ✓ Projekt-Kostenstelle gefunden: '{gefundene_kst}' (über '{suchbegriff}')") + break + if gefundene_kst: + break + + # Default-Kostenstelle nur bei Kostenkonten + if not gefundene_kst and default_projekt: + gefundene_kst = default_projekt + print(f" → Keine Kostenstelle gefunden → Default-KST {default_projekt}") + + # --- TEIL 3: ERGEBNISSE ZUSAMMENFÜHREN --- + konto = mandant.get('standard_bank_kto', mandant.get('konto', '1200')) + kostenstelle = gefundene_kst + + # DIAGNOSE-AUSGABE + print("\n" + "="*25 + " FINALES ZUORDNUNGSERGEBNIS " + "="*25) + print(f" - KONTO: {konto}") + print(f" - GEGENKONTO: {gegenkonto}") + print(f" - KOSTENSTELLE: '{kostenstelle}'") + print(f" - BESCHREIBUNG: {beschreibung}") + print(f" - BUCHUNGSTYP: {buchungstyp}") + print("="*70 + "\n") + + return str(konto), str(gegenkonto), str(kostenstelle), beschreibung + + + +# ===== CSV EINLESEN ===== + +def lese_csv(csv_datei): + """Liest CSV-Datei ein und gibt Liste von Buchungen zurück""" + buchungen = [] + + with open(csv_datei, 'r', encoding='utf-8') as f: + reader = csv.DictReader(f, delimiter=';') + + # Spaltennamen bereinigen (Leerzeichen UND BOM entfernen) + reader.fieldnames = [name.strip().lstrip('\ufeff') if name else name for name in reader.fieldnames] + + # Debug: Spaltennamen anzeigen + print(f"\n📋 Gefundene CSV-Spalten: {reader.fieldnames}") + + # Flexible Spaltenerkennung + datum_spalte = None + text_spalte = None + betrag_spalte = None + + for name in reader.fieldnames: + name_lower = name.lower() + if 'buchungstag' in name_lower or 'datum' in name_lower: + datum_spalte = name + if 'buchungstext' in name_lower or 'verwendungszweck' in name_lower or 'text' in name_lower: + text_spalte = name + if 'betrag' in name_lower or 'amount' in name_lower: + betrag_spalte = name + + if not datum_spalte: + raise ValueError(f"Keine Datums-Spalte gefunden! Verfügbare Spalten: {reader.fieldnames}") + if not text_spalte: + raise ValueError(f"Keine Text-Spalte gefunden! Verfügbare Spalten: {reader.fieldnames}") + if not betrag_spalte: + raise ValueError(f"Keine Betrags-Spalte gefunden! Verfügbare Spalten: {reader.fieldnames}") + + print(f"✓ Verwende Spalten: Datum='{datum_spalte}', Text='{text_spalte}', Betrag='{betrag_spalte}'") + + for row in reader: + try: + # Datum parsen (Format: DD.MM.YYYY) + datum_str = row[datum_spalte].strip() + datum = datetime.strptime(datum_str, '%d.%m.%Y') + + # Betrag konvertieren (Format: -123,45 → -123.45) + betrag_str = row[betrag_spalte].strip().replace('.', '').replace(',', '.') + betrag = float(betrag_str) + + # Buchungstext + buchungstext = row[text_spalte].strip() if row[text_spalte] else '' + + buchung = { + 'datum': datum, + 'buchungstext': buchungstext, + 'betrag': betrag, + 'umsatzart': row.get('Umsatzart', '').strip() if row.get('Umsatzart') else '' + } + buchungen.append(buchung) + except Exception as e: + print(f"⚠️ Zeile übersprungen (Fehler: {e}): {row}") + continue + + return buchungen + +def filtere_buchungen(buchungen, monate, jahre): + """Filtert Buchungen nach ausgewählten Monaten und Jahren""" + gefiltert = [] + for buchung in buchungen: + if buchung['datum'].month in monate and buchung['datum'].year in jahre: + gefiltert.append(buchung) + return gefiltert + +# ===== DBF MANUELL ERSTELLEN ===== + +# --- START: ERSETZEN SIE IHRE ALTE FUNKTION DURCH DIESE --- + +def erstelle_dbf_manuell(mandant, buchungen, mieter, kostenkonten, projekte, default_projekt, ausgabe_datei_praefix, start_beleg_nr): + """ + Gruppiert Buchungen nach Monat und erstellt für jeden Monat eine eigene, + Byte-genaue DBF-Datei. + Returns: letzte_beleg_nr + """ + + # NEU: Schritt 1 - Buchungen nach Monat gruppieren + buchungen_pro_monat = {} + for buchung in buchungen: + monats_schluessel = buchung['datum'].strftime('%Y-%m') # z.B. '2023-10' + if monats_schluessel not in buchungen_pro_monat: + buchungen_pro_monat[monats_schluessel] = [] + buchungen_pro_monat[monats_schluessel].append(buchung) + + print(f"\nFunde {len(buchungen_pro_monat)} verschiedene Monate in den Buchungsdaten.") + + beleg_nr = start_beleg_nr + + # NEU: Schritt 2 - Für jeden Monat eine eigene DBF-Datei erstellen + for monat_key, monats_buchungen in buchungen_pro_monat.items(): + + jahr, monat = map(int, monat_key.split('-')) + jahr_kurz = str(jahr)[-2:] + + # NEU: Dateinamen für diesen Monat generieren + ausgabe_datei = f"{ausgabe_datei_praefix}_{monat:02d}{jahr_kurz}.dbf" + print(f"\n{'='*70}") + print(f"Erstelle DBF-Datei für Monat {monat_key}: {ausgabe_datei}") + print(f"{'='*70}") + + # --- AB HIER BEGINNT IHRE BEWÄHRTE LOGIK, ANGEPASST FÜR EINE DATEI --- + + # DBF Header (32 bytes) - angepasst für die Anzahl der Monatsbuchungen + heute = datetime.now() + num_records = len(monats_buchungen) + header_len = 32 + 43 * 32 + 1 # 32 Header + 43 Felder * 32 + 1 Terminator + record_len = 368 # 1 (deletion) + 367 (fields) + + header = bytearray(32) + header[0] = 0x03 # dBase III + header[1] = heute.year - 1900 + header[2] = heute.month + header[3] = heute.day + struct.pack_into(' 0 else f"{int(value):{length}d}" + record[pos:pos+length] = formatted.rjust(length)[:length].encode('ascii') + else: + encoded = value.encode('cp850')[:length] + record[pos:pos+length] = encoded.ljust(length) + pos += length + + # Felder schreiben (Ihre exakte Reihenfolge) + write_field(datum.day, 2, is_numeric=True); write_field(datum.month, 2, is_numeric=True) + write_field(beleg_str, 10); write_field(konto_num, 8, is_numeric=True) + write_field(gegenkonto_num, 8, is_numeric=True); write_field(kostenstelle, 8) + write_field('', 10); write_field(betrag, 14, decimals=2, is_numeric=True) + write_field(0.00, 5, decimals=2, is_numeric=True); write_field(0.00, 14, decimals=2, is_numeric=True) + write_field(buchungstext, 30); write_field(0.00, 14, decimals=2, is_numeric=True) + write_field('', 3); write_field(0, 8, is_numeric=True); write_field(True, 1, is_logical=True) + write_field(betrag, 14, decimals=2, is_numeric=True); write_field(None, 14, empty=True) + write_field(None, 1, empty=True); write_field(text2, 30); write_field(None, 1, empty=True) + write_field(standard_datum, 8, is_date=True); write_field(False, 1, is_logical=True) + write_field(datum.year, 4, is_numeric=True); write_field('', 20); write_field(None, 8, empty=True) + write_field(None, 15, empty=True); write_field(0.00, 14, decimals=2, is_numeric=True) + write_field(standard_datum, 8, is_date=True); write_field(None, 1, empty=True) + write_field(False, 1, is_logical=True); write_field(0, 8, is_numeric=True) + write_field('', 20); write_field(0.00, 14, decimals=2, is_numeric=True) + write_field(None, 6, empty=True); write_field('', 3); write_field(0, 1, is_numeric=True) + write_field(standard_datum, 8, is_date=True); write_field(None, 1, empty=True) + write_field(None, 1, empty=True); write_field('', 1); write_field(0, 4, is_numeric=True) + write_field(None, 8, empty=True); write_field('', 15) + + f.write(record) + beleg_nr += 1 + + f.write(b'\x1A') # EOF + + print(f"✓ DBF-Datei '{ausgabe_datei}' erfolgreich mit {num_records} Einträgen erstellt.") + + letzte_beleg_nr = beleg_nr - 1 + return letzte_beleg_nr + +# --- ENDE: BIS HIER ALLES ERSETZEN --- + +# ===== HILFSFUNKTIONEN ===== + +def parse_monatseingabe(eingabe): + """Parst Monatseingabe wie '1,2,3' oder '1-3' oder '12' """ + monate = set() + teile = eingabe.split(',') + + for teil in teile: + teil = teil.strip() + if '-' in teil: + start, ende = map(int, teil.split('-')) + monate.update(range(start, ende + 1)) + else: + monate.add(int(teil)) + + return sorted(list(monate)) + +def parse_jahreseingabe(eingabe): + """Parst Jahreseingabe wie '2024' oder '2023,2024' """ + jahre = set() + teile = eingabe.split(',') + + for teil in teile: + teil = teil.strip() + if '-' in teil: + start, ende = map(int, teil.split('-')) + jahre.update(range(start, ende + 1)) + else: + jahre.add(int(teil)) + + return sorted(list(jahre)) + +# ===== HAUPTPROGRAMM ===== + +def main(): + print("=" * 70) + print(" CSV zu DBF Konverter für Hausverwaltung (v2)") + print("=" * 70) + + create_default_configs() + + mandanten = load_mandanten() + + print("\n📋 Verfügbare Mandanten:") + for idx, m in enumerate(mandanten, 1): + print(f" {idx}. {m['name']} (Nr. {m['nummer']}, Konto {m['konto']}, nächster Beleg: {m.get('beleg_index', 1)})") + + auswahl = int(input("\n➤ Mandant auswählen (Nummer): ")) - 1 + mandant = mandanten[auswahl] + + print(f"\n✓ Mandant gewählt: {mandant['name']}") + print(f" Mandantennummer: {mandant['nummer']}") + print(f" Konto: {mandant['konto']}") + print(f" Standard-Gegenkonto: {mandant['standard_gegenkonto']}") + print(f" Start-Belegnummer: {mandant.get('beleg_index', 1)}") + + mieter = load_mieter(mandant['nummer']) + print(f" Mieter geladen: {len(mieter)} Einträge") + + kostenkonten = load_kostenkonten(mandant['nummer']) + print(f" Kostenkonten geladen: {len(kostenkonten)} Einträge") + + projekte, default_projekt = load_projekte(mandant['nummer']) + print(f" Projekte geladen: {len(projekte)} Einträge") + print(f" Default-Projekt: {default_projekt}") + + csv_datei = input("\n➤ CSV-Datei Pfad: ") + alle_buchungen = lese_csv(csv_datei) + print(f"\n✓ {len(alle_buchungen)} Buchungen aus CSV geladen") + + print("\n📅 Für welche Monate sollen DBF-Dateien erstellt werden?") + print(" Beispiele: '12' oder '1,2,3' oder '1-12'") + monate_eingabe = input("➤ Monate: ") + monate = parse_monatseingabe(monate_eingabe) + print(f"✓ Ausgewählte Monate: {', '.join(map(str, monate))}") + + print("\n📅 Für welche Jahre sollen DBF-Dateien erstellt werden?") + print(" Beispiele: '2024' oder '2023,2024' oder '2023-2024'") + jahre_eingabe = input("➤ Jahre: ") + jahre = parse_jahreseingabe(jahre_eingabe) + print(f"✓ Ausgewählte Jahre: {', '.join(map(str, jahre))}") + + gefilterte_buchungen = filtere_buchungen(alle_buchungen, monate, jahre) + print(f"\n✓ {len(gefilterte_buchungen)} Buchungen gefiltert (von {len(alle_buchungen)} gesamt)") + + if len(gefilterte_buchungen) == 0: + print("\n⚠️ Keine Buchungen für die ausgewählten Monate/Jahre gefunden!") + return + + monate_str = '_'.join(map(str, monate)) if len(monate) <= 3 else f"{min(monate)}-{max(monate)}" + jahre_str = '_'.join(map(str, jahre)) + ausgabe_datei = f"buchungen_{mandant['nummer']}_M{monate_str}_J{jahre_str}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.dbf" + + print(f"\n🔨 Erstelle DBF-Datei: {ausgabe_datei}") + print("-" * 70) + + start_beleg_nr = mandant.get('beleg_index', 1) + letzte_beleg_nr = erstelle_dbf_manuell( + mandant, gefilterte_buchungen, mieter, kostenkonten, + projekte, default_projekt, ausgabe_datei, start_beleg_nr + ) + + # Beleg-Index in Config aktualisieren + mandant['beleg_index'] = letzte_beleg_nr + 1 + save_mandanten(mandanten) + print(f"\n✓ Mandanten-Config aktualisiert: Nächster Beleg startet bei {mandant['beleg_index']}") + + print("\n" + "=" * 70) + print("✅ Fertig! DBF-Datei erfolgreich erstellt.") + print("=" * 70) + print(f"\n💾 Datei: {ausgabe_datei}") + print(f"📊 Buchungen: {len(gefilterte_buchungen)}") + print(f"🔢 Belegnummern: {start_beleg_nr} bis {letzte_beleg_nr}") + print(f"📄 Mieter-Config: identitaeten_mieter_{mandant['nummer']}.json") + print(f"📄 Kosten-Config: identitaeten_kosten_{mandant['nummer']}.json") + print(f"📄 Projekte-Config: projekte_{mandant['nummer']}.json") + +if __name__ == "__main__": + main()