commit 061ab744be4f6a732f34f685f1a9037517b8b4ad Author: Sebastian Zell Date: Fri Jan 16 11:30:15 2026 +0100 Initial commit diff --git a/.abacus.donotdelete b/.abacus.donotdelete new file mode 100644 index 0000000..b6b8f5f --- /dev/null +++ b/.abacus.donotdelete @@ -0,0 +1 @@ +gAAAAABpahEZgX1HGkQvRAaZ-87PqNFzt9Wer-07S9N8P2HwoSZB8j5R2rZyKdpdrzdTZ_A5bc2vYKZYVctxF0XyBFA9fad-xNImsD13P5JUdEGEMq64L9k= \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cbe1ac6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,131 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +*.spec +*.manifest +*.exe + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +# IDE +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# macOS +.DS_Store + +# Windows +Thumbs.db +ehthumbs.db +Desktop.ini + +# Generierte DBF-Dateien (können sensible Daten enthalten) +*.dbf +buchungen_*.dbf + +# Backup-Dateien +*.bak +*.backup + +# Eigene sensible Konfigurationsdateien im Hauptverzeichnis +# (Die Beispiele in config/examples/ sind erlaubt) +mandanten_config.json +identitaeten_*.json +projekte_*.json +!config/examples/*.json diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0af72cf --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Sebastian Zell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c382b97 --- /dev/null +++ b/README.md @@ -0,0 +1,317 @@ +# CSV zu DBF Konverter für Hausverwaltung + +Ein Python-Tool zur Konvertierung von CSV-Bankauszügen in DBF-Dateien für Hausverwaltungssoftware. + +## 📋 Beschreibung + +Dieses Programm konvertiert CSV-Exporte von Bankauszügen in das DBF-Format (dBase III), das von vielen älteren Hausverwaltungsprogrammen benötigt wird. Es analysiert automatisch Buchungstexte und ordnet diese den entsprechenden Konten, Gegenkonten und Kostenstellen zu. + +### Hauptfunktionen + +- **Automatische Kontenzuordnung**: Erkennt Mieter und Kostenkonten anhand konfigurierbarer Suchbegriffe +- **Mehrmandantenfähig**: Unterstützt beliebig viele Mandanten mit eigenen Konfigurationen +- **Projektzuordnung**: Automatische Zuordnung zu Kostenstellen/Projekten +- **Monatliche Trennung**: Erstellt separate DBF-Dateien pro Monat +- **Flexible Filterung**: Auswahl von Monaten und Jahren für die Konvertierung +- **GUI und CLI**: Verfügbar als grafische Oberfläche und Kommandozeilenversion + +## 🖥️ Systemanforderungen + +### Python-Version +- Python 3.8 oder höher + +### Betriebssysteme +- **Linux**: Ubuntu, Debian, Fedora, etc. +- **Windows**: Windows 10/11 + +### Abhängigkeiten +- `tkinter` (für GUI-Version, meist vorinstalliert) +- Alle anderen verwendeten Module sind Teil der Python-Standardbibliothek + +## 📦 Installation + +### Linux (Ubuntu/Debian) + +```bash +# Python und tkinter installieren +sudo apt update +sudo apt install python3 python3-tk + +# Repository klonen +git clone https://github.com/IHR-BENUTZERNAME/csv-dbf-converter.git +cd csv-dbf-converter + +# Optional: Für Build als Standalone-Executable +pip3 install pyinstaller +``` + +### Windows + +1. Python von [python.org](https://www.python.org/downloads/) herunterladen und installieren + - Bei der Installation "Add Python to PATH" aktivieren +2. Repository herunterladen oder klonen +3. Optional: Für Build als Executable + ```cmd + pip install pyinstaller + ``` + +## 🚀 Nutzung + +### GUI-Version (Empfohlen) + +```bash +# Linux +python3 csv_dbf_converter_gui.py + +# Windows +python csv_dbf_converter_gui.py +``` + +Die grafische Oberfläche führt Sie durch folgende Schritte: +1. Mandant auswählen +2. CSV-Datei auswählen +3. Zeitraum (Monate/Jahre) festlegen +4. Konvertierung starten + +### CLI-Version (Kommandozeile) + +```bash +# Linux +python3 csv_dbf_converter.py + +# Windows +python csv_dbf_converter.py +``` + +Die Kommandozeilenversion führt interaktiv durch den Prozess und zeigt detaillierte Informationen zur Verarbeitung an. + +## ⚙️ Konfiguration + +Das Programm verwendet JSON-Konfigurationsdateien. Beim ersten Start werden Beispieldateien erstellt. + +### Verzeichnisstruktur der Konfigurationsdateien + +``` +./ +├── mandanten_config.json # Mandanten-Übersicht +├── identitaeten_mieter_XXX.json # Mieter pro Mandant (XXX = Mandantennummer) +├── identitaeten_kosten_XXX.json # Kostenkonten pro Mandant +└── projekte_XXX.json # Projekte/Kostenstellen pro Mandant +``` + +### mandanten_config.json + +Definiert die verfügbaren Mandanten: + +```json +{ + "mandanten": [ + { + "name": "Mein Mandant", + "nummer": "001", + "konto": "1200", + "standard_gegenkonto": "1300", + "standard_bank_kto": "1200", + "beleg_index": 1 + } + ] +} +``` + +| Feld | Beschreibung | +|------|--------------| +| `name` | Name des Mandanten | +| `nummer` | Eindeutige Mandantennummer (3-stellig) | +| `konto` | Standard-Bankkonto | +| `standard_gegenkonto` | Gegenkonto für nicht zugeordnete Buchungen | +| `standard_bank_kto` | Bankkonto für Buchungen | +| `beleg_index` | Fortlaufende Belegnummer (wird automatisch erhöht) | + +### identitaeten_mieter_XXX.json + +Definiert Mieter mit Suchbegriffen für automatische Erkennung: + +```json +{ + "mandant": "Mein Mandant", + "mandantennummer": "001", + "mieter": [ + { + "suchbegriffe": ["Müller", "Thomas Müller", "MUELLER"], + "konto": "10010", + "beschreibung": "Thomas Müller" + } + ] +} +``` + +### identitaeten_kosten_XXX.json + +Definiert Kostenkonten für Ausgaben: + +```json +{ + "kostenkonten": [ + { + "gegenkonto": 4002, + "bezeichnung": "Be- und Entwässerung", + "suchbegriffe": ["Berliner Wasser", "WASSERBETRIEBE"] + } + ] +} +``` + +### projekte_XXX.json + +Definiert Projekte/Kostenstellen: + +```json +{ + "mandant": "Mein Mandant", + "mandantennummer": "001", + "default_projekt": "001", + "projekte": [ + { + "suchbegriffe": ["Hauptstraße", "Hauptstr"], + "kst": "001", + "beschreibung": "Objekt Hauptstraße" + } + ] +} +``` + +## 📄 CSV-Format + +Das Programm erwartet CSV-Dateien mit Semikolon als Trennzeichen und folgenden Spalten: + +| Erwartete Spalte | Alternative Namen | +|------------------|-------------------| +| Buchungstag | Datum | +| Buchungstext | Verwendungszweck, Text | +| Betrag | Amount | + +### Beispiel CSV-Datei + +```csv +Buchungstag;Buchungstext;Betrag +01.01.2024;Miete Thomas Müller Januar;750,00 +02.01.2024;Berliner Wasserbetriebe Abschlag;-125,50 +``` + +## 🔨 Build (Standalone Executable) + +### Linux + +```bash +cd scripts +chmod +x build_linux.sh +./build_linux.sh +``` + +Die ausführbare Datei befindet sich dann in `dist/csv_dbf_converter_gui`. + +### Windows + +```cmd +cd scripts +build_windows.bat +``` + +Die EXE-Datei befindet sich dann in `dist\CSV_DBF_Konverter.exe`. + +## 📁 Projektstruktur + +``` +csv-dbf-converter/ +├── csv_dbf_converter.py # CLI-Version (Hauptprogramm) +├── csv_dbf_converter_gui.py # GUI-Version mit tkinter +├── config/ +│ └── examples/ # Beispiel-Konfigurationsdateien +│ ├── mandanten_config.json +│ ├── identitaeten_mieter_*.json +│ ├── identitaeten_kosten_*.json +│ └── projekte_*.json +├── scripts/ +│ ├── build_linux.sh # Linux Build-Script +│ └── build_windows.bat # Windows Build-Script +├── requirements.txt # Python-Abhängigkeiten +├── LICENSE # MIT-Lizenz +└── README.md # Diese Datei +``` + +## 🔍 Funktionsweise + +### Zuordnungslogik + +1. **Mieternummer-Parsing**: Das Programm sucht zunächst nach 5-stelligen Zahlen beginnend mit "1" im Buchungstext, die auf ein Mieterkonto verweisen könnten. + +2. **Suchbegriff-Matching**: Falls keine Mieternummer gefunden wird, werden die konfigurierten Suchbegriffe geprüft: + - Zuerst Mieter-Suchbegriffe + - Dann Kostenkonten-Suchbegriffe + +3. **Projekt-Zuordnung**: Bei Kostenkonten wird zusätzlich nach Projekt-Suchbegriffen gesucht. Falls kein Projekt gefunden wird, wird das Default-Projekt verwendet. + +### Text-Normalisierung + +Für den Vergleich werden Texte normalisiert: +- Kleinschreibung +- Umlaute: ü→ue, ö→oe, ä→ae, ß→ss + +## 📝 Beispiele + +### Beispiel-Workflow + +1. **Mandanten konfigurieren** in `mandanten_config.json` +2. **Mieter anlegen** in `identitaeten_mieter_001.json` +3. **Kostenkonten anlegen** in `identitaeten_kosten_001.json` +4. **CSV-Datei von Bank exportieren** +5. **Konverter starten** und CSV-Datei auswählen +6. **Monate/Jahre auswählen** für die Konvertierung +7. **DBF-Dateien** werden pro Monat erstellt + +### Ausgabe-Dateien + +Die generierten DBF-Dateien folgen dem Namensschema: +``` +buchungen_[MANDANTENNR]_[MONAT][JAHR_KURZ].dbf +``` + +Beispiel: `buchungen_001_0124.dbf` (Mandant 001, Januar 2024) + +## 🐛 Fehlerbehebung + +### Häufige Probleme + +**"tkinter nicht gefunden"** +```bash +# Ubuntu/Debian +sudo apt install python3-tk + +# Fedora +sudo dnf install python3-tkinter +``` + +**"CSV-Spalte nicht gefunden"** +- Prüfen Sie, ob die CSV-Datei Semikolon als Trennzeichen verwendet +- Prüfen Sie die Spaltennamen (Buchungstag, Buchungstext, Betrag) + +**"Keine Buchungen für den gewählten Zeitraum"** +- Prüfen Sie das Datumsformat in der CSV (DD.MM.YYYY) +- Prüfen Sie die gewählten Monate und Jahre + +## 📜 Lizenz + +Dieses Projekt ist unter der MIT-Lizenz lizenziert. Siehe [LICENSE](LICENSE) für Details. + +## 👤 Autor + +**Sebastian Zell** +- E-Mail: sebastian.zell@zell-aufmass.de + +## 🤝 Beitragen + +Beiträge sind willkommen! Bitte erstellen Sie einen Pull Request oder öffnen Sie ein Issue für Verbesserungsvorschläge. + +--- + +*Erstellt für die Hausverwaltung mit ❤️ und Python* diff --git a/config/examples/identitaeten_kosten_001.json b/config/examples/identitaeten_kosten_001.json new file mode 100644 index 0000000..8d07427 --- /dev/null +++ b/config/examples/identitaeten_kosten_001.json @@ -0,0 +1,283 @@ +{ + "kostenkonten": [ + { + "gegenkonto": 4001, + "bezeichnung": "Sprengwasserzähler", + "suchbegriffe": [] + }, + { + "gegenkonto": 4002, + "bezeichnung": "Be- und Endwässerung", + "suchbegriffe": [ + "Berliner Wasser", + "BERLINER WASSERBETRIEBE" + ] + }, + { + "gegenkonto": 4003, + "bezeichnung": "Winterdienst", + "suchbegriffe": [ + "Thomas Hansen" + ] + }, + { + "gegenkonto": 4004, + "bezeichnung": "Strassenreinigung/Müllabfuhr", + "suchbegriffe": [ + "Brs", + "Berliner Stadtreinig", + "BR Berlin", + "bsr" + ] + }, + { + "gegenkonto": 4005, + "bezeichnung": "Hauswart", + "suchbegriffe": [ + "Bundesknappschaft", + "VERWALTUNGS-BG,HAMBURG", + "Knappschaft", + "Gehalt", + "Verwaltungs-Berufs", + "Hauswart", + "Entgeld", + "Verwaltungs-bg" + ] + }, + { + "gegenkonto": 4006, + "bezeichnung": "Gartenpflege", + "suchbegriffe": [ + "Erden GmbH" + ] + }, + { + "gegenkonto": 4007, + "bezeichnung": "Gebäudeversicherung", + "suchbegriffe": [ + "Flenker", + "Victoria Versicherung", + "BASLER", + "signal" + ] + }, + { + "gegenkonto": 4008, + "bezeichnung": "Strom", + "suchbegriffe": [ + "VATTENFALL", + "Yello", + "Bewag" + ] + }, + { + "gegenkonto": 4009, + "bezeichnung": "Waschküche", + "suchbegriffe": [] + }, + { + "gegenkonto": 4010, + "bezeichnung": "Kabelfernsehen", + "suchbegriffe": [ + "Kabel" + ] + }, + { + "gegenkonto": 4011, + "bezeichnung": "Grundsteuer", + "suchbegriffe": [ + "GrSt", + "Grundsteuer", + "gst" + ] + }, + { + "gegenkonto": 4012, + "bezeichnung": "prüfung technischer Anlagen", + "suchbegriffe": [] + }, + { + "gegenkonto": 4013, + "bezeichnung": "Wartung Lüftungsanlage", + "suchbegriffe": [ + "kliwa" + ] + }, + { + "gegenkonto": 4014, + "bezeichnung": "Kleinmaterial Hausmeister", + "suchbegriffe": [] + }, + { + "gegenkonto": 4015, + "bezeichnung": "Kontoführung", + "suchbegriffe": [ + "Kontofuehrung", + "EC-KARTE", + "Rechnungsabschluss", + "Aufgrund eines technischen Feh", + "Sollzinsen", + "vodafone", + "ney", + "Separierung zur Pfaendung", + "BENACHRICHTIGUNGSENTGELT", + "AUSGABE EINER DEBITKARTE", + "rueckgabe ue", + "ab dem 01.01" + ] + }, + { + "gegenkonto": 4016, + "bezeichnung": "Kaltwasserablesung", + "suchbegriffe": [] + }, + { + "gegenkonto": 4017, + "bezeichnung": "Grundsteuer individuell", + "suchbegriffe": [] + }, + { + "gegenkonto": 4018, + "bezeichnung": "Aufzugkosten", + "suchbegriffe": [] + }, + { + "gegenkonto": 4019, + "bezeichnung": "Hausreinigung", + "suchbegriffe": [] + }, + { + "gegenkonto": 4020, + "bezeichnung": "Schädlingsbekämpfung", + "suchbegriffe": [ + "Akut Schäd" + ] + }, + { + "gegenkonto": 4048, + "bezeichnung": "Reparaturkosten nicht Umlagefähig", + "suchbegriffe": [] + }, + { + "gegenkonto": 4049, + "bezeichnung": "Werbekosten nicht Umlagefähig", + "suchbegriffe": [] + }, + { + "gegenkonto": 4050, + "bezeichnung": "Heizungswartung", + "suchbegriffe": [ + "H+R", + "gegenbauer" + ] + }, + { + "gegenkonto": 4051, + "bezeichnung": "Brennstoffkosten", + "suchbegriffe": [ + "Bama", + "Gasag" + ] + }, + { + "gegenkonto": 4052, + "bezeichnung": "Schornsteinfeger", + "suchbegriffe": [ + "Schornsteinf", + "BSFM" + ] + }, + { + "gegenkonto": 4053, + "bezeichnung": "Verbrauchsmessung", + "suchbegriffe": [] + }, + { + "gegenkonto": 4054, + "bezeichnung": "Abrechnung", + "suchbegriffe": [ + "KALORIMETA", + "Brunata" + ] + }, + { + "gegenkonto": 4060, + "bezeichnung": "Heizkostenabrechnung", + "suchbegriffe": [] + }, + { + "gegenkonto": 4070, + "bezeichnung": "Instandhaltung", + "suchbegriffe": [ + "Fabian Zell Auslage", + "Sto AG", + "Behrendt Werb", + "Hahne", + "Rossoll", + "Possling", + "Schlieper", + "HERMINGHAUS", + "allmilmö", + "Bahag", + "nenn", + "bito", + "Gutmaier", + "rossol", + "bauhaus", + "kravag", + "auslage", + "würth", + "Bergmann", + "Öltankservice" + ] + }, + { + "gegenkonto": 4080, + "bezeichnung": "Kosten der Rechtsverfolgung", + "suchbegriffe": [ + "Landeshauptkasse", + "Rechtsanwalt", + "Kosteneinziehungsstelle", + "gericht", + "pufahl" + ] + }, + { + "gegenkonto": 4090, + "bezeichnung": "Wohngeld", + "suchbegriffe": [ + "WOHNGELD", + "WEG TEGELER", + "Weg CONTESSAWEG", + "BETA IMMOBILIEN", + "ABC", + "WEG DERN", + "WEG Am Teg", + "WEG Bock", + "WEG FAHRE", + "Kremer", + "WEG Neue Bergstr" + ] + }, + { + "gegenkonto": 4100, + "bezeichnung": "Annuitäten", + "suchbegriffe": [ + "Aareal Bank", + "Weberbank", + "IBB", + "DSL", + "DG HYP", + "Annuität", + "INVESTITIONSBANK", + "Commerzbank", + "Finanzierungskosten", + "Darlehen", + "Tilgung", + "Zinsen", + "Weberbank", + "Darl" + ] + } + ] +} diff --git a/config/examples/identitaeten_kosten_007.json b/config/examples/identitaeten_kosten_007.json new file mode 100644 index 0000000..e31471a --- /dev/null +++ b/config/examples/identitaeten_kosten_007.json @@ -0,0 +1,225 @@ +{ + "kostenkonten": [ + { + "gegenkonto": 4001, + "bezeichnung": "Hauswart Nebenkosten", + "suchbegriffe": [] + }, + { + "gegenkonto": 4002, + "bezeichnung": "Be-/Entwässerung Abrechnung", + "suchbegriffe": [] + }, + { + "gegenkonto": 4003, + "bezeichnung": "Winterdienst", + "suchbegriffe": [] + }, + { + "gegenkonto": 4004, + "bezeichnung": "Strassenreinigung/Müllabfuhr", + "suchbegriffe": [ + "Berlin Recycling", + "Berliner", + "Berliner Stadtreinigungsbetriebe" + ] + }, + { + "gegenkonto": 4005, + "bezeichnung": "Hauswart", + "suchbegriffe": [ + "Fabian", + "Fabian Zell" + ] + }, + { + "gegenkonto": 4006, + "bezeichnung": "Gartenpflege", + "suchbegriffe": [ + "Biowork", + "Fahrländer Erden", + "Fahrländer" + ] + }, + { + "gegenkonto": 4007, + "bezeichnung": "Gebäudeversicherung", + "suchbegriffe": [ + "Signal", + "Signal Iduna" + ] + }, + { + "gegenkonto": 4008, + "bezeichnung": "Strom", + "suchbegriffe": [ + "VATTENFALL", + "Vattenfall" + ] + }, + { + "gegenkonto": 4009, + "bezeichnung": "Be-/Entwässerung Verbrauch", + "suchbegriffe": [ + "WASSER" + ] + }, + { + "gegenkonto": 4010, + "bezeichnung": "Grundsteuer", + "suchbegriffe": [] + }, + { + "gegenkonto": 4011, + "bezeichnung": "Gartenpflege / Baumbeschnitt", + "suchbegriffe": [] + }, + { + "gegenkonto": 4012, + "bezeichnung": "Prüfung technischer Anlagen", + "suchbegriffe": [] + }, + { + "gegenkonto": 4013, + "bezeichnung": "Fernsehanschluss", + "suchbegriffe": [ + "Vodafone" + ] + }, + { + "gegenkonto": 4014, + "bezeichnung": "Kleinmaterial Hausmeister", + "suchbegriffe": [] + }, + { + "gegenkonto": 4015, + "bezeichnung": "Kontoführung", + "suchbegriffe": [ + "Kontoführung", + "Kontofuehrung", + "Kontofuhrung", + "BENACHRICHTIGUNGSENTGELT", + "Rechnungsabschluss Konto", + "Benachrich", + "Aufgrund eines technischen" + ] + }, + { + "gegenkonto": 4016, + "bezeichnung": "Grundsteuer Gewerbeeinheiten", + "suchbegriffe": [] + }, + { + "gegenkonto": 4017, + "bezeichnung": "Eigentümerhaftpflichtversicherung", + "suchbegriffe": [] + }, + { + "gegenkonto": 4018, + "bezeichnung": "Verwalterhonorare", + "suchbegriffe": [] + }, + { + "gegenkonto": 4019, + "bezeichnung": "Lohnbuchhaltung", + "suchbegriffe": [] + }, + { + "gegenkonto": 4020, + "bezeichnung": "Mahngebühren", + "suchbegriffe": [] + }, + { + "gegenkonto": 4021, + "bezeichnung": "Verlustkonto aus Abrechnung NK 1", + "suchbegriffe": [] + }, + { + "gegenkonto": 4022, + "bezeichnung": "Verlustkonto aus Abrechnung NK 2", + "suchbegriffe": [] + }, + { + "gegenkonto": 4023, + "bezeichnung": "Verlustkonto aus Abrechnung NK 3", + "suchbegriffe": [] + }, + { + "gegenkonto": 4024, + "bezeichnung": "Sprengwasserzähler", + "suchbegriffe": [] + }, + { + "gegenkonto": 4025, + "bezeichnung": "Sonderkosten", + "suchbegriffe": [] + }, + { + "gegenkonto": 4026, + "bezeichnung": "Strom PKW Ladesäule", + "suchbegriffe": [] + }, + { + "gegenkonto": 4045, + "bezeichnung": "Regenrinnenreinigung", + "suchbegriffe": [] + }, + { + "gegenkonto": 4050, + "bezeichnung": "Heizungswartung", + "suchbegriffe": [ + "Gutmaier" + ] + }, + { + "gegenkonto": 4051, + "bezeichnung": "Heizkosten", + "suchbegriffe": [ + "GASAG" + ] + }, + { + "gegenkonto": 4052, + "bezeichnung": "Schornsteinfeger", + "suchbegriffe": [ + "Bsfm" + ] + }, + { + "gegenkonto": 4053, + "bezeichnung": "Verbrauchsmessung", + "suchbegriffe": [] + }, + { + "gegenkonto": 4054, + "bezeichnung": "Abrechnung", + "suchbegriffe": [ + "Ista", + "Kalorimeta" + ] + }, + { + "gegenkonto": 4055, + "bezeichnung": "Heizstrom", + "suchbegriffe": [] + }, + { + "gegenkonto": 4060, + "bezeichnung": "Heizkostenabrechnung", + "suchbegriffe": [] + }, + { + "gegenkonto": 4070, + "bezeichnung": "Instandhaltung", + "suchbegriffe": [ + "Kunert Tischlerei", + "Kunert" + ] + }, + { + "gegenkonto": 4080, + "bezeichnung": "Gericht / Gerichtsvollzieher", + "suchbegriffe": [] + } + ] +} diff --git a/config/examples/identitaeten_mieter_002.json b/config/examples/identitaeten_mieter_002.json new file mode 100644 index 0000000..7a384cc --- /dev/null +++ b/config/examples/identitaeten_mieter_002.json @@ -0,0 +1,743 @@ +{ + "mandant": "Mustermann GmbH", + "mandantennummer": "001", + "mieter": [ + { + "suchbegriffe": [ + "Renate Steffensen", + "Steffensen", + "STEFFENSEN,RENATE" + ], + "konto": "10020", + "beschreibung": "Renate Steffensen" + }, + { + "suchbegriffe": [ + "Renate Steffensen", + "Steffensen" + ], + "konto": "10030", + "beschreibung": "Renate Steffensen" + }, + { + "suchbegriffe": [ + "Konstantinos Papadopoulos", + "Papadopoulos", + "Papadopoulos", + "PAPAD" + ], + "konto": "10035", + "beschreibung": "Konstantinos Papadopoulos" + }, + { + "suchbegriffe": [ + "Dorit Werner", + "Werner", + "Werner", + "Werner" + ], + "konto": "10036", + "beschreibung": "Dorit Werner" + }, + { + "suchbegriffe": [ + "Marcel Probstmeyer", + "Probstmeyer", + "Probstmeyer" + ], + "konto": "10039", + "beschreibung": "Marcel Probstmeyer" + }, + { + "suchbegriffe": [ + "Walter Mindach", + "Mindach", + "Krieger", + "MINDACH, WALTER" + ], + "konto": "10040", + "beschreibung": "Walter Mindach" + }, + { + "suchbegriffe": [ + "Fritz Werner", + "Werner" + ], + "konto": "10042", + "beschreibung": "Fritz Werner" + }, + { + "suchbegriffe": [ + "Marco Mecarelli", + "Mecarelli", + "Mecarelli" + ], + "konto": "10045", + "beschreibung": "Marco Mecarelli" + }, + { + "suchbegriffe": [ + "Marianne Poplawski", + "Poplawski", + "POPLAWSKI,MARIANNE" + ], + "konto": "10046", + "beschreibung": "Marianne Poplawski" + }, + { + "suchbegriffe": [ + "Helga Hartinger", + "Hartinger", + "Hartinger", + "Hartinger" + ], + "konto": "10048", + "beschreibung": "Helga Hartinger" + }, + { + "suchbegriffe": [ + "Bärbel Jaehring", + "Jaehring", + "Krug", + "Jaehring", + "Krug" + ], + "konto": "10049", + "beschreibung": "Bärbel Jaehring" + }, + { + "suchbegriffe": [ + "Günter Van de Weghe", + "Van de Weghe", + "Van de Weghe", + "vande Weghe" + ], + "konto": "10052", + "beschreibung": "Günter Van de Weghe" + }, + { + "suchbegriffe": [ + "Manuela Richter", + "Richter", + "Richter", + "Richter" + ], + "konto": "10068", + "beschreibung": "Manuela Richter" + }, + { + "suchbegriffe": [ + "Heinz Lange", + "Lange", + "Lange" + ], + "konto": "10072", + "beschreibung": "Heinz Lange" + }, + { + "suchbegriffe": [ + "Antje Keck", + "Keck", + "Kozik", + "Keck" + ], + "konto": "10073", + "beschreibung": "Antje Keck" + }, + { + "suchbegriffe": [ + "Markus Weichert", + "Weichert", + "Weichert" + ], + "konto": "10074", + "beschreibung": "Markus Weichert" + }, + { + "suchbegriffe": [ + "Monika Ramlow", + "Ramlow", + "Ramlow" + ], + "konto": "10075", + "beschreibung": "Monika Ramlow" + }, + { + "suchbegriffe": [ + "Sarah Clarissa Schroedter", + "Schroedter", + "Schroedter", + "SCHROEDTER", + "Wanke" + ], + "konto": "10076", + "beschreibung": "Sarah Clarissa Schroedter" + }, + { + "suchbegriffe": [ + "Conny-Maren Krause", + "Krause", + "KRAUSE" + ], + "konto": "10078", + "beschreibung": "Conny-Maren Krause" + }, + { + "suchbegriffe": [ + "Kurt Hans Jessel", + "Jessel" + ], + "konto": "10079", + "beschreibung": "Kurt Hans Jessel" + }, + { + "suchbegriffe": [ + " ATC Germany Munich", + "ATC Germany Munich", + "ehem.E-Plus", + "E-PLUS MOBILFUNK GMBH", + "ATC" + ], + "konto": "10080", + "beschreibung": " ATC Germany Munich" + }, + { + "suchbegriffe": [ + " Vantage Towers AG", + "Vantage Towers AG", + "ehem. Vodafone", + "VODAFONE D2 GMBH", + "Vantage" + ], + "konto": "10081", + "beschreibung": " Vantage Towers AG" + }, + { + "suchbegriffe": [ + "Heidi Thürling", + "Thürling", + "THURLING", + "TH?RLING" + ], + "konto": "10082", + "beschreibung": "Heidi Thürling" + }, + { + "suchbegriffe": [ + "Anne Horn", + "Horn", + "Knust" + ], + "konto": "10083", + "beschreibung": "Anne Horn" + }, + { + "suchbegriffe": [ + "Irmgard Dupke", + "Dupke", + "DUPKE" + ], + "konto": "10084", + "beschreibung": "Irmgard Dupke" + }, + { + "suchbegriffe": [ + "Manfred Hannasky", + "Hannasky", + "HANNASKY" + ], + "konto": "10085", + "beschreibung": "Manfred Hannasky" + }, + { + "suchbegriffe": [ + "Mandy Jaqueline Gey", + "Gey", + "Schrammek" + ], + "konto": "10086", + "beschreibung": "Mandy Jaqueline Gey" + }, + { + "suchbegriffe": [ + "Alexis-Roman Kruppa", + "Kruppa" + ], + "konto": "10087", + "beschreibung": "Alexis-Roman Kruppa" + }, + { + "suchbegriffe": [ + "Fuad Vokrri", + "Vokrri", + "VOKRRI", + "FUAD" + ], + "konto": "10088", + "beschreibung": "Fuad Vokrri" + }, + { + "suchbegriffe": [ + "Eveline Olsson", + "Olsson", + "Olsson", + "UWE OLSSON" + ], + "konto": "10089", + "beschreibung": "Eveline Olsson" + }, + { + "suchbegriffe": [ + "Ingrid Ida Fitzner", + "Fitzner", + "Fitzner", + "Fitzner" + ], + "konto": "10090", + "beschreibung": "Ingrid Ida Fitzner" + }, + { + "suchbegriffe": [ + "Sophie Charlotte Wanke", + "Wanke", + "Borngr" + ], + "konto": "10091", + "beschreibung": "Sophie Charlotte Wanke" + }, + { + "suchbegriffe": [ + "Ingrid Ida Fitzner", + "Fitzner" + ], + "konto": "10092", + "beschreibung": "Ingrid Ida Fitzner" + }, + { + "suchbegriffe": [ + "Tanja Barth", + "Barth", + "Wilhelm" + ], + "konto": "10093", + "beschreibung": "Tanja Barth" + }, + { + "suchbegriffe": [ + "Manuela Richter", + "Richter", + "Richter" + ], + "konto": "10094", + "beschreibung": "Manuela Richter" + }, + { + "suchbegriffe": [ + "Irene Bauer", + "Bauer", + "Bauer" + ], + "konto": "10095", + "beschreibung": "Irene Bauer" + }, + { + "suchbegriffe": [ + "Bert Kunert", + "Kunert", + "Kunert" + ], + "konto": "10096", + "beschreibung": "Bert Kunert" + }, + { + "suchbegriffe": [ + "Hannelore Dittmann", + "Dittmann", + "Dittmar" + ], + "konto": "10097", + "beschreibung": "Hannelore Dittmann" + }, + { + "suchbegriffe": [ + "Sandra Hedke", + "Hedke", + "Kahl", + "Kahl", + "Hedke" + ], + "konto": "10098", + "beschreibung": "Sandra Hedke" + }, + { + "suchbegriffe": [ + "Julia Ellinger", + "Ellinger", + "Ellinger" + ], + "konto": "10099", + "beschreibung": "Julia Ellinger" + }, + { + "suchbegriffe": [ + " Eins in Christus", + "Eins in Christus" + ], + "konto": "10100", + "beschreibung": " Eins in Christus" + }, + { + "suchbegriffe": [ + "Andrea-Britta Lüth", + "Lüth", + "Lüth" + ], + "konto": "10101", + "beschreibung": "Andrea-Britta Lüth" + }, + { + "suchbegriffe": [ + "Andreas Holtz", + "Holtz" + ], + "konto": "10102", + "beschreibung": "Andreas Holtz" + }, + { + "suchbegriffe": [ + "Angela Kranz", + "Kranz" + ], + "konto": "10103", + "beschreibung": "Angela Kranz" + }, + { + "suchbegriffe": [ + "Monique Rosenow", + "Rosenow" + ], + "konto": "10104", + "beschreibung": "Monique Rosenow" + }, + { + "suchbegriffe": [ + "Manfred Robert Posselt", + "Posselt", + "Posselt" + ], + "konto": "10105", + "beschreibung": "Manfred Robert Posselt" + }, + { + "suchbegriffe": [ + "Marianne Reimer", + "Reimer" + ], + "konto": "10106", + "beschreibung": "Marianne Reimer" + }, + { + "suchbegriffe": [ + "Jürgen Alois Pichler", + "Pichler" + ], + "konto": "10107", + "beschreibung": "Jürgen Alois Pichler" + }, + { + "suchbegriffe": [ + "Andreas Daniel Kandt", + "Kandt" + ], + "konto": "10108", + "beschreibung": "Andreas Daniel Kandt" + }, + { + "suchbegriffe": [ + "Heike Günther", + "Günther", + "Schneider" + ], + "konto": "10109", + "beschreibung": "Heike Günther" + }, + { + "suchbegriffe": [ + "Silke Schröder", + "Schröder" + ], + "konto": "10110", + "beschreibung": "Silke Schröder" + }, + { + "suchbegriffe": [ + "Rotraud Rospert", + "Rospert" + ], + "konto": "10111", + "beschreibung": "Rotraud Rospert" + }, + { + "suchbegriffe": [ + "Jennifer Weber", + "Weber" + ], + "konto": "10112", + "beschreibung": "Jennifer Weber" + }, + { + "suchbegriffe": [ + "Joe-Marvin Henson", + "Henson", + "Henson" + ], + "konto": "10113", + "beschreibung": "Joe-Marvin Henson" + }, + { + "suchbegriffe": [ + "Jacqueline Esther De Vries", + "De Vries" + ], + "konto": "10114", + "beschreibung": "Jacqueline Esther De Vries" + }, + { + "suchbegriffe": [ + "Catrin Zonsius", + "Zonsius", + "Zonsius" + ], + "konto": "10115", + "beschreibung": "Catrin Zonsius" + }, + { + "suchbegriffe": [ + "Charlen Heidergott", + "Heidergott", + "Brüning" + ], + "konto": "10116", + "beschreibung": "Charlen Heidergott" + }, + { + "suchbegriffe": [ + "Jennifer Weber", + "Weber, Jennifer", + "Winkler" + ], + "konto": "10117", + "beschreibung": "Jennifer Weber" + }, + { + "suchbegriffe": [ + "Gerrit Küstermann", + "Küstermann" + ], + "konto": "10118", + "beschreibung": "Gerrit Küstermann" + }, + { + "suchbegriffe": [ + "Sofya Iluchshenko", + "Iluchshenko" + ], + "konto": "10119", + "beschreibung": "Sofya Iluchshenko" + }, + { + "suchbegriffe": [ + "Sarah Ketzer", + "Ketzer", + "Malkowski" + ], + "konto": "10121", + "beschreibung": "Sarah Ketzer" + }, + { + "suchbegriffe": [ + "Fuad Vokrri", + "Vokrri" + ], + "konto": "10122", + "beschreibung": "Fuad Vokrri" + }, + { + "suchbegriffe": [ + "Jenny Binder", + "Binder", + "Paul" + ], + "konto": "10123", + "beschreibung": "Jenny Binder" + }, + { + "suchbegriffe": [ + "Anton Glaser", + "Glaser" + ], + "konto": "10124", + "beschreibung": "Anton Glaser" + }, + { + "suchbegriffe": [ + "Kathrin Hoyer", + "Hoyer", + "Starosta" + ], + "konto": "10125", + "beschreibung": "Kathrin Hoyer" + }, + { + "suchbegriffe": [ + "Tom Stampfuss", + "Stampfuss", + "Höne", + "Hone" + ], + "konto": "10126", + "beschreibung": "Tom Stampfuss" + }, + { + "suchbegriffe": [ + "Artem Poltavets", + "Poltavets" + ], + "konto": "10127", + "beschreibung": "Artem Poltavets" + }, + { + "suchbegriffe": [ + "Leon Maximilian Posselt" + ], + "konto": "10128", + "beschreibung": "Leon Maximilian Posselt" + }, + { + "suchbegriffe": [ + "Brigitte Moskal", + "Moskal" + ], + "konto": "10129", + "beschreibung": "Brigitte Moskal" + }, + { + "suchbegriffe": [ + "Oksana Roman", + "Roman" + ], + "konto": "10130", + "beschreibung": "Oksana Roman" + }, + { + "suchbegriffe": [ + "Maryam Zinoury", + "Zinoury" + ], + "konto": "10131", + "beschreibung": "Maryam Zinoury" + }, + { + "suchbegriffe": [ + "Julia Ellinger", + "Ellinger" + ], + "konto": "10132", + "beschreibung": "Julia Ellinger" + }, + { + "suchbegriffe": [ + "Phillipp Weber", + "Weber, Phillipp" + ], + "konto": "10133", + "beschreibung": "Phillipp Weber" + }, + { + "suchbegriffe": [ + "Artem Poltavets", + "Poltavets" + ], + "konto": "10134", + "beschreibung": "Artem Poltavets" + }, + { + "suchbegriffe": [ + "C.U.T", + "kwe" + ], + "konto": "40000", + "beschreibung": "C.U.T" + }, + { + "suchbegriffe": [ + "ZV Wasser", + "SIBA", + "Kommunales Wirt", + "LOS-KWU", + "Scharmützelsee", + "Scharmutzelsee", + "Ewe", + "E.dis", + "CUT", + "Hopp", + "HELIOS", + "Ingrid Und Klaus Zell", + "Jabado, Samir Andreas", + "Ingrid Zell Sparkasse", + "Dr Jochen Küller", + "GEZ", + "KLAUS ZELL SPARKASSE", + "INGRID ZELL SPARKASSE" + ], + "konto": "40000", + "beschreibung": "Ingrid Und Klaus Zell" + }, + { + "suchbegriffe": [ + "WEG Elster" + ], + "konto": "40001", + "beschreibung": "WEG Elster" + }, + { + "suchbegriffe": [ + "INGRID ZELL HAUSVERW", + "Ingrid Zell GmbH" + ], + "konto": "40009", + "beschreibung": "INGRID ZELL HAUSVERW" + }, + { + "suchbegriffe": [ + "FABIAN ZELL FIRMA", + "fabian Zell COBA" + ], + "konto": "40011", + "beschreibung": "FABIAN ZELL FIRMA" + }, + { + "suchbegriffe": [ + "SEBASTIAN ZELL bekannt", + "Sebastian Zell COBA", + "SEBASTIAN ZELL e.k." + ], + "konto": "40006", + "beschreibung": "Sebastian Zell" + }, + { + "suchbegriffe": [ + "paypal" + ], + "konto": "40020", + "beschreibung": "Paypal" + }, + { + "suchbegriffe": [ + "finanza" + ], + "konto": "40030", + "beschreibung": "Finanzamt" + } + ] +} diff --git a/config/examples/identitaeten_mieter_007.json b/config/examples/identitaeten_mieter_007.json new file mode 100644 index 0000000..d69877f --- /dev/null +++ b/config/examples/identitaeten_mieter_007.json @@ -0,0 +1,30 @@ +{ + "mandant": "WEG Contessaweg", + "mandantennummer": "007", + "mieter": [ + { + "suchbegriffe": [ + "mueller", + "müller", + "carsten" + ], + "konto": "20017", + "beschreibung": "Carsten Müller" + }, + { + "suchbegriffe": [ + "knust", + "horn" + ], + "konto": "20019", + "beschreibung": "Ulrich Knust, Anne Horn" + }, + { + "suchbegriffe": [ + "zell" + ], + "konto": "20018", + "beschreibung": "Fabian und Sebastian Zell" + } + ] +} diff --git a/config/examples/mandanten_config.json b/config/examples/mandanten_config.json new file mode 100644 index 0000000..5b79074 --- /dev/null +++ b/config/examples/mandanten_config.json @@ -0,0 +1,28 @@ +{ + "mandanten": [ + { + "name": "Fabian und Sebastian Zell", + "nummer": "001", + "konto": "1200", + "standard_gegenkonto": "1300", + "standard_bank_kto": "1200", + "beleg_index": 2826 + }, + { + "name": "Ingrid und Klaus Zell", + "nummer": "002", + "konto": "1800", + "standard_gegenkonto": "8500", + "standard_bank_kto": "1800", + "beleg_index": 1 + }, + { + "name": "WEG Contessaweg", + "nummer": "007", + "konto": "1208", + "standard_gegenkonto": "1590", + "standard_bank_kto": "1208", + "beleg_index": 517 + } + ] +} \ No newline at end of file diff --git a/config/examples/projekte_001.json b/config/examples/projekte_001.json new file mode 100644 index 0000000..3ee1390 --- /dev/null +++ b/config/examples/projekte_001.json @@ -0,0 +1,73 @@ +{ + "mandant": "default", + "mandantennummer": "001", + "default_projekt": "001", + "projekte": [ + { + "suchbegriffe": [ + "Con" + ], + "kst": "001", + "beschreibung": "Con" + }, + { + "suchbegriffe": [ + "Schr" + ], + "kst": "002", + "beschreibung": "Schr" + }, + { + "suchbegriffe": [ + "Mar" + ], + "kst": "003", + "beschreibung": "Mar" + }, + { + "suchbegriffe": [ + "Spa" + ], + "kst": "004", + "beschreibung": "Spa" + }, + { + "suchbegriffe": [ + "Gatow", + "abc" + ], + "kst": "005", + "beschreibung": "Gatow" + }, + { + "suchbegriffe": [ + "Burg", + "kremer" + ], + "kst": "006", + "beschreibung": "Burg" + }, + { + "suchbegriffe": [ + "Teg", + "beta" + ], + "kst": "007", + "beschreibung": "Teg" + }, + { + "suchbegriffe": [ + "Fah" + ], + "kst": "008", + "beschreibung": "Fah" + }, + { + "suchbegriffe": [ + "Pich" + ], + "kst": "009", + "beschreibung": "Pich" + } + ] +} diff --git a/config/examples/projekte_007.json b/config/examples/projekte_007.json new file mode 100644 index 0000000..84033dc --- /dev/null +++ b/config/examples/projekte_007.json @@ -0,0 +1,15 @@ +{ + "mandant": "default", + "mandantennummer": "001", + "default_projekt": "001", + "projekte": [ + { + "suchbegriffe": [ + "Con", + " " + ], + "kst": "3", + "beschreibung": "Con" + } + ] +} diff --git a/csv_dbf_converter.py b/csv_dbf_converter.py new file mode 100644 index 0000000..7fca9a2 --- /dev/null +++ b/csv_dbf_converter.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() diff --git a/csv_dbf_converter_gui.py b/csv_dbf_converter_gui.py new file mode 100644 index 0000000..e8ffbdb --- /dev/null +++ b/csv_dbf_converter_gui.py @@ -0,0 +1,520 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +Hausverwaltung CSV zu DBF Konverter +GUI Version für Linux (Debian, Ubuntu, Kubuntu) +Version 3.0 +""" + +import tkinter as tk +from tkinter import ttk, filedialog, messagebox, scrolledtext +import json +import csv +import os +import struct +from datetime import datetime, date +import threading +import re +import sys + +# ===== BACKEND FUNKTIONEN (aus Original-Code) ===== + +def normalize_text(text): + """Normalisiert Text für Vergleiche""" + 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 + +def load_mandanten(): + """Lädt Mandanten aus Config""" + if not os.path.exists('mandanten_config.json'): + return [] + 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" + if not os.path.exists(mieter_datei): + return [] + 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" + if not os.path.exists(kosten_datei): + return [] + 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" + if not os.path.exists(projekte_datei): + return [], '' + with open(projekte_datei, 'r', encoding='utf-8') as f: + config = json.load(f) + return config['projekte'], config.get('default_projekt', '') + +# ===== GUI KLASSE ===== + +class HausverwaltungGUI: + def __init__(self, root): + self.root = root + self.root.title("Hausverwaltung CSV→DBF Konverter v3.0") + self.root.geometry("900x700") + + # Moderne Farben + self.bg_color = "#f0f0f0" + self.accent_color = "#2196F3" + self.success_color = "#4CAF50" + self.error_color = "#f44336" + + self.root.configure(bg=self.bg_color) + + # Variablen + self.csv_file = None + self.mandanten = load_mandanten() + self.selected_mandant = None + self.processing = False + + self.setup_ui() + self.load_initial_data() + + def setup_ui(self): + """Erstellt die GUI-Komponenten""" + + # Style konfigurieren + style = ttk.Style() + style.theme_use('clam') + style.configure('Title.TLabel', font=('Ubuntu', 16, 'bold')) + style.configure('Header.TLabel', font=('Ubuntu', 11, 'bold')) + style.configure('Success.TLabel', foreground=self.success_color) + style.configure('Error.TLabel', foreground=self.error_color) + + # Hauptcontainer + main_frame = ttk.Frame(self.root, padding="20") + main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) + + # Titel + title_label = ttk.Label(main_frame, text="📊 CSV zu DBF Konverter für Hausverwaltung", + style='Title.TLabel') + title_label.grid(row=0, column=0, columnspan=3, pady=(0, 20)) + + # 1. Mandanten-Auswahl + ttk.Label(main_frame, text="1. Mandant auswählen:", style='Header.TLabel').grid( + row=1, column=0, sticky=tk.W, pady=(10, 5)) + + self.mandant_frame = ttk.LabelFrame(main_frame, text="Mandanten", padding="10") + self.mandant_frame.grid(row=2, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(0, 15)) + + self.mandant_combo = ttk.Combobox(self.mandant_frame, width=50, state="readonly") + self.mandant_combo.grid(row=0, column=0, padx=(0, 10)) + self.mandant_combo.bind('<>', self.on_mandant_select) + + self.mandant_info = ttk.Label(self.mandant_frame, text="") + self.mandant_info.grid(row=0, column=1) + + # 2. CSV-Datei auswählen + ttk.Label(main_frame, text="2. CSV-Datei auswählen:", style='Header.TLabel').grid( + row=3, column=0, sticky=tk.W, pady=(10, 5)) + + file_frame = ttk.Frame(main_frame) + file_frame.grid(row=4, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(0, 15)) + + self.file_label = ttk.Label(file_frame, text="Keine Datei ausgewählt", + relief=tk.SUNKEN, padding="5") + self.file_label.grid(row=0, column=0, sticky=(tk.W, tk.E)) + + ttk.Button(file_frame, text="📁 Datei wählen", + command=self.select_csv_file).grid(row=0, column=1, padx=(10, 0)) + + file_frame.columnconfigure(0, weight=1) + + # 3. Zeitraum auswählen + ttk.Label(main_frame, text="3. Zeitraum auswählen:", style='Header.TLabel').grid( + row=5, column=0, sticky=tk.W, pady=(10, 5)) + + time_frame = ttk.LabelFrame(main_frame, text="Zeitraum", padding="10") + time_frame.grid(row=6, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(0, 15)) + + # Monate + ttk.Label(time_frame, text="Monate:").grid(row=0, column=0, sticky=tk.W) + self.months_var = tk.StringVar(value="1-12") + ttk.Entry(time_frame, textvariable=self.months_var, width=20).grid( + row=0, column=1, padx=(5, 20)) + ttk.Label(time_frame, text="(z.B. 1-12 oder 1,2,3)", + font=('Ubuntu', 9)).grid(row=0, column=2) + + # Jahre + ttk.Label(time_frame, text="Jahre:").grid(row=1, column=0, sticky=tk.W, pady=(5, 0)) + current_year = datetime.now().year + self.years_var = tk.StringVar(value=str(current_year)) + ttk.Entry(time_frame, textvariable=self.years_var, width=20).grid( + row=1, column=1, padx=(5, 20), pady=(5, 0)) + ttk.Label(time_frame, text="(z.B. 2024 oder 2023,2024)", + font=('Ubuntu', 9)).grid(row=1, column=2, pady=(5, 0)) + + # 4. Ausgabe-Log + ttk.Label(main_frame, text="Verarbeitungsprotokoll:", style='Header.TLabel').grid( + row=7, column=0, sticky=tk.W, pady=(10, 5)) + + # Log-Textfeld mit Scrollbar + log_frame = ttk.Frame(main_frame) + log_frame.grid(row=8, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 15)) + + self.log_text = scrolledtext.ScrolledText(log_frame, height=12, width=80, + wrap=tk.WORD, font=('Courier', 9)) + self.log_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) + + log_frame.columnconfigure(0, weight=1) + log_frame.rowconfigure(0, weight=1) + + # Fortschrittsbalken + self.progress = ttk.Progressbar(main_frame, mode='indeterminate') + self.progress.grid(row=9, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(0, 10)) + + # Buttons + button_frame = ttk.Frame(main_frame) + button_frame.grid(row=10, column=0, columnspan=3) + + self.process_btn = ttk.Button(button_frame, text="🔄 Konvertierung starten", + command=self.start_processing) + self.process_btn.grid(row=0, column=0, padx=5) + + ttk.Button(button_frame, text="⚙️ Konfiguration", + command=self.open_config).grid(row=0, column=1, padx=5) + + ttk.Button(button_frame, text="❌ Beenden", + command=self.root.quit).grid(row=0, column=2, padx=5) + + # Grid-Konfiguration + main_frame.columnconfigure(1, weight=1) + main_frame.rowconfigure(8, weight=1) + self.root.columnconfigure(0, weight=1) + self.root.rowconfigure(0, weight=1) + + def load_initial_data(self): + """Lädt initiale Daten""" + if self.mandanten: + mandant_list = [f"{m['name']} (Nr. {m['nummer']})" for m in self.mandanten] + self.mandant_combo['values'] = mandant_list + if mandant_list: + self.mandant_combo.current(0) + self.on_mandant_select(None) + else: + self.log("⚠️ Keine Mandanten gefunden. Bitte Konfiguration prüfen.") + + def on_mandant_select(self, event): + """Wird aufgerufen wenn ein Mandant ausgewählt wird""" + if self.mandant_combo.current() >= 0: + self.selected_mandant = self.mandanten[self.mandant_combo.current()] + info_text = (f"Konto: {self.selected_mandant['konto']}, " + f"Gegenkonto: {self.selected_mandant['standard_gegenkonto']}, " + f"Nächster Beleg: {self.selected_mandant.get('beleg_index', 1)}") + self.mandant_info.config(text=info_text) + self.log(f"✓ Mandant gewählt: {self.selected_mandant['name']}") + + def select_csv_file(self): + """Öffnet Dialog zur CSV-Auswahl""" + filename = filedialog.askopenfilename( + title="CSV-Datei auswählen", + filetypes=[("CSV Dateien", "*.csv"), ("Alle Dateien", "*.*")] + ) + if filename: + self.csv_file = filename + self.file_label.config(text=os.path.basename(filename)) + self.log(f"✓ CSV-Datei gewählt: {filename}") + + def log(self, message): + """Fügt eine Nachricht zum Log hinzu""" + timestamp = datetime.now().strftime("%H:%M:%S") + self.log_text.insert(tk.END, f"[{timestamp}] {message}\n") + self.log_text.see(tk.END) + self.root.update_idletasks() + + def start_processing(self): + """Startet die Konvertierung in einem separaten Thread""" + if self.processing: + messagebox.showwarning("Warnung", "Konvertierung läuft bereits!") + return + + if not self.selected_mandant: + messagebox.showerror("Fehler", "Bitte wählen Sie einen Mandanten!") + return + + if not self.csv_file: + messagebox.showerror("Fehler", "Bitte wählen Sie eine CSV-Datei!") + return + + # Starte Verarbeitung in Thread + self.processing = True + self.process_btn.config(state='disabled') + self.progress.start() + + thread = threading.Thread(target=self.process_conversion) + thread.daemon = True + thread.start() + + def process_conversion(self): + """Führt die eigentliche Konvertierung durch""" + try: + self.log("\n" + "="*60) + self.log("🚀 Starte Konvertierung...") + + # Lade Konfigurationen + mieter = load_mieter(self.selected_mandant['nummer']) + self.log(f"✓ {len(mieter)} Mieter geladen") + + kostenkonten = load_kostenkonten(self.selected_mandant['nummer']) + self.log(f"✓ {len(kostenkonten)} Kostenkonten geladen") + + projekte, default_projekt = load_projekte(self.selected_mandant['nummer']) + self.log(f"✓ {len(projekte)} Projekte geladen") + + # Lese CSV + buchungen = self.lese_csv_mit_log(self.csv_file) + self.log(f"✓ {len(buchungen)} Buchungen aus CSV geladen") + + # Parse Zeitraum + monate = self.parse_monatseingabe(self.months_var.get()) + jahre = self.parse_jahreseingabe(self.years_var.get()) + + # Filtere Buchungen + gefilterte = self.filtere_buchungen(buchungen, monate, jahre) + self.log(f"✓ {len(gefilterte)} Buchungen nach Filterung") + + if not gefilterte: + self.log("⚠️ Keine Buchungen für den gewählten Zeitraum!") + return + + # Erstelle DBF + ausgabe_prefix = f"buchungen_{self.selected_mandant['nummer']}" + start_beleg = self.selected_mandant.get('beleg_index', 1) + + letzte_beleg = self.erstelle_dbf_mit_log( + self.selected_mandant, gefilterte, mieter, kostenkonten, + projekte, default_projekt, ausgabe_prefix, start_beleg + ) + + # Update Beleg-Index + self.selected_mandant['beleg_index'] = letzte_beleg + 1 + save_mandanten(self.mandanten) + + self.log("\n" + "="*60) + self.log("✅ KONVERTIERUNG ERFOLGREICH ABGESCHLOSSEN!") + self.log(f"📊 Buchungen verarbeitet: {len(gefilterte)}") + self.log(f"📁 DBF-Dateien erstellt im Verzeichnis: {os.getcwd()}") + self.log("="*60) + + messagebox.showinfo("Erfolg", + f"Konvertierung erfolgreich!\n\n" + f"Verarbeitete Buchungen: {len(gefilterte)}\n" + f"Belegnummern: {start_beleg} bis {letzte_beleg}") + + except Exception as e: + self.log(f"❌ FEHLER: {str(e)}") + messagebox.showerror("Fehler", f"Konvertierung fehlgeschlagen:\n{str(e)}") + + finally: + self.processing = False + self.process_btn.config(state='normal') + self.progress.stop() + + def lese_csv_mit_log(self, csv_datei): + """Liest CSV-Datei mit Logging""" + buchungen = [] + with open(csv_datei, 'r', encoding='utf-8') as f: + reader = csv.DictReader(f, delimiter=';') + reader.fieldnames = [name.strip().lstrip('\ufeff') if name else name + for name in reader.fieldnames] + + # 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: + text_spalte = name + if 'betrag' in name_lower: + betrag_spalte = name + + for row in reader: + try: + datum_str = row[datum_spalte].strip() + datum = datetime.strptime(datum_str, '%d.%m.%Y') + + betrag_str = row[betrag_spalte].strip().replace('.', '').replace(',', '.') + betrag = float(betrag_str) + + buchung = { + 'datum': datum, + 'buchungstext': row[text_spalte].strip() if row[text_spalte] else '', + 'betrag': betrag + } + buchungen.append(buchung) + except: + continue + + return buchungen + + def parse_monatseingabe(self, eingabe): + """Parst Monatseingabe""" + 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(self, eingabe): + """Parst Jahreseingabe""" + 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)) + + def filtere_buchungen(self, buchungen, monate, jahre): + """Filtert Buchungen nach Zeitraum""" + return [b for b in buchungen + if b['datum'].month in monate and b['datum'].year in jahre] + + def erstelle_dbf_mit_log(self, mandant, buchungen, mieter, kostenkonten, + projekte, default_projekt, ausgabe_datei_praefix, start_beleg_nr): + """Erstellt DBF-Dateien mit Logging (vereinfachte Version)""" + # Gruppiere nach Monat + buchungen_pro_monat = {} + for buchung in buchungen: + monats_key = buchung['datum'].strftime('%Y-%m') + if monats_key not in buchungen_pro_monat: + buchungen_pro_monat[monats_key] = [] + buchungen_pro_monat[monats_key].append(buchung) + + self.log(f"📅 Erstelle DBF-Dateien für {len(buchungen_pro_monat)} Monate...") + + beleg_nr = start_beleg_nr + + for monat_key, monats_buchungen in buchungen_pro_monat.items(): + jahr, monat = map(int, monat_key.split('-')) + jahr_kurz = str(jahr)[-2:] + ausgabe_datei = f"{ausgabe_datei_praefix}_{monat:02d}{jahr_kurz}.dbf" + + self.log(f" → Erstelle {ausgabe_datei} ({len(monats_buchungen)} Buchungen)") + + # DBF erstellen (vereinfacht - nutzt Ihre Original-Logik) + self.create_dbf_file(ausgabe_datei, monats_buchungen, beleg_nr, + mandant, mieter, kostenkonten, projekte, default_projekt) + + beleg_nr += len(monats_buchungen) + + return beleg_nr - 1 + + def create_dbf_file(self, filename, buchungen, start_beleg, mandant, + mieter, kostenkonten, projekte, default_projekt): + """Erstellt eine DBF-Datei (vereinfacht aus Original)""" + # Hier würde Ihre Original DBF-Erstellungslogik stehen + # Aus Platzgründen nur Grundstruktur + + heute = datetime.now() + num_records = len(buchungen) + header_len = 32 + 43 * 32 + 1 + record_len = 368 + + header = bytearray(32) + header[0] = 0x03 # dBase III + header[1] = heute.year - 1900 + header[2] = heute.month + header[3] = heute.day + struct.pack_into('=5.0 + +# Auf Linux tkinter installieren: +# Ubuntu/Debian: sudo apt install python3-tk +# Fedora/RHEL: sudo dnf install python3-tkinter +# Arch Linux: sudo pacman -S tk diff --git a/scripts/build_linux.sh b/scripts/build_linux.sh new file mode 100755 index 0000000..42ea755 --- /dev/null +++ b/scripts/build_linux.sh @@ -0,0 +1,134 @@ +#!/bin/bash +# ==== +# CSV zu DBF Konverter - Linux Build Script +# Version 1.0 +# ==== + +set -e # Bei Fehlern abbrechen + +echo "" +echo "====" +echo " CSV zu DBF Konverter - Linux Build" +echo " Version 1.0" +echo "====" +echo "" + +# Farben für Ausgabe +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Arbeitsverzeichnis +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(dirname "$SCRIPT_DIR")" +cd "$PROJECT_DIR" + +echo -e "${BLUE}[1/6] Prüfe Voraussetzungen...${NC}" + +# Prüfe ob Python3 installiert ist +if ! command -v python3 &> /dev/null; then + echo -e "${RED}[FEHLER] Python3 ist nicht installiert.${NC}" + echo "" + echo "Installation:" + echo " Ubuntu/Debian: sudo apt install python3 python3-pip python3-tk" + echo " Fedora: sudo dnf install python3 python3-pip python3-tkinter" + exit 1 +fi + +PYTHON_VERSION=$(python3 --version 2>&1) +echo -e "${GREEN}✓ Python3 gefunden: $PYTHON_VERSION${NC}" + +# Prüfe ob tkinter installiert ist +echo -e "${BLUE}[2/6] Prüfe tkinter...${NC}" +if ! python3 -c "import tkinter" 2>/dev/null; then + echo -e "${YELLOW}[WARNUNG] tkinter nicht gefunden.${NC}" + echo "" + echo "Installation von tkinter:" + echo " Ubuntu/Debian: sudo apt install python3-tk" + echo " Fedora: sudo dnf install python3-tkinter" + exit 1 +fi +echo -e "${GREEN}✓ tkinter verfügbar${NC}" + +# Prüfe ob pip installiert ist +echo -e "${BLUE}[3/6] Prüfe pip...${NC}" +if ! command -v pip3 &> /dev/null && ! python3 -m pip --version &> /dev/null; then + echo -e "${YELLOW}[INFO] pip3 nicht gefunden.${NC}" + echo "" + echo "Installation von pip:" + echo " Ubuntu/Debian: sudo apt install python3-pip" + exit 1 +fi +echo -e "${GREEN}✓ pip verfügbar${NC}" + +# Prüfe ob PyInstaller installiert ist +echo -e "${BLUE}[4/6] Prüfe PyInstaller...${NC}" +if ! python3 -m PyInstaller --version &> /dev/null 2>&1; then + echo -e "${YELLOW}[INFO] PyInstaller nicht gefunden. Installiere...${NC}" + python3 -m pip install --user pyinstaller || { + echo -e "${RED}[FEHLER] PyInstaller konnte nicht installiert werden.${NC}" + exit 1 + } +fi +PYINSTALLER_VERSION=$(python3 -m PyInstaller --version 2>&1) +echo -e "${GREEN}✓ PyInstaller installiert: $PYINSTALLER_VERSION${NC}" + +# Prüfe Projektdateien +echo -e "${BLUE}[5/6] Prüfe Projektdateien...${NC}" +if [ ! -f "csv_dbf_converter_gui.py" ]; then + echo -e "${RED}[FEHLER] csv_dbf_converter_gui.py nicht gefunden!${NC}" + exit 1 +fi +echo -e "${GREEN}✓ Projektdateien vorhanden${NC}" + +# Lösche alte Build-Verzeichnisse +echo "" +echo -e "${BLUE}[6/6] Starte Build-Prozess...${NC}" +echo "[INFO] Räume alte Build-Dateien auf..." +rm -rf build dist *.spec 2>/dev/null || true + +echo "[INFO] Erstelle Linux-Binary für GUI-Version..." +echo "" + +# PyInstaller für GUI-Version ausführen +python3 -m PyInstaller \ + --onefile \ + --name "csv_dbf_converter_gui" \ + csv_dbf_converter_gui.py + +if [ $? -ne 0 ]; then + echo "" + echo -e "${RED}[FEHLER] Build fehlgeschlagen!${NC}" + exit 1 +fi + +echo "" +echo -e "${GREEN}[INFO] Build erfolgreich!${NC}" +echo "" + +# Kopiere notwendige Dateien in dist-Ordner +echo "[INFO] Kopiere Konfigurationsdateien..." +mkdir -p dist/config +cp -r config/examples dist/config/ +cp csv_dbf_converter.py dist/ +[ -f README.md ] && cp README.md dist/ + +# Mache das Binary ausführbar +chmod +x dist/csv_dbf_converter_gui + +# Zeige Ergebnis +echo "" +echo -e "${GREEN}====${NC}" +echo -e "${GREEN} BUILD ERFOLGREICH!${NC}" +echo -e "${GREEN}====${NC}" +echo "" +echo "Erstellte Dateien:" +ls -lh dist/ +echo "" +echo "Verwendung:" +echo " cd dist && ./csv_dbf_converter_gui" +echo "" +echo "Für Distribution alle Dateien aus dist/ kopieren." +echo "" diff --git a/scripts/build_windows.bat b/scripts/build_windows.bat new file mode 100644 index 0000000..a73eec6 --- /dev/null +++ b/scripts/build_windows.bat @@ -0,0 +1,101 @@ +@echo off +REM ==== +REM CSV zu DBF Konverter - Windows Build Script +REM Version 1.0 +REM ==== + +setlocal enabledelayedexpansion + +echo. +echo ==== +echo CSV zu DBF Konverter - Windows Build +echo ==== +echo. + +REM Wechsle ins Projektverzeichnis +cd /d "%~dp0\.." + +REM Prüfe Python Installation +echo [1/5] Pruefe Python Installation... +py --version >nul 2>&1 +if errorlevel 1 ( + echo. + echo FEHLER: Python wurde nicht gefunden! + echo. + echo Bitte installieren Sie Python von: + echo https://www.python.org/downloads/ + echo. + pause + exit /b 1 +) + +for /f "tokens=2" %%v in ('py --version 2^>^&1') do set PYTHON_VERSION=%%v +echo Python %PYTHON_VERSION% gefunden. + +REM Prüfe/Installiere PyInstaller +echo. +echo [2/5] Pruefe PyInstaller... +py -m PyInstaller --version >nul 2>&1 +if errorlevel 1 ( + echo PyInstaller nicht gefunden. Installiere... + py -m pip install pyinstaller + if errorlevel 1 ( + echo. + echo FEHLER: PyInstaller konnte nicht installiert werden! + pause + exit /b 1 + ) +) +for /f "tokens=*" %%v in ('py -m PyInstaller --version 2^>^&1') do set PYINSTALLER_VERSION=%%v +echo PyInstaller %PYINSTALLER_VERSION% gefunden. + +REM Bereinige alte Builds +echo. +echo [3/5] Bereinige alte Build-Dateien... +if exist build rmdir /s /q build +if exist dist rmdir /s /q dist +if exist *.spec del /f /q *.spec +echo Alte Dateien entfernt. + +REM Erstelle Executable +echo. +echo [4/5] Erstelle Windows Executable... +echo Dies kann einige Minuten dauern... +echo. + +py -m PyInstaller --onefile --windowed --name "CSV_DBF_Konverter" ^ + csv_dbf_converter_gui.py + +if errorlevel 1 ( + echo. + echo FEHLER: Build fehlgeschlagen! + pause + exit /b 1 +) + +REM Kopiere zusätzliche Dateien +echo. +echo [5/5] Kopiere zusaetzliche Dateien... +xcopy config dist\config\ /E /I /Y >nul 2>&1 +copy csv_dbf_converter.py dist\ >nul 2>&1 +copy README.md dist\ >nul 2>&1 +echo Dateien kopiert. + +REM Erfolgsmeldung +echo. +echo ==== +echo BUILD ERFOLGREICH! +echo ==== +echo. +echo Die Executable befindet sich in: +echo dist\CSV_DBF_Konverter.exe +echo. +echo Zusaetzliche Dateien in dist\: +echo - config\ (Beispiel-Konfigurationsdateien) +echo - csv_dbf_converter.py (CLI-Version) +echo - README.md +echo. +echo Hinweis: Die Konfigurationsdateien muessen +echo im gleichen Ordner wie die .exe liegen! +echo. +pause