Dateien nach "/" hochladen
This commit is contained in:
parent
b3b275c898
commit
d8d749779f
|
|
@ -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('<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()
|
||||||
Loading…
Reference in New Issue