Zhaus/csv_dbf_converter_v2.py

678 lines
28 KiB
Python

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('<I', header, 4, num_records)
struct.pack_into('<H', header, 8, header_len)
struct.pack_into('<H', header, 10, record_len)
# Field Descriptors (exakte Struktur aus Ihrer Funktion)
fields = [
('BTT', 'N', 2, 0), ('BMM', 'N', 2, 0), ('BELEG', 'C', 10, 0),
('KONTO', 'N', 8, 0), ('GEGENKONTO', 'N', 8, 0), ('KST', 'C', 8, 0),
('KTG', 'C', 10, 0), ('BETRAG', 'N', 14, 2), ('STEUER', 'N', 5, 2),
('SKONTO', 'N', 14, 2), ('TEXT', 'C', 30, 0), ('BEZAHLT', 'N', 14, 2),
('KZ', 'C', 3, 0), ('LFDNR', 'N', 8, 0), ('EURO', 'L', 1, 0),
('ZAHLBETRAG', 'N', 14, 2), ('BEZAHLT_NK', 'N', 14, 2), ('FAELLIG', 'L', 1, 0),
('TEXT2', 'C', 30, 0), ('DATEV', 'L', 1, 0), ('FAELLIG_AM', 'D', 8, 0),
('STORNO', 'L', 1, 0), ('BJJ', 'N', 4, 0), ('TEMP1', 'C', 20, 0),
('HNDLNR', 'N', 8, 0), ('GBLFDNR', 'N', 15, 0), ('SKONTO2', 'N', 14, 2),
('DATUM2', 'D', 8, 0), ('KEIN_ZV', 'L', 1, 0), ('MANUELL', 'L', 1, 0),
('SOLLKONTO', 'N', 8, 0), ('STAPEL', 'C', 20, 0), ('SKONTSTFR', 'N', 14, 2),
('REB_LFDNR', 'N', 6, 0), ('RECHART', 'C', 3, 0), ('ZAHLART', 'N', 1, 0),
('LDATUM', 'D', 8, 0), ('XFINANZ', 'L', 1, 0), ('INZV', 'L', 1, 0),
('DUMMY', 'C', 1, 0), ('ABRJAHR', 'N', 4, 0), ('EDATUM', 'D', 8, 0),
('ENAME', 'C', 15, 0)
]
field_descriptors = bytearray()
for name, ftype, flen, fdec in fields:
fd = bytearray(32)
name_bytes = name.encode('cp850')[:11]
fd[0:len(name_bytes)] = name_bytes
fd[11] = ord(ftype)
fd[16] = flen
fd[17] = fdec
field_descriptors.extend(fd)
field_descriptors.append(0x0D)
# Datei für den aktuellen Monat schreiben
with open(ausgabe_datei, 'wb') as f:
f.write(header)
f.write(field_descriptors)
standard_datum = date(1899, 12, 30)
# NEU: Nur die Buchungen für DIESEN Monat verarbeiten
for idx, buchung in enumerate(monats_buchungen, 1):
datum = buchung['datum']
buchungstext = buchung['buchungstext'][:30]
betrag = buchung['betrag']
print(f"\nVerarbeite Buchung {idx}/{num_records} für {monat_key}...")
konto, gegenkonto, kostenstelle, beschreibung = bestimme_konten_und_kst(
buchung['buchungstext'], mandant, mieter, kostenkonten, projekte, default_projekt
)
text2 = beschreibung[:30] if beschreibung else ''
konto_num = int(konto)
gegenkonto_num = int(gegenkonto)
beleg_str = str(beleg_nr)
record = bytearray(368)
record[0] = 0x20
pos = 1
def write_field(value, length, decimals=0, is_numeric=False, is_logical=False, is_date=False, empty=False):
nonlocal pos
if empty: record[pos:pos+length] = b' ' * length
elif is_date: record[pos:pos+8] = value.strftime('%Y%m%d').encode('ascii') if value else b' ' * 8
elif is_logical: record[pos] = ord('T') if value else ord('F')
elif is_numeric:
formatted = f"{value:{length}.{decimals}f}" if decimals > 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()